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

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import java.util.ArrayList;
  6. import java.util.HashMap;
  7. import java.util.HashSet;
  8. import java.util.Iterator;
  9. import java.util.LinkedList;
  10. import java.util.List;
  11. import java.util.Set;
  12. import com.google.gwt.core.client.JavaScriptObject;
  13. import com.google.gwt.dom.client.Document;
  14. import com.google.gwt.dom.client.NativeEvent;
  15. import com.google.gwt.dom.client.NodeList;
  16. import com.google.gwt.dom.client.Style;
  17. import com.google.gwt.dom.client.Style.Display;
  18. import com.google.gwt.dom.client.Style.Position;
  19. import com.google.gwt.dom.client.Style.Unit;
  20. import com.google.gwt.dom.client.Style.Visibility;
  21. import com.google.gwt.dom.client.TableCellElement;
  22. import com.google.gwt.dom.client.TableRowElement;
  23. import com.google.gwt.dom.client.TableSectionElement;
  24. import com.google.gwt.event.dom.client.BlurEvent;
  25. import com.google.gwt.event.dom.client.BlurHandler;
  26. import com.google.gwt.event.dom.client.FocusEvent;
  27. import com.google.gwt.event.dom.client.FocusHandler;
  28. import com.google.gwt.event.dom.client.KeyCodes;
  29. import com.google.gwt.event.dom.client.KeyDownEvent;
  30. import com.google.gwt.event.dom.client.KeyDownHandler;
  31. import com.google.gwt.event.dom.client.KeyPressEvent;
  32. import com.google.gwt.event.dom.client.KeyPressHandler;
  33. import com.google.gwt.event.dom.client.ScrollEvent;
  34. import com.google.gwt.event.dom.client.ScrollHandler;
  35. import com.google.gwt.user.client.Command;
  36. import com.google.gwt.user.client.DOM;
  37. import com.google.gwt.user.client.DeferredCommand;
  38. import com.google.gwt.user.client.Element;
  39. import com.google.gwt.user.client.Event;
  40. import com.google.gwt.user.client.Timer;
  41. import com.google.gwt.user.client.Window;
  42. import com.google.gwt.user.client.ui.FlowPanel;
  43. import com.google.gwt.user.client.ui.Panel;
  44. import com.google.gwt.user.client.ui.RootPanel;
  45. import com.google.gwt.user.client.ui.UIObject;
  46. import com.google.gwt.user.client.ui.Widget;
  47. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  48. import com.vaadin.terminal.gwt.client.BrowserInfo;
  49. import com.vaadin.terminal.gwt.client.Container;
  50. import com.vaadin.terminal.gwt.client.Focusable;
  51. import com.vaadin.terminal.gwt.client.MouseEventDetails;
  52. import com.vaadin.terminal.gwt.client.Paintable;
  53. import com.vaadin.terminal.gwt.client.RenderSpace;
  54. import com.vaadin.terminal.gwt.client.UIDL;
  55. import com.vaadin.terminal.gwt.client.Util;
  56. import com.vaadin.terminal.gwt.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow;
  57. import com.vaadin.terminal.gwt.client.ui.dd.DDUtil;
  58. import com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler;
  59. import com.vaadin.terminal.gwt.client.ui.dd.VAcceptCallback;
  60. import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager;
  61. import com.vaadin.terminal.gwt.client.ui.dd.VDragEvent;
  62. import com.vaadin.terminal.gwt.client.ui.dd.VHasDropHandler;
  63. import com.vaadin.terminal.gwt.client.ui.dd.VTransferable;
  64. import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation;
  65. /**
  66. * VScrollTable
  67. *
  68. * VScrollTable is a FlowPanel having two widgets in it: * TableHead component *
  69. * ScrollPanel
  70. *
  71. * TableHead contains table's header and widgets + logic for resizing,
  72. * reordering and hiding columns.
  73. *
  74. * ScrollPanel contains VScrollTableBody object which handles content. To save
  75. * some bandwidth and to improve clients responsiveness with loads of data, in
  76. * VScrollTableBody all rows are not necessary rendered. There are "spacers" in
  77. * VScrollTableBody to use the exact same space as non-rendered rows would use.
  78. * This way we can use seamlessly traditional scrollbars and scrolling to fetch
  79. * more rows instead of "paging".
  80. *
  81. * In VScrollTable we listen to scroll events. On horizontal scrolling we also
  82. * update TableHeads scroll position which has its scrollbars hidden. On
  83. * vertical scroll events we will check if we are reaching the end of area where
  84. * we have rows rendered and
  85. *
  86. * TODO implement unregistering for child components in Cells
  87. */
  88. public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
  89. VHasDropHandler, KeyPressHandler, KeyDownHandler, FocusHandler,
  90. BlurHandler, Focusable {
  91. public static final String CLASSNAME = "v-table";
  92. public static final String CLASSNAME_SELECTION_FOCUS = CLASSNAME + "-focus";
  93. public static final String ITEM_CLICK_EVENT_ID = "itemClick";
  94. public static final String HEADER_CLICK_EVENT_ID = "handleHeaderClick";
  95. public static final String FOOTER_CLICK_EVENT_ID = "handleFooterClick";
  96. public static final String COLUMN_RESIZE_EVENT_ID = "columnResize";
  97. private static final double CACHE_RATE_DEFAULT = 2;
  98. /**
  99. * The default multi select mode where simple left clicks only selects one
  100. * item, CTRL+left click selects multiple items and SHIFT-left click selects
  101. * a range of items.
  102. */
  103. private static final int MULTISELECT_MODE_DEFAULT = 0;
  104. /**
  105. * multiple of pagelength which component will cache when requesting more
  106. * rows
  107. */
  108. private double cache_rate = CACHE_RATE_DEFAULT;
  109. /**
  110. * fraction of pageLenght which can be scrolled without making new request
  111. */
  112. private double cache_react_rate = 0.75 * cache_rate;
  113. public static final char ALIGN_CENTER = 'c';
  114. public static final char ALIGN_LEFT = 'b';
  115. public static final char ALIGN_RIGHT = 'e';
  116. private int firstRowInViewPort = 0;
  117. private int pageLength = 15;
  118. private int lastRequestedFirstvisible = 0; // to detect "serverside scroll"
  119. protected boolean showRowHeaders = false;
  120. private String[] columnOrder;
  121. protected ApplicationConnection client;
  122. protected String paintableId;
  123. private boolean immediate;
  124. private boolean nullSelectionAllowed = true;
  125. private int selectMode = Table.SELECT_MODE_NONE;
  126. private final HashSet<String> selectedRowKeys = new HashSet<String>();
  127. /*
  128. * These are used when jumping between pages when pressing Home and End
  129. */
  130. private boolean selectLastItemInNextRender = false;
  131. private boolean selectFirstItemInNextRender = false;
  132. private boolean focusFirstItemInNextRender = false;
  133. private boolean focusLastItemInNextRender = false;
  134. /*
  135. * The currently focused row
  136. */
  137. private VScrollTableRow focusedRow;
  138. /*
  139. * Helper to store selection range start in when using the keyboard
  140. */
  141. private VScrollTableRow selectionRangeStart;
  142. /*
  143. * Flag for notifying when the selection has changed and should be sent to
  144. * the server
  145. */
  146. private boolean selectionChanged = false;
  147. /*
  148. * The speed (in pixels) which the scrolling scrolls vertically/horizontally
  149. */
  150. private int scrollingVelocity = 10;
  151. private Timer scrollingVelocityTimer = null;;
  152. /**
  153. * Represents a select range of rows
  154. */
  155. private class SelectionRange {
  156. /**
  157. * The starting key of the range
  158. */
  159. private int startRowKey;
  160. /**
  161. * The ending key of the range
  162. */
  163. private int endRowKey;
  164. /**
  165. * Constuctor.
  166. *
  167. * @param startRowKey
  168. * The range start. Must be less than endRowKey
  169. * @param endRowKey
  170. * The range end. Must be bigger than startRowKey
  171. */
  172. public SelectionRange(int startRowKey, int endRowKey) {
  173. this.startRowKey = startRowKey;
  174. this.endRowKey = endRowKey;
  175. }
  176. /*
  177. * (non-Javadoc)
  178. *
  179. * @see java.lang.Object#toString()
  180. */
  181. @Override
  182. public String toString() {
  183. return startRowKey + "-" + endRowKey;
  184. }
  185. public boolean inRange(int key) {
  186. return key >= startRowKey && key <= endRowKey;
  187. }
  188. public int getStartKey() {
  189. return startRowKey;
  190. }
  191. public int getEndKey() {
  192. return endRowKey;
  193. }
  194. };
  195. private final HashSet<SelectionRange> selectedRowRanges = new HashSet<SelectionRange>();
  196. private boolean initializedAndAttached = false;
  197. /**
  198. * Flag to indicate if a column width recalculation is needed due update.
  199. */
  200. private boolean headerChangedDuringUpdate = false;
  201. private final TableHead tHead = new TableHead();
  202. private final TableFooter tFoot = new TableFooter();
  203. private final FocusableScrollPanel scrollBodyPanel = new FocusableScrollPanel();
  204. private int totalRows;
  205. private Set<String> collapsedColumns;
  206. private final RowRequestHandler rowRequestHandler;
  207. private VScrollTableBody scrollBody;
  208. private int firstvisible = 0;
  209. private boolean sortAscending;
  210. private String sortColumn;
  211. private boolean columnReordering;
  212. /**
  213. * This map contains captions and icon urls for actions like: * "33_c" ->
  214. * "Edit" * "33_i" -> "http://dom.com/edit.png"
  215. */
  216. private final HashMap<Object, String> actionMap = new HashMap<Object, String>();
  217. private String[] visibleColOrder;
  218. private boolean initialContentReceived = false;
  219. private Element scrollPositionElement;
  220. private boolean enabled;
  221. private boolean showColHeaders;
  222. private boolean showColFooters;
  223. /** flag to indicate that table body has changed */
  224. private boolean isNewBody = true;
  225. /*
  226. * Read from the "recalcWidths" -attribute. When it is true, the table will
  227. * recalculate the widths for columns - desirable in some cases. For #1983,
  228. * marked experimental.
  229. */
  230. boolean recalcWidths = false;
  231. private final ArrayList<Panel> lazyUnregistryBag = new ArrayList<Panel>();
  232. private String height;
  233. private String width = "";
  234. private boolean rendering = false;
  235. private boolean hasFocus = false;
  236. private int dragmode;
  237. private int multiselectmode;
  238. public VScrollTable() {
  239. scrollBodyPanel.setStyleName(CLASSNAME + "-body-wrapper");
  240. /*
  241. * Firefox auto-repeat works correctly only if we use a key press
  242. * handler, other browsers handle it correctly when using a key down
  243. * handler
  244. */
  245. if (BrowserInfo.get().isGecko()) {
  246. scrollBodyPanel.addKeyPressHandler(this);
  247. } else {
  248. scrollBodyPanel.addKeyDownHandler(this);
  249. }
  250. scrollBodyPanel.addFocusHandler(this);
  251. scrollBodyPanel.addBlurHandler(this);
  252. scrollBodyPanel.addScrollHandler(this);
  253. scrollBodyPanel.setStyleName(CLASSNAME + "-body");
  254. setStyleName(CLASSNAME);
  255. add(tHead);
  256. add(scrollBodyPanel);
  257. add(tFoot);
  258. rowRequestHandler = new RowRequestHandler();
  259. /*
  260. * We need to use the sinkEvents method to catch the keyUp events so we
  261. * can cache a single shift. KeyUpHandler cannot do this.
  262. */
  263. sinkEvents(Event.ONKEYUP);
  264. }
  265. /*
  266. * (non-Javadoc)
  267. *
  268. * @see
  269. * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user
  270. * .client.Event)
  271. */
  272. @Override
  273. public void onBrowserEvent(Event event) {
  274. if (event.getTypeInt() == Event.ONKEYUP) {
  275. if (event.getKeyCode() == KeyCodes.KEY_SHIFT) {
  276. sendSelectedRows();
  277. selectionRangeStart = null;
  278. } else if ((event.getKeyCode() == getNavigationUpKey()
  279. || event.getKeyCode() == getNavigationDownKey()
  280. || event.getKeyCode() == getNavigationPageUpKey() || event
  281. .getKeyCode() == getNavigationPageDownKey())
  282. && !event.getShiftKey()) {
  283. sendSelectedRows();
  284. if (scrollingVelocityTimer != null) {
  285. scrollingVelocityTimer.cancel();
  286. scrollingVelocityTimer = null;
  287. scrollingVelocity = 10;
  288. }
  289. }
  290. }
  291. }
  292. /**
  293. * Fires a column resize event which sends the resize information to the
  294. * server.
  295. *
  296. * @param columnId
  297. * The columnId of the column which was resized
  298. * @param originalWidth
  299. * The width in pixels of the column before the resize event
  300. * @param newWidth
  301. * The width in pixels of the column after the resize event
  302. */
  303. private void fireColumnResizeEvent(String columnId, int originalWidth,
  304. int newWidth) {
  305. client.updateVariable(paintableId, "columnResizeEventColumn", columnId,
  306. false);
  307. client.updateVariable(paintableId, "columnResizeEventPrev",
  308. originalWidth, false);
  309. client.updateVariable(paintableId, "columnResizeEventCurr", newWidth,
  310. immediate);
  311. }
  312. /**
  313. * Moves the focus one step down
  314. *
  315. * @return Returns true if succeeded
  316. */
  317. private boolean moveFocusDown() {
  318. return moveFocusDown(0);
  319. }
  320. /**
  321. * Moves the focus down by 1+offset rows
  322. *
  323. * @return Returns true if succeeded, else false if the selection could not
  324. * be move downwards
  325. */
  326. private boolean moveFocusDown(int offset) {
  327. if (selectMode > VScrollTable.SELECT_MODE_NONE) {
  328. if (focusedRow == null && scrollBody.iterator().hasNext()) {
  329. return setRowFocus((VScrollTableRow) scrollBody.iterator()
  330. .next());
  331. } else {
  332. VScrollTableRow next = getNextRow(focusedRow, offset);
  333. if (next != null) {
  334. return setRowFocus(next);
  335. }
  336. }
  337. }
  338. return false;
  339. }
  340. /**
  341. * Moves the selection one step up
  342. *
  343. * @return Returns true if succeeded
  344. */
  345. private boolean moveFocusUp() {
  346. return moveFocusUp(0);
  347. }
  348. /**
  349. * Moves the focus row upwards
  350. *
  351. * @return Returns true if succeeded, else false if the selection could not
  352. * be move upwards
  353. *
  354. */
  355. private boolean moveFocusUp(int offset) {
  356. if (selectMode > VScrollTable.SELECT_MODE_NONE) {
  357. if (focusedRow == null && scrollBody.iterator().hasNext()) {
  358. return setRowFocus((VScrollTableRow) scrollBody.iterator()
  359. .next());
  360. } else {
  361. VScrollTableRow prev = getPreviousRow(focusedRow, offset);
  362. if (prev != null) {
  363. return setRowFocus(prev);
  364. }
  365. }
  366. }
  367. return false;
  368. }
  369. /**
  370. * Selects a row where the current selection head is
  371. *
  372. * @param ctrlSelect
  373. * Is the selection a ctrl+selection
  374. * @param shiftSelect
  375. * Is the selection a shift+selection
  376. * @return Returns truw
  377. */
  378. private void selectFocusedRow(boolean ctrlSelect, boolean shiftSelect) {
  379. if (focusedRow != null) {
  380. // Arrows moves the selection and clears previous selections
  381. if (selectMode > SELECT_MODE_NONE && !ctrlSelect && !shiftSelect) {
  382. deselectAll();
  383. focusedRow.toggleSelection();
  384. selectionRangeStart = focusedRow;
  385. }
  386. // Ctrl+arrows moves selection head
  387. else if (selectMode > SELECT_MODE_NONE && ctrlSelect
  388. && !shiftSelect) {
  389. selectionRangeStart = focusedRow;
  390. // No selection, only selection head is moved
  391. }
  392. // Shift+arrows selection selects a range
  393. else if (selectMode == SELECT_MODE_MULTI && !ctrlSelect
  394. && shiftSelect) {
  395. focusedRow.toggleShiftSelection(shiftSelect);
  396. }
  397. }
  398. }
  399. /**
  400. * Sends the selection to the server if changed since the last update/visit.
  401. */
  402. protected void sendSelectedRows() {
  403. // Don't send anything if selection has not changed
  404. if (!selectionChanged) {
  405. return;
  406. }
  407. // Reset selection changed flag
  408. selectionChanged = false;
  409. // Note: changing the immediateness of this
  410. // might
  411. // require changes to "clickEvent" immediateness
  412. // also.
  413. if (multiselectmode == MULTISELECT_MODE_DEFAULT) {
  414. // Convert ranges to a set of strings
  415. Set<String> ranges = new HashSet<String>();
  416. for (SelectionRange range : selectedRowRanges) {
  417. ranges.add(range.toString());
  418. }
  419. // Send the selected row ranges
  420. client.updateVariable(paintableId, "selectedRanges",
  421. ranges.toArray(new String[selectedRowRanges.size()]), false);
  422. }
  423. // Send the selected rows
  424. client.updateVariable(paintableId, "selected",
  425. selectedRowKeys.toArray(new String[selectedRowKeys.size()]),
  426. immediate);
  427. }
  428. /**
  429. * Get the key that moves the selection head upwards. By default it is the
  430. * up arrow key but by overriding this you can change the key to whatever
  431. * you want.
  432. *
  433. * @return The keycode of the key
  434. */
  435. protected int getNavigationUpKey() {
  436. return KeyCodes.KEY_UP;
  437. }
  438. /**
  439. * Get the key that moves the selection head downwards. By default it is the
  440. * down arrow key but by overriding this you can change the key to whatever
  441. * you want.
  442. *
  443. * @return The keycode of the key
  444. */
  445. protected int getNavigationDownKey() {
  446. return KeyCodes.KEY_DOWN;
  447. }
  448. /**
  449. * Get the key that scrolls to the left in the table. By default it is the
  450. * left arrow key but by overriding this you can change the key to whatever
  451. * you want.
  452. *
  453. * @return The keycode of the key
  454. */
  455. protected int getNavigationLeftKey() {
  456. return KeyCodes.KEY_LEFT;
  457. }
  458. /**
  459. * Get the key that scroll to the right on the table. By default it is the
  460. * right arrow key but by overriding this you can change the key to whatever
  461. * you want.
  462. *
  463. * @return The keycode of the key
  464. */
  465. protected int getNavigationRightKey() {
  466. return KeyCodes.KEY_RIGHT;
  467. }
  468. /**
  469. * Get the key that selects an item in the table. By default it is the space
  470. * bar key but by overriding this you can change the key to whatever you
  471. * want.
  472. *
  473. * @return
  474. */
  475. protected int getNavigationSelectKey() {
  476. return 32;
  477. }
  478. /**
  479. * Get the key the moves the selection one page up in the table. By default
  480. * this is the Page Up key but by overriding this you can change the key to
  481. * whatever you want.
  482. *
  483. * @return
  484. */
  485. protected int getNavigationPageUpKey() {
  486. return KeyCodes.KEY_PAGEUP;
  487. }
  488. /**
  489. * Get the key the moves the selection one page down in the table. By
  490. * default this is the Page Down key but by overriding this you can change
  491. * the key to whatever you want.
  492. *
  493. * @return
  494. */
  495. protected int getNavigationPageDownKey() {
  496. return KeyCodes.KEY_PAGEDOWN;
  497. }
  498. /**
  499. * Get the key the moves the selection to the beginning of the table. By
  500. * default this is the Home key but by overriding this you can change the
  501. * key to whatever you want.
  502. *
  503. * @return
  504. */
  505. protected int getNavigationStartKey() {
  506. return KeyCodes.KEY_HOME;
  507. }
  508. /**
  509. * Get the key the moves the selection to the end of the table. By default
  510. * this is the End key but by overriding this you can change the key to
  511. * whatever you want.
  512. *
  513. * @return
  514. */
  515. protected int getNavigationEndKey() {
  516. return KeyCodes.KEY_END;
  517. }
  518. /*
  519. * (non-Javadoc)
  520. *
  521. * @see
  522. * com.vaadin.terminal.gwt.client.Paintable#updateFromUIDL(com.vaadin.terminal
  523. * .gwt.client.UIDL, com.vaadin.terminal.gwt.client.ApplicationConnection)
  524. */
  525. @SuppressWarnings("unchecked")
  526. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  527. rendering = true;
  528. /*
  529. * We need to do this before updateComponent since updateComponent calls
  530. * this.setHeight() which will calculate a new body height depending on
  531. * the space available.
  532. */
  533. if (uidl.hasAttribute("colfooters")) {
  534. showColFooters = uidl.getBooleanAttribute("colfooters");
  535. }
  536. tFoot.setVisible(showColFooters);
  537. if (client.updateComponent(this, uidl, true)) {
  538. rendering = false;
  539. return;
  540. }
  541. // we may have pending cache row fetch, cancel it. See #2136
  542. rowRequestHandler.cancel();
  543. enabled = !uidl.hasAttribute("disabled");
  544. this.client = client;
  545. paintableId = uidl.getStringAttribute("id");
  546. immediate = uidl.getBooleanAttribute("immediate");
  547. final int newTotalRows = uidl.getIntAttribute("totalrows");
  548. if (newTotalRows != totalRows) {
  549. if (scrollBody != null) {
  550. if (totalRows == 0) {
  551. tHead.clear();
  552. tFoot.clear();
  553. }
  554. initializedAndAttached = false;
  555. initialContentReceived = false;
  556. isNewBody = true;
  557. }
  558. totalRows = newTotalRows;
  559. }
  560. dragmode = uidl.hasAttribute("dragmode") ? uidl
  561. .getIntAttribute("dragmode") : 0;
  562. multiselectmode = uidl.hasAttribute("multiselectmode") ? uidl
  563. .getIntAttribute("multiselectmode") : MULTISELECT_MODE_DEFAULT;
  564. setCacheRate(uidl.hasAttribute("cr") ? uidl.getDoubleAttribute("cr")
  565. : CACHE_RATE_DEFAULT);
  566. recalcWidths = uidl.hasAttribute("recalcWidths");
  567. if (recalcWidths) {
  568. tHead.clear();
  569. tFoot.clear();
  570. }
  571. if (uidl.hasAttribute("pagelength")) {
  572. pageLength = uidl.getIntAttribute("pagelength");
  573. } else {
  574. // pagelenght is "0" meaning scrolling is turned off
  575. pageLength = totalRows;
  576. }
  577. firstvisible = uidl.hasVariable("firstvisible") ? uidl
  578. .getIntVariable("firstvisible") : 0;
  579. if (firstvisible != lastRequestedFirstvisible && scrollBody != null) {
  580. // received 'surprising' firstvisible from server: scroll there
  581. firstRowInViewPort = firstvisible;
  582. scrollBodyPanel.setScrollPosition((int) (firstvisible * scrollBody
  583. .getRowHeight()));
  584. }
  585. showRowHeaders = uidl.getBooleanAttribute("rowheaders");
  586. showColHeaders = uidl.getBooleanAttribute("colheaders");
  587. nullSelectionAllowed = uidl.hasAttribute("nsa") ? uidl
  588. .getBooleanAttribute("nsa") : true;
  589. if (uidl.hasVariable("sortascending")) {
  590. sortAscending = uidl.getBooleanVariable("sortascending");
  591. sortColumn = uidl.getStringVariable("sortcolumn");
  592. }
  593. if (uidl.hasVariable("selected")) {
  594. final Set<String> selectedKeys = uidl
  595. .getStringArrayVariableAsSet("selected");
  596. if (scrollBody != null) {
  597. Iterator<Widget> iterator = scrollBody.iterator();
  598. while (iterator.hasNext()) {
  599. VScrollTableRow row = (VScrollTableRow) iterator.next();
  600. boolean selected = selectedKeys.contains(row.getKey());
  601. if (selected != row.isSelected()) {
  602. row.toggleSelection();
  603. }
  604. }
  605. }
  606. }
  607. if (uidl.hasAttribute("selectmode")) {
  608. if (uidl.getBooleanAttribute("readonly")) {
  609. selectMode = Table.SELECT_MODE_NONE;
  610. } else if (uidl.getStringAttribute("selectmode").equals("multi")) {
  611. selectMode = Table.SELECT_MODE_MULTI;
  612. } else if (uidl.getStringAttribute("selectmode").equals("single")) {
  613. selectMode = Table.SELECT_MODE_SINGLE;
  614. } else {
  615. selectMode = Table.SELECT_MODE_NONE;
  616. }
  617. }
  618. if (uidl.hasVariable("columnorder")) {
  619. columnReordering = true;
  620. columnOrder = uidl.getStringArrayVariable("columnorder");
  621. } else {
  622. columnReordering = false;
  623. columnOrder = null;
  624. }
  625. if (uidl.hasVariable("collapsedcolumns")) {
  626. tHead.setColumnCollapsingAllowed(true);
  627. collapsedColumns = uidl
  628. .getStringArrayVariableAsSet("collapsedcolumns");
  629. } else {
  630. tHead.setColumnCollapsingAllowed(false);
  631. }
  632. UIDL rowData = null;
  633. UIDL ac = null;
  634. for (final Iterator it = uidl.getChildIterator(); it.hasNext();) {
  635. final UIDL c = (UIDL) it.next();
  636. if (c.getTag().equals("rows")) {
  637. rowData = c;
  638. } else if (c.getTag().equals("actions")) {
  639. updateActionMap(c);
  640. } else if (c.getTag().equals("visiblecolumns")) {
  641. tHead.updateCellsFromUIDL(c);
  642. tFoot.updateCellsFromUIDL(c);
  643. } else if (c.getTag().equals("-ac")) {
  644. ac = c;
  645. }
  646. }
  647. if (ac == null) {
  648. if (dropHandler != null) {
  649. // remove dropHandler if not present anymore
  650. dropHandler = null;
  651. }
  652. } else {
  653. if (dropHandler == null) {
  654. dropHandler = new VScrollTableDropHandler();
  655. }
  656. dropHandler.updateAcceptRules(ac);
  657. }
  658. updateHeader(uidl.getStringArrayAttribute("vcolorder"));
  659. updateFooter(uidl.getStringArrayAttribute("vcolorder"));
  660. if (!recalcWidths && initializedAndAttached) {
  661. updateBody(rowData, uidl.getIntAttribute("firstrow"),
  662. uidl.getIntAttribute("rows"));
  663. if (headerChangedDuringUpdate) {
  664. lazyAdjustColumnWidths.schedule(1);
  665. } else {
  666. // webkits may still bug with their disturbing scrollbar bug,
  667. // See #3457
  668. // run overflow fix for scrollable area
  669. DeferredCommand.addCommand(new Command() {
  670. public void execute() {
  671. Util.runWebkitOverflowAutoFix(scrollBodyPanel
  672. .getElement());
  673. }
  674. });
  675. }
  676. } else {
  677. if (scrollBody != null) {
  678. scrollBody.removeFromParent();
  679. lazyUnregistryBag.add(scrollBody);
  680. }
  681. scrollBody = createScrollBody();
  682. scrollBody.renderInitialRows(rowData,
  683. uidl.getIntAttribute("firstrow"),
  684. uidl.getIntAttribute("rows"));
  685. scrollBodyPanel.add(scrollBody);
  686. initialContentReceived = true;
  687. if (isAttached()) {
  688. sizeInit();
  689. }
  690. scrollBody.restoreRowVisibility();
  691. }
  692. if (selectMode == Table.SELECT_MODE_NONE) {
  693. scrollBody.addStyleName(CLASSNAME + "-body-noselection");
  694. } else {
  695. scrollBody.removeStyleName(CLASSNAME + "-body-noselection");
  696. }
  697. hideScrollPositionAnnotation();
  698. purgeUnregistryBag();
  699. // selection is no in sync with server, avoid excessive server visits by
  700. // clearing to flag used during the normal operation
  701. selectionChanged = false;
  702. // This is called when the Home button has been pressed and the pages
  703. // changes
  704. if (selectFirstItemInNextRender) {
  705. selectFirstRenderedRow(false);
  706. selectFirstItemInNextRender = false;
  707. }
  708. if (focusFirstItemInNextRender) {
  709. selectFirstRenderedRow(true);
  710. focusFirstItemInNextRender = false;
  711. }
  712. // This is called when the End button has been pressed and the pages
  713. // changes
  714. if (selectLastItemInNextRender) {
  715. selectLastRenderedRow(false);
  716. selectLastItemInNextRender = false;
  717. }
  718. if (focusLastItemInNextRender) {
  719. selectLastRenderedRow(true);
  720. focusLastItemInNextRender = false;
  721. }
  722. if (focusedRow != null) {
  723. if (!focusedRow.isAttached()) {
  724. // focused row has orphaned, can't focus
  725. focusedRow = null;
  726. if (SELECT_MODE_SINGLE == selectMode
  727. && selectedRowKeys.size() > 0) {
  728. // try to focusa row currently selected and in viewport
  729. String selectedRowKey = selectedRowKeys.iterator().next();
  730. if (selectedRowKey != null) {
  731. setRowFocus(getRenderedRowByKey(selectedRowKey));
  732. }
  733. }
  734. // TODO what should happen in multiselect mode?
  735. } else {
  736. setRowFocus(getRenderedRowByKey(focusedRow.getKey()));
  737. }
  738. }
  739. if (!isFocusable()) {
  740. scrollBodyPanel.getElement().setTabIndex(-1);
  741. } else {
  742. scrollBodyPanel.getElement().setTabIndex(0);
  743. }
  744. rendering = false;
  745. headerChangedDuringUpdate = false;
  746. // Ensure that the focus has not scrolled outside the viewport
  747. if (focusedRow != null)
  748. ensureRowIsVisible(focusedRow);
  749. }
  750. protected VScrollTableBody createScrollBody() {
  751. return new VScrollTableBody();
  752. }
  753. /**
  754. * Selects the last rendered row in the table
  755. *
  756. * @param focusOnly
  757. * Should the focus only be moved to the last row
  758. */
  759. private void selectLastRenderedRow(boolean focusOnly) {
  760. VScrollTableRow row = null;
  761. Iterator<Widget> it = scrollBody.iterator();
  762. while (it.hasNext()) {
  763. row = (VScrollTableRow) it.next();
  764. }
  765. if (row != null) {
  766. setRowFocus(row);
  767. if (!focusOnly) {
  768. deselectAll();
  769. selectFocusedRow(false, false);
  770. sendSelectedRows();
  771. }
  772. }
  773. }
  774. /**
  775. * Selects the first rendered row
  776. *
  777. * @param focusOnly
  778. * Should the focus only be moved to the first row
  779. */
  780. private void selectFirstRenderedRow(boolean focusOnly) {
  781. setRowFocus((VScrollTableRow) scrollBody.iterator().next());
  782. if (!focusOnly) {
  783. deselectAll();
  784. selectFocusedRow(false, false);
  785. sendSelectedRows();
  786. }
  787. }
  788. private void setCacheRate(double d) {
  789. if (cache_rate != d) {
  790. cache_rate = d;
  791. cache_react_rate = 0.75 * d;
  792. }
  793. }
  794. /**
  795. * Unregisters Paintables in "trashed" HasWidgets (IScrollTableBodys or
  796. * IScrollTableRows). This is done lazily as Table must survive from
  797. * "subtreecaching" logic.
  798. */
  799. private void purgeUnregistryBag() {
  800. for (Iterator<Panel> iterator = lazyUnregistryBag.iterator(); iterator
  801. .hasNext();) {
  802. client.unregisterChildPaintables(iterator.next());
  803. }
  804. lazyUnregistryBag.clear();
  805. }
  806. private void updateActionMap(UIDL c) {
  807. final Iterator<?> it = c.getChildIterator();
  808. while (it.hasNext()) {
  809. final UIDL action = (UIDL) it.next();
  810. final String key = action.getStringAttribute("key");
  811. final String caption = action.getStringAttribute("caption");
  812. actionMap.put(key + "_c", caption);
  813. if (action.hasAttribute("icon")) {
  814. // TODO need some uri handling ??
  815. actionMap.put(key + "_i", client.translateVaadinUri(action
  816. .getStringAttribute("icon")));
  817. }
  818. }
  819. }
  820. public String getActionCaption(String actionKey) {
  821. return actionMap.get(actionKey + "_c");
  822. }
  823. public String getActionIcon(String actionKey) {
  824. return actionMap.get(actionKey + "_i");
  825. }
  826. private void updateHeader(String[] strings) {
  827. if (strings == null) {
  828. return;
  829. }
  830. int visibleCols = strings.length;
  831. int colIndex = 0;
  832. if (showRowHeaders) {
  833. tHead.enableColumn("0", colIndex);
  834. visibleCols++;
  835. visibleColOrder = new String[visibleCols];
  836. visibleColOrder[colIndex] = "0";
  837. colIndex++;
  838. } else {
  839. visibleColOrder = new String[visibleCols];
  840. tHead.removeCell("0");
  841. }
  842. int i;
  843. for (i = 0; i < strings.length; i++) {
  844. final String cid = strings[i];
  845. visibleColOrder[colIndex] = cid;
  846. tHead.enableColumn(cid, colIndex);
  847. colIndex++;
  848. }
  849. tHead.setVisible(showColHeaders);
  850. setContainerHeight();
  851. }
  852. /**
  853. * Updates footers.
  854. * <p>
  855. * Update headers whould be called before this method is called!
  856. * </p>
  857. *
  858. * @param strings
  859. */
  860. private void updateFooter(String[] strings) {
  861. if (strings == null) {
  862. return;
  863. }
  864. // Add dummy column if row headers are present
  865. int colIndex = 0;
  866. if (showRowHeaders) {
  867. tFoot.enableColumn("0", colIndex);
  868. colIndex++;
  869. } else {
  870. tFoot.removeCell("0");
  871. }
  872. int i;
  873. for (i = 0; i < strings.length; i++) {
  874. final String cid = strings[i];
  875. tFoot.enableColumn(cid, colIndex);
  876. colIndex++;
  877. }
  878. tFoot.setVisible(showColFooters);
  879. }
  880. /**
  881. * @param uidl
  882. * which contains row data
  883. * @param firstRow
  884. * first row in data set
  885. * @param reqRows
  886. * amount of rows in data set
  887. */
  888. private void updateBody(UIDL uidl, int firstRow, int reqRows) {
  889. if (uidl == null || reqRows < 1) {
  890. // container is empty, remove possibly existing rows
  891. if (firstRow < 0) {
  892. while (scrollBody.getLastRendered() > scrollBody.firstRendered) {
  893. scrollBody.unlinkRow(false);
  894. }
  895. scrollBody.unlinkRow(false);
  896. }
  897. return;
  898. }
  899. scrollBody.renderRows(uidl, firstRow, reqRows);
  900. final int optimalFirstRow = (int) (firstRowInViewPort - pageLength
  901. * cache_rate);
  902. boolean cont = true;
  903. while (cont && scrollBody.getLastRendered() > optimalFirstRow
  904. && scrollBody.getFirstRendered() < optimalFirstRow) {
  905. // removing row from start
  906. cont = scrollBody.unlinkRow(true);
  907. }
  908. final int optimalLastRow = (int) (firstRowInViewPort + pageLength + pageLength
  909. * cache_rate);
  910. cont = true;
  911. while (cont && scrollBody.getLastRendered() > optimalLastRow) {
  912. // removing row from the end
  913. cont = scrollBody.unlinkRow(false);
  914. }
  915. scrollBody.fixSpacers();
  916. scrollBody.restoreRowVisibility();
  917. }
  918. /**
  919. * Gives correct column index for given column key ("cid" in UIDL).
  920. *
  921. * @param colKey
  922. * @return column index of visible columns, -1 if column not visible
  923. */
  924. private int getColIndexByKey(String colKey) {
  925. // return 0 if asked for rowHeaders
  926. if ("0".equals(colKey)) {
  927. return 0;
  928. }
  929. for (int i = 0; i < visibleColOrder.length; i++) {
  930. if (visibleColOrder[i].equals(colKey)) {
  931. return i;
  932. }
  933. }
  934. return -1;
  935. }
  936. private boolean isCollapsedColumn(String colKey) {
  937. if (collapsedColumns == null) {
  938. return false;
  939. }
  940. if (collapsedColumns.contains(colKey)) {
  941. return true;
  942. }
  943. return false;
  944. }
  945. private String getColKeyByIndex(int index) {
  946. return tHead.getHeaderCell(index).getColKey();
  947. }
  948. private void setColWidth(int colIndex, int w, boolean isDefinedWidth) {
  949. // Set header column width
  950. final HeaderCell hcell = tHead.getHeaderCell(colIndex);
  951. hcell.setWidth(w, isDefinedWidth);
  952. // Set body column width
  953. scrollBody.setColWidth(colIndex, w);
  954. // Set footer column width
  955. FooterCell fcell = tFoot.getFooterCell(colIndex);
  956. fcell.setWidth(w, isDefinedWidth);
  957. }
  958. private int getColWidth(String colKey) {
  959. return tHead.getHeaderCell(colKey).getWidth();
  960. }
  961. /**
  962. * Get a rendered row by its key
  963. *
  964. * @param key
  965. * The key to search with
  966. * @return
  967. */
  968. private VScrollTableRow getRenderedRowByKey(String key) {
  969. if (scrollBody != null) {
  970. final Iterator<Widget> it = scrollBody.iterator();
  971. VScrollTableRow r = null;
  972. while (it.hasNext()) {
  973. r = (VScrollTableRow) it.next();
  974. if (r.getKey().equals(key)) {
  975. return r;
  976. }
  977. }
  978. }
  979. return null;
  980. }
  981. /**
  982. * Returns the next row to the given row
  983. *
  984. * @param row
  985. * The row to calculate from
  986. *
  987. * @return The next row or null if no row exists
  988. */
  989. private VScrollTableRow getNextRow(VScrollTableRow row, int offset) {
  990. final Iterator<Widget> it = scrollBody.iterator();
  991. VScrollTableRow r = null;
  992. while (it.hasNext()) {
  993. r = (VScrollTableRow) it.next();
  994. if (r == row) {
  995. r = null;
  996. while (offset >= 0 && it.hasNext()) {
  997. r = (VScrollTableRow) it.next();
  998. offset--;
  999. }
  1000. return r;
  1001. }
  1002. }
  1003. return null;
  1004. }
  1005. /**
  1006. * Returns the previous row from the given row
  1007. *
  1008. * @param row
  1009. * The row to calculate from
  1010. * @return The previous row or null if no row exists
  1011. */
  1012. private VScrollTableRow getPreviousRow(VScrollTableRow row, int offset) {
  1013. final Iterator<Widget> it = scrollBody.iterator();
  1014. final Iterator<Widget> offsetIt = scrollBody.iterator();
  1015. VScrollTableRow r = null;
  1016. VScrollTableRow prev = null;
  1017. while (it.hasNext()) {
  1018. r = (VScrollTableRow) it.next();
  1019. if (offset < 0) {
  1020. prev = (VScrollTableRow) offsetIt.next();
  1021. }
  1022. if (r == row) {
  1023. return prev;
  1024. }
  1025. offset--;
  1026. }
  1027. return null;
  1028. }
  1029. protected void reOrderColumn(String columnKey, int newIndex) {
  1030. final int oldIndex = getColIndexByKey(columnKey);
  1031. // Change header order
  1032. tHead.moveCell(oldIndex, newIndex);
  1033. // Change body order
  1034. scrollBody.moveCol(oldIndex, newIndex);
  1035. // Change footer order
  1036. tFoot.moveCell(oldIndex, newIndex);
  1037. /*
  1038. * Build new columnOrder and update it to server Note that columnOrder
  1039. * also contains collapsed columns so we cannot directly build it from
  1040. * cells vector Loop the old columnOrder and append in order to new
  1041. * array unless on moved columnKey. On new index also put the moved key
  1042. * i == index on columnOrder, j == index on newOrder
  1043. */
  1044. final String oldKeyOnNewIndex = visibleColOrder[newIndex];
  1045. if (showRowHeaders) {
  1046. newIndex--; // columnOrder don't have rowHeader
  1047. }
  1048. // add back hidden rows,
  1049. for (int i = 0; i < columnOrder.length; i++) {
  1050. if (columnOrder[i].equals(oldKeyOnNewIndex)) {
  1051. break; // break loop at target
  1052. }
  1053. if (isCollapsedColumn(columnOrder[i])) {
  1054. newIndex++;
  1055. }
  1056. }
  1057. // finally we can build the new columnOrder for server
  1058. final String[] newOrder = new String[columnOrder.length];
  1059. for (int i = 0, j = 0; j < newOrder.length; i++) {
  1060. if (j == newIndex) {
  1061. newOrder[j] = columnKey;
  1062. j++;
  1063. }
  1064. if (i == columnOrder.length) {
  1065. break;
  1066. }
  1067. if (columnOrder[i].equals(columnKey)) {
  1068. continue;
  1069. }
  1070. newOrder[j] = columnOrder[i];
  1071. j++;
  1072. }
  1073. columnOrder = newOrder;
  1074. // also update visibleColumnOrder
  1075. int i = showRowHeaders ? 1 : 0;
  1076. for (int j = 0; j < newOrder.length; j++) {
  1077. final String cid = newOrder[j];
  1078. if (!isCollapsedColumn(cid)) {
  1079. visibleColOrder[i++] = cid;
  1080. }
  1081. }
  1082. client.updateVariable(paintableId, "columnorder", columnOrder, false);
  1083. }
  1084. @Override
  1085. protected void onAttach() {
  1086. super.onAttach();
  1087. if (initialContentReceived) {
  1088. sizeInit();
  1089. }
  1090. }
  1091. @Override
  1092. protected void onDetach() {
  1093. rowRequestHandler.cancel();
  1094. super.onDetach();
  1095. // ensure that scrollPosElement will be detached
  1096. if (scrollPositionElement != null) {
  1097. final Element parent = DOM.getParent(scrollPositionElement);
  1098. if (parent != null) {
  1099. DOM.removeChild(parent, scrollPositionElement);
  1100. }
  1101. }
  1102. }
  1103. /**
  1104. * Run only once when component is attached and received its initial
  1105. * content. This function : * Syncs headers and bodys "natural widths and
  1106. * saves the values. * Sets proper width and height * Makes deferred request
  1107. * to get some cache rows
  1108. */
  1109. private void sizeInit() {
  1110. /*
  1111. * We will use browsers table rendering algorithm to find proper column
  1112. * widths. If content and header take less space than available, we will
  1113. * divide extra space relatively to each column which has not width set.
  1114. *
  1115. * Overflow pixels are added to last column.
  1116. */
  1117. Iterator<Widget> headCells = tHead.iterator();
  1118. Iterator<Widget> footCells = tFoot.iterator();
  1119. int i = 0;
  1120. int totalExplicitColumnsWidths = 0;
  1121. int total = 0;
  1122. float expandRatioDivider = 0;
  1123. final int[] widths = new int[tHead.visibleCells.size()];
  1124. tHead.enableBrowserIntelligence();
  1125. tFoot.enableBrowserIntelligence();
  1126. // first loop: collect natural widths
  1127. while (headCells.hasNext()) {
  1128. final HeaderCell hCell = (HeaderCell) headCells.next();
  1129. final FooterCell fCell = (FooterCell) footCells.next();
  1130. int w = hCell.getWidth();
  1131. if (hCell.isDefinedWidth()) {
  1132. // server has defined column width explicitly
  1133. totalExplicitColumnsWidths += w;
  1134. } else {
  1135. if (hCell.getExpandRatio() > 0) {
  1136. expandRatioDivider += hCell.getExpandRatio();
  1137. w = 0;
  1138. } else {
  1139. // get and store greater of header width and column width,
  1140. // and
  1141. // store it as a minimumn natural col width
  1142. int headerWidth = hCell.getNaturalColumnWidth(i);
  1143. int footerWidth = fCell.getNaturalColumnWidth(i);
  1144. w = headerWidth > footerWidth ? headerWidth : footerWidth;
  1145. }
  1146. hCell.setNaturalMinimumColumnWidth(w);
  1147. fCell.setNaturalMinimumColumnWidth(w);
  1148. }
  1149. widths[i] = w;
  1150. total += w;
  1151. i++;
  1152. }
  1153. tHead.disableBrowserIntelligence();
  1154. tFoot.disableBrowserIntelligence();
  1155. boolean willHaveScrollbarz = willHaveScrollbars();
  1156. // fix "natural" width if width not set
  1157. if (width == null || "".equals(width)) {
  1158. int w = total;
  1159. w += scrollBody.getCellExtraWidth() * visibleColOrder.length;
  1160. if (willHaveScrollbarz) {
  1161. w += Util.getNativeScrollbarSize();
  1162. }
  1163. setContentWidth(w);
  1164. }
  1165. int availW = scrollBody.getAvailableWidth();
  1166. if (BrowserInfo.get().isIE()) {
  1167. // Hey IE, are you really sure about this?
  1168. availW = scrollBody.getAvailableWidth();
  1169. }
  1170. availW -= scrollBody.getCellExtraWidth() * visibleColOrder.length;
  1171. if (willHaveScrollbarz) {
  1172. availW -= Util.getNativeScrollbarSize();
  1173. }
  1174. // TODO refactor this code to be the same as in resize timer
  1175. boolean needsReLayout = false;
  1176. if (availW > total) {
  1177. // natural size is smaller than available space
  1178. final int extraSpace = availW - total;
  1179. final int totalWidthR = total - totalExplicitColumnsWidths;
  1180. needsReLayout = true;
  1181. if (expandRatioDivider > 0) {
  1182. // visible columns have some active expand ratios, excess
  1183. // space is divided according to them
  1184. headCells = tHead.iterator();
  1185. i = 0;
  1186. while (headCells.hasNext()) {
  1187. HeaderCell hCell = (HeaderCell) headCells.next();
  1188. if (hCell.getExpandRatio() > 0) {
  1189. int w = widths[i];
  1190. final int newSpace = (int) (extraSpace * (hCell
  1191. .getExpandRatio() / expandRatioDivider));
  1192. w += newSpace;
  1193. widths[i] = w;
  1194. }
  1195. i++;
  1196. }
  1197. } else if (totalWidthR > 0) {
  1198. // no expand ratios defined, we will share extra space
  1199. // relatively to "natural widths" among those without
  1200. // explicit width
  1201. headCells = tHead.iterator();
  1202. i = 0;
  1203. while (headCells.hasNext()) {
  1204. HeaderCell hCell = (HeaderCell) headCells.next();
  1205. if (!hCell.isDefinedWidth()) {
  1206. int w = widths[i];
  1207. final int newSpace = extraSpace * w / totalWidthR;
  1208. w += newSpace;
  1209. widths[i] = w;
  1210. }
  1211. i++;
  1212. }
  1213. }
  1214. } else {
  1215. // bodys size will be more than available and scrollbar will appear
  1216. }
  1217. // last loop: set possibly modified values or reset if new tBody
  1218. i = 0;
  1219. headCells = tHead.iterator();
  1220. while (headCells.hasNext()) {
  1221. final HeaderCell hCell = (HeaderCell) headCells.next();
  1222. if (isNewBody || hCell.getWidth() == -1) {
  1223. final int w = widths[i];
  1224. setColWidth(i, w, false);
  1225. }
  1226. i++;
  1227. }
  1228. initializedAndAttached = true;
  1229. if (needsReLayout) {
  1230. scrollBody.reLayoutComponents();
  1231. }
  1232. updatePageLength();
  1233. /*
  1234. * Fix "natural" height if height is not set. This must be after width
  1235. * fixing so the components' widths have been adjusted.
  1236. */
  1237. if (height == null || "".equals(height)) {
  1238. /*
  1239. * We must force an update of the row height as this point as it
  1240. * might have been (incorrectly) calculated earlier
  1241. */
  1242. int bodyHeight;
  1243. if (pageLength == totalRows) {
  1244. /*
  1245. * A hack to support variable height rows when paging is off.
  1246. * Generally this is not supported by scrolltable. We want to
  1247. * show all rows so the bodyHeight should be equal to the table
  1248. * height.
  1249. */
  1250. // int bodyHeight = scrollBody.getOffsetHeight();
  1251. bodyHeight = scrollBody.getRequiredHeight();
  1252. } else {
  1253. bodyHeight = (int) Math.round(scrollBody.getRowHeight(true)
  1254. * pageLength);
  1255. }
  1256. boolean needsSpaceForHorizontalSrollbar = (total > availW);
  1257. if (needsSpaceForHorizontalSrollbar) {
  1258. bodyHeight += Util.getNativeScrollbarSize();
  1259. }
  1260. scrollBodyPanel.setHeight(bodyHeight + "px");
  1261. Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
  1262. }
  1263. isNewBody = false;
  1264. if (firstvisible > 0) {
  1265. // Deferred due some Firefox oddities. IE & Safari could survive
  1266. // without
  1267. DeferredCommand.addCommand(new Command() {
  1268. public void execute() {
  1269. scrollBodyPanel
  1270. .setScrollPosition((int) (firstvisible * scrollBody
  1271. .getRowHeight()));
  1272. firstRowInViewPort = firstvisible;
  1273. }
  1274. });
  1275. }
  1276. if (enabled) {
  1277. // Do we need cache rows
  1278. if (scrollBody.getLastRendered() + 1 < firstRowInViewPort
  1279. + pageLength + (int) cache_react_rate * pageLength) {
  1280. if (totalRows - 1 > scrollBody.getLastRendered()) {
  1281. // fetch cache rows
  1282. int firstInNewSet = scrollBody.getLastRendered() + 1;
  1283. rowRequestHandler.setReqFirstRow(firstInNewSet);
  1284. int lastInNewSet = (int) (firstRowInViewPort + pageLength + cache_rate
  1285. * pageLength);
  1286. if (lastInNewSet > totalRows - 1) {
  1287. lastInNewSet = totalRows - 1;
  1288. }
  1289. rowRequestHandler.setReqRows(lastInNewSet - firstInNewSet
  1290. + 1);
  1291. rowRequestHandler.deferRowFetch(1);
  1292. }
  1293. }
  1294. }
  1295. /*
  1296. * Ensures the column alignments are correct at initial loading. <br/>
  1297. * (child components widths are correct)
  1298. */
  1299. scrollBody.reLayoutComponents();
  1300. DeferredCommand.addCommand(new Command() {
  1301. public void execute() {
  1302. Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
  1303. }
  1304. });
  1305. }
  1306. /**
  1307. * Note, this method is not official api although declared as protected.
  1308. * Extend at you own risk.
  1309. *
  1310. * @return true if content area will have scrollbars visible.
  1311. */
  1312. protected boolean willHaveScrollbars() {
  1313. if (!(height != null && !height.equals(""))) {
  1314. if (pageLength < totalRows) {
  1315. return true;
  1316. }
  1317. } else {
  1318. int fakeheight = (int) Math.round(scrollBody.getRowHeight()
  1319. * totalRows);
  1320. int availableHeight = scrollBodyPanel.getElement().getPropertyInt(
  1321. "clientHeight");
  1322. if (fakeheight > availableHeight) {
  1323. return true;
  1324. }
  1325. }
  1326. return false;
  1327. }
  1328. private void announceScrollPosition() {
  1329. if (scrollPositionElement == null) {
  1330. scrollPositionElement = DOM.createDiv();
  1331. scrollPositionElement.setClassName(CLASSNAME + "-scrollposition");
  1332. scrollPositionElement.getStyle().setPosition(Position.ABSOLUTE);
  1333. scrollPositionElement.getStyle().setDisplay(Display.NONE);
  1334. getElement().appendChild(scrollPositionElement);
  1335. }
  1336. Style style = scrollPositionElement.getStyle();
  1337. style.setMarginLeft(getElement().getOffsetWidth() / 2 - 80, Unit.PX);
  1338. style.setMarginTop(-scrollBodyPanel.getOffsetHeight(), Unit.PX);
  1339. // indexes go from 1-totalRows, as rowheaders in index-mode indicate
  1340. int last = (firstRowInViewPort + pageLength);
  1341. if (last > totalRows) {
  1342. last = totalRows;
  1343. }
  1344. scrollPositionElement.setInnerHTML("<span>" + (firstRowInViewPort + 1)
  1345. + " &ndash; " + (last) + "..." + "</span>");
  1346. style.setDisplay(Display.BLOCK);
  1347. }
  1348. private void hideScrollPositionAnnotation() {
  1349. if (scrollPositionElement != null) {
  1350. DOM.setStyleAttribute(scrollPositionElement, "display", "none");
  1351. }
  1352. }
  1353. private class RowRequestHandler extends Timer {
  1354. private int reqFirstRow = 0;
  1355. private int reqRows = 0;
  1356. public void deferRowFetch() {
  1357. deferRowFetch(250);
  1358. }
  1359. public void deferRowFetch(int msec) {
  1360. if (reqRows > 0 && reqFirstRow < totalRows) {
  1361. schedule(msec);
  1362. // tell scroll position to user if currently "visible" rows are
  1363. // not rendered
  1364. if ((firstRowInViewPort + pageLength > scrollBody
  1365. .getLastRendered())
  1366. || (firstRowInViewPort < scrollBody.getFirstRendered())) {
  1367. announceScrollPosition();
  1368. } else {
  1369. hideScrollPositionAnnotation();
  1370. }
  1371. }
  1372. }
  1373. public void setReqFirstRow(int reqFirstRow) {
  1374. if (reqFirstRow < 0) {
  1375. reqFirstRow = 0;
  1376. } else if (reqFirstRow >= totalRows) {
  1377. reqFirstRow = totalRows - 1;
  1378. }
  1379. this.reqFirstRow = reqFirstRow;
  1380. }
  1381. public void setReqRows(int reqRows) {
  1382. this.reqRows = reqRows;
  1383. }
  1384. @Override
  1385. public void run() {
  1386. if (client.hasActiveRequest()) {
  1387. // if client connection is busy, don't bother loading it more
  1388. schedule(250);
  1389. } else {
  1390. int firstToBeRendered = scrollBody.firstRendered;
  1391. if (reqFirstRow < firstToBeRendered) {
  1392. firstToBeRendered = reqFirstRow;
  1393. } else if (firstRowInViewPort - (int) (cache_rate * pageLength) > firstToBeRendered) {
  1394. firstToBeRendered = firstRowInViewPort
  1395. - (int) (cache_rate * pageLength);
  1396. if (firstToBeRendered < 0) {
  1397. firstToBeRendered = 0;
  1398. }
  1399. }
  1400. int lastToBeRendered = scrollBody.lastRendered;
  1401. if (reqFirstRow + reqRows - 1 > lastToBeRendered) {
  1402. lastToBeRendered = reqFirstRow + reqRows - 1;
  1403. } else if (firstRowInViewPort + pageLength + pageLength
  1404. * cache_rate < lastToBeRendered) {
  1405. lastToBeRendered = (firstRowInViewPort + pageLength + (int) (pageLength * cache_rate));
  1406. if (lastToBeRendered >= totalRows) {
  1407. lastToBeRendered = totalRows - 1;
  1408. }
  1409. // due Safari 3.1 bug (see #2607), verify reqrows, original
  1410. // problem unknown, but this should catch the issue
  1411. if (reqFirstRow + reqRows - 1 > lastToBeRendered) {
  1412. reqRows = lastToBeRendered - reqFirstRow;
  1413. }
  1414. }
  1415. client.updateVariable(paintableId, "firstToBeRendered",
  1416. firstToBeRendered, false);
  1417. client.updateVariable(paintableId, "lastToBeRendered",
  1418. lastToBeRendered, false);
  1419. // remember which firstvisible we requested, in case the server
  1420. // has
  1421. // a differing opinion
  1422. lastRequestedFirstvisible = firstRowInViewPort;
  1423. client.updateVariable(paintableId, "firstvisible",
  1424. firstRowInViewPort, false);
  1425. client.updateVariable(paintableId, "reqfirstrow", reqFirstRow,
  1426. false);
  1427. client.updateVariable(paintableId, "reqrows", reqRows, true);
  1428. }
  1429. }
  1430. public int getReqFirstRow() {
  1431. return reqFirstRow;
  1432. }
  1433. public int getReqRows() {
  1434. return reqRows;
  1435. }
  1436. /**
  1437. * Sends request to refresh content at this position.
  1438. */
  1439. public void refreshContent() {
  1440. int first = (int) (firstRowInViewPort - pageLength * cache_rate);
  1441. int reqRows = (int) (2 * pageLength * cache_rate + pageLength);
  1442. if (first < 0) {
  1443. reqRows = reqRows + first;
  1444. first = 0;
  1445. }
  1446. setReqFirstRow(first);
  1447. setReqRows(reqRows);
  1448. run();
  1449. }
  1450. }
  1451. public class HeaderCell extends Widget {
  1452. Element td = DOM.createTD();
  1453. Element captionContainer = DOM.createDiv();
  1454. Element colResizeWidget = DOM.createDiv();
  1455. Element floatingCopyOfHeaderCell;
  1456. private boolean sortable = false;
  1457. private final String cid;
  1458. private boolean dragging;
  1459. private int dragStartX;
  1460. private int colIndex;
  1461. private int originalWidth;
  1462. private boolean isResizing;
  1463. private int headerX;
  1464. private boolean moved;
  1465. private int closestSlot;
  1466. private int width = -1;
  1467. private int naturalWidth = -1;
  1468. private char align = ALIGN_LEFT;
  1469. boolean definedWidth = false;
  1470. private float expandRatio = 0;
  1471. public void setSortable(boolean b) {
  1472. sortable = b;
  1473. }
  1474. public void setNaturalMinimumColumnWidth(int w) {
  1475. naturalWidth = w;
  1476. }
  1477. public HeaderCell(String colId, String headerText) {
  1478. cid = colId;
  1479. DOM.setElementProperty(colResizeWidget, "className", CLASSNAME
  1480. + "-resizer");
  1481. DOM.sinkEvents(colResizeWidget, Event.MOUSEEVENTS);
  1482. setText(headerText);
  1483. DOM.appendChild(td, colResizeWidget);
  1484. DOM.setElementProperty(captionContainer, "className", CLASSNAME
  1485. + "-caption-container");
  1486. // ensure no clipping initially (problem on column additions)
  1487. DOM.setStyleAttribute(captionContainer, "overflow", "visible");
  1488. DOM.sinkEvents(captionContainer, Event.MOUSEEVENTS);
  1489. DOM.appendChild(td, captionContainer);
  1490. DOM.sinkEvents(td, Event.MOUSEEVENTS);
  1491. setElement(td);
  1492. }
  1493. public void setWidth(int w, boolean ensureDefinedWidth) {
  1494. if (ensureDefinedWidth) {
  1495. definedWidth = true;
  1496. // on column resize expand ratio becomes zero
  1497. expandRatio = 0;
  1498. }
  1499. if (width == w) {
  1500. return;
  1501. }
  1502. if (width == -1) {
  1503. // go to default mode, clip content if necessary
  1504. DOM.setStyleAttribute(captionContainer, "overflow", "");
  1505. }
  1506. width = w;
  1507. if (w == -1) {
  1508. DOM.setStyleAttribute(captionContainer, "width", "");
  1509. setWidth("");
  1510. } else {
  1511. captionContainer.getStyle().setPropertyPx("width", w);
  1512. /*
  1513. * if we already have tBody, set the header width properly, if
  1514. * not defer it. IE will fail with complex float in table header
  1515. * unless TD width is not explicitly set.
  1516. */
  1517. if (scrollBody != null) {
  1518. int tdWidth = width + scrollBody.getCellExtraWidth();
  1519. setWidth(tdWidth + "px");
  1520. } else {
  1521. DeferredCommand.addCommand(new Command() {
  1522. public void execute() {
  1523. int tdWidth = width
  1524. + scrollBody.getCellExtraWidth();
  1525. setWidth(tdWidth + "px");
  1526. }
  1527. });
  1528. }
  1529. }
  1530. }
  1531. public void setUndefinedWidth() {
  1532. definedWidth = false;
  1533. setWidth(-1, false);
  1534. }
  1535. /**
  1536. * Detects if width is fixed by developer on server side or resized to
  1537. * current width by user.
  1538. *
  1539. * @return true if defined, false if "natural" width
  1540. */
  1541. public boolean isDefinedWidth() {
  1542. return definedWidth;
  1543. }
  1544. public int getWidth() {
  1545. return width;
  1546. }
  1547. public void setText(String headerText) {
  1548. DOM.setInnerHTML(captionContainer, headerText);
  1549. }
  1550. public String getColKey() {
  1551. return cid;
  1552. }
  1553. private void setSorted(boolean sorted) {
  1554. if (sorted) {
  1555. if (sortAscending) {
  1556. this.setStyleName(CLASSNAME + "-header-cell-asc");
  1557. } else {
  1558. this.setStyleName(CLASSNAME + "-header-cell-desc");
  1559. }
  1560. } else {
  1561. this.setStyleName(CLASSNAME + "-header-cell");
  1562. }
  1563. }
  1564. /**
  1565. * Handle column reordering.
  1566. */
  1567. @Override
  1568. public void onBrowserEvent(Event event) {
  1569. if (enabled && event != null) {
  1570. if (isResizing
  1571. || event.getEventTarget().cast() == colResizeWidget) {
  1572. if (dragging && DOM.eventGetType(event) == Event.ONMOUSEUP) {
  1573. // Handle releasing column header on spacer #5318
  1574. handleCaptionEvent(event);
  1575. } else {
  1576. onResizeEvent(event);
  1577. }
  1578. } else {
  1579. handleCaptionEvent(event);
  1580. if (DOM.eventGetType(event) == Event.ONMOUSEUP) {
  1581. scrollBodyPanel.setFocus(true);
  1582. }
  1583. event.stopPropagation();
  1584. event.preventDefault();
  1585. }
  1586. }
  1587. }
  1588. private void createFloatingCopy() {
  1589. floatingCopyOfHeaderCell = DOM.createDiv();
  1590. DOM.setInnerHTML(floatingCopyOfHeaderCell, DOM.getInnerHTML(td));
  1591. floatingCopyOfHeaderCell = DOM
  1592. .getChild(floatingCopyOfHeaderCell, 1);
  1593. DOM.setElementProperty(floatingCopyOfHeaderCell, "className",
  1594. CLASSNAME + "-header-drag");
  1595. updateFloatingCopysPosition(DOM.getAbsoluteLeft(td),
  1596. DOM.getAbsoluteTop(td));
  1597. DOM.appendChild(RootPanel.get().getElement(),
  1598. floatingCopyOfHeaderCell);
  1599. }
  1600. private void updateFloatingCopysPosition(int x, int y) {
  1601. x -= DOM.getElementPropertyInt(floatingCopyOfHeaderCell,
  1602. "offsetWidth") / 2;
  1603. DOM.setStyleAttribute(floatingCopyOfHeaderCell, "left", x + "px");
  1604. if (y > 0) {
  1605. DOM.setStyleAttribute(floatingCopyOfHeaderCell, "top", (y + 7)
  1606. + "px");
  1607. }
  1608. }
  1609. private void hideFloatingCopy() {
  1610. DOM.removeChild(RootPanel.get().getElement(),
  1611. floatingCopyOfHeaderCell);
  1612. floatingCopyOfHeaderCell = null;
  1613. }
  1614. /**
  1615. * Fires a header click event after the user has clicked a column header
  1616. * cell
  1617. *
  1618. * @param event
  1619. * The click event
  1620. */
  1621. private void fireHeaderClickedEvent(Event event) {
  1622. if (client.hasEventListeners(VScrollTable.this,
  1623. HEADER_CLICK_EVENT_ID)) {
  1624. MouseEventDetails details = new MouseEventDetails(event);
  1625. client.updateVariable(paintableId, "headerClickEvent",
  1626. details.toString(), false);
  1627. client.updateVariable(paintableId, "headerClickCID", cid, true);
  1628. }
  1629. }
  1630. protected void handleCaptionEvent(Event event) {
  1631. switch (DOM.eventGetType(event)) {
  1632. case Event.ONMOUSEDOWN:
  1633. if (columnReordering) {
  1634. dragging = true;
  1635. moved = false;
  1636. colIndex = getColIndexByKey(cid);
  1637. DOM.setCapture(getElement());
  1638. headerX = tHead.getAbsoluteLeft();
  1639. DOM.eventPreventDefault(event); // prevent selecting text
  1640. }
  1641. break;
  1642. case Event.ONMOUSEUP:
  1643. if (columnReordering) {
  1644. dragging = false;
  1645. DOM.releaseCapture(getElement());
  1646. if (moved) {
  1647. hideFloatingCopy();
  1648. tHead.removeSlotFocus();
  1649. if (closestSlot != colIndex
  1650. && closestSlot != (colIndex + 1)) {
  1651. if (closestSlot > colIndex) {
  1652. reOrderColumn(cid, closestSlot - 1);
  1653. } else {
  1654. reOrderColumn(cid, closestSlot);
  1655. }
  1656. }
  1657. }
  1658. }
  1659. if (!moved) {
  1660. // mouse event was a click to header -> sort column
  1661. if (sortable) {
  1662. if (sortColumn.equals(cid)) {
  1663. // just toggle order
  1664. client.updateVariable(paintableId, "sortascending",
  1665. !sortAscending, false);
  1666. } else {
  1667. // set table scrolled by this column
  1668. client.updateVariable(paintableId, "sortcolumn",
  1669. cid, false);
  1670. }
  1671. // get also cache columns at the same request
  1672. scrollBodyPanel.setScrollPosition(0);
  1673. firstvisible = 0;
  1674. rowRequestHandler.setReqFirstRow(0);
  1675. rowRequestHandler.setReqRows((int) (2 * pageLength
  1676. * cache_rate + pageLength));
  1677. rowRequestHandler.deferRowFetch();
  1678. }
  1679. fireHeaderClickedEvent(event);
  1680. break;
  1681. }
  1682. break;
  1683. case Event.ONMOUSEMOVE:
  1684. if (dragging) {
  1685. if (!moved) {
  1686. createFloatingCopy();
  1687. moved = true;
  1688. }
  1689. final int x = DOM.eventGetClientX(event)
  1690. + DOM.getElementPropertyInt(tHead.hTableWrapper,
  1691. "scrollLeft");
  1692. int slotX = headerX;
  1693. closestSlot = colIndex;
  1694. int closestDistance = -1;
  1695. int start = 0;
  1696. if (showRowHeaders) {
  1697. start++;
  1698. }
  1699. final int visibleCellCount = tHead.getVisibleCellCount();
  1700. for (int i = start; i <= visibleCellCount; i++) {
  1701. if (i > 0) {
  1702. final String colKey = getColKeyByIndex(i - 1);
  1703. slotX += getColWidth(colKey);
  1704. }
  1705. final int dist = Math.abs(x - slotX);
  1706. if (closestDistance == -1 || dist < closestDistance) {
  1707. closestDistance = dist;
  1708. closestSlot = i;
  1709. }
  1710. }
  1711. tHead.focusSlot(closestSlot);
  1712. updateFloatingCopysPosition(DOM.eventGetClientX(event), -1);
  1713. }
  1714. break;
  1715. default:
  1716. break;
  1717. }
  1718. }
  1719. private void onResizeEvent(Event event) {
  1720. switch (DOM.eventGetType(event)) {
  1721. case Event.ONMOUSEDOWN:
  1722. isResizing = true;
  1723. DOM.setCapture(getElement());
  1724. dragStartX = DOM.eventGetClientX(event);
  1725. colIndex = getColIndexByKey(cid);
  1726. originalWidth = getWidth();
  1727. DOM.eventPreventDefault(event);
  1728. break;
  1729. case Event.ONMOUSEUP:
  1730. isResizing = false;
  1731. DOM.releaseCapture(getElement());
  1732. // readjust undefined width columns
  1733. lazyAdjustColumnWidths.cancel();
  1734. lazyAdjustColumnWidths.schedule(1);
  1735. fireColumnResizeEvent(cid, originalWidth, getColWidth(cid));
  1736. break;
  1737. case Event.ONMOUSEMOVE:
  1738. if (isResizing) {
  1739. final int deltaX = DOM.eventGetClientX(event) - dragStartX;
  1740. if (deltaX == 0) {
  1741. return;
  1742. }
  1743. int newWidth = originalWidth + deltaX;
  1744. if (newWidth < scrollBody.getCellExtraWidth()) {
  1745. newWidth = scrollBody.getCellExtraWidth();
  1746. }
  1747. setColWidth(colIndex, newWidth, true);
  1748. }
  1749. break;
  1750. default:
  1751. break;
  1752. }
  1753. }
  1754. public String getCaption() {
  1755. return DOM.getInnerText(captionContainer);
  1756. }
  1757. public boolean isEnabled() {
  1758. return getParent() != null;
  1759. }
  1760. public void setAlign(char c) {
  1761. if (align != c) {
  1762. switch (c) {
  1763. case ALIGN_CENTER:
  1764. DOM.setStyleAttribute(captionContainer, "textAlign",
  1765. "center");
  1766. break;
  1767. case ALIGN_RIGHT:
  1768. DOM.setStyleAttribute(captionContainer, "textAlign",
  1769. "right");
  1770. break;
  1771. default:
  1772. DOM.setStyleAttribute(captionContainer, "textAlign", "");
  1773. break;
  1774. }
  1775. }
  1776. align = c;
  1777. }
  1778. public char getAlign() {
  1779. return align;
  1780. }
  1781. /**
  1782. * Detects the natural minimum width for the column of this header cell.
  1783. * If column is resized by user or the width is defined by server the
  1784. * actual width is returned. Else the natural min width is returned.
  1785. *
  1786. * @param columnIndex
  1787. * column index hint, if -1 (unknown) it will be detected
  1788. *
  1789. * @return
  1790. */
  1791. public int getNaturalColumnWidth(int columnIndex) {
  1792. if (isDefinedWidth()) {
  1793. return width;
  1794. } else {
  1795. if (naturalWidth < 0) {
  1796. // This is recently revealed column. Try to detect a proper
  1797. // value (greater of header and data
  1798. // cols)
  1799. final int hw = ((Element) getElement().getLastChild())
  1800. .getOffsetWidth() + scrollBody.getCellExtraWidth();
  1801. if (columnIndex < 0) {
  1802. columnIndex = 0;
  1803. for (Iterator<Widget> it = tHead.iterator(); it
  1804. .hasNext(); columnIndex++) {
  1805. if (it.next() == this) {
  1806. break;
  1807. }
  1808. }
  1809. }
  1810. final int cw = scrollBody.getColWidth(columnIndex);
  1811. naturalWidth = (hw > cw ? hw : cw);
  1812. }
  1813. return naturalWidth;
  1814. }
  1815. }
  1816. public void setExpandRatio(float floatAttribute) {
  1817. expandRatio = floatAttribute;
  1818. }
  1819. public float getExpandRatio() {
  1820. return expandRatio;
  1821. }
  1822. }
  1823. /**
  1824. * HeaderCell that is header cell for row headers.
  1825. *
  1826. * Reordering disabled and clicking on it resets sorting.
  1827. */
  1828. public class RowHeadersHeaderCell extends HeaderCell {
  1829. RowHeadersHeaderCell() {
  1830. super("0", "");
  1831. }
  1832. @Override
  1833. protected void handleCaptionEvent(Event event) {
  1834. // NOP: RowHeaders cannot be reordered
  1835. // TODO It'd be nice to reset sorting here
  1836. }
  1837. }
  1838. public class TableHead extends Panel implements ActionOwner {
  1839. private static final int WRAPPER_WIDTH = 900000;
  1840. ArrayList<Widget> visibleCells = new ArrayList<Widget>();
  1841. HashMap<String, HeaderCell> availableCells = new HashMap<String, HeaderCell>();
  1842. Element div = DOM.createDiv();
  1843. Element hTableWrapper = DOM.createDiv();
  1844. Element hTableContainer = DOM.createDiv();
  1845. Element table = DOM.createTable();
  1846. Element headerTableBody = DOM.createTBody();
  1847. Element tr = DOM.createTR();
  1848. private final Element columnSelector = DOM.createDiv();
  1849. private int focusedSlot = -1;
  1850. public TableHead() {
  1851. if (BrowserInfo.get().isIE()) {
  1852. table.setPropertyInt("cellSpacing", 0);
  1853. }
  1854. DOM.setStyleAttribute(hTableWrapper, "overflow", "hidden");
  1855. DOM.setElementProperty(hTableWrapper, "className", CLASSNAME
  1856. + "-header");
  1857. // TODO move styles to CSS
  1858. DOM.setElementProperty(columnSelector, "className", CLASSNAME
  1859. + "-column-selector");
  1860. DOM.setStyleAttribute(columnSelector, "display", "none");
  1861. DOM.appendChild(table, headerTableBody);
  1862. DOM.appendChild(headerTableBody, tr);
  1863. DOM.appendChild(hTableContainer, table);
  1864. DOM.appendChild(hTableWrapper, hTableContainer);
  1865. DOM.appendChild(div, hTableWrapper);
  1866. DOM.appendChild(div, columnSelector);
  1867. setElement(div);
  1868. setStyleName(CLASSNAME + "-header-wrap");
  1869. DOM.sinkEvents(columnSelector, Event.ONCLICK);
  1870. availableCells.put("0", new RowHeadersHeaderCell());
  1871. }
  1872. @Override
  1873. public void clear() {
  1874. for (String cid : availableCells.keySet()) {
  1875. removeCell(cid);
  1876. }
  1877. availableCells.clear();
  1878. availableCells.put("0", new RowHeadersHeaderCell());
  1879. }
  1880. public void updateCellsFromUIDL(UIDL uidl) {
  1881. Iterator<?> it = uidl.getChildIterator();
  1882. HashSet<String> updated = new HashSet<String>();
  1883. updated.add("0");
  1884. while (it.hasNext()) {
  1885. final UIDL col = (UIDL) it.next();
  1886. final String cid = col.getStringAttribute("cid");
  1887. updated.add(cid);
  1888. String caption = buildCaptionHtmlSnippet(col);
  1889. HeaderCell c = getHeaderCell(cid);
  1890. if (c == null) {
  1891. c = new HeaderCell(cid, caption);
  1892. availableCells.put(cid, c);
  1893. if (initializedAndAttached) {
  1894. // we will need a column width recalculation
  1895. initializedAndAttached = false;
  1896. initialContentReceived = false;
  1897. isNewBody = true;
  1898. }
  1899. } else {
  1900. c.setText(caption);
  1901. }
  1902. if (col.hasAttribute("sortable")) {
  1903. c.setSortable(true);
  1904. if (cid.equals(sortColumn)) {
  1905. c.setSorted(true);
  1906. } else {
  1907. c.setSorted(false);
  1908. }
  1909. } else {
  1910. c.setSortable(false);
  1911. }
  1912. if (col.hasAttribute("align")) {
  1913. c.setAlign(col.getStringAttribute("align").charAt(0));
  1914. }
  1915. if (col.hasAttribute("width")) {
  1916. final String width = col.getStringAttribute("width");
  1917. c.setWidth(Integer.parseInt(width), true);
  1918. } else if (recalcWidths) {
  1919. c.setUndefinedWidth();
  1920. }
  1921. if (col.hasAttribute("er")) {
  1922. c.setExpandRatio(col.getFloatAttribute("er"));
  1923. }
  1924. if (col.hasAttribute("collapsed")) {
  1925. // ensure header is properly removed from parent (case when
  1926. // collapsing happens via servers side api)
  1927. if (c.isAttached()) {
  1928. c.removeFromParent();
  1929. headerChangedDuringUpdate = true;
  1930. }
  1931. }
  1932. }
  1933. // check for orphaned header cells
  1934. for (Iterator<String> cit = availableCells.keySet().iterator(); cit
  1935. .hasNext();) {
  1936. String cid = cit.next();
  1937. if (!updated.contains(cid)) {
  1938. removeCell(cid);
  1939. cit.remove();
  1940. }
  1941. }
  1942. }
  1943. public void enableColumn(String cid, int index) {
  1944. final HeaderCell c = getHeaderCell(cid);
  1945. if (!c.isEnabled() || getHeaderCell(index) != c) {
  1946. setHeaderCell(index, c);
  1947. if (initializedAndAttached) {
  1948. headerChangedDuringUpdate = true;
  1949. }
  1950. }
  1951. }
  1952. public int getVisibleCellCount() {
  1953. return visibleCells.size();
  1954. }
  1955. public void setHorizontalScrollPosition(int scrollLeft) {
  1956. if (BrowserInfo.get().isIE6()) {
  1957. hTableWrapper.getStyle().setProperty("position", "relative");
  1958. hTableWrapper.getStyle().setPropertyPx("left", -scrollLeft);
  1959. } else {
  1960. hTableWrapper.setScrollLeft(scrollLeft);
  1961. }
  1962. }
  1963. public void setColumnCollapsingAllowed(boolean cc) {
  1964. if (cc) {
  1965. DOM.setStyleAttribute(columnSelector, "display", "block");
  1966. } else {
  1967. DOM.setStyleAttribute(columnSelector, "display", "none");
  1968. }
  1969. }
  1970. public void disableBrowserIntelligence() {
  1971. DOM.setStyleAttribute(hTableContainer, "width", WRAPPER_WIDTH
  1972. + "px");
  1973. }
  1974. public void enableBrowserIntelligence() {
  1975. DOM.setStyleAttribute(hTableContainer, "width", "");
  1976. }
  1977. public void setHeaderCell(int index, HeaderCell cell) {
  1978. if (cell.isEnabled()) {
  1979. // we're moving the cell
  1980. DOM.removeChild(tr, cell.getElement());
  1981. orphan(cell);
  1982. }
  1983. if (index < visibleCells.size()) {
  1984. // insert to right slot
  1985. DOM.insertChild(tr, cell.getElement(), index);
  1986. adopt(cell);
  1987. visibleCells.add(index, cell);
  1988. } else if (index == visibleCells.size()) {
  1989. // simply append
  1990. DOM.appendChild(tr, cell.getElement());
  1991. adopt(cell);
  1992. visibleCells.add(cell);
  1993. } else {
  1994. throw new RuntimeException(
  1995. "Header cells must be appended in order");
  1996. }
  1997. }
  1998. public HeaderCell getHeaderCell(int index) {
  1999. if (index < visibleCells.size()) {
  2000. return (HeaderCell) visibleCells.get(index);
  2001. } else {
  2002. return null;
  2003. }
  2004. }
  2005. /**
  2006. * Get's HeaderCell by it's column Key.
  2007. *
  2008. * Note that this returns HeaderCell even if it is currently collapsed.
  2009. *
  2010. * @param cid
  2011. * Column key of accessed HeaderCell
  2012. * @return HeaderCell
  2013. */
  2014. public HeaderCell getHeaderCell(String cid) {
  2015. return availableCells.get(cid);
  2016. }
  2017. public void moveCell(int oldIndex, int newIndex) {
  2018. final HeaderCell hCell = getHeaderCell(oldIndex);
  2019. final Element cell = hCell.getElement();
  2020. visibleCells.remove(oldIndex);
  2021. DOM.removeChild(tr, cell);
  2022. DOM.insertChild(tr, cell, newIndex);
  2023. visibleCells.add(newIndex, hCell);
  2024. }
  2025. public Iterator<Widget> iterator() {
  2026. return visibleCells.iterator();
  2027. }
  2028. @Override
  2029. public boolean remove(Widget w) {
  2030. if (visibleCells.contains(w)) {
  2031. visibleCells.remove(w);
  2032. orphan(w);
  2033. DOM.removeChild(DOM.getParent(w.getElement()), w.getElement());
  2034. return true;
  2035. }
  2036. return false;
  2037. }
  2038. public void removeCell(String colKey) {
  2039. final HeaderCell c = getHeaderCell(colKey);
  2040. remove(c);
  2041. }
  2042. private void focusSlot(int index) {
  2043. removeSlotFocus();
  2044. if (index > 0) {
  2045. DOM.setElementProperty(
  2046. DOM.getFirstChild(DOM.getChild(tr, index - 1)),
  2047. "className", CLASSNAME + "-resizer " + CLASSNAME
  2048. + "-focus-slot-right");
  2049. } else {
  2050. DOM.setElementProperty(
  2051. DOM.getFirstChild(DOM.getChild(tr, index)),
  2052. "className", CLASSNAME + "-resizer " + CLASSNAME
  2053. + "-focus-slot-left");
  2054. }
  2055. focusedSlot = index;
  2056. }
  2057. private void removeSlotFocus() {
  2058. if (focusedSlot < 0) {
  2059. return;
  2060. }
  2061. if (focusedSlot == 0) {
  2062. DOM.setElementProperty(
  2063. DOM.getFirstChild(DOM.getChild(tr, focusedSlot)),
  2064. "className", CLASSNAME + "-resizer");
  2065. } else if (focusedSlot > 0) {
  2066. DOM.setElementProperty(
  2067. DOM.getFirstChild(DOM.getChild(tr, focusedSlot - 1)),
  2068. "className", CLASSNAME + "-resizer");
  2069. }
  2070. focusedSlot = -1;
  2071. }
  2072. @Override
  2073. public void onBrowserEvent(Event event) {
  2074. if (enabled) {
  2075. if (event.getEventTarget().cast() == columnSelector) {
  2076. final int left = DOM.getAbsoluteLeft(columnSelector);
  2077. final int top = DOM.getAbsoluteTop(columnSelector)
  2078. + DOM.getElementPropertyInt(columnSelector,
  2079. "offsetHeight");
  2080. client.getContextMenu().showAt(this, left, top);
  2081. }
  2082. }
  2083. }
  2084. @Override
  2085. protected void onDetach() {
  2086. super.onDetach();
  2087. if (client != null) {
  2088. client.getContextMenu().ensureHidden(this);
  2089. }
  2090. }
  2091. class VisibleColumnAction extends Action {
  2092. String colKey;
  2093. private boolean collapsed;
  2094. public VisibleColumnAction(String colKey) {
  2095. super(VScrollTable.TableHead.this);
  2096. this.colKey = colKey;
  2097. caption = tHead.getHeaderCell(colKey).getCaption();
  2098. }
  2099. @Override
  2100. public void execute() {
  2101. client.getContextMenu().hide();
  2102. // toggle selected column
  2103. if (collapsedColumns.contains(colKey)) {
  2104. collapsedColumns.remove(colKey);
  2105. } else {
  2106. tHead.removeCell(colKey);
  2107. collapsedColumns.add(colKey);
  2108. lazyAdjustColumnWidths.schedule(1);
  2109. }
  2110. // update variable to server
  2111. client.updateVariable(paintableId, "collapsedcolumns",
  2112. collapsedColumns.toArray(new String[collapsedColumns
  2113. .size()]), false);
  2114. // let rowRequestHandler determine proper rows
  2115. rowRequestHandler.refreshContent();
  2116. }
  2117. public void setCollapsed(boolean b) {
  2118. collapsed = b;
  2119. }
  2120. /**
  2121. * Override default method to distinguish on/off columns
  2122. */
  2123. @Override
  2124. public String getHTML() {
  2125. final StringBuffer buf = new StringBuffer();
  2126. if (collapsed) {
  2127. buf.append("<span class=\"v-off\">");
  2128. } else {
  2129. buf.append("<span class=\"v-on\">");
  2130. }
  2131. buf.append(super.getHTML());
  2132. buf.append("</span>");
  2133. return buf.toString();
  2134. }
  2135. }
  2136. /*
  2137. * Returns columns as Action array for column select popup
  2138. */
  2139. public Action[] getActions() {
  2140. Object[] cols;
  2141. if (columnReordering && columnOrder != null) {
  2142. cols = columnOrder;
  2143. } else {
  2144. // if columnReordering is disabled, we need different way to get
  2145. // all available columns
  2146. cols = visibleColOrder;
  2147. cols = new Object[visibleColOrder.length
  2148. + collapsedColumns.size()];
  2149. int i;
  2150. for (i = 0; i < visibleColOrder.length; i++) {
  2151. cols[i] = visibleColOrder[i];
  2152. }
  2153. for (final Iterator<String> it = collapsedColumns.iterator(); it
  2154. .hasNext();) {
  2155. cols[i++] = it.next();
  2156. }
  2157. }
  2158. final Action[] actions = new Action[cols.length];
  2159. for (int i = 0; i < cols.length; i++) {
  2160. final String cid = (String) cols[i];
  2161. final HeaderCell c = getHeaderCell(cid);
  2162. final VisibleColumnAction a = new VisibleColumnAction(
  2163. c.getColKey());
  2164. a.setCaption(c.getCaption());
  2165. if (!c.isEnabled()) {
  2166. a.setCollapsed(true);
  2167. }
  2168. actions[i] = a;
  2169. }
  2170. return actions;
  2171. }
  2172. public ApplicationConnection getClient() {
  2173. return client;
  2174. }
  2175. public String getPaintableId() {
  2176. return paintableId;
  2177. }
  2178. /**
  2179. * Returns column alignments for visible columns
  2180. */
  2181. public char[] getColumnAlignments() {
  2182. final Iterator<Widget> it = visibleCells.iterator();
  2183. final char[] aligns = new char[visibleCells.size()];
  2184. int colIndex = 0;
  2185. while (it.hasNext()) {
  2186. aligns[colIndex++] = ((HeaderCell) it.next()).getAlign();
  2187. }
  2188. return aligns;
  2189. }
  2190. }
  2191. /**
  2192. * A cell in the footer
  2193. */
  2194. public class FooterCell extends Widget {
  2195. private Element td = DOM.createTD();
  2196. private Element captionContainer = DOM.createDiv();
  2197. private char align = ALIGN_LEFT;
  2198. private int width = -1;
  2199. private float expandRatio = 0;
  2200. private String cid;
  2201. boolean definedWidth = false;
  2202. private int naturalWidth = -1;
  2203. public FooterCell(String colId, String headerText) {
  2204. cid = colId;
  2205. setText(headerText);
  2206. DOM.setElementProperty(captionContainer, "className", CLASSNAME
  2207. + "-footer-container");
  2208. // ensure no clipping initially (problem on column additions)
  2209. DOM.setStyleAttribute(captionContainer, "overflow", "visible");
  2210. DOM.sinkEvents(captionContainer, Event.MOUSEEVENTS);
  2211. DOM.appendChild(td, captionContainer);
  2212. DOM.sinkEvents(td, Event.MOUSEEVENTS);
  2213. setElement(td);
  2214. }
  2215. /**
  2216. * Sets the text of the footer
  2217. *
  2218. * @param footerText
  2219. * The text in the footer
  2220. */
  2221. public void setText(String footerText) {
  2222. DOM.setInnerHTML(captionContainer, footerText);
  2223. }
  2224. /**
  2225. * Set alignment of the text in the cell
  2226. *
  2227. * @param c
  2228. * The alignment which can be ALIGN_CENTER, ALIGN_LEFT,
  2229. * ALIGN_RIGHT
  2230. */
  2231. public void setAlign(char c) {
  2232. if (align != c) {
  2233. switch (c) {
  2234. case ALIGN_CENTER:
  2235. DOM.setStyleAttribute(captionContainer, "textAlign",
  2236. "center");
  2237. break;
  2238. case ALIGN_RIGHT:
  2239. DOM.setStyleAttribute(captionContainer, "textAlign",
  2240. "right");
  2241. break;
  2242. default:
  2243. DOM.setStyleAttribute(captionContainer, "textAlign", "");
  2244. break;
  2245. }
  2246. }
  2247. align = c;
  2248. }
  2249. /**
  2250. * Get the alignment of the text int the cell
  2251. *
  2252. * @return Returns either ALIGN_CENTER, ALIGN_LEFT or ALIGN_RIGHT
  2253. */
  2254. public char getAlign() {
  2255. return align;
  2256. }
  2257. /**
  2258. * Sets the width of the cell
  2259. *
  2260. * @param w
  2261. * The width of the cell
  2262. * @param ensureDefinedWidth
  2263. * Ensures the the given width is not recalculated
  2264. */
  2265. public void setWidth(int w, boolean ensureDefinedWidth) {
  2266. if (ensureDefinedWidth) {
  2267. definedWidth = true;
  2268. // on column resize expand ratio becomes zero
  2269. expandRatio = 0;
  2270. }
  2271. if (width == w) {
  2272. return;
  2273. }
  2274. if (width == -1) {
  2275. // go to default mode, clip content if necessary
  2276. DOM.setStyleAttribute(captionContainer, "overflow", "");
  2277. }
  2278. width = w;
  2279. if (w == -1) {
  2280. DOM.setStyleAttribute(captionContainer, "width", "");
  2281. setWidth("");
  2282. } else {
  2283. /*
  2284. * Reduce width with one pixel for the right border since the
  2285. * footers does not have any spacers between them.
  2286. */
  2287. int borderWidths = 1;
  2288. // Set the container width (check for negative value)
  2289. if (w - borderWidths >= 0) {
  2290. captionContainer.getStyle().setPropertyPx("width",
  2291. w - borderWidths);
  2292. } else {
  2293. captionContainer.getStyle().setPropertyPx("width", 0);
  2294. }
  2295. /*
  2296. * if we already have tBody, set the header width properly, if
  2297. * not defer it. IE will fail with complex float in table header
  2298. * unless TD width is not explicitly set.
  2299. */
  2300. if (scrollBody != null) {
  2301. /*
  2302. * Reduce with one since footer does not have any spacers,
  2303. * instead a 1 pixel border.
  2304. */
  2305. int tdWidth = width + scrollBody.getCellExtraWidth()
  2306. - borderWidths;
  2307. setWidth(tdWidth + "px");
  2308. } else {
  2309. DeferredCommand.addCommand(new Command() {
  2310. public void execute() {
  2311. int borderWidths = 1;
  2312. int tdWidth = width
  2313. + scrollBody.getCellExtraWidth()
  2314. - borderWidths;
  2315. setWidth(tdWidth + "px");
  2316. }
  2317. });
  2318. }
  2319. }
  2320. }
  2321. /**
  2322. * Sets the width to undefined
  2323. */
  2324. public void setUndefinedWidth() {
  2325. setWidth(-1, false);
  2326. }
  2327. /**
  2328. * Detects if width is fixed by developer on server side or resized to
  2329. * current width by user.
  2330. *
  2331. * @return true if defined, false if "natural" width
  2332. */
  2333. public boolean isDefinedWidth() {
  2334. return definedWidth;
  2335. }
  2336. /**
  2337. * Returns the pixels width of the footer cell
  2338. *
  2339. * @return The width in pixels
  2340. */
  2341. public int getWidth() {
  2342. return width;
  2343. }
  2344. /**
  2345. * Sets the expand ratio of the cell
  2346. *
  2347. * @param floatAttribute
  2348. * The expand ratio
  2349. */
  2350. public void setExpandRatio(float floatAttribute) {
  2351. expandRatio = floatAttribute;
  2352. }
  2353. /**
  2354. * Returns the expand ration of the cell
  2355. *
  2356. * @return The expand ratio
  2357. */
  2358. public float getExpandRatio() {
  2359. return expandRatio;
  2360. }
  2361. /**
  2362. * Is the cell enabled?
  2363. *
  2364. * @return True if enabled else False
  2365. */
  2366. public boolean isEnabled() {
  2367. return getParent() != null;
  2368. }
  2369. /**
  2370. * Handle column clicking
  2371. */
  2372. @Override
  2373. public void onBrowserEvent(Event event) {
  2374. if (enabled && event != null) {
  2375. handleCaptionEvent(event);
  2376. if (DOM.eventGetType(event) == Event.ONMOUSEUP) {
  2377. scrollBodyPanel.setFocus(true);
  2378. }
  2379. }
  2380. }
  2381. /**
  2382. * Handles a event on the captions
  2383. *
  2384. * @param event
  2385. * The event to handle
  2386. */
  2387. protected void handleCaptionEvent(Event event) {
  2388. if (DOM.eventGetType(event) == Event.ONMOUSEUP) {
  2389. fireFooterClickedEvent(event);
  2390. }
  2391. }
  2392. /**
  2393. * Fires a footer click event after the user has clicked a column footer
  2394. * cell
  2395. *
  2396. * @param event
  2397. * The click event
  2398. */
  2399. private void fireFooterClickedEvent(Event event) {
  2400. if (client.hasEventListeners(VScrollTable.this,
  2401. FOOTER_CLICK_EVENT_ID)) {
  2402. MouseEventDetails details = new MouseEventDetails(event);
  2403. client.updateVariable(paintableId, "footerClickEvent",
  2404. details.toString(), false);
  2405. client.updateVariable(paintableId, "footerClickCID", cid, true);
  2406. }
  2407. }
  2408. /**
  2409. * Returns the column key of the column
  2410. *
  2411. * @return The column key
  2412. */
  2413. public String getColKey() {
  2414. return cid;
  2415. }
  2416. /**
  2417. * Detects the natural minimum width for the column of this header cell.
  2418. * If column is resized by user or the width is defined by server the
  2419. * actual width is returned. Else the natural min width is returned.
  2420. *
  2421. * @param columnIndex
  2422. * column index hint, if -1 (unknown) it will be detected
  2423. *
  2424. * @return
  2425. */
  2426. public int getNaturalColumnWidth(int columnIndex) {
  2427. if (isDefinedWidth()) {
  2428. return width;
  2429. } else {
  2430. if (naturalWidth < 0) {
  2431. // This is recently revealed column. Try to detect a proper
  2432. // value (greater of header and data
  2433. // cols)
  2434. final int hw = ((Element) getElement().getLastChild())
  2435. .getOffsetWidth() + scrollBody.getCellExtraWidth();
  2436. if (columnIndex < 0) {
  2437. columnIndex = 0;
  2438. for (Iterator<Widget> it = tHead.iterator(); it
  2439. .hasNext(); columnIndex++) {
  2440. if (it.next() == this) {
  2441. break;
  2442. }
  2443. }
  2444. }
  2445. final int cw = scrollBody.getColWidth(columnIndex);
  2446. naturalWidth = (hw > cw ? hw : cw);
  2447. }
  2448. return naturalWidth;
  2449. }
  2450. }
  2451. public void setNaturalMinimumColumnWidth(int w) {
  2452. naturalWidth = w;
  2453. }
  2454. }
  2455. /**
  2456. * HeaderCell that is header cell for row headers.
  2457. *
  2458. * Reordering disabled and clicking on it resets sorting.
  2459. */
  2460. public class RowHeadersFooterCell extends FooterCell {
  2461. RowHeadersFooterCell() {
  2462. super("0", "");
  2463. }
  2464. @Override
  2465. protected void handleCaptionEvent(Event event) {
  2466. // NOP: RowHeaders cannot be reordered
  2467. // TODO It'd be nice to reset sorting here
  2468. }
  2469. }
  2470. /**
  2471. * The footer of the table which can be seen in the bottom of the Table.
  2472. */
  2473. public class TableFooter extends Panel {
  2474. private static final int WRAPPER_WIDTH = 900000;
  2475. ArrayList<Widget> visibleCells = new ArrayList<Widget>();
  2476. HashMap<String, FooterCell> availableCells = new HashMap<String, FooterCell>();
  2477. Element div = DOM.createDiv();
  2478. Element hTableWrapper = DOM.createDiv();
  2479. Element hTableContainer = DOM.createDiv();
  2480. Element table = DOM.createTable();
  2481. Element headerTableBody = DOM.createTBody();
  2482. Element tr = DOM.createTR();
  2483. public TableFooter() {
  2484. DOM.setStyleAttribute(hTableWrapper, "overflow", "hidden");
  2485. DOM.setElementProperty(hTableWrapper, "className", CLASSNAME
  2486. + "-footer");
  2487. DOM.appendChild(table, headerTableBody);
  2488. DOM.appendChild(headerTableBody, tr);
  2489. DOM.appendChild(hTableContainer, table);
  2490. DOM.appendChild(hTableWrapper, hTableContainer);
  2491. DOM.appendChild(div, hTableWrapper);
  2492. setElement(div);
  2493. setStyleName(CLASSNAME + "-footer-wrap");
  2494. availableCells.put("0", new RowHeadersFooterCell());
  2495. }
  2496. @Override
  2497. public void clear() {
  2498. for (String cid : availableCells.keySet()) {
  2499. removeCell(cid);
  2500. }
  2501. availableCells.clear();
  2502. availableCells.put("0", new RowHeadersFooterCell());
  2503. }
  2504. /*
  2505. * (non-Javadoc)
  2506. *
  2507. * @see
  2508. * com.google.gwt.user.client.ui.Panel#remove(com.google.gwt.user.client
  2509. * .ui.Widget)
  2510. */
  2511. @Override
  2512. public boolean remove(Widget w) {
  2513. if (visibleCells.contains(w)) {
  2514. visibleCells.remove(w);
  2515. orphan(w);
  2516. DOM.removeChild(DOM.getParent(w.getElement()), w.getElement());
  2517. return true;
  2518. }
  2519. return false;
  2520. }
  2521. /*
  2522. * (non-Javadoc)
  2523. *
  2524. * @see com.google.gwt.user.client.ui.HasWidgets#iterator()
  2525. */
  2526. public Iterator<Widget> iterator() {
  2527. return visibleCells.iterator();
  2528. }
  2529. /**
  2530. * Gets a footer cell which represents the given columnId
  2531. *
  2532. * @param cid
  2533. * The columnId
  2534. *
  2535. * @return The cell
  2536. */
  2537. public FooterCell getFooterCell(String cid) {
  2538. return availableCells.get(cid);
  2539. }
  2540. /**
  2541. * Gets a footer cell by using a column index
  2542. *
  2543. * @param index
  2544. * The index of the column
  2545. * @return The Cell
  2546. */
  2547. public FooterCell getFooterCell(int index) {
  2548. if (index < visibleCells.size()) {
  2549. return (FooterCell) visibleCells.get(index);
  2550. } else {
  2551. return null;
  2552. }
  2553. }
  2554. /**
  2555. * Updates the cells contents when updateUIDL request is received
  2556. *
  2557. * @param uidl
  2558. * The UIDL
  2559. */
  2560. public void updateCellsFromUIDL(UIDL uidl) {
  2561. Iterator<?> columnIterator = uidl.getChildIterator();
  2562. HashSet<String> updated = new HashSet<String>();
  2563. updated.add("0");
  2564. while (columnIterator.hasNext()) {
  2565. final UIDL col = (UIDL) columnIterator.next();
  2566. final String cid = col.getStringAttribute("cid");
  2567. updated.add(cid);
  2568. String caption = col.getStringAttribute("fcaption");
  2569. FooterCell c = getFooterCell(cid);
  2570. if (c == null) {
  2571. c = new FooterCell(cid, caption);
  2572. availableCells.put(cid, c);
  2573. if (initializedAndAttached) {
  2574. // we will need a column width recalculation
  2575. initializedAndAttached = false;
  2576. initialContentReceived = false;
  2577. isNewBody = true;
  2578. }
  2579. } else {
  2580. c.setText(caption);
  2581. }
  2582. if (col.hasAttribute("align")) {
  2583. c.setAlign(col.getStringAttribute("align").charAt(0));
  2584. }
  2585. if (col.hasAttribute("width")) {
  2586. final String width = col.getStringAttribute("width");
  2587. c.setWidth(Integer.parseInt(width), true);
  2588. } else if (recalcWidths) {
  2589. c.setUndefinedWidth();
  2590. }
  2591. if (col.hasAttribute("er")) {
  2592. c.setExpandRatio(col.getFloatAttribute("er"));
  2593. }
  2594. if (col.hasAttribute("collapsed")) {
  2595. // ensure header is properly removed from parent (case when
  2596. // collapsing happens via servers side api)
  2597. if (c.isAttached()) {
  2598. c.removeFromParent();
  2599. headerChangedDuringUpdate = true;
  2600. }
  2601. }
  2602. }
  2603. // check for orphaned header cells
  2604. for (Iterator<String> cit = availableCells.keySet().iterator(); cit
  2605. .hasNext();) {
  2606. String cid = cit.next();
  2607. if (!updated.contains(cid)) {
  2608. removeCell(cid);
  2609. cit.remove();
  2610. }
  2611. }
  2612. }
  2613. /**
  2614. * Set a footer cell for a specified column index
  2615. *
  2616. * @param index
  2617. * The index
  2618. * @param cell
  2619. * The footer cell
  2620. */
  2621. public void setFooterCell(int index, FooterCell cell) {
  2622. if (cell.isEnabled()) {
  2623. // we're moving the cell
  2624. DOM.removeChild(tr, cell.getElement());
  2625. orphan(cell);
  2626. }
  2627. if (index < visibleCells.size()) {
  2628. // insert to right slot
  2629. DOM.insertChild(tr, cell.getElement(), index);
  2630. adopt(cell);
  2631. visibleCells.add(index, cell);
  2632. } else if (index == visibleCells.size()) {
  2633. // simply append
  2634. DOM.appendChild(tr, cell.getElement());
  2635. adopt(cell);
  2636. visibleCells.add(cell);
  2637. } else {
  2638. throw new RuntimeException(
  2639. "Header cells must be appended in order");
  2640. }
  2641. }
  2642. /**
  2643. * Remove a cell by using the columnId
  2644. *
  2645. * @param colKey
  2646. * The columnId to remove
  2647. */
  2648. public void removeCell(String colKey) {
  2649. final FooterCell c = getFooterCell(colKey);
  2650. remove(c);
  2651. }
  2652. /**
  2653. * Enable a column (Sets the footer cell)
  2654. *
  2655. * @param cid
  2656. * The columnId
  2657. * @param index
  2658. * The index of the column
  2659. */
  2660. public void enableColumn(String cid, int index) {
  2661. final FooterCell c = getFooterCell(cid);
  2662. if (!c.isEnabled() || getFooterCell(index) != c) {
  2663. setFooterCell(index, c);
  2664. if (initializedAndAttached) {
  2665. headerChangedDuringUpdate = true;
  2666. }
  2667. }
  2668. }
  2669. /**
  2670. * Disable browser measurement of the table width
  2671. */
  2672. public void disableBrowserIntelligence() {
  2673. DOM.setStyleAttribute(hTableContainer, "width", WRAPPER_WIDTH
  2674. + "px");
  2675. }
  2676. /**
  2677. * Enable browser measurement of the table width
  2678. */
  2679. public void enableBrowserIntelligence() {
  2680. DOM.setStyleAttribute(hTableContainer, "width", "");
  2681. }
  2682. /**
  2683. * Set the horizontal position in the cell in the footer. This is done
  2684. * when a horizontal scrollbar is present.
  2685. *
  2686. * @param scrollLeft
  2687. * The value of the leftScroll
  2688. */
  2689. public void setHorizontalScrollPosition(int scrollLeft) {
  2690. if (BrowserInfo.get().isIE6()) {
  2691. hTableWrapper.getStyle().setProperty("position", "relative");
  2692. hTableWrapper.getStyle().setPropertyPx("left", -scrollLeft);
  2693. } else {
  2694. hTableWrapper.setScrollLeft(scrollLeft);
  2695. }
  2696. }
  2697. /**
  2698. * Swap cells when the column are dragged
  2699. *
  2700. * @param oldIndex
  2701. * The old index of the cell
  2702. * @param newIndex
  2703. * The new index of the cell
  2704. */
  2705. public void moveCell(int oldIndex, int newIndex) {
  2706. final FooterCell hCell = getFooterCell(oldIndex);
  2707. final Element cell = hCell.getElement();
  2708. visibleCells.remove(oldIndex);
  2709. DOM.removeChild(tr, cell);
  2710. DOM.insertChild(tr, cell, newIndex);
  2711. visibleCells.add(newIndex, hCell);
  2712. }
  2713. }
  2714. /**
  2715. * This Panel can only contain VScrollTableRow type of widgets. This
  2716. * "simulates" very large table, keeping spacers which take room of
  2717. * unrendered rows.
  2718. *
  2719. */
  2720. public class VScrollTableBody extends Panel {
  2721. public static final int DEFAULT_ROW_HEIGHT = 24;
  2722. private double rowHeight = -1;
  2723. private final List<Widget> renderedRows = new ArrayList<Widget>();
  2724. /**
  2725. * Due some optimizations row height measuring is deferred and initial
  2726. * set of rows is rendered detached. Flag set on when table body has
  2727. * been attached in dom and rowheight has been measured.
  2728. */
  2729. private boolean tBodyMeasurementsDone = false;
  2730. Element preSpacer = DOM.createDiv();
  2731. Element postSpacer = DOM.createDiv();
  2732. Element container = DOM.createDiv();
  2733. TableSectionElement tBodyElement = Document.get().createTBodyElement();
  2734. Element table = DOM.createTable();
  2735. private int firstRendered;
  2736. private int lastRendered;
  2737. private char[] aligns;
  2738. protected VScrollTableBody() {
  2739. constructDOM();
  2740. setElement(container);
  2741. }
  2742. /**
  2743. * @return the height of scrollable body, subpixels ceiled.
  2744. */
  2745. public int getRequiredHeight() {
  2746. return preSpacer.getOffsetHeight() + postSpacer.getOffsetHeight()
  2747. + Util.getRequiredHeight(table);
  2748. }
  2749. private void constructDOM() {
  2750. DOM.setElementProperty(table, "className", CLASSNAME + "-table");
  2751. if (BrowserInfo.get().isIE()) {
  2752. table.setPropertyInt("cellSpacing", 0);
  2753. }
  2754. DOM.setElementProperty(preSpacer, "className", CLASSNAME
  2755. + "-row-spacer");
  2756. DOM.setElementProperty(postSpacer, "className", CLASSNAME
  2757. + "-row-spacer");
  2758. table.appendChild(tBodyElement);
  2759. DOM.appendChild(container, preSpacer);
  2760. DOM.appendChild(container, table);
  2761. DOM.appendChild(container, postSpacer);
  2762. }
  2763. public int getAvailableWidth() {
  2764. int availW = scrollBodyPanel.getOffsetWidth() - getBorderWidth();
  2765. return availW;
  2766. }
  2767. public void renderInitialRows(UIDL rowData, int firstIndex, int rows) {
  2768. firstRendered = firstIndex;
  2769. lastRendered = firstIndex + rows - 1;
  2770. final Iterator<?> it = rowData.getChildIterator();
  2771. aligns = tHead.getColumnAlignments();
  2772. while (it.hasNext()) {
  2773. final VScrollTableRow row = createRow((UIDL) it.next(), aligns);
  2774. addRow(row);
  2775. }
  2776. if (isAttached()) {
  2777. fixSpacers();
  2778. }
  2779. }
  2780. public void renderRows(UIDL rowData, int firstIndex, int rows) {
  2781. // FIXME REVIEW
  2782. aligns = tHead.getColumnAlignments();
  2783. final Iterator<?> it = rowData.getChildIterator();
  2784. if (firstIndex == lastRendered + 1) {
  2785. while (it.hasNext()) {
  2786. final VScrollTableRow row = prepareRow((UIDL) it.next());
  2787. addRow(row);
  2788. lastRendered++;
  2789. }
  2790. fixSpacers();
  2791. } else if (firstIndex + rows == firstRendered) {
  2792. final VScrollTableRow[] rowArray = new VScrollTableRow[rows];
  2793. int i = rows;
  2794. while (it.hasNext()) {
  2795. i--;
  2796. rowArray[i] = prepareRow((UIDL) it.next());
  2797. }
  2798. for (i = 0; i < rows; i++) {
  2799. addRowBeforeFirstRendered(rowArray[i]);
  2800. firstRendered--;
  2801. }
  2802. } else {
  2803. // completely new set of rows
  2804. while (lastRendered + 1 > firstRendered) {
  2805. unlinkRow(false);
  2806. }
  2807. final VScrollTableRow row = prepareRow((UIDL) it.next());
  2808. firstRendered = firstIndex;
  2809. lastRendered = firstIndex - 1;
  2810. addRow(row);
  2811. lastRendered++;
  2812. setContainerHeight();
  2813. fixSpacers();
  2814. while (it.hasNext()) {
  2815. addRow(prepareRow((UIDL) it.next()));
  2816. lastRendered++;
  2817. }
  2818. fixSpacers();
  2819. }
  2820. // this may be a new set of rows due content change,
  2821. // ensure we have proper cache rows
  2822. int reactFirstRow = (int) (firstRowInViewPort - pageLength
  2823. * cache_react_rate);
  2824. int reactLastRow = (int) (firstRowInViewPort + pageLength + pageLength
  2825. * cache_react_rate);
  2826. if (reactFirstRow < 0) {
  2827. reactFirstRow = 0;
  2828. }
  2829. if (reactLastRow >= totalRows) {
  2830. reactLastRow = totalRows - 1;
  2831. }
  2832. if (lastRendered < reactLastRow) {
  2833. // get some cache rows below visible area
  2834. rowRequestHandler.setReqFirstRow(lastRendered + 1);
  2835. rowRequestHandler.setReqRows(reactLastRow - lastRendered);
  2836. rowRequestHandler.deferRowFetch(1);
  2837. } else if (scrollBody.getFirstRendered() > reactFirstRow) {
  2838. /*
  2839. * Branch for fetching cache above visible area.
  2840. *
  2841. * If cache needed for both before and after visible area, this
  2842. * will be rendered after-cache is reveived and rendered. So in
  2843. * some rare situations table may take two cache visits to
  2844. * server.
  2845. */
  2846. rowRequestHandler.setReqFirstRow(reactFirstRow);
  2847. rowRequestHandler.setReqRows(firstRendered - reactFirstRow);
  2848. rowRequestHandler.deferRowFetch(1);
  2849. }
  2850. }
  2851. /**
  2852. * This method is used to instantiate new rows for this table. It
  2853. * automatically sets correct widths to rows cells and assigns correct
  2854. * client reference for child widgets.
  2855. *
  2856. * This method can be called only after table has been initialized
  2857. *
  2858. * @param uidl
  2859. */
  2860. private VScrollTableRow prepareRow(UIDL uidl) {
  2861. final VScrollTableRow row = createRow(uidl, aligns);
  2862. final int cells = DOM.getChildCount(row.getElement());
  2863. for (int i = 0; i < cells; i++) {
  2864. final Element cell = DOM.getChild(row.getElement(), i);
  2865. int w = VScrollTable.this.getColWidth(getColKeyByIndex(i));
  2866. if (w < 0) {
  2867. w = 0;
  2868. }
  2869. cell.getFirstChildElement().getStyle()
  2870. .setPropertyPx("width", w);
  2871. cell.getStyle().setPropertyPx("width", w);
  2872. }
  2873. return row;
  2874. }
  2875. protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) {
  2876. return new VScrollTableRow(uidl, aligns);
  2877. }
  2878. private void addRowBeforeFirstRendered(VScrollTableRow row) {
  2879. VScrollTableRow first = null;
  2880. if (renderedRows.size() > 0) {
  2881. first = (VScrollTableRow) renderedRows.get(0);
  2882. }
  2883. if (first != null && first.getStyleName().indexOf("-odd") == -1) {
  2884. row.addStyleName(CLASSNAME + "-row-odd");
  2885. } else {
  2886. row.addStyleName(CLASSNAME + "-row");
  2887. }
  2888. if (row.isSelected()) {
  2889. row.addStyleName("v-selected");
  2890. }
  2891. tBodyElement.insertBefore(row.getElement(),
  2892. tBodyElement.getFirstChild());
  2893. adopt(row);
  2894. renderedRows.add(0, row);
  2895. }
  2896. private void addRow(VScrollTableRow row) {
  2897. VScrollTableRow last = null;
  2898. if (renderedRows.size() > 0) {
  2899. last = (VScrollTableRow) renderedRows
  2900. .get(renderedRows.size() - 1);
  2901. }
  2902. if (last != null && last.getStyleName().indexOf("-odd") == -1) {
  2903. row.addStyleName(CLASSNAME + "-row-odd");
  2904. } else {
  2905. row.addStyleName(CLASSNAME + "-row");
  2906. }
  2907. if (row.isSelected()) {
  2908. row.addStyleName("v-selected");
  2909. }
  2910. tBodyElement.appendChild(row.getElement());
  2911. adopt(row);
  2912. renderedRows.add(row);
  2913. }
  2914. public Iterator<Widget> iterator() {
  2915. return renderedRows.iterator();
  2916. }
  2917. /**
  2918. * @return false if couldn't remove row
  2919. */
  2920. public boolean unlinkRow(boolean fromBeginning) {
  2921. if (lastRendered - firstRendered < 0) {
  2922. return false;
  2923. }
  2924. int index;
  2925. if (fromBeginning) {
  2926. index = 0;
  2927. firstRendered++;
  2928. } else {
  2929. index = renderedRows.size() - 1;
  2930. lastRendered--;
  2931. }
  2932. if (index >= 0) {
  2933. final VScrollTableRow toBeRemoved = (VScrollTableRow) renderedRows
  2934. .get(index);
  2935. lazyUnregistryBag.add(toBeRemoved);
  2936. tBodyElement.removeChild(toBeRemoved.getElement());
  2937. orphan(toBeRemoved);
  2938. renderedRows.remove(index);
  2939. fixSpacers();
  2940. return true;
  2941. } else {
  2942. return false;
  2943. }
  2944. }
  2945. @Override
  2946. public boolean remove(Widget w) {
  2947. throw new UnsupportedOperationException();
  2948. }
  2949. @Override
  2950. protected void onAttach() {
  2951. super.onAttach();
  2952. setContainerHeight();
  2953. }
  2954. /**
  2955. * Fix container blocks height according to totalRows to avoid
  2956. * "bouncing" when scrolling
  2957. */
  2958. private void setContainerHeight() {
  2959. fixSpacers();
  2960. DOM.setStyleAttribute(container, "height", totalRows
  2961. * getRowHeight() + "px");
  2962. }
  2963. private void fixSpacers() {
  2964. int prepx = (int) Math.round(getRowHeight() * firstRendered);
  2965. if (prepx < 0) {
  2966. prepx = 0;
  2967. }
  2968. preSpacer.getStyle().setPropertyPx("height", prepx);
  2969. int postpx = (int) (getRowHeight() * (totalRows - 1 - lastRendered));
  2970. if (postpx < 0) {
  2971. postpx = 0;
  2972. }
  2973. postSpacer.getStyle().setPropertyPx("height", postpx);
  2974. }
  2975. public double getRowHeight() {
  2976. return getRowHeight(false);
  2977. }
  2978. public double getRowHeight(boolean forceUpdate) {
  2979. if (tBodyMeasurementsDone && !forceUpdate) {
  2980. return rowHeight;
  2981. } else {
  2982. if (tBodyElement.getRows().getLength() > 0) {
  2983. int tableHeight = getTableHeight();
  2984. int rowCount = tBodyElement.getRows().getLength();
  2985. rowHeight = tableHeight / (double) rowCount;
  2986. } else {
  2987. if (isAttached()) {
  2988. // measure row height by adding a dummy row
  2989. VScrollTableRow scrollTableRow = new VScrollTableRow();
  2990. tBodyElement.appendChild(scrollTableRow.getElement());
  2991. getRowHeight(forceUpdate);
  2992. tBodyElement.removeChild(scrollTableRow.getElement());
  2993. } else {
  2994. // TODO investigate if this can never happen anymore
  2995. return DEFAULT_ROW_HEIGHT;
  2996. }
  2997. }
  2998. tBodyMeasurementsDone = true;
  2999. return rowHeight;
  3000. }
  3001. }
  3002. public int getTableHeight() {
  3003. return table.getOffsetHeight();
  3004. }
  3005. /**
  3006. * Returns the width available for column content.
  3007. *
  3008. * @param columnIndex
  3009. * @return
  3010. */
  3011. public int getColWidth(int columnIndex) {
  3012. if (tBodyMeasurementsDone) {
  3013. NodeList<TableRowElement> rows = tBodyElement.getRows();
  3014. if (rows.getLength() == 0) {
  3015. // no rows yet rendered
  3016. return 0;
  3017. } else {
  3018. com.google.gwt.dom.client.Element wrapperdiv = rows
  3019. .getItem(0).getCells().getItem(columnIndex)
  3020. .getFirstChildElement();
  3021. return wrapperdiv.getOffsetWidth();
  3022. }
  3023. } else {
  3024. return 0;
  3025. }
  3026. }
  3027. /**
  3028. * Sets the content width of a column.
  3029. *
  3030. * Due IE limitation, we must set the width to a wrapper elements inside
  3031. * table cells (with overflow hidden, which does not work on td
  3032. * elements).
  3033. *
  3034. * To get this work properly crossplatform, we will also set the width
  3035. * of td.
  3036. *
  3037. * @param colIndex
  3038. * @param w
  3039. */
  3040. public void setColWidth(int colIndex, int w) {
  3041. NodeList<TableRowElement> rows2 = tBodyElement.getRows();
  3042. final int rows = rows2.getLength();
  3043. for (int i = 0; i < rows; i++) {
  3044. TableRowElement row = rows2.getItem(i);
  3045. TableCellElement cell = row.getCells().getItem(colIndex);
  3046. cell.getFirstChildElement().getStyle()
  3047. .setPropertyPx("width", w);
  3048. cell.getStyle().setPropertyPx("width", w);
  3049. }
  3050. }
  3051. private int cellExtraWidth = -1;
  3052. /**
  3053. * Method to return the space used for cell paddings + border.
  3054. */
  3055. private int getCellExtraWidth() {
  3056. if (cellExtraWidth < 0) {
  3057. detectExtrawidth();
  3058. }
  3059. return cellExtraWidth;
  3060. }
  3061. private void detectExtrawidth() {
  3062. NodeList<TableRowElement> rows = tBodyElement.getRows();
  3063. if (rows.getLength() == 0) {
  3064. /* need to temporary add empty row and detect */
  3065. VScrollTableRow scrollTableRow = new VScrollTableRow();
  3066. tBodyElement.appendChild(scrollTableRow.getElement());
  3067. detectExtrawidth();
  3068. tBodyElement.removeChild(scrollTableRow.getElement());
  3069. } else {
  3070. boolean noCells = false;
  3071. TableRowElement item = rows.getItem(0);
  3072. TableCellElement firstTD = item.getCells().getItem(0);
  3073. if (firstTD == null) {
  3074. // content is currently empty, we need to add a fake cell
  3075. // for measuring
  3076. noCells = true;
  3077. VScrollTableRow next = (VScrollTableRow) iterator().next();
  3078. next.addCell(null, "", ALIGN_LEFT, "", true);
  3079. firstTD = item.getCells().getItem(0);
  3080. }
  3081. com.google.gwt.dom.client.Element wrapper = firstTD
  3082. .getFirstChildElement();
  3083. cellExtraWidth = firstTD.getOffsetWidth()
  3084. - wrapper.getOffsetWidth();
  3085. if (noCells) {
  3086. firstTD.getParentElement().removeChild(firstTD);
  3087. }
  3088. }
  3089. }
  3090. private void reLayoutComponents() {
  3091. for (Widget w : this) {
  3092. VScrollTableRow r = (VScrollTableRow) w;
  3093. for (Widget widget : r) {
  3094. client.handleComponentRelativeSize(widget);
  3095. }
  3096. }
  3097. }
  3098. public int getLastRendered() {
  3099. return lastRendered;
  3100. }
  3101. public int getFirstRendered() {
  3102. return firstRendered;
  3103. }
  3104. public void moveCol(int oldIndex, int newIndex) {
  3105. // loop all rows and move given index to its new place
  3106. final Iterator<?> rows = iterator();
  3107. while (rows.hasNext()) {
  3108. final VScrollTableRow row = (VScrollTableRow) rows.next();
  3109. final Element td = DOM.getChild(row.getElement(), oldIndex);
  3110. DOM.removeChild(row.getElement(), td);
  3111. DOM.insertChild(row.getElement(), td, newIndex);
  3112. }
  3113. }
  3114. /**
  3115. * Restore row visibility which is set to "none" when the row is
  3116. * rendered (due a performance optimization).
  3117. */
  3118. private void restoreRowVisibility() {
  3119. for (Widget row : renderedRows) {
  3120. row.getElement().getStyle().setProperty("visibility", "");
  3121. }
  3122. }
  3123. public class VScrollTableRow extends Panel implements ActionOwner,
  3124. Container {
  3125. private static final int DRAGMODE_MULTIROW = 2;
  3126. protected ArrayList<Widget> childWidgets = new ArrayList<Widget>();
  3127. private boolean selected = false;
  3128. protected final int rowKey;
  3129. private List<UIDL> pendingComponentPaints;
  3130. private String[] actionKeys = null;
  3131. private final TableRowElement rowElement;
  3132. private boolean mDown;
  3133. private VScrollTableRow(int rowKey) {
  3134. this.rowKey = rowKey;
  3135. rowElement = Document.get().createTRElement();
  3136. setElement(rowElement);
  3137. DOM.sinkEvents(getElement(), Event.MOUSEEVENTS
  3138. | Event.ONDBLCLICK | Event.ONCONTEXTMENU
  3139. | Event.ONKEYDOWN);
  3140. }
  3141. private void paintComponent(Paintable p, UIDL uidl) {
  3142. if (isAttached()) {
  3143. p.updateFromUIDL(uidl, client);
  3144. } else {
  3145. if (pendingComponentPaints == null) {
  3146. pendingComponentPaints = new LinkedList<UIDL>();
  3147. }
  3148. pendingComponentPaints.add(uidl);
  3149. }
  3150. }
  3151. @Override
  3152. protected void onAttach() {
  3153. super.onAttach();
  3154. if (pendingComponentPaints != null) {
  3155. for (UIDL uidl : pendingComponentPaints) {
  3156. Paintable paintable = client.getPaintable(uidl);
  3157. paintable.updateFromUIDL(uidl, client);
  3158. }
  3159. }
  3160. }
  3161. @Override
  3162. protected void onDetach() {
  3163. super.onDetach();
  3164. client.getContextMenu().ensureHidden(this);
  3165. }
  3166. public String getKey() {
  3167. return String.valueOf(rowKey);
  3168. }
  3169. public VScrollTableRow(UIDL uidl, char[] aligns) {
  3170. this(uidl.getIntAttribute("key"));
  3171. /*
  3172. * Rendering the rows as hidden improves Firefox and Safari
  3173. * performance drastically.
  3174. */
  3175. getElement().getStyle().setProperty("visibility", "hidden");
  3176. String rowStyle = uidl.getStringAttribute("rowstyle");
  3177. if (rowStyle != null) {
  3178. addStyleName(CLASSNAME + "-row-" + rowStyle);
  3179. }
  3180. tHead.getColumnAlignments();
  3181. int col = 0;
  3182. int visibleColumnIndex = -1;
  3183. // row header
  3184. if (showRowHeaders) {
  3185. addCell(uidl, buildCaptionHtmlSnippet(uidl), aligns[col++],
  3186. "", true);
  3187. }
  3188. if (uidl.hasAttribute("al")) {
  3189. actionKeys = uidl.getStringArrayAttribute("al");
  3190. }
  3191. final Iterator<?> cells = uidl.getChildIterator();
  3192. while (cells.hasNext()) {
  3193. final Object cell = cells.next();
  3194. visibleColumnIndex++;
  3195. String columnId = visibleColOrder[visibleColumnIndex];
  3196. String style = "";
  3197. if (uidl.hasAttribute("style-" + columnId)) {
  3198. style = uidl.getStringAttribute("style-" + columnId);
  3199. }
  3200. if (cell instanceof String) {
  3201. addCell(uidl, cell.toString(), aligns[col++], style,
  3202. false);
  3203. } else {
  3204. final Paintable cellContent = client
  3205. .getPaintable((UIDL) cell);
  3206. addCell(uidl, (Widget) cellContent, aligns[col++],
  3207. style);
  3208. paintComponent(cellContent, (UIDL) cell);
  3209. }
  3210. }
  3211. if (uidl.hasAttribute("selected") && !isSelected()) {
  3212. toggleSelection();
  3213. }
  3214. }
  3215. /**
  3216. * Add a dummy row, used for measurements if Table is empty.
  3217. */
  3218. public VScrollTableRow() {
  3219. this(0);
  3220. addStyleName(CLASSNAME + "-row");
  3221. addCell(null, "_", 'b', "", true);
  3222. }
  3223. public void addCell(UIDL rowUidl, String text, char align,
  3224. String style, boolean textIsHTML) {
  3225. // String only content is optimized by not using Label widget
  3226. final Element td = DOM.createTD();
  3227. final Element container = DOM.createDiv();
  3228. String className = CLASSNAME + "-cell-content";
  3229. if (style != null && !style.equals("")) {
  3230. className += " " + CLASSNAME + "-cell-content-" + style;
  3231. }
  3232. td.setClassName(className);
  3233. container.setClassName(CLASSNAME + "-cell-wrapper");
  3234. if (textIsHTML) {
  3235. container.setInnerHTML(text);
  3236. } else {
  3237. container.setInnerText(text);
  3238. }
  3239. if (align != ALIGN_LEFT) {
  3240. switch (align) {
  3241. case ALIGN_CENTER:
  3242. container.getStyle().setProperty("textAlign", "center");
  3243. break;
  3244. case ALIGN_RIGHT:
  3245. default:
  3246. container.getStyle().setProperty("textAlign", "right");
  3247. break;
  3248. }
  3249. }
  3250. td.appendChild(container);
  3251. getElement().appendChild(td);
  3252. }
  3253. public void addCell(UIDL rowUidl, Widget w, char align, String style) {
  3254. final Element td = DOM.createTD();
  3255. final Element container = DOM.createDiv();
  3256. String className = CLASSNAME + "-cell-content";
  3257. if (style != null && !style.equals("")) {
  3258. className += " " + CLASSNAME + "-cell-content-" + style;
  3259. }
  3260. td.setClassName(className);
  3261. container.setClassName(CLASSNAME + "-cell-wrapper");
  3262. // TODO most components work with this, but not all (e.g.
  3263. // Select)
  3264. // Old comment: make widget cells respect align.
  3265. // text-align:center for IE, margin: auto for others
  3266. if (align != ALIGN_LEFT) {
  3267. switch (align) {
  3268. case ALIGN_CENTER:
  3269. container.getStyle().setProperty("textAlign", "center");
  3270. break;
  3271. case ALIGN_RIGHT:
  3272. default:
  3273. container.getStyle().setProperty("textAlign", "right");
  3274. break;
  3275. }
  3276. }
  3277. td.appendChild(container);
  3278. getElement().appendChild(td);
  3279. // ensure widget not attached to another element (possible tBody
  3280. // change)
  3281. w.removeFromParent();
  3282. container.appendChild(w.getElement());
  3283. adopt(w);
  3284. childWidgets.add(w);
  3285. }
  3286. public Iterator<Widget> iterator() {
  3287. return childWidgets.iterator();
  3288. }
  3289. @Override
  3290. public boolean remove(Widget w) {
  3291. if (childWidgets.contains(w)) {
  3292. orphan(w);
  3293. DOM.removeChild(DOM.getParent(w.getElement()),
  3294. w.getElement());
  3295. childWidgets.remove(w);
  3296. return true;
  3297. } else {
  3298. return false;
  3299. }
  3300. }
  3301. private void handleClickEvent(Event event, Element targetTdOrTr) {
  3302. if (client.hasEventListeners(VScrollTable.this,
  3303. ITEM_CLICK_EVENT_ID)) {
  3304. boolean doubleClick = (DOM.eventGetType(event) == Event.ONDBLCLICK);
  3305. /* This row was clicked */
  3306. client.updateVariable(paintableId, "clickedKey", ""
  3307. + rowKey, false);
  3308. if (getElement() == targetTdOrTr.getParentElement()) {
  3309. /* A specific column was clicked */
  3310. int childIndex = DOM.getChildIndex(getElement(),
  3311. targetTdOrTr);
  3312. String colKey = null;
  3313. colKey = tHead.getHeaderCell(childIndex).getColKey();
  3314. client.updateVariable(paintableId, "clickedColKey",
  3315. colKey, false);
  3316. }
  3317. MouseEventDetails details = new MouseEventDetails(event);
  3318. // Note: the 'immediate' logic would need to be more
  3319. // involved (see #2104), but iscrolltable always sends
  3320. // select event, even though nullselectionallowed wont let
  3321. // the change trough. Will need to be updated if that is
  3322. // changed.
  3323. client.updateVariable(
  3324. paintableId,
  3325. "clickEvent",
  3326. details.toString(),
  3327. !(event.getButton() == Event.BUTTON_LEFT
  3328. && !doubleClick
  3329. && selectMode > Table.SELECT_MODE_NONE && immediate));
  3330. }
  3331. }
  3332. /**
  3333. * Add this to the element mouse down event by using
  3334. * element.setPropertyJSO
  3335. * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it
  3336. * then again when the mouse is depressed in the mouse up event.
  3337. *
  3338. * @return Returns the JSO preventing text selection
  3339. */
  3340. private native JavaScriptObject applyDisableTextSelectionIEHack()
  3341. /*-{
  3342. return function(){ return false; };
  3343. }-*/;
  3344. /*
  3345. * React on click that occur on content cells only
  3346. */
  3347. @Override
  3348. public void onBrowserEvent(Event event) {
  3349. if (enabled) {
  3350. int type = event.getTypeInt();
  3351. Element targetTdOrTr = getEventTargetTdOrTr(event);
  3352. if (type == Event.ONCONTEXTMENU) {
  3353. showContextMenu(event);
  3354. event.stopPropagation();
  3355. return;
  3356. }
  3357. if (targetTdOrTr != null) {
  3358. switch (type) {
  3359. case Event.ONDBLCLICK:
  3360. handleClickEvent(event, targetTdOrTr);
  3361. break;
  3362. case Event.ONMOUSEUP:
  3363. mDown = false;
  3364. handleClickEvent(event, targetTdOrTr);
  3365. scrollBodyPanel.setFocus(true);
  3366. if (event.getButton() == Event.BUTTON_LEFT
  3367. && selectMode > Table.SELECT_MODE_NONE) {
  3368. // Ctrl+Shift click
  3369. if ((event.getCtrlKey() || event.getMetaKey())
  3370. && event.getShiftKey()
  3371. && selectMode == SELECT_MODE_MULTI
  3372. && multiselectmode == MULTISELECT_MODE_DEFAULT) {
  3373. toggleShiftSelection(false);
  3374. setRowFocus(this);
  3375. // Ctrl click
  3376. } else if ((event.getCtrlKey() || event
  3377. .getMetaKey())
  3378. && selectMode == SELECT_MODE_MULTI
  3379. && multiselectmode == MULTISELECT_MODE_DEFAULT) {
  3380. toggleSelection();
  3381. setRowFocus(this);
  3382. // Ctrl click (Single selection)
  3383. } else if ((event.getCtrlKey() || event
  3384. .getMetaKey()
  3385. && selectMode == SELECT_MODE_SINGLE)) {
  3386. if (!isSelected()
  3387. || (isSelected() && nullSelectionAllowed)) {
  3388. if (!isSelected()) {
  3389. deselectAll();
  3390. }
  3391. toggleSelection();
  3392. setRowFocus(this);
  3393. }
  3394. // Shift click
  3395. } else if (event.getShiftKey()
  3396. && selectMode == SELECT_MODE_MULTI
  3397. && multiselectmode == MULTISELECT_MODE_DEFAULT) {
  3398. toggleShiftSelection(true);
  3399. // click
  3400. } else {
  3401. boolean currentlyJustThisRowSelected = selectedRowKeys
  3402. .size() == 1
  3403. && selectedRowKeys
  3404. .contains(getKey());
  3405. if (!currentlyJustThisRowSelected) {
  3406. if (multiselectmode == MULTISELECT_MODE_DEFAULT) {
  3407. deselectAll();
  3408. }
  3409. toggleSelection();
  3410. } else if (selectMode == SELECT_MODE_SINGLE
  3411. && nullSelectionAllowed) {
  3412. toggleSelection();
  3413. }/*
  3414. * else NOP to avoid excessive server
  3415. * visits (selection is removed with
  3416. * CTRL/META click)
  3417. */
  3418. selectionRangeStart = this;
  3419. setRowFocus(this);
  3420. }
  3421. // Remove IE text selection hack
  3422. if (BrowserInfo.get().isIE()) {
  3423. ((Element) event.getEventTarget().cast())
  3424. .setPropertyJSO("onselectstart",
  3425. null);
  3426. }
  3427. sendSelectedRows();
  3428. }
  3429. break;
  3430. case Event.ONMOUSEDOWN:
  3431. if (dragmode != 0
  3432. && event.getButton() == NativeEvent.BUTTON_LEFT) {
  3433. mDown = true;
  3434. VTransferable transferable = new VTransferable();
  3435. transferable.setDragSource(VScrollTable.this);
  3436. transferable.setData("itemId", "" + rowKey);
  3437. NodeList<TableCellElement> cells = rowElement
  3438. .getCells();
  3439. for (int i = 0; i < cells.getLength(); i++) {
  3440. if (cells.getItem(i).isOrHasChild(
  3441. targetTdOrTr)) {
  3442. HeaderCell headerCell = tHead
  3443. .getHeaderCell(i);
  3444. transferable.setData("propertyId",
  3445. headerCell.cid);
  3446. break;
  3447. }
  3448. }
  3449. VDragEvent ev = VDragAndDropManager.get()
  3450. .startDrag(transferable, event, true);
  3451. if (dragmode == DRAGMODE_MULTIROW
  3452. && selectMode == SELECT_MODE_MULTI
  3453. && selectedRowKeys
  3454. .contains("" + rowKey)) {
  3455. ev.createDragImage(
  3456. (Element) scrollBody.tBodyElement
  3457. .cast(), true);
  3458. Element dragImage = ev.getDragImage();
  3459. int i = 0;
  3460. for (Iterator<Widget> iterator = scrollBody
  3461. .iterator(); iterator.hasNext();) {
  3462. VScrollTableRow next = (VScrollTableRow) iterator
  3463. .next();
  3464. Element child = (Element) dragImage
  3465. .getChild(i++);
  3466. if (!selectedRowKeys.contains(""
  3467. + next.rowKey)) {
  3468. child.getStyle().setVisibility(
  3469. Visibility.HIDDEN);
  3470. }
  3471. }
  3472. } else {
  3473. ev.createDragImage(getElement(), true);
  3474. }
  3475. event.preventDefault();
  3476. event.stopPropagation();
  3477. } else if (event.getCtrlKey()
  3478. || event.getShiftKey()
  3479. || event.getMetaKey()
  3480. && selectMode == SELECT_MODE_MULTI
  3481. && multiselectmode == MULTISELECT_MODE_DEFAULT) {
  3482. // Prevent default text selection in Firefox
  3483. event.preventDefault();
  3484. // Prevent default text selection in IE
  3485. if (BrowserInfo.get().isIE()) {
  3486. ((Element) event.getEventTarget().cast())
  3487. .setPropertyJSO(
  3488. "onselectstart",
  3489. applyDisableTextSelectionIEHack());
  3490. }
  3491. event.stopPropagation();
  3492. }
  3493. if (!isFocusable()) {
  3494. scrollBodyPanel.getElement().setTabIndex(-1);
  3495. } else {
  3496. scrollBodyPanel.getElement().setTabIndex(0);
  3497. }
  3498. break;
  3499. case Event.ONMOUSEOUT:
  3500. mDown = false;
  3501. break;
  3502. default:
  3503. break;
  3504. }
  3505. }
  3506. }
  3507. super.onBrowserEvent(event);
  3508. }
  3509. /**
  3510. * Finds the TD that the event interacts with. Returns null if the
  3511. * target of the event should not be handled. If the event target is
  3512. * the row directly this method returns the TR element instead of
  3513. * the TD.
  3514. *
  3515. * @param event
  3516. * @return TD or TR element that the event targets (the actual event
  3517. * target is this element or a child of it)
  3518. */
  3519. private Element getEventTargetTdOrTr(Event event) {
  3520. Element targetTdOrTr = null;
  3521. final Element eventTarget = DOM.eventGetTarget(event);
  3522. final Element eventTargetParent = DOM.getParent(eventTarget);
  3523. final Element eventTargetGrandParent = DOM
  3524. .getParent(eventTargetParent);
  3525. final Element thisTrElement = getElement();
  3526. if (eventTarget == thisTrElement) {
  3527. // This was a click on the TR element
  3528. targetTdOrTr = eventTarget;
  3529. // rowTarget = true;
  3530. } else if (thisTrElement == eventTargetParent) {
  3531. // Target parent is the TR, so the actual target is the TD
  3532. targetTdOrTr = eventTarget;
  3533. } else if (thisTrElement == eventTargetGrandParent) {
  3534. // Target grand parent is the TR, so the parent is the TD
  3535. targetTdOrTr = eventTargetParent;
  3536. } else {
  3537. /*
  3538. * This is a workaround to make Labels, read only TextFields
  3539. * and Embedded in a Table clickable (see #2688). It is
  3540. * really not a fix as it does not work with a custom read
  3541. * only components (not extending VLabel/VEmbedded).
  3542. */
  3543. Widget widget = Util.findWidget(eventTarget, null);
  3544. if (widget != this) {
  3545. while (widget != null && widget.getParent() != this) {
  3546. widget = widget.getParent();
  3547. }
  3548. if (widget != null) {
  3549. // widget is now the closest widget to this row
  3550. if (widget instanceof VLabel
  3551. || widget instanceof VEmbedded
  3552. || (widget instanceof VTextField && ((VTextField) widget)
  3553. .isReadOnly())) {
  3554. Element tdElement = eventTargetParent;
  3555. while (DOM.getParent(tdElement) != thisTrElement) {
  3556. tdElement = DOM.getParent(tdElement);
  3557. }
  3558. targetTdOrTr = tdElement;
  3559. }
  3560. }
  3561. }
  3562. }
  3563. return targetTdOrTr;
  3564. }
  3565. public void showContextMenu(Event event) {
  3566. if (enabled && actionKeys != null) {
  3567. int left = event.getClientX();
  3568. int top = event.getClientY();
  3569. top += Window.getScrollTop();
  3570. left += Window.getScrollLeft();
  3571. client.getContextMenu().showAt(this, left, top);
  3572. }
  3573. event.stopPropagation();
  3574. event.preventDefault();
  3575. }
  3576. /**
  3577. * Has the row been selected?
  3578. *
  3579. * @return Returns true if selected, else false
  3580. */
  3581. public boolean isSelected() {
  3582. return selected;
  3583. }
  3584. /**
  3585. * Toggle the selection of the row
  3586. */
  3587. public void toggleSelection() {
  3588. selected = !selected;
  3589. selectionChanged = true;
  3590. if (selected) {
  3591. selectedRowKeys.add(String.valueOf(rowKey));
  3592. addStyleName("v-selected");
  3593. } else {
  3594. removeStyleName("v-selected");
  3595. selectedRowKeys.remove(String.valueOf(rowKey));
  3596. }
  3597. removeKeyFromSelectedRange(rowKey);
  3598. }
  3599. /**
  3600. * Is called when a user clicks an item when holding SHIFT key down.
  3601. * This will select a new range from the last cell clicked
  3602. *
  3603. * @param deselectPrevious
  3604. * Should the previous selected range be deselected
  3605. */
  3606. private void toggleShiftSelection(boolean deselectPrevious) {
  3607. /*
  3608. * Ensures that we are in multiselect mode and that we have a
  3609. * previous selection which was not a deselection
  3610. */
  3611. if (selectMode == SELECT_MODE_SINGLE) {
  3612. // No previous selection found
  3613. deselectAll();
  3614. toggleSelection();
  3615. return;
  3616. }
  3617. // Set the selectable range
  3618. int startKey;
  3619. if (selectionRangeStart != null) {
  3620. startKey = Integer.valueOf(selectionRangeStart.getKey());
  3621. } else {
  3622. startKey = Integer.valueOf(focusedRow.getKey());
  3623. }
  3624. int endKey = rowKey;
  3625. if (endKey < startKey) {
  3626. // Swap keys if in the wrong order
  3627. startKey ^= endKey;
  3628. endKey ^= startKey;
  3629. startKey ^= endKey;
  3630. }
  3631. // Deselect previous items if so desired
  3632. if (deselectPrevious) {
  3633. deselectAll();
  3634. }
  3635. // Select the range (not including this row)
  3636. VScrollTableRow startRow = getRenderedRowByKey(String
  3637. .valueOf(startKey));
  3638. VScrollTableRow endRow = getRenderedRowByKey(String
  3639. .valueOf(endKey));
  3640. // If start row is null then we have a multipage selection from
  3641. // above
  3642. if (startRow == null) {
  3643. startRow = (VScrollTableRow) scrollBody.iterator().next();
  3644. setRowFocus(endRow);
  3645. }
  3646. if (endRow == null) {
  3647. setRowFocus(startRow);
  3648. }
  3649. Iterator<Widget> rows = scrollBody.iterator();
  3650. boolean startSelection = false;
  3651. while (rows.hasNext()) {
  3652. VScrollTableRow row = (VScrollTableRow) rows.next();
  3653. if (row == startRow || startSelection) {
  3654. startSelection = true;
  3655. if (!row.isSelected()) {
  3656. row.toggleSelection();
  3657. }
  3658. selectedRowKeys.add(row.getKey());
  3659. }
  3660. if (row == endRow && row != null) {
  3661. startSelection = false;
  3662. }
  3663. }
  3664. // Add range
  3665. if (startRow != endRow) {
  3666. selectedRowRanges.add(new SelectionRange(startKey, endKey));
  3667. }
  3668. }
  3669. /*
  3670. * (non-Javadoc)
  3671. *
  3672. * @see com.vaadin.terminal.gwt.client.ui.IActionOwner#getActions ()
  3673. */
  3674. public Action[] getActions() {
  3675. if (actionKeys == null) {
  3676. return new Action[] {};
  3677. }
  3678. final Action[] actions = new Action[actionKeys.length];
  3679. for (int i = 0; i < actions.length; i++) {
  3680. final String actionKey = actionKeys[i];
  3681. final TreeAction a = new TreeAction(this,
  3682. String.valueOf(rowKey), actionKey);
  3683. a.setCaption(getActionCaption(actionKey));
  3684. a.setIconUrl(getActionIcon(actionKey));
  3685. actions[i] = a;
  3686. }
  3687. return actions;
  3688. }
  3689. public ApplicationConnection getClient() {
  3690. return client;
  3691. }
  3692. public String getPaintableId() {
  3693. return paintableId;
  3694. }
  3695. public RenderSpace getAllocatedSpace(Widget child) {
  3696. int w = 0;
  3697. int i = getColIndexOf(child);
  3698. HeaderCell headerCell = tHead.getHeaderCell(i);
  3699. if (headerCell != null) {
  3700. if (initializedAndAttached) {
  3701. w = headerCell.getWidth();
  3702. } else {
  3703. // header offset width is not absolutely correct value,
  3704. // but a best guess (expecting similar content in all
  3705. // columns ->
  3706. // if one component is relative width so are others)
  3707. w = headerCell.getOffsetWidth() - getCellExtraWidth();
  3708. }
  3709. }
  3710. return new RenderSpace(w, 0) {
  3711. @Override
  3712. public int getHeight() {
  3713. return (int) getRowHeight();
  3714. }
  3715. };
  3716. }
  3717. private int getColIndexOf(Widget child) {
  3718. com.google.gwt.dom.client.Element widgetCell = child
  3719. .getElement().getParentElement().getParentElement();
  3720. NodeList<TableCellElement> cells = rowElement.getCells();
  3721. for (int i = 0; i < cells.getLength(); i++) {
  3722. if (cells.getItem(i) == widgetCell) {
  3723. return i;
  3724. }
  3725. }
  3726. return -1;
  3727. }
  3728. public boolean hasChildComponent(Widget component) {
  3729. return childWidgets.contains(component);
  3730. }
  3731. public void replaceChildComponent(Widget oldComponent,
  3732. Widget newComponent) {
  3733. com.google.gwt.dom.client.Element parentElement = oldComponent
  3734. .getElement().getParentElement();
  3735. int index = childWidgets.indexOf(oldComponent);
  3736. oldComponent.removeFromParent();
  3737. parentElement.appendChild(newComponent.getElement());
  3738. childWidgets.add(index, newComponent);
  3739. adopt(newComponent);
  3740. }
  3741. public boolean requestLayout(Set<Paintable> children) {
  3742. // row size should never change and system wouldn't event
  3743. // survive as this is a kind of fake paitable
  3744. return true;
  3745. }
  3746. public void updateCaption(Paintable component, UIDL uidl) {
  3747. // NOP, not rendered
  3748. }
  3749. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  3750. // Should never be called,
  3751. // Component container interface faked here to get layouts
  3752. // render properly
  3753. }
  3754. }
  3755. }
  3756. /**
  3757. * Deselects all items
  3758. */
  3759. public void deselectAll() {
  3760. final Object[] keys = selectedRowKeys.toArray();
  3761. for (int i = 0; i < keys.length; i++) {
  3762. final VScrollTableRow row = getRenderedRowByKey((String) keys[i]);
  3763. if (row != null && row.isSelected()) {
  3764. row.toggleSelection();
  3765. removeKeyFromSelectedRange(Integer.parseInt(row.getKey()));
  3766. }
  3767. }
  3768. // still ensure all selects are removed from (not necessary rendered)
  3769. selectedRowKeys.clear();
  3770. selectedRowRanges.clear();
  3771. }
  3772. /**
  3773. * Determines the pagelength when the table height is fixed.
  3774. */
  3775. public void updatePageLength() {
  3776. // Only update if visible and enabled
  3777. if (!isVisible() || !enabled) {
  3778. return;
  3779. }
  3780. if (scrollBody == null) {
  3781. return;
  3782. }
  3783. if (height == null || height.equals("")) {
  3784. return;
  3785. }
  3786. int rowHeight = (int) scrollBody.getRowHeight();
  3787. int bodyH = scrollBodyPanel.getOffsetHeight();
  3788. int rowsAtOnce = bodyH / rowHeight;
  3789. boolean anotherPartlyVisible = ((bodyH % rowHeight) != 0);
  3790. if (anotherPartlyVisible) {
  3791. rowsAtOnce++;
  3792. }
  3793. if (pageLength != rowsAtOnce) {
  3794. pageLength = rowsAtOnce;
  3795. client.updateVariable(paintableId, "pagelength", pageLength, false);
  3796. if (!rendering) {
  3797. int currentlyVisible = scrollBody.lastRendered
  3798. - scrollBody.firstRendered;
  3799. if (currentlyVisible < pageLength
  3800. && currentlyVisible < totalRows) {
  3801. // shake scrollpanel to fill empty space
  3802. scrollBodyPanel.setScrollPosition(scrollTop + 1);
  3803. scrollBodyPanel.setScrollPosition(scrollTop - 1);
  3804. }
  3805. }
  3806. }
  3807. }
  3808. @Override
  3809. public void setWidth(String width) {
  3810. if (this.width.equals(width)) {
  3811. return;
  3812. }
  3813. this.width = width;
  3814. if (width != null && !"".equals(width)) {
  3815. super.setWidth(width);
  3816. int innerPixels = getOffsetWidth() - getBorderWidth();
  3817. if (innerPixels < 0) {
  3818. innerPixels = 0;
  3819. }
  3820. setContentWidth(innerPixels);
  3821. if (!rendering) {
  3822. // readjust undefined width columns
  3823. lazyAdjustColumnWidths.cancel();
  3824. lazyAdjustColumnWidths.schedule(LAZY_COLUMN_ADJUST_TIMEOUT);
  3825. }
  3826. } else {
  3827. super.setWidth("");
  3828. }
  3829. if (!isFocusable()) {
  3830. scrollBodyPanel.getElement().setTabIndex(-1);
  3831. } else {
  3832. scrollBodyPanel.getElement().setTabIndex(0);
  3833. }
  3834. }
  3835. private static final int LAZY_COLUMN_ADJUST_TIMEOUT = 300;
  3836. private final Timer lazyAdjustColumnWidths = new Timer() {
  3837. /**
  3838. * Check for column widths, and available width, to see if we can fix
  3839. * column widths "optimally". Doing this lazily to avoid expensive
  3840. * calculation when resizing is not yet finished.
  3841. */
  3842. @Override
  3843. public void run() {
  3844. Iterator<Widget> headCells = tHead.iterator();
  3845. int usedMinimumWidth = 0;
  3846. int totalExplicitColumnsWidths = 0;
  3847. float expandRatioDivider = 0;
  3848. int colIndex = 0;
  3849. while (headCells.hasNext()) {
  3850. final HeaderCell hCell = (HeaderCell) headCells.next();
  3851. if (hCell.isDefinedWidth()) {
  3852. totalExplicitColumnsWidths += hCell.getWidth();
  3853. usedMinimumWidth += hCell.getWidth();
  3854. } else {
  3855. usedMinimumWidth += hCell.getNaturalColumnWidth(colIndex);
  3856. expandRatioDivider += hCell.getExpandRatio();
  3857. }
  3858. colIndex++;
  3859. }
  3860. int availW = scrollBody.getAvailableWidth();
  3861. // Hey IE, are you really sure about this?
  3862. availW = scrollBody.getAvailableWidth();
  3863. int visibleCellCount = tHead.getVisibleCellCount();
  3864. availW -= scrollBody.getCellExtraWidth() * visibleCellCount;
  3865. if (willHaveScrollbars()) {
  3866. availW -= Util.getNativeScrollbarSize();
  3867. }
  3868. int extraSpace = availW - usedMinimumWidth;
  3869. if (extraSpace < 0) {
  3870. extraSpace = 0;
  3871. }
  3872. int totalUndefinedNaturaWidths = usedMinimumWidth
  3873. - totalExplicitColumnsWidths;
  3874. // we have some space that can be divided optimally
  3875. HeaderCell hCell;
  3876. colIndex = 0;
  3877. headCells = tHead.iterator();
  3878. while (headCells.hasNext()) {
  3879. hCell = (HeaderCell) headCells.next();
  3880. if (!hCell.isDefinedWidth()) {
  3881. int w = hCell.getNaturalColumnWidth(colIndex);
  3882. int newSpace;
  3883. if (expandRatioDivider > 0) {
  3884. // divide excess space by expand ratios
  3885. newSpace = (int) (w + extraSpace
  3886. * hCell.getExpandRatio() / expandRatioDivider);
  3887. } else {
  3888. if (totalUndefinedNaturaWidths != 0) {
  3889. // divide relatively to natural column widths
  3890. newSpace = w + extraSpace * w
  3891. / totalUndefinedNaturaWidths;
  3892. } else {
  3893. newSpace = w;
  3894. }
  3895. }
  3896. setColWidth(colIndex, newSpace, false);
  3897. }
  3898. colIndex++;
  3899. }
  3900. if ((height == null || "".equals(height))
  3901. && totalRows == pageLength) {
  3902. // fix body height (may vary if lazy loading is offhorizontal
  3903. // scrollbar appears/disappears)
  3904. int bodyHeight = scrollBody.getRequiredHeight();
  3905. boolean needsSpaceForHorizontalSrollbar = (availW < usedMinimumWidth);
  3906. if (needsSpaceForHorizontalSrollbar) {
  3907. bodyHeight += Util.getNativeScrollbarSize();
  3908. }
  3909. int heightBefore = getOffsetHeight();
  3910. scrollBodyPanel.setHeight(bodyHeight + "px");
  3911. if (heightBefore != getOffsetHeight()) {
  3912. Util.notifyParentOfSizeChange(VScrollTable.this, false);
  3913. }
  3914. }
  3915. scrollBody.reLayoutComponents();
  3916. DeferredCommand.addCommand(new Command() {
  3917. public void execute() {
  3918. Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
  3919. }
  3920. });
  3921. }
  3922. };
  3923. /**
  3924. * helper to set pixel size of head and body part
  3925. *
  3926. * @param pixels
  3927. */
  3928. private void setContentWidth(int pixels) {
  3929. tHead.setWidth(pixels + "px");
  3930. scrollBodyPanel.setWidth(pixels + "px");
  3931. tFoot.setWidth(pixels + "px");
  3932. }
  3933. private int borderWidth = -1;
  3934. /**
  3935. * @return border left + border right
  3936. */
  3937. private int getBorderWidth() {
  3938. if (borderWidth < 0) {
  3939. borderWidth = Util.measureHorizontalPaddingAndBorder(
  3940. scrollBodyPanel.getElement(), 2);
  3941. if (borderWidth < 0) {
  3942. borderWidth = 0;
  3943. }
  3944. }
  3945. return borderWidth;
  3946. }
  3947. /**
  3948. * Ensures scrollable area is properly sized. This method is used when fixed
  3949. * size is used.
  3950. */
  3951. private int containerHeight;
  3952. private void setContainerHeight() {
  3953. if (height != null && !"".equals(height)) {
  3954. containerHeight = getOffsetHeight();
  3955. containerHeight -= showColHeaders ? tHead.getOffsetHeight() : 0;
  3956. containerHeight -= tFoot.getOffsetHeight();
  3957. containerHeight -= getContentAreaBorderHeight();
  3958. if (containerHeight < 0) {
  3959. containerHeight = 0;
  3960. }
  3961. scrollBodyPanel.setHeight(containerHeight + "px");
  3962. }
  3963. }
  3964. private int contentAreaBorderHeight = -1;
  3965. private int scrollLeft;
  3966. private int scrollTop;
  3967. private VScrollTableDropHandler dropHandler;
  3968. /**
  3969. * @return border top + border bottom of the scrollable area of table
  3970. */
  3971. private int getContentAreaBorderHeight() {
  3972. if (contentAreaBorderHeight < 0) {
  3973. if (BrowserInfo.get().isIE7() || BrowserInfo.get().isIE6()) {
  3974. contentAreaBorderHeight = Util
  3975. .measureVerticalBorder(scrollBodyPanel.getElement());
  3976. } else {
  3977. DOM.setStyleAttribute(scrollBodyPanel.getElement(), "overflow",
  3978. "hidden");
  3979. int oh = scrollBodyPanel.getOffsetHeight();
  3980. int ch = scrollBodyPanel.getElement().getPropertyInt(
  3981. "clientHeight");
  3982. contentAreaBorderHeight = oh - ch;
  3983. DOM.setStyleAttribute(scrollBodyPanel.getElement(), "overflow",
  3984. "auto");
  3985. }
  3986. }
  3987. return contentAreaBorderHeight;
  3988. }
  3989. @Override
  3990. public void setHeight(String height) {
  3991. this.height = height;
  3992. super.setHeight(height);
  3993. setContainerHeight();
  3994. if (initializedAndAttached) {
  3995. updatePageLength();
  3996. }
  3997. if (!rendering) {
  3998. // Webkit may sometimes get an odd rendering bug (white space
  3999. // between header and body), see bug #3875. Running
  4000. // overflow hack here to shake body element a bit.
  4001. Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
  4002. }
  4003. if (!isFocusable()) {
  4004. scrollBodyPanel.getElement().setTabIndex(-1);
  4005. } else {
  4006. scrollBodyPanel.getElement().setTabIndex(0);
  4007. }
  4008. }
  4009. /*
  4010. * Overridden due Table might not survive of visibility change (scroll pos
  4011. * lost). Example ITabPanel just set contained components invisible and back
  4012. * when changing tabs.
  4013. */
  4014. @Override
  4015. public void setVisible(boolean visible) {
  4016. if (isVisible() != visible) {
  4017. super.setVisible(visible);
  4018. if (initializedAndAttached) {
  4019. if (visible) {
  4020. DeferredCommand.addCommand(new Command() {
  4021. public void execute() {
  4022. scrollBodyPanel
  4023. .setScrollPosition((int) (firstRowInViewPort * scrollBody
  4024. .getRowHeight()));
  4025. }
  4026. });
  4027. }
  4028. }
  4029. }
  4030. }
  4031. /**
  4032. * Helper function to build html snippet for column or row headers
  4033. *
  4034. * @param uidl
  4035. * possibly with values caption and icon
  4036. * @return html snippet containing possibly an icon + caption text
  4037. */
  4038. protected String buildCaptionHtmlSnippet(UIDL uidl) {
  4039. String s = uidl.getStringAttribute("caption");
  4040. if (uidl.hasAttribute("icon")) {
  4041. s = "<img src=\""
  4042. + client.translateVaadinUri(uidl.getStringAttribute("icon"))
  4043. + "\" alt=\"icon\" class=\"v-icon\">" + s;
  4044. }
  4045. return s;
  4046. }
  4047. /**
  4048. * This method has logic which rows needs to be requested from server when
  4049. * user scrolls
  4050. */
  4051. public void onScroll(ScrollEvent event) {
  4052. scrollLeft = scrollBodyPanel.getElement().getScrollLeft();
  4053. scrollTop = scrollBodyPanel.getScrollPosition();
  4054. if (!initializedAndAttached) {
  4055. return;
  4056. }
  4057. if (!enabled) {
  4058. scrollBodyPanel
  4059. .setScrollPosition((int) (firstRowInViewPort * scrollBody
  4060. .getRowHeight()));
  4061. return;
  4062. }
  4063. rowRequestHandler.cancel();
  4064. if (BrowserInfo.get().isSafari() && event != null && scrollTop == 0) {
  4065. // due to the webkitoverflowworkaround, top may sometimes report 0
  4066. // for webkit, although it really is not. Expecting to have the
  4067. // correct
  4068. // value available soon.
  4069. DeferredCommand.addCommand(new Command() {
  4070. public void execute() {
  4071. onScroll(null);
  4072. }
  4073. });
  4074. return;
  4075. }
  4076. // fix headers horizontal scrolling
  4077. tHead.setHorizontalScrollPosition(scrollLeft);
  4078. // fix footers horizontal scrolling
  4079. tFoot.setHorizontalScrollPosition(scrollLeft);
  4080. firstRowInViewPort = (int) Math.ceil(scrollTop
  4081. / scrollBody.getRowHeight());
  4082. if (firstRowInViewPort > totalRows - pageLength) {
  4083. firstRowInViewPort = totalRows - pageLength;
  4084. }
  4085. int postLimit = (int) (firstRowInViewPort + (pageLength - 1) + pageLength
  4086. * cache_react_rate);
  4087. if (postLimit > totalRows - 1) {
  4088. postLimit = totalRows - 1;
  4089. }
  4090. int preLimit = (int) (firstRowInViewPort - pageLength
  4091. * cache_react_rate);
  4092. if (preLimit < 0) {
  4093. preLimit = 0;
  4094. }
  4095. final int lastRendered = scrollBody.getLastRendered();
  4096. final int firstRendered = scrollBody.getFirstRendered();
  4097. if (postLimit <= lastRendered && preLimit >= firstRendered) {
  4098. // remember which firstvisible we requested, in case the server has
  4099. // a differing opinion
  4100. lastRequestedFirstvisible = firstRowInViewPort;
  4101. client.updateVariable(paintableId, "firstvisible",
  4102. firstRowInViewPort, false);
  4103. return; // scrolled withing "non-react area"
  4104. }
  4105. if (firstRowInViewPort - pageLength * cache_rate > lastRendered
  4106. || firstRowInViewPort + pageLength + pageLength * cache_rate < firstRendered) {
  4107. // need a totally new set
  4108. rowRequestHandler
  4109. .setReqFirstRow((firstRowInViewPort - (int) (pageLength * cache_rate)));
  4110. int last = firstRowInViewPort + (int) (cache_rate * pageLength)
  4111. + pageLength - 1;
  4112. if (last >= totalRows) {
  4113. last = totalRows - 1;
  4114. }
  4115. rowRequestHandler.setReqRows(last
  4116. - rowRequestHandler.getReqFirstRow() + 1);
  4117. rowRequestHandler.deferRowFetch();
  4118. return;
  4119. }
  4120. if (preLimit < firstRendered) {
  4121. // need some rows to the beginning of the rendered area
  4122. rowRequestHandler
  4123. .setReqFirstRow((int) (firstRowInViewPort - pageLength
  4124. * cache_rate));
  4125. rowRequestHandler.setReqRows(firstRendered
  4126. - rowRequestHandler.getReqFirstRow());
  4127. rowRequestHandler.deferRowFetch();
  4128. return;
  4129. }
  4130. if (postLimit > lastRendered) {
  4131. // need some rows to the end of the rendered area
  4132. rowRequestHandler.setReqFirstRow(lastRendered + 1);
  4133. rowRequestHandler.setReqRows((int) ((firstRowInViewPort
  4134. + pageLength + pageLength * cache_rate) - lastRendered));
  4135. rowRequestHandler.deferRowFetch();
  4136. }
  4137. }
  4138. public VScrollTableDropHandler getDropHandler() {
  4139. return dropHandler;
  4140. }
  4141. private static class TableDDDetails {
  4142. int overkey = -1;
  4143. VerticalDropLocation dropLocation;
  4144. String colkey;
  4145. @Override
  4146. public boolean equals(Object obj) {
  4147. if (obj instanceof TableDDDetails) {
  4148. TableDDDetails other = (TableDDDetails) obj;
  4149. return dropLocation == other.dropLocation
  4150. && overkey == other.overkey
  4151. && ((colkey != null && colkey.equals(other.colkey)) || (colkey == null && other.colkey == null));
  4152. }
  4153. return false;
  4154. }
  4155. // @Override
  4156. // public int hashCode() {
  4157. // return overkey;
  4158. // }
  4159. }
  4160. public class VScrollTableDropHandler extends VAbstractDropHandler {
  4161. private static final String ROWSTYLEBASE = "v-table-row-drag-";
  4162. private TableDDDetails dropDetails;
  4163. private TableDDDetails lastEmphasized;
  4164. @Override
  4165. public void dragEnter(VDragEvent drag) {
  4166. updateDropDetails(drag);
  4167. super.dragEnter(drag);
  4168. }
  4169. private void updateDropDetails(VDragEvent drag) {
  4170. dropDetails = new TableDDDetails();
  4171. Element elementOver = drag.getElementOver();
  4172. VScrollTableRow row = Util.findWidget(elementOver, getRowClass());
  4173. if (row != null) {
  4174. dropDetails.overkey = row.rowKey;
  4175. Element tr = row.getElement();
  4176. Element element = elementOver;
  4177. while (element != null && element.getParentElement() != tr) {
  4178. element = (Element) element.getParentElement();
  4179. }
  4180. int childIndex = DOM.getChildIndex(tr, element);
  4181. dropDetails.colkey = tHead.getHeaderCell(childIndex)
  4182. .getColKey();
  4183. dropDetails.dropLocation = DDUtil.getVerticalDropLocation(row
  4184. .getElement(), drag.getCurrentGwtEvent().getClientY(),
  4185. 0.2);
  4186. }
  4187. drag.getDropDetails().put("itemIdOver", dropDetails.overkey + "");
  4188. drag.getDropDetails().put(
  4189. "detail",
  4190. dropDetails.dropLocation != null ? dropDetails.dropLocation
  4191. .toString() : null);
  4192. }
  4193. private Class<? extends Widget> getRowClass() {
  4194. // get the row type this way to make dd work in derived
  4195. // implementations
  4196. return scrollBody.iterator().next().getClass();
  4197. }
  4198. @Override
  4199. public void dragOver(VDragEvent drag) {
  4200. TableDDDetails oldDetails = dropDetails;
  4201. updateDropDetails(drag);
  4202. if (!oldDetails.equals(dropDetails)) {
  4203. deEmphasis();
  4204. final TableDDDetails newDetails = dropDetails;
  4205. VAcceptCallback cb = new VAcceptCallback() {
  4206. public void accepted(VDragEvent event) {
  4207. if (newDetails.equals(dropDetails)) {
  4208. dragAccepted(event);
  4209. }
  4210. /*
  4211. * Else new target slot already defined, ignore
  4212. */
  4213. }
  4214. };
  4215. validate(cb, drag);
  4216. }
  4217. }
  4218. @Override
  4219. public void dragLeave(VDragEvent drag) {
  4220. deEmphasis();
  4221. super.dragLeave(drag);
  4222. }
  4223. @Override
  4224. public boolean drop(VDragEvent drag) {
  4225. deEmphasis();
  4226. return super.drop(drag);
  4227. }
  4228. private void deEmphasis() {
  4229. UIObject.setStyleName(getElement(), CLASSNAME + "-drag", false);
  4230. if (lastEmphasized == null) {
  4231. return;
  4232. }
  4233. for (Widget w : scrollBody.renderedRows) {
  4234. VScrollTableRow row = (VScrollTableRow) w;
  4235. if (lastEmphasized != null
  4236. && row.rowKey == lastEmphasized.overkey) {
  4237. if (row != null) {
  4238. String stylename = ROWSTYLEBASE
  4239. + lastEmphasized.dropLocation.toString()
  4240. .toLowerCase();
  4241. VScrollTableRow.setStyleName(row.getElement(),
  4242. stylename, false);
  4243. }
  4244. lastEmphasized = null;
  4245. return;
  4246. }
  4247. }
  4248. }
  4249. /**
  4250. * TODO needs different drop modes ?? (on cells, on rows), now only
  4251. * supports rows
  4252. */
  4253. private void emphasis(TableDDDetails details) {
  4254. deEmphasis();
  4255. UIObject.setStyleName(getElement(), CLASSNAME + "-drag", true);
  4256. // iterate old and new emphasized row
  4257. for (Widget w : scrollBody.renderedRows) {
  4258. VScrollTableRow row = (VScrollTableRow) w;
  4259. if (details != null && details.overkey == row.rowKey) {
  4260. if (row != null) {
  4261. String stylename = ROWSTYLEBASE
  4262. + details.dropLocation.toString().toLowerCase();
  4263. VScrollTableRow.setStyleName(row.getElement(),
  4264. stylename, true);
  4265. }
  4266. lastEmphasized = details;
  4267. return;
  4268. }
  4269. }
  4270. }
  4271. @Override
  4272. protected void dragAccepted(VDragEvent drag) {
  4273. emphasis(dropDetails);
  4274. }
  4275. @Override
  4276. public Paintable getPaintable() {
  4277. return VScrollTable.this;
  4278. }
  4279. public ApplicationConnection getApplicationConnection() {
  4280. return client;
  4281. }
  4282. }
  4283. protected VScrollTableRow getFocusedRow() {
  4284. return focusedRow;
  4285. }
  4286. /**
  4287. * Moves the selection head to a specific row
  4288. *
  4289. * @param row
  4290. * The row to where the selection head should move
  4291. * @return Returns true if focus was moved successfully, else false
  4292. */
  4293. private boolean setRowFocus(VScrollTableRow row) {
  4294. if (selectMode == SELECT_MODE_NONE) {
  4295. return false;
  4296. }
  4297. // Remove previous selection
  4298. if (focusedRow != null && focusedRow != row) {
  4299. focusedRow.removeStyleName(CLASSNAME_SELECTION_FOCUS);
  4300. }
  4301. if (row != null) {
  4302. // Apply focus style to new selection
  4303. row.addStyleName(CLASSNAME_SELECTION_FOCUS);
  4304. // Trying to set focus on already focused row
  4305. if (row == focusedRow) {
  4306. return false;
  4307. }
  4308. // Set new focused row
  4309. focusedRow = row;
  4310. ensureRowIsVisible(row);
  4311. return true;
  4312. }
  4313. return false;
  4314. }
  4315. /**
  4316. * Ensures that the row is visible
  4317. *
  4318. * @param row
  4319. * The row to ensure is visible
  4320. */
  4321. private void ensureRowIsVisible(VScrollTableRow row) {
  4322. scrollIntoViewVertically(row.getElement());
  4323. }
  4324. /**
  4325. * Scrolls an element into view vertically only. Modified version of
  4326. * Element.scrollIntoView.
  4327. *
  4328. * @param elem
  4329. * The element to scroll into view
  4330. */
  4331. private native void scrollIntoViewVertically(Element elem)
  4332. /*-{
  4333. var top = elem.offsetTop;
  4334. var height = elem.offsetHeight;
  4335. if (elem.parentNode != elem.offsetParent) {
  4336. top -= elem.parentNode.offsetTop;
  4337. }
  4338. var cur = elem.parentNode;
  4339. while (cur && (cur.nodeType == 1)) {
  4340. if (top < cur.scrollTop) {
  4341. cur.scrollTop = top;
  4342. }
  4343. if (top + height > cur.scrollTop + cur.clientHeight) {
  4344. cur.scrollTop = (top + height) - cur.clientHeight;
  4345. }
  4346. var offsetTop = cur.offsetTop;
  4347. if (cur.parentNode != cur.offsetParent) {
  4348. offsetTop -= cur.parentNode.offsetTop;
  4349. }
  4350. top += offsetTop - cur.scrollTop;
  4351. cur = cur.parentNode;
  4352. }
  4353. }-*/;
  4354. /**
  4355. * Handles the keyboard events handled by the table
  4356. *
  4357. * @param event
  4358. * The keyboard event received
  4359. * @return true iff the navigation event was handled
  4360. */
  4361. protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
  4362. if (keycode == KeyCodes.KEY_TAB) {
  4363. // Do not handle tab key
  4364. return false;
  4365. }
  4366. // Down navigation
  4367. if (selectMode == SELECT_MODE_NONE && keycode == getNavigationDownKey()) {
  4368. scrollBodyPanel.setScrollPosition(scrollBodyPanel
  4369. .getScrollPosition() + scrollingVelocity);
  4370. return true;
  4371. } else if (keycode == getNavigationDownKey()) {
  4372. if (selectMode == SELECT_MODE_MULTI && moveFocusDown()) {
  4373. selectFocusedRow(ctrl, shift);
  4374. } else if (selectMode == SELECT_MODE_SINGLE && !shift
  4375. && moveFocusDown()) {
  4376. selectFocusedRow(ctrl, shift);
  4377. }
  4378. return true;
  4379. }
  4380. // Up navigation
  4381. if (selectMode == SELECT_MODE_NONE && keycode == getNavigationUpKey()) {
  4382. scrollBodyPanel.setScrollPosition(scrollBodyPanel
  4383. .getScrollPosition() - scrollingVelocity);
  4384. return true;
  4385. } else if (keycode == getNavigationUpKey()) {
  4386. if (selectMode == SELECT_MODE_MULTI && moveFocusUp()) {
  4387. selectFocusedRow(ctrl, shift);
  4388. } else if (selectMode == SELECT_MODE_SINGLE && !shift
  4389. && moveFocusUp()) {
  4390. selectFocusedRow(ctrl, shift);
  4391. }
  4392. return true;
  4393. }
  4394. if (keycode == getNavigationLeftKey()) {
  4395. // Left navigation
  4396. scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel
  4397. .getHorizontalScrollPosition() - scrollingVelocity);
  4398. return true;
  4399. } else if (keycode == getNavigationRightKey()) {
  4400. // Right navigation
  4401. scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel
  4402. .getHorizontalScrollPosition() + scrollingVelocity);
  4403. return true;
  4404. }
  4405. // Select navigation
  4406. if (selectMode > SELECT_MODE_NONE
  4407. && keycode == getNavigationSelectKey()) {
  4408. if (selectMode == SELECT_MODE_SINGLE) {
  4409. boolean wasSelected = focusedRow.isSelected();
  4410. deselectAll();
  4411. if (!wasSelected || !nullSelectionAllowed) {
  4412. focusedRow.toggleSelection();
  4413. }
  4414. } else {
  4415. focusedRow.toggleSelection();
  4416. }
  4417. sendSelectedRows();
  4418. return true;
  4419. }
  4420. // Page Down navigation
  4421. if (keycode == getNavigationPageDownKey()) {
  4422. int rowHeight = (int) scrollBody.getRowHeight();
  4423. int offset = pageLength * rowHeight - rowHeight;
  4424. scrollBodyPanel.setScrollPosition(scrollBodyPanel
  4425. .getScrollPosition() + offset);
  4426. if (selectMode > SELECT_MODE_NONE) {
  4427. if (!moveFocusDown(pageLength - 2)) {
  4428. final int lastRendered = scrollBody.getLastRendered();
  4429. if (lastRendered == totalRows - 1) {
  4430. selectLastRenderedRow(false);
  4431. } else {
  4432. selectLastItemInNextRender = true;
  4433. }
  4434. } else {
  4435. selectFocusedRow(false, false);
  4436. sendSelectedRows();
  4437. }
  4438. }
  4439. return true;
  4440. }
  4441. // Page Up navigation
  4442. if (keycode == getNavigationPageUpKey()) {
  4443. int rowHeight = (int) scrollBody.getRowHeight();
  4444. int offset = pageLength * rowHeight - rowHeight;
  4445. scrollBodyPanel.setScrollPosition(scrollBodyPanel
  4446. .getScrollPosition() - offset);
  4447. if (selectMode > SELECT_MODE_NONE) {
  4448. if (!moveFocusUp(pageLength - 2)) {
  4449. final int firstRendered = scrollBody.getFirstRendered();
  4450. if (firstRendered == 0) {
  4451. selectFirstRenderedRow(false);
  4452. } else {
  4453. selectFirstItemInNextRender = true;
  4454. }
  4455. } else {
  4456. selectFocusedRow(false, false);
  4457. sendSelectedRows();
  4458. }
  4459. }
  4460. return true;
  4461. }
  4462. // Goto start navigation
  4463. if (keycode == getNavigationStartKey()) {
  4464. if (selectMode > SELECT_MODE_NONE) {
  4465. final int firstRendered = scrollBody.getFirstRendered();
  4466. boolean focusOnly = ctrl;
  4467. if (firstRendered == 0) {
  4468. selectFirstRenderedRow(focusOnly);
  4469. } else if (focusOnly) {
  4470. focusFirstItemInNextRender = true;
  4471. } else {
  4472. selectFirstItemInNextRender = true;
  4473. }
  4474. }
  4475. scrollBodyPanel.setScrollPosition(0);
  4476. return true;
  4477. }
  4478. // Goto end navigation
  4479. if (keycode == getNavigationEndKey()) {
  4480. if (selectMode > SELECT_MODE_NONE) {
  4481. final int lastRendered = scrollBody.getLastRendered();
  4482. boolean focusOnly = ctrl;
  4483. if (lastRendered == totalRows - 1) {
  4484. selectLastRenderedRow(focusOnly);
  4485. } else if (focusOnly) {
  4486. focusLastItemInNextRender = true;
  4487. } else {
  4488. selectLastItemInNextRender = true;
  4489. }
  4490. }
  4491. scrollBodyPanel.setScrollPosition(scrollBody.getOffsetHeight());
  4492. return true;
  4493. }
  4494. return false;
  4495. }
  4496. /*
  4497. * (non-Javadoc)
  4498. *
  4499. * @see
  4500. * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
  4501. * .gwt.event.dom.client.KeyPressEvent)
  4502. */
  4503. public void onKeyPress(KeyPressEvent event) {
  4504. if (hasFocus) {
  4505. if (handleNavigation(event.getNativeEvent().getKeyCode(),
  4506. event.isControlKeyDown() || event.isMetaKeyDown(),
  4507. event.isShiftKeyDown())) {
  4508. event.preventDefault();
  4509. }
  4510. // Start the velocityTimer
  4511. if (scrollingVelocityTimer == null) {
  4512. scrollingVelocityTimer = new Timer() {
  4513. @Override
  4514. public void run() {
  4515. scrollingVelocity++;
  4516. }
  4517. };
  4518. scrollingVelocityTimer.scheduleRepeating(100);
  4519. }
  4520. }
  4521. }
  4522. /*
  4523. * (non-Javadoc)
  4524. *
  4525. * @see
  4526. * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
  4527. * .event.dom.client.KeyDownEvent)
  4528. */
  4529. public void onKeyDown(KeyDownEvent event) {
  4530. if (hasFocus) {
  4531. if (handleNavigation(event.getNativeEvent().getKeyCode(),
  4532. event.isControlKeyDown() || event.isMetaKeyDown(),
  4533. event.isShiftKeyDown())) {
  4534. event.preventDefault();
  4535. }
  4536. // Start the velocityTimer
  4537. if (scrollingVelocityTimer == null) {
  4538. scrollingVelocityTimer = new Timer() {
  4539. @Override
  4540. public void run() {
  4541. scrollingVelocity++;
  4542. }
  4543. };
  4544. scrollingVelocityTimer.scheduleRepeating(100);
  4545. }
  4546. }
  4547. }
  4548. /*
  4549. * (non-Javadoc)
  4550. *
  4551. * @see
  4552. * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
  4553. * .dom.client.FocusEvent)
  4554. */
  4555. public void onFocus(FocusEvent event) {
  4556. if (isFocusable()) {
  4557. scrollBodyPanel.addStyleName("focused");
  4558. hasFocus = true;
  4559. // Focus a row if no row is in focus
  4560. if (focusedRow == null) {
  4561. setRowFocus((VScrollTableRow) scrollBody.iterator().next());
  4562. } else {
  4563. setRowFocus(focusedRow);
  4564. }
  4565. }
  4566. }
  4567. /*
  4568. * (non-Javadoc)
  4569. *
  4570. * @see
  4571. * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
  4572. * .dom.client.BlurEvent)
  4573. */
  4574. public void onBlur(BlurEvent event) {
  4575. scrollBodyPanel.removeStyleName("focused");
  4576. hasFocus = false;
  4577. // Unfocus any row
  4578. setRowFocus(null);
  4579. }
  4580. /**
  4581. * Removes a key from a range if the key is found in a selected range
  4582. *
  4583. * @param key
  4584. * The key to remove
  4585. */
  4586. private void removeKeyFromSelectedRange(int key) {
  4587. for (SelectionRange range : selectedRowRanges) {
  4588. if (range.inRange(key)) {
  4589. int start = range.getStartKey();
  4590. int end = range.getEndKey();
  4591. if (start < key && end > key) {
  4592. selectedRowRanges.add(new SelectionRange(start, key - 1));
  4593. selectedRowRanges.add(new SelectionRange(key + 1, end));
  4594. } else if (start == key && start < end) {
  4595. selectedRowRanges.add(new SelectionRange(start + 1, end));
  4596. } else if (end == key && start < end) {
  4597. selectedRowRanges.add(new SelectionRange(start, end - 1));
  4598. }
  4599. selectedRowRanges.remove(range);
  4600. break;
  4601. }
  4602. }
  4603. }
  4604. /**
  4605. * Can the Table be focused?
  4606. *
  4607. * @return True if the table can be focused, else false
  4608. */
  4609. public boolean isFocusable() {
  4610. if (scrollBody != null) {
  4611. boolean hasVerticalScrollbars = scrollBody.getOffsetHeight() > scrollBodyPanel
  4612. .getOffsetHeight();
  4613. boolean hasHorizontalScrollbars = scrollBody.getOffsetWidth() > scrollBodyPanel
  4614. .getOffsetWidth();
  4615. return !(!hasHorizontalScrollbars && !hasVerticalScrollbars && selectMode == SELECT_MODE_NONE);
  4616. }
  4617. return false;
  4618. }
  4619. /*
  4620. * (non-Javadoc)
  4621. *
  4622. * @see com.vaadin.terminal.gwt.client.Focusable#focus()
  4623. */
  4624. public void focus() {
  4625. scrollBodyPanel.focus();
  4626. }
  4627. }