123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880 |
- /*
- * Copyright 2000-2014 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
- package com.vaadin.client.ui.grid;
-
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Set;
- import java.util.logging.Level;
- import java.util.logging.Logger;
-
- import com.google.gwt.core.client.Scheduler;
- import com.google.gwt.core.client.Scheduler.ScheduledCommand;
- import com.google.gwt.core.shared.GWT;
- import com.google.gwt.dom.client.BrowserEvents;
- import com.google.gwt.dom.client.Element;
- import com.google.gwt.dom.client.EventTarget;
- import com.google.gwt.dom.client.TableCellElement;
- import com.google.gwt.dom.client.TableRowElement;
- import com.google.gwt.dom.client.Touch;
- import com.google.gwt.event.dom.client.KeyCodeEvent;
- import com.google.gwt.event.dom.client.KeyCodes;
- import com.google.gwt.event.shared.HandlerRegistration;
- import com.google.gwt.touch.client.Point;
- import com.google.gwt.user.client.DOM;
- import com.google.gwt.user.client.Event;
- import com.google.gwt.user.client.Timer;
- import com.google.gwt.user.client.ui.Composite;
- import com.google.gwt.user.client.ui.HasVisibility;
- import com.google.gwt.user.client.ui.Widget;
- import com.vaadin.client.Util;
- import com.vaadin.client.data.DataChangeHandler;
- import com.vaadin.client.data.DataSource;
- import com.vaadin.client.ui.SubPartAware;
- import com.vaadin.client.ui.grid.EditorRow.State;
- import com.vaadin.client.ui.grid.GridFooter.FooterRow;
- import com.vaadin.client.ui.grid.GridHeader.HeaderRow;
- import com.vaadin.client.ui.grid.GridStaticSection.StaticCell;
- import com.vaadin.client.ui.grid.events.AbstractGridKeyEventHandler;
- import com.vaadin.client.ui.grid.events.BodyKeyDownHandler;
- import com.vaadin.client.ui.grid.events.BodyKeyPressHandler;
- import com.vaadin.client.ui.grid.events.BodyKeyUpHandler;
- import com.vaadin.client.ui.grid.events.FooterKeyDownHandler;
- import com.vaadin.client.ui.grid.events.FooterKeyPressHandler;
- import com.vaadin.client.ui.grid.events.FooterKeyUpHandler;
- import com.vaadin.client.ui.grid.events.GridKeyDownEvent;
- import com.vaadin.client.ui.grid.events.GridKeyPressEvent;
- import com.vaadin.client.ui.grid.events.GridKeyUpEvent;
- import com.vaadin.client.ui.grid.events.HeaderKeyDownHandler;
- import com.vaadin.client.ui.grid.events.HeaderKeyPressHandler;
- import com.vaadin.client.ui.grid.events.HeaderKeyUpHandler;
- import com.vaadin.client.ui.grid.events.ScrollEvent;
- import com.vaadin.client.ui.grid.events.ScrollHandler;
- import com.vaadin.client.ui.grid.renderers.ComplexRenderer;
- import com.vaadin.client.ui.grid.renderers.WidgetRenderer;
- import com.vaadin.client.ui.grid.selection.HasSelectionChangeHandlers;
- import com.vaadin.client.ui.grid.selection.SelectionChangeEvent;
- import com.vaadin.client.ui.grid.selection.SelectionChangeHandler;
- import com.vaadin.client.ui.grid.selection.SelectionModel;
- import com.vaadin.client.ui.grid.selection.SelectionModelMulti;
- import com.vaadin.client.ui.grid.selection.SelectionModelNone;
- import com.vaadin.client.ui.grid.selection.SelectionModelSingle;
- import com.vaadin.client.ui.grid.sort.Sort;
- import com.vaadin.client.ui.grid.sort.SortEvent;
- import com.vaadin.client.ui.grid.sort.SortEventHandler;
- import com.vaadin.client.ui.grid.sort.SortOrder;
- import com.vaadin.shared.ui.grid.GridConstants;
- import com.vaadin.shared.ui.grid.GridStaticCellType;
- import com.vaadin.shared.ui.grid.HeightMode;
- import com.vaadin.shared.ui.grid.Range;
- import com.vaadin.shared.ui.grid.ScrollDestination;
- import com.vaadin.shared.ui.grid.SortDirection;
- import com.vaadin.shared.ui.grid.SortEventOriginator;
-
- /**
- * A data grid view that supports columns and lazy loading of data rows from a
- * data source.
- *
- * <h3>Columns</h3>
- * <p>
- * The {@link GridColumn} class defines the renderer used to render a cell in
- * the grid. Implement {@link GridColumn#getValue(Object)} to retrieve the cell
- * value from the row object and return the cell renderer to render that cell.
- * </p>
- * <p>
- * {@link GridColumn}s contain other properties like the width of the column and
- * the visiblity of the column. If you want to change a column's properties
- * after it has been added to the grid you can get a column object for a
- * specific column index using {@link Grid#getColumn(int)}.
- * </p>
- * <p>
- *
- * TODO Explain about headers/footers once the multiple header/footer api has
- * been implemented
- *
- * <h3>Data sources</h3>
- * <p>
- * TODO Explain about what a data source is and how it should be implemented.
- * </p>
- *
- * @param <T>
- * The row type of the grid. The row type is the POJO type from where
- * the data is retrieved into the column cells.
- * @since
- * @author Vaadin Ltd
- */
- public class Grid<T> extends Composite implements
- HasSelectionChangeHandlers<T>, SubPartAware {
-
- public static abstract class AbstractGridKeyEvent<HANDLER extends AbstractGridKeyEventHandler>
- extends KeyCodeEvent<HANDLER> {
-
- /**
- * Enum describing different section of Grid.
- */
- public enum GridSection {
- HEADER, BODY, FOOTER
- }
-
- private Grid<?> grid;
- protected Cell activeCell;
- private final Type<HANDLER> associatedType = new Type<HANDLER>(
- getBrowserEventType(), this);
-
- public AbstractGridKeyEvent(Grid<?> grid) {
- this.grid = grid;
- }
-
- protected abstract String getBrowserEventType();
-
- /**
- * Gets the Grid instance for this event.
- *
- * @return grid
- */
- public Grid<?> getGrid() {
- return grid;
- }
-
- /**
- * Gets the active cell for this event.
- *
- * @return active cell
- */
- public Cell getActiveCell() {
- return activeCell;
- }
-
- @Override
- protected void dispatch(HANDLER handler) {
- EventTarget target = getNativeEvent().getEventTarget();
- if (Element.is(target)
- && Util.findWidget(Element.as(target), null) == grid) {
-
- activeCell = grid.activeCellHandler.getActiveCell();
- GridSection section = GridSection.FOOTER;
- final RowContainer container = grid.activeCellHandler.container;
- if (container == grid.escalator.getHeader()) {
- section = GridSection.HEADER;
- } else if (container == grid.escalator.getBody()) {
- section = GridSection.BODY;
- }
-
- doDispatch(handler, section);
- }
- }
-
- protected abstract void doDispatch(HANDLER handler, GridSection seciton);
-
- @Override
- public Type<HANDLER> getAssociatedType() {
- return associatedType;
- }
- }
-
- private GridKeyDownEvent keyDown = new GridKeyDownEvent(this);
- private GridKeyUpEvent keyUp = new GridKeyUpEvent(this);
- private GridKeyPressEvent keyPress = new GridKeyPressEvent(this);
-
- private class ActiveCellHandler {
-
- private RowContainer container = escalator.getBody();
- private int activeRow = 0;
- private Range activeCellRange = Range.withLength(0, 1);
- private int lastActiveBodyRow = 0;
- private int lastActiveHeaderRow = 0;
- private int lastActiveFooterRow = 0;
- private TableCellElement cellWithActiveStyle = null;
- private TableRowElement rowWithActiveStyle = null;
-
- public ActiveCellHandler() {
- sinkEvents(getNavigationEvents());
- }
-
- private Cell getActiveCell() {
- return new Cell(activeRow, activeCellRange.getStart(),
- cellWithActiveStyle);
- }
-
- /**
- * Sets style names for given cell when needed.
- */
- public void updateActiveCellStyle(FlyweightCell cell,
- RowContainer cellContainer) {
- int cellRow = cell.getRow();
- int cellColumn = cell.getColumn();
- int colSpan = cell.getColSpan();
- boolean columnActive = Range.withLength(cellColumn, colSpan)
- .intersects(activeCellRange);
-
- if (cellContainer == container) {
- // Cell is in the current container
- if (cellRow == activeRow && columnActive) {
- if (cellWithActiveStyle != cell.getElement()) {
- // Cell is correct but it does not have active style
- if (cellWithActiveStyle != null) {
- // Remove old active style
- setStyleName(cellWithActiveStyle,
- cellActiveStyleName, false);
- }
- cellWithActiveStyle = cell.getElement();
-
- // Add active style to correct cell.
- setStyleName(cellWithActiveStyle, cellActiveStyleName,
- true);
- }
- } else if (cellWithActiveStyle == cell.getElement()) {
- // Due to escalator reusing cells, a new cell has the same
- // element but is not the active cell.
- setStyleName(cellWithActiveStyle, cellActiveStyleName,
- false);
- cellWithActiveStyle = null;
- }
- }
-
- if (cellContainer == escalator.getHeader()
- || cellContainer == escalator.getFooter()) {
- // Correct header and footer column also needs highlighting
- setStyleName(cell.getElement(), headerFooterActiveStyleName,
- columnActive);
- }
- }
-
- /**
- * Sets active row style name for given row if needed.
- *
- * @param row
- * a row object
- */
- public void updateActiveRowStyle(Row row) {
- if (activeRow == row.getRow() && container == escalator.getBody()) {
- if (row.getElement() != rowWithActiveStyle) {
- // Row should have active style but does not have it.
- if (rowWithActiveStyle != null) {
- setStyleName(rowWithActiveStyle, rowActiveStyleName,
- false);
- }
- rowWithActiveStyle = row.getElement();
- setStyleName(rowWithActiveStyle, rowActiveStyleName, true);
- }
- } else if (rowWithActiveStyle == row.getElement()
- || (container != escalator.getBody() && rowWithActiveStyle != null)) {
- // Remove active style.
- setStyleName(rowWithActiveStyle, rowActiveStyleName, false);
- rowWithActiveStyle = null;
- }
- }
-
- /**
- * Sets currently active cell to a cell in given container with given
- * indices.
- *
- * @param row
- * new active row
- * @param column
- * new active column
- * @param container
- * new container
- */
- private void setActiveCell(int row, int column, RowContainer container) {
- if (row == activeRow && activeCellRange.contains(column)
- && container == this.container) {
- refreshRow(activeRow);
- return;
- }
-
- int oldRow = activeRow;
- activeRow = row;
- Range oldRange = activeCellRange;
-
- if (container == escalator.getBody()) {
- scrollToRow(activeRow);
- activeCellRange = Range.withLength(column, 1);
- } else {
- int i = 0;
- Element cell = container.getRowElement(activeRow)
- .getFirstChildElement();
- do {
- int colSpan = cell
- .getPropertyInt(FlyweightCell.COLSPAN_ATTR);
- Range cellRange = Range.withLength(i, colSpan);
- if (cellRange.contains(column)) {
- activeCellRange = cellRange;
- break;
- }
- cell = cell.getNextSiblingElement();
- ++i;
- } while (cell != null);
- }
-
- if (column >= escalator.getColumnConfiguration()
- .getFrozenColumnCount()) {
- escalator.scrollToColumn(column, ScrollDestination.ANY, 10);
- }
-
- if (this.container == container) {
- if (oldRange.equals(activeCellRange) && oldRow != activeRow) {
- refreshRow(oldRow);
- } else {
- refreshHeader();
- refreshFooter();
- }
- } else {
- RowContainer oldContainer = this.container;
- this.container = container;
-
- if (oldContainer == escalator.getBody()) {
- lastActiveBodyRow = oldRow;
- } else if (oldContainer == escalator.getHeader()) {
- lastActiveHeaderRow = oldRow;
- } else {
- lastActiveFooterRow = oldRow;
- }
-
- if (!oldRange.equals(activeCellRange)) {
- refreshHeader();
- refreshFooter();
- if (oldContainer == escalator.getBody()) {
- oldContainer.refreshRows(oldRow, 1);
- }
- } else {
- oldContainer.refreshRows(oldRow, 1);
- }
- }
- refreshRow(activeRow);
- }
-
- /**
- * Sets currently active cell used for keyboard navigation. Note that
- * active cell is not JavaScript {@code document.activeElement}.
- *
- * @param cell
- * a cell object
- */
- public void setActiveCell(Cell cell) {
- setActiveCell(cell.getRow(), cell.getColumn(),
- escalator.findRowContainer(cell.getElement()));
- }
-
- /**
- * Gets list of events that can be used for active cell navigation.
- *
- * @return list of navigation related event types
- */
- public Collection<String> getNavigationEvents() {
- return Arrays.asList(BrowserEvents.KEYDOWN, BrowserEvents.CLICK);
- }
-
- /**
- * Handle events that can change the currently active cell.
- */
- public void handleNavigationEvent(Event event, Cell cell) {
- if (event.getType().equals(BrowserEvents.CLICK)) {
- setActiveCell(cell);
- // Grid should have focus when clicked.
- getElement().focus();
- } else if (event.getType().equals(BrowserEvents.KEYDOWN)) {
- int newRow = activeRow;
- RowContainer newContainer = container;
- int newColumn = activeCellRange.getStart();
-
- switch (event.getKeyCode()) {
- case KeyCodes.KEY_DOWN:
- ++newRow;
- break;
- case KeyCodes.KEY_UP:
- --newRow;
- break;
- case KeyCodes.KEY_RIGHT:
- if (activeCellRange.getEnd() >= getVisibleColumnIndices()
- .size()) {
- return;
- }
- ++newColumn;
- break;
- case KeyCodes.KEY_LEFT:
- if (newColumn == 0) {
- return;
- }
- --newColumn;
- break;
- case KeyCodes.KEY_TAB:
- if (event.getShiftKey()) {
- newContainer = getPreviousContainer(container);
- } else {
- newContainer = getNextContainer(container);
- }
-
- if (newContainer == container) {
- return;
- }
- break;
- default:
- return;
- }
-
- if (newContainer != container) {
- if (newContainer == escalator.getBody()) {
- newRow = lastActiveBodyRow;
- } else if (newContainer == escalator.getHeader()) {
- newRow = lastActiveHeaderRow;
- } else {
- newRow = lastActiveFooterRow;
- }
- } else if (newRow < 0) {
- newContainer = getPreviousContainer(newContainer);
-
- if (newContainer == container) {
- newRow = 0;
- } else if (newContainer == escalator.getBody()) {
- newRow = getLastVisibleRowIndex();
- } else {
- newRow = newContainer.getRowCount() - 1;
- }
- } else if (newRow >= container.getRowCount()) {
- newContainer = getNextContainer(newContainer);
-
- if (newContainer == container) {
- newRow = container.getRowCount() - 1;
- } else if (newContainer == escalator.getBody()) {
- newRow = getFirstVisibleRowIndex();
- } else {
- newRow = 0;
- }
- }
-
- if (newContainer.getRowCount() == 0) {
- // There are no rows in the container. Can't change the
- // active cell.
- return;
- }
-
- event.preventDefault();
- event.stopPropagation();
-
- setActiveCell(newRow, newColumn, newContainer);
- }
-
- }
-
- private RowContainer getPreviousContainer(RowContainer current) {
- if (current == escalator.getFooter()) {
- current = escalator.getBody();
- } else if (current == escalator.getBody()) {
- current = escalator.getHeader();
- } else {
- return current;
- }
-
- if (current.getRowCount() == 0) {
- return getPreviousContainer(current);
- }
- return current;
- }
-
- private RowContainer getNextContainer(RowContainer current) {
- if (current == escalator.getHeader()) {
- current = escalator.getBody();
- } else if (current == escalator.getBody()) {
- current = escalator.getFooter();
- } else {
- return current;
- }
-
- if (current.getRowCount() == 0) {
- return getNextContainer(current);
- }
- return current;
- }
-
- private void refreshRow(int row) {
- container.refreshRows(row, 1);
- }
-
- /**
- * Offset active cell range by given integer.
- *
- * @param offset
- * offset for fixing active cell range
- */
- public void offsetRangeBy(int offset) {
- activeCellRange = activeCellRange.offsetBy(offset);
- }
-
- /*
- * Informs ActiveCellHandler that certain range of rows has been added.
- * ActiveCellHandler will fix indices accordingly.
- *
- * @param added a range of added rows
- */
- public void rowsAdded(Range added) {
- if (added.getStart() <= activeRow) {
- setActiveCell(activeRow + added.length(),
- activeCellRange.getStart(), container);
- }
- }
-
- /**
- * Informs ActiveCellHandler that certain range of rows has been
- * removed. ActiveCellHandler will fix indices accordingly.
- *
- * @param removed
- * a range of removed rows
- */
- public void rowsRemoved(Range removed) {
- int activeColumn = activeCellRange.getStart();
- if (container != escalator.getBody()) {
- return;
- } else if (!removed.contains(activeRow)) {
- if (removed.getStart() > activeRow) {
- return;
- }
- setActiveCell(activeRow - removed.length(), activeColumn,
- container);
- } else {
- if (container.getRowCount() > removed.getEnd()) {
- setActiveCell(removed.getStart(), activeColumn, container);
- } else if (removed.getStart() > 0) {
- setActiveCell(removed.getStart() - 1, activeColumn,
- container);
- } else {
- if (escalator.getHeader().getRowCount() > 0) {
- setActiveCell(lastActiveHeaderRow, activeColumn,
- escalator.getHeader());
- } else if (escalator.getFooter().getRowCount() > 0) {
- setActiveCell(lastActiveFooterRow, activeColumn,
- escalator.getFooter());
- }
- }
- }
- }
- }
-
- private class SelectionColumn extends GridColumn<Boolean, T> {
- private boolean initDone = false;
-
- public SelectionColumn(final Renderer<Boolean> selectColumnRenderer) {
- super(selectColumnRenderer);
- }
-
- public void initDone() {
- initDone = true;
- }
-
- @Override
- public void setVisible(boolean visible) {
- if (!visible && initDone) {
- throw new UnsupportedOperationException("The selection "
- + "column cannot be modified after init");
- } else {
- super.setVisible(visible);
- }
- }
-
- @Override
- public void setWidth(int pixels) {
- if (pixels != getWidth() && initDone) {
- throw new UnsupportedOperationException("The selection "
- + "column cannot be modified after init");
- } else {
- super.setWidth(pixels);
- }
- }
-
- @Override
- public Boolean getValue(T row) {
- return Boolean.valueOf(isSelected(row));
- }
- }
-
- /**
- * Helper class for performing sorting through the user interface. Controls
- * the sort() method, reporting USER as the event originator. This is a
- * completely internal class, and is, as such, safe to re-name should a more
- * descriptive name come to mind.
- */
- private final class UserSorter {
-
- private final Timer timer;
- private Cell scheduledCell;
- private boolean scheduledMultisort;
-
- private UserSorter() {
- timer = new Timer() {
- @Override
- public void run() {
- UserSorter.this.sort(scheduledCell, scheduledMultisort);
- }
- };
- }
-
- /**
- * Toggle sorting for a cell. If the multisort parameter is set to true,
- * the cell's sort order is modified as a natural part of a multi-sort
- * chain. If false, the sorting order is set to ASCENDING for that
- * cell's column. If that column was already the only sorted column in
- * the Grid, the sort direction is flipped.
- *
- * @param cell
- * a valid cell reference
- * @param multisort
- * whether the sort command should act as a multi-sort stack
- * or not
- */
- public void sort(Cell cell, boolean multisort) {
-
- final GridColumn<?, T> column = getColumnFromVisibleIndex(cell
- .getColumn());
- final SortOrder so = getSortOrder(column);
-
- if (multisort) {
-
- // If the sort order exists, replace existing value with its
- // opposite
- if (so != null) {
- final int idx = sortOrder.indexOf(so);
- sortOrder.set(idx, so.getOpposite());
- } else {
- // If it doesn't, just add a new sort order to the end of
- // the list
- sortOrder.add(new SortOrder(column));
- }
-
- } else {
-
- // Since we're doing single column sorting, first clear the
- // list. Then, if the sort order existed, add its opposite,
- // otherwise just add a new sort value
-
- int items = sortOrder.size();
- sortOrder.clear();
- if (so != null && items == 1) {
- sortOrder.add(so.getOpposite());
- } else {
- sortOrder.add(new SortOrder(column));
- }
- }
-
- // sortOrder has been changed; tell the Grid to re-sort itself by
- // user request.
- Grid.this.sort(SortEventOriginator.USER);
- }
-
- /**
- * Perform a sort after a delay.
- *
- * @param delay
- * delay, in milliseconds
- */
- public void sortAfterDelay(int delay, Cell cell, boolean multisort) {
- scheduledCell = cell;
- scheduledMultisort = multisort;
- timer.schedule(delay);
- }
-
- /**
- * Check if a delayed sort command has been issued but not yet carried
- * out.
- *
- * @return a boolean value
- */
- public boolean isDelayedSortScheduled() {
- return timer.isRunning();
- }
-
- /**
- * Cancel a scheduled sort.
- */
- public void cancelDelayedSort() {
- timer.cancel();
- }
-
- }
-
- /**
- * Escalator used internally by grid to render the rows
- */
- private Escalator escalator = GWT.create(Escalator.class);
-
- private final GridHeader header = GWT.create(GridHeader.class);
-
- private final GridFooter footer = GWT.create(GridFooter.class);
-
- /**
- * List of columns in the grid. Order defines the visible order.
- */
- private final List<GridColumn<?, T>> columns = new ArrayList<GridColumn<?, T>>();
-
- /**
- * The datasource currently in use. <em>Note:</em> it is <code>null</code>
- * on initialization, but not after that.
- */
- private DataSource<T> dataSource;
-
- /**
- * Currently available row range in DataSource.
- */
- private Range currentDataAvailable = Range.withLength(0, 0);
-
- /**
- * The last column frozen counter from the left
- */
- private GridColumn<?, T> lastFrozenColumn;
-
- /**
- * Current sort order. The (private) sort() method reads this list to
- * determine the order in which to present rows.
- */
- private List<SortOrder> sortOrder = new ArrayList<SortOrder>();
-
- private Renderer<Boolean> selectColumnRenderer = null;
-
- private SelectionColumn selectionColumn;
-
- private String rowHasDataStyleName;
- private String rowSelectedStyleName;
- private String cellActiveStyleName;
- private String rowActiveStyleName;
- private String headerFooterActiveStyleName;
-
- /**
- * Current selection model.
- */
- private SelectionModel<T> selectionModel;
-
- protected final ActiveCellHandler activeCellHandler;
-
- private final UserSorter sorter = new UserSorter();
-
- private final EditorRow<T> editorRow = GWT.create(EditorRow.class);
-
- /**
- * Enumeration for easy setting of selection mode.
- */
- public enum SelectionMode {
-
- /**
- * Shortcut for {@link SelectionModelSingle}.
- */
- SINGLE {
-
- @Override
- protected <T> SelectionModel<T> createModel() {
- return new SelectionModelSingle<T>();
- }
- },
-
- /**
- * Shortcut for {@link SelectionModelMulti}.
- */
- MULTI {
-
- @Override
- protected <T> SelectionModel<T> createModel() {
- return new SelectionModelMulti<T>();
- }
- },
-
- /**
- * Shortcut for {@link SelectionModelNone}.
- */
- NONE {
-
- @Override
- protected <T> SelectionModel<T> createModel() {
- return new SelectionModelNone<T>();
- }
- };
-
- protected abstract <T> SelectionModel<T> createModel();
- }
-
- /**
- * Base class for grid columns internally used by the Grid. The user should
- * use {@link GridColumn} when creating new columns.
- *
- * @param <C>
- * the column type
- *
- * @param <T>
- * the row type
- */
- static abstract class AbstractGridColumn<C, T> implements HasVisibility {
-
- /**
- * the column is associated with
- */
- private Grid<T> grid;
-
- /**
- * Should the column be visible in the grid
- */
- private boolean visible = true;
-
- /**
- * Width of column in pixels
- */
- private int width = 100;
-
- /**
- * Renderer for rendering a value into the cell
- */
- private Renderer<? super C> bodyRenderer;
-
- private boolean sortable = false;
-
- /**
- * Constructs a new column with a custom renderer.
- *
- * @param renderer
- * The renderer to use for rendering the cells
- */
- public AbstractGridColumn(Renderer<? super C> renderer) {
- if (renderer == null) {
- throw new IllegalArgumentException("Renderer cannot be null.");
- }
- bodyRenderer = renderer;
- }
-
- /**
- * Internally used by the grid to set itself
- *
- * @param grid
- */
- private void setGrid(Grid<T> grid) {
- if (this.grid != null && grid != null) {
- // Trying to replace grid
- throw new IllegalStateException(
- "Column already is attached to grid. Remove the column first from the grid and then add it.");
- }
-
- this.grid = grid;
- }
-
- /**
- * Is the column visible. By default all columns are visible.
- *
- * @return <code>true</code> if the column is visible
- */
- @Override
- public boolean isVisible() {
- return visible;
- }
-
- /**
- * Sets a column as visible in the grid.
- *
- * @param visible
- * <code>true</code> if the column should be displayed in the
- * grid
- */
- @Override
- public void setVisible(boolean visible) {
- if (this.visible == visible) {
- return;
- }
-
- /*
- * We need to guarantee that both insertColumns and removeColumns
- * have this particular column accessible. Therefore, if we're
- * turning the column visible, it's set before the other logic.
- * Analogously, if we're turning the column invisible, we do that
- * only after the logic has been performed.
- */
-
- if (visible) {
- this.visible = true;
- }
-
- if (grid != null) {
- int index = findIndexOfColumn();
- ColumnConfiguration conf = grid.escalator
- .getColumnConfiguration();
-
- if (visible) {
- conf.insertColumns(index, 1);
- } else {
- conf.removeColumns(index, 1);
- }
- }
-
- if (!visible) {
- this.visible = false;
- }
-
- if (grid != null) {
- for (HeaderRow row : grid.getHeader().getRows()) {
- row.calculateColspans();
- }
-
- for (FooterRow row : grid.getFooter().getRows()) {
- row.calculateColspans();
- }
- }
- }
-
- /**
- * Returns the data that should be rendered into the cell. By default
- * returning Strings and Widgets are supported. If the return type is a
- * String then it will be treated as preformatted text.
- * <p>
- * To support other types you will need to pass a custom renderer to the
- * column via the column constructor.
- *
- * @param row
- * The row object that provides the cell content.
- *
- * @return The cell content
- */
- public abstract C getValue(T row);
-
- /**
- * The renderer to render the cell width. By default renders the data as
- * a String or adds the widget into the cell if the column type is of
- * widget type.
- *
- * @return The renderer to render the cell content with
- */
- public Renderer<? super C> getRenderer() {
- return bodyRenderer;
- }
-
- /**
- * Finds the index of this column instance
- *
- */
- private int findIndexOfColumn() {
- return grid.findVisibleColumnIndex((GridColumn<?, T>) this);
- }
-
- /**
- * Sets the pixel width of the column. Use a negative value for the grid
- * to autosize column based on content and available space
- *
- * @param pixels
- * the width in pixels or negative for auto sizing
- */
- public void setWidth(int pixels) {
- width = pixels;
-
- if (grid != null && isVisible()) {
- int index = findIndexOfColumn();
- ColumnConfiguration conf = grid.escalator
- .getColumnConfiguration();
- conf.setColumnWidth(index, pixels);
- }
- }
-
- /**
- * Returns the pixel width of the column
- *
- * @return pixel width of the column
- */
- public int getWidth() {
- return width;
- }
-
- /**
- * Enables sort indicators for the grid.
- * <p>
- * <b>Note:</b>The API can still sort the column even if this is set to
- * <code>false</code>.
- *
- * @param sortable
- * <code>true</code> when column sort indicators are visible.
- */
- public void setSortable(boolean sortable) {
- if (this.sortable != sortable) {
- this.sortable = sortable;
- grid.refreshHeader();
- }
- }
-
- /**
- * Are sort indicators shown for the column.
- *
- * @return <code>true</code> if the column is sortable
- */
- public boolean isSortable() {
- return sortable;
- }
- }
-
- protected class BodyUpdater implements EscalatorUpdater {
-
- @Override
- public void preAttach(Row row, Iterable<FlyweightCell> cellsToAttach) {
- for (FlyweightCell cell : cellsToAttach) {
- Renderer<?> renderer = findRenderer(cell);
- if (renderer instanceof ComplexRenderer) {
- ((ComplexRenderer<?>) renderer).init(cell);
- }
- }
- }
-
- @Override
- public void postAttach(Row row, Iterable<FlyweightCell> attachedCells) {
- for (FlyweightCell cell : attachedCells) {
- Renderer<?> renderer = findRenderer(cell);
- if (renderer instanceof WidgetRenderer) {
- WidgetRenderer<?, ?> widgetRenderer = (WidgetRenderer<?, ?>) renderer;
-
- Widget widget = widgetRenderer.createWidget();
- assert widget != null : "WidgetRenderer.createWidget() returned null. It should return a widget.";
- assert widget.getParent() == null : "WidgetRenderer.createWidget() returned a widget which already is attached.";
- assert cell.getElement().getChildCount() == 0 : "Cell content should be empty when adding Widget";
-
- // Physical attach
- cell.getElement().appendChild(widget.getElement());
-
- // Logical attach
- setParent(widget, Grid.this);
- }
- }
- }
-
- @Override
- public void update(Row row, Iterable<FlyweightCell> cellsToUpdate) {
- int rowIndex = row.getRow();
- TableRowElement rowElement = row.getElement();
- T rowData = dataSource.getRow(rowIndex);
-
- boolean hasData = rowData != null;
-
- // Assign stylename for rows with data
- boolean usedToHaveData = rowElement
- .hasClassName(rowHasDataStyleName);
-
- if (usedToHaveData != hasData) {
- setStyleName(rowElement, rowHasDataStyleName, hasData);
- }
-
- // Assign stylename for selected rows
- if (hasData) {
- setStyleName(rowElement, rowSelectedStyleName,
- isSelected(rowData));
- } else if (usedToHaveData) {
- setStyleName(rowElement, rowSelectedStyleName, false);
- }
-
- activeCellHandler.updateActiveRowStyle(row);
-
- for (FlyweightCell cell : cellsToUpdate) {
- GridColumn<?, T> column = getColumnFromVisibleIndex(cell
- .getColumn());
-
- assert column != null : "Column was not found from cell ("
- + cell.getColumn() + "," + cell.getRow() + ")";
-
- activeCellHandler.updateActiveCellStyle(cell,
- escalator.getBody());
-
- Renderer renderer = column.getRenderer();
-
- if (renderer instanceof ComplexRenderer) {
- // Hide cell content if needed
- ComplexRenderer clxRenderer = (ComplexRenderer) renderer;
- if (hasData) {
- if (!usedToHaveData) {
- // Prepare cell for rendering
- clxRenderer.setContentVisible(cell, true);
- }
-
- Object value = column.getValue(rowData);
- clxRenderer.render(cell, value);
-
- } else {
- // Prepare cell for no data
- clxRenderer.setContentVisible(cell, false);
- }
-
- } else if (hasData) {
- // Simple renderers just render
- Object value = column.getValue(rowData);
- renderer.render(cell, value);
-
- } else {
- // Clear cell if there is no data
- cell.getElement().removeAllChildren();
- }
- }
- }
-
- @Override
- public void preDetach(Row row, Iterable<FlyweightCell> cellsToDetach) {
- for (FlyweightCell cell : cellsToDetach) {
- Renderer renderer = findRenderer(cell);
- if (renderer instanceof WidgetRenderer) {
- Widget w = Util.findWidget(cell.getElement()
- .getFirstChildElement(), Widget.class);
- if (w != null) {
-
- // Logical detach
- setParent(w, null);
-
- // Physical detach
- cell.getElement().removeChild(w.getElement());
- }
- }
- }
- }
-
- @Override
- public void postDetach(Row row, Iterable<FlyweightCell> detachedCells) {
- for (FlyweightCell cell : detachedCells) {
- Renderer renderer = findRenderer(cell);
- if (renderer instanceof ComplexRenderer) {
- ((ComplexRenderer) renderer).destroy(cell);
- }
- }
- }
- }
-
- protected class StaticSectionUpdater implements EscalatorUpdater {
-
- private GridStaticSection<?> section;
- private RowContainer container;
-
- public StaticSectionUpdater(GridStaticSection<?> section,
- RowContainer container) {
- super();
- this.section = section;
- this.container = container;
- }
-
- @Override
- public void update(Row row, Iterable<FlyweightCell> cellsToUpdate) {
- GridStaticSection.StaticRow<?> staticRow = section.getRow(row
- .getRow());
-
- final List<Integer> columnIndices = getVisibleColumnIndices();
-
- for (FlyweightCell cell : cellsToUpdate) {
-
- int index = columnIndices.get(cell.getColumn());
- final StaticCell metadata = staticRow.getCell(index);
-
- // Decorate default row with sorting indicators
- if (staticRow instanceof HeaderRow) {
- addSortingIndicatorsToHeaderRow((HeaderRow) staticRow, cell);
- }
-
- // Assign colspan to cell before rendering
- cell.setColSpan(metadata.getColspan());
-
- switch (metadata.getType()) {
- case TEXT:
- cell.getElement().setInnerText(metadata.getText());
- break;
- case HTML:
- cell.getElement().setInnerHTML(metadata.getHtml());
- break;
- case WIDGET:
- preDetach(row, Arrays.asList(cell));
- cell.getElement().setInnerHTML("");
- postAttach(row, Arrays.asList(cell));
- break;
- }
-
- activeCellHandler.updateActiveCellStyle(cell, container);
- }
- }
-
- private void addSortingIndicatorsToHeaderRow(HeaderRow headerRow,
- FlyweightCell cell) {
-
- cleanup(cell);
-
- GridColumn<?, ?> column = getColumnFromVisibleIndex(cell
- .getColumn());
- SortOrder sortingOrder = getSortOrder(column);
- if (!headerRow.isDefault() || !column.isSortable()
- || sortingOrder == null) {
- // Only apply sorting indicators to sortable header columns in
- // the default header row
- return;
- }
-
- Element cellElement = cell.getElement();
-
- if (SortDirection.ASCENDING == sortingOrder.getDirection()) {
- cellElement.addClassName("sort-asc");
- } else {
- cellElement.addClassName("sort-desc");
- }
-
- int sortIndex = Grid.this.getSortOrder().indexOf(sortingOrder);
- if (sortIndex > -1 && Grid.this.getSortOrder().size() > 1) {
- // Show sort order indicator if column is
- // sorted and other sorted columns also exists.
- cellElement.setAttribute("sort-order",
- String.valueOf(sortIndex + 1));
- }
- }
-
- /**
- * Finds the sort order for this column
- */
- private SortOrder getSortOrder(GridColumn<?, ?> column) {
- for (SortOrder order : Grid.this.getSortOrder()) {
- if (order.getColumn() == column) {
- return order;
- }
- }
- return null;
- }
-
- private void cleanup(FlyweightCell cell) {
- Element cellElement = cell.getElement();
- cellElement.removeAttribute("sort-order");
- cellElement.removeClassName("sort-desc");
- cellElement.removeClassName("sort-asc");
- }
-
- @Override
- public void preAttach(Row row, Iterable<FlyweightCell> cellsToAttach) {
- }
-
- @Override
- public void postAttach(Row row, Iterable<FlyweightCell> attachedCells) {
- GridStaticSection.StaticRow<?> gridRow = section.getRow(row
- .getRow());
- List<Integer> columnIndices = getVisibleColumnIndices();
-
- for (FlyweightCell cell : attachedCells) {
- int index = columnIndices.get(cell.getColumn());
- StaticCell metadata = gridRow.getCell(index);
- /*
- * If the cell contains widgets that are not currently attach
- * then attach them now.
- */
- if (GridStaticCellType.WIDGET.equals(metadata.getType())) {
- final Widget widget = metadata.getWidget();
- final Element cellElement = cell.getElement();
-
- if (!widget.isAttached()) {
-
- // Physical attach
- cellElement.appendChild(widget.getElement());
-
- // Logical attach
- setParent(widget, Grid.this);
- }
- }
- }
- }
-
- @Override
- public void preDetach(Row row, Iterable<FlyweightCell> cellsToDetach) {
- if (section.getRowCount() > row.getRow()) {
- GridStaticSection.StaticRow<?> gridRow = section.getRow(row
- .getRow());
- List<Integer> columnIndices = getVisibleColumnIndices();
- for (FlyweightCell cell : cellsToDetach) {
- int index = columnIndices.get(cell.getColumn());
- StaticCell metadata = gridRow.getCell(index);
-
- if (GridStaticCellType.WIDGET.equals(metadata.getType())
- && metadata.getWidget().isAttached()) {
-
- Widget widget = metadata.getWidget();
-
- // Logical detach
- setParent(widget, null);
-
- // Physical detach
- widget.getElement().removeFromParent();
- }
- }
- }
- }
-
- @Override
- public void postDetach(Row row, Iterable<FlyweightCell> detachedCells) {
- }
- };
-
- /**
- * Creates a new instance.
- */
- public Grid() {
- initWidget(escalator);
- getElement().setTabIndex(0);
- activeCellHandler = new ActiveCellHandler();
-
- setStylePrimaryName("v-grid");
-
- escalator.getHeader().setEscalatorUpdater(createHeaderUpdater());
- escalator.getBody().setEscalatorUpdater(createBodyUpdater());
- escalator.getFooter().setEscalatorUpdater(createFooterUpdater());
-
- header.setGrid(this);
- HeaderRow defaultRow = header.appendRow();
- header.setDefaultRow(defaultRow);
-
- footer.setGrid(this);
-
- editorRow.setGrid(this);
-
- setSelectionMode(SelectionMode.SINGLE);
-
- escalator.addScrollHandler(new ScrollHandler() {
- @Override
- public void onScroll(ScrollEvent event) {
- fireEvent(new ScrollEvent());
- }
- });
-
- escalator
- .addRowVisibilityChangeHandler(new RowVisibilityChangeHandler() {
- @Override
- public void onRowVisibilityChange(
- RowVisibilityChangeEvent event) {
- if (dataSource != null) {
- dataSource.ensureAvailability(
- event.getFirstVisibleRow(),
- event.getVisibleRowCount());
- }
- }
- });
-
- // Default action on SelectionChangeEvents. Refresh the body so changed
- // become visible.
- addSelectionChangeHandler(new SelectionChangeHandler<T>() {
-
- @Override
- public void onSelectionChange(SelectionChangeEvent<T> event) {
- refreshBody();
- }
- });
-
- // Sink header events and key events
- sinkEvents(getHeader().getConsumedEvents());
- sinkEvents(Arrays.asList(BrowserEvents.KEYDOWN, BrowserEvents.KEYUP,
- BrowserEvents.KEYPRESS, BrowserEvents.DBLCLICK));
-
- // Make ENTER and SHIFT+ENTER in the header perform sorting
- addHeaderKeyUpHandler(new HeaderKeyUpHandler() {
- @Override
- public void onKeyUp(GridKeyUpEvent event) {
- if (event.getNativeKeyCode() != KeyCodes.KEY_ENTER) {
- return;
- }
-
- sorter.sort(event.getActiveCell(), event.isShiftKeyDown());
- }
- });
- }
-
- @Override
- public void setStylePrimaryName(String style) {
- super.setStylePrimaryName(style);
- escalator.setStylePrimaryName(style);
- editorRow.setStylePrimaryName(style);
-
- rowHasDataStyleName = getStylePrimaryName() + "-row-has-data";
- rowSelectedStyleName = getStylePrimaryName() + "-row-selected";
- cellActiveStyleName = getStylePrimaryName() + "-cell-active";
- headerFooterActiveStyleName = getStylePrimaryName() + "-header-active";
- rowActiveStyleName = getStylePrimaryName() + "-row-active";
-
- if (isAttached()) {
- refreshHeader();
- refreshBody();
- refreshFooter();
- }
- }
-
- /**
- * Creates the escalator updater used to update the header rows in this
- * grid. The updater is invoked when header rows or columns are added or
- * removed, or the content of existing header cells is changed.
- *
- * @return the new header updater instance
- *
- * @see GridHeader
- * @see Grid#getHeader()
- */
- protected EscalatorUpdater createHeaderUpdater() {
- return new StaticSectionUpdater(header, escalator.getHeader());
- }
-
- /**
- * Creates the escalator updater used to update the body rows in this grid.
- * The updater is invoked when body rows or columns are added or removed,
- * the content of body cells is changed, or the body is scrolled to expose
- * previously hidden content.
- *
- * @return the new body updater instance
- */
- protected EscalatorUpdater createBodyUpdater() {
- return new BodyUpdater();
- }
-
- /**
- * Creates the escalator updater used to update the footer rows in this
- * grid. The updater is invoked when header rows or columns are added or
- * removed, or the content of existing header cells is changed.
- *
- * @return the new footer updater instance
- *
- * @see GridFooter
- * @see #getFooter()
- */
- protected EscalatorUpdater createFooterUpdater() {
- return new StaticSectionUpdater(footer, escalator.getFooter());
- }
-
- /**
- * Refreshes header or footer rows on demand
- *
- * @param rows
- * The row container
- * @param firstRowIsVisible
- * is the first row visible
- * @param isHeader
- * <code>true</code> if we refreshing the header, else assumed
- * the footer
- */
- private void refreshRowContainer(RowContainer rows,
- GridStaticSection<?> section) {
-
- // Add or Remove rows on demand
- int rowDiff = section.getVisibleRowCount() - rows.getRowCount();
- if (rowDiff > 0) {
- rows.insertRows(0, rowDiff);
- } else if (rowDiff < 0) {
- rows.removeRows(0, -rowDiff);
- }
-
- // Refresh all the rows
- if (rows.getRowCount() > 0) {
- rows.refreshRows(0, rows.getRowCount());
- }
- }
-
- /**
- * Refreshes all header rows
- */
- void refreshHeader() {
- refreshRowContainer(escalator.getHeader(), header);
- }
-
- /**
- * Refreshes all body rows
- */
- private void refreshBody() {
- escalator.getBody().refreshRows(0, escalator.getBody().getRowCount());
- }
-
- /**
- * Refreshes all footer rows
- */
- void refreshFooter() {
- refreshRowContainer(escalator.getFooter(), footer);
- }
-
- /**
- * Adds a column as the last column in the grid.
- *
- * @param column
- * the column to add
- */
- public void addColumn(GridColumn<?, T> column) {
- addColumn(column, getColumnCount());
- }
-
- /**
- * Inserts a column into a specific position in the grid.
- *
- * @param index
- * the index where the column should be inserted into
- * @param column
- * the column to add
- * @throws IllegalStateException
- * if Grid's current selection model renders a selection column,
- * and {@code index} is 0.
- */
- public void addColumn(GridColumn<?, T> column, int index) {
- if (column == selectionColumn) {
- throw new IllegalArgumentException("The selection column many "
- + "not be added manually");
- } else if (selectionColumn != null && index == 0) {
- throw new IllegalStateException("A column cannot be inserted "
- + "before the selection column");
- }
-
- addColumnSkipSelectionColumnCheck(column, index);
- }
-
- private void addColumnSkipSelectionColumnCheck(GridColumn<?, T> column,
- int index) {
- // Register column with grid
- columns.add(index, column);
-
- header.addColumn(column, index);
- footer.addColumn(column, index);
-
- // Register this grid instance with the column
- ((AbstractGridColumn<?, T>) column).setGrid(this);
-
- // Insert column into escalator
- if (column.isVisible()) {
- int visibleIndex = findVisibleColumnIndex(column);
- ColumnConfiguration conf = escalator.getColumnConfiguration();
-
- // Insert column
- conf.insertColumns(visibleIndex, 1);
-
- // Transfer column width from column object to escalator
- conf.setColumnWidth(visibleIndex, column.getWidth());
- }
-
- if (lastFrozenColumn != null
- && ((AbstractGridColumn<?, T>) lastFrozenColumn)
- .findIndexOfColumn() < index) {
- refreshFrozenColumns();
- }
-
- // Sink all renderer events
- Set<String> events = new HashSet<String>();
- events.addAll(getConsumedEventsForRenderer(column.getRenderer()));
-
- sinkEvents(events);
- }
-
- private void sinkEvents(Collection<String> events) {
- assert events != null;
-
- int eventsToSink = 0;
- for (String typeName : events) {
- int typeInt = Event.getTypeInt(typeName);
- if (typeInt < 0) {
- // Type not recognized by typeInt
- sinkBitlessEvent(typeName);
- } else {
- eventsToSink |= typeInt;
- }
- }
-
- if (eventsToSink > 0) {
- sinkEvents(eventsToSink);
- }
- }
-
- protected int findVisibleColumnIndex(GridColumn<?, T> column) {
- int idx = 0;
- for (GridColumn<?, T> c : columns) {
- if (c == column) {
- return idx;
- } else if (c.isVisible()) {
- idx++;
- }
- }
- return -1;
- }
-
- protected GridColumn<?, T> getColumnFromVisibleIndex(int index) {
- int idx = -1;
- for (GridColumn<?, T> c : columns) {
- if (c.isVisible()) {
- idx++;
- }
- if (index == idx) {
- return c;
- }
- }
- return null;
- }
-
- private Renderer<?> findRenderer(FlyweightCell cell) {
- GridColumn<?, T> column = getColumnFromVisibleIndex(cell.getColumn());
- assert column != null : "Could not find column at index:"
- + cell.getColumn();
- return column.getRenderer();
- }
-
- /**
- * Removes a column from the grid.
- *
- * @param column
- * the column to remove
- */
- public void removeColumn(GridColumn<?, T> column) {
- if (column != null && column.equals(selectionColumn)) {
- throw new IllegalArgumentException(
- "The selection column may not be removed manually.");
- }
-
- removeColumnSkipSelectionColumnCheck(column);
- }
-
- private void removeColumnSkipSelectionColumnCheck(GridColumn<?, T> column) {
- int columnIndex = columns.indexOf(column);
- int visibleIndex = findVisibleColumnIndex(column);
- columns.remove(columnIndex);
-
- header.removeColumn(columnIndex);
- footer.removeColumn(columnIndex);
-
- // de-register column with grid
- ((AbstractGridColumn<?, T>) column).setGrid(null);
-
- if (column.isVisible()) {
- ColumnConfiguration conf = escalator.getColumnConfiguration();
- conf.removeColumns(visibleIndex, 1);
- }
-
- if (column.equals(lastFrozenColumn)) {
- setLastFrozenColumn(null);
- } else {
- refreshFrozenColumns();
- }
- }
-
- /**
- * Returns the amount of columns in the grid.
- *
- * @return The number of columns in the grid
- */
- public int getColumnCount() {
- return columns.size();
- }
-
- /**
- * Returns a list of columns in the grid.
- *
- * @return A unmodifiable list of the columns in the grid
- */
- public List<GridColumn<?, T>> getColumns() {
- return Collections.unmodifiableList(new ArrayList<GridColumn<?, T>>(
- columns));
- }
-
- /**
- * Returns a column by its index in the grid.
- *
- * @param index
- * the index of the column
- * @return The column in the given index
- * @throws IllegalArgumentException
- * if the column index does not exist in the grid
- */
- public GridColumn<?, T> getColumn(int index)
- throws IllegalArgumentException {
- if (index < 0 || index >= columns.size()) {
- throw new IllegalStateException("Column not found.");
- }
- return columns.get(index);
- }
-
- /**
- * Returns a list of column indices that are currently visible.
- *
- * @return a list of indices
- */
- private List<Integer> getVisibleColumnIndices() {
- List<Integer> indices = new ArrayList<Integer>(getColumnCount());
- for (int i = 0; i < getColumnCount(); i++) {
- if (getColumn(i).isVisible()) {
- indices.add(i);
- }
- }
- return indices;
- }
-
- /**
- * Returns the header section of this grid. The default header contains a
- * single row displaying the column captions.
- *
- * @return the header
- */
- public GridHeader getHeader() {
- return header;
- }
-
- /**
- * Returns the footer section of this grid. The default footer is empty.
- *
- * @return the footer
- */
- public GridFooter getFooter() {
- return footer;
- }
-
- public EditorRow<T> getEditorRow() {
- return editorRow;
- }
-
- protected Escalator getEscalator() {
- return escalator;
- }
-
- /**
- * {@inheritDoc}
- * <p>
- * <em>Note:</em> This method will change the widget's size in the browser
- * only if {@link #getHeightMode()} returns {@link HeightMode#CSS}.
- *
- * @see #setHeightMode(HeightMode)
- */
- @Override
- public void setHeight(String height) {
- escalator.setHeight(height);
- }
-
- @Override
- public void setWidth(String width) {
- escalator.setWidth(width);
- }
-
- /**
- * Sets the data source used by this grid.
- *
- * @param dataSource
- * the data source to use, not null
- * @throws IllegalArgumentException
- * if <code>dataSource</code> is <code>null</code>
- */
- public void setDataSource(DataSource<T> dataSource)
- throws IllegalArgumentException {
- if (dataSource == null) {
- throw new IllegalArgumentException("dataSource can't be null.");
- }
-
- selectionModel.reset();
-
- if (this.dataSource != null) {
- this.dataSource.setDataChangeHandler(null);
- }
-
- this.dataSource = dataSource;
- dataSource.setDataChangeHandler(new DataChangeHandler() {
- @Override
- public void dataUpdated(int firstIndex, int numberOfItems) {
- escalator.getBody().refreshRows(firstIndex, numberOfItems);
- }
-
- @Override
- public void dataRemoved(int firstIndex, int numberOfItems) {
- escalator.getBody().removeRows(firstIndex, numberOfItems);
- Range removed = Range.withLength(firstIndex, numberOfItems);
- activeCellHandler.rowsRemoved(removed);
- }
-
- @Override
- public void dataAdded(int firstIndex, int numberOfItems) {
- escalator.getBody().insertRows(firstIndex, numberOfItems);
- Range added = Range.withLength(firstIndex, numberOfItems);
- activeCellHandler.rowsAdded(added);
- }
-
- @Override
- public void dataAvailable(int firstIndex, int numberOfItems) {
- currentDataAvailable = Range.withLength(firstIndex,
- numberOfItems);
- fireEvent(new DataAvailableEvent(currentDataAvailable));
- }
- });
-
- int previousRowCount = escalator.getBody().getRowCount();
- if (previousRowCount != 0) {
- escalator.getBody().removeRows(0, previousRowCount);
- }
-
- int estimatedSize = dataSource.getEstimatedSize();
- if (estimatedSize > 0) {
- escalator.getBody().insertRows(0, estimatedSize);
- }
-
- }
-
- /**
- * Gets the {@Link DataSource} for this Grid.
- *
- * @return the data source used by this grid
- */
- public DataSource<T> getDataSource() {
- return dataSource;
- }
-
- /**
- * Sets the rightmost frozen column in the grid.
- * <p>
- * All columns up to and including the given column will be frozen in place
- * when the grid is scrolled sideways.
- *
- * @param lastFrozenColumn
- * the rightmost column to freeze, or <code>null</code> to not
- * have any columns frozen
- * @throws IllegalArgumentException
- * if {@code lastFrozenColumn} is not a column from this grid
- */
- public void setLastFrozenColumn(GridColumn<?, T> lastFrozenColumn) {
- this.lastFrozenColumn = lastFrozenColumn;
- refreshFrozenColumns();
- }
-
- private void refreshFrozenColumns() {
- final int frozenCount;
- if (lastFrozenColumn != null) {
- frozenCount = columns.indexOf(lastFrozenColumn) + 1;
- if (frozenCount == 0) {
- throw new IllegalArgumentException(
- "The given column isn't attached to this grid");
- }
- } else {
- frozenCount = 0;
- }
-
- escalator.getColumnConfiguration().setFrozenColumnCount(frozenCount);
- }
-
- /**
- * Gets the rightmost frozen column in the grid.
- * <p>
- * <em>Note:</em> Most usually, this method returns the very value set with
- * {@link #setLastFrozenColumn(GridColumn)}. This value, however, can be
- * reset to <code>null</code> if the column is removed from this grid.
- *
- * @return the rightmost frozen column in the grid, or <code>null</code> if
- * no columns are frozen.
- */
- public GridColumn<?, T> getLastFrozenColumn() {
- return lastFrozenColumn;
- }
-
- public HandlerRegistration addRowVisibilityChangeHandler(
- RowVisibilityChangeHandler handler) {
- /*
- * Reusing Escalator's RowVisibilityChangeHandler, since a scroll
- * concept is too abstract. e.g. the event needs to be re-sent when the
- * widget is resized.
- */
- return escalator.addRowVisibilityChangeHandler(handler);
- }
-
- /**
- * Scrolls to a certain row, using {@link ScrollDestination#ANY}.
- *
- * @param rowIndex
- * zero-based index of the row to scroll to.
- * @throws IllegalArgumentException
- * if rowIndex is below zero, or above the maximum value
- * supported by the data source.
- */
- public void scrollToRow(int rowIndex) throws IllegalArgumentException {
- scrollToRow(rowIndex, ScrollDestination.ANY,
- GridConstants.DEFAULT_PADDING);
- }
-
- /**
- * Scrolls to a certain row, using user-specified scroll destination.
- *
- * @param rowIndex
- * zero-based index of the row to scroll to.
- * @param destination
- * desired destination placement of scrolled-to-row. See
- * {@link ScrollDestination} for more information.
- * @throws IllegalArgumentException
- * if rowIndex is below zero, or above the maximum value
- * supported by the data source.
- */
- public void scrollToRow(int rowIndex, ScrollDestination destination)
- throws IllegalArgumentException {
- scrollToRow(rowIndex, destination,
- destination == ScrollDestination.MIDDLE ? 0
- : GridConstants.DEFAULT_PADDING);
- }
-
- /**
- * Scrolls to a certain row using only user-specified parameters.
- *
- * @param rowIndex
- * zero-based index of the row to scroll to.
- * @param destination
- * desired destination placement of scrolled-to-row. See
- * {@link ScrollDestination} for more information.
- * @param paddingPx
- * number of pixels to overscroll. Behavior depends on
- * destination.
- * @throws IllegalArgumentException
- * if {@code destination} is {@link ScrollDestination#MIDDLE}
- * and padding is nonzero, because having a padding on a
- * centered row is undefined behavior, or if rowIndex is below
- * zero or above the row count of the data source.
- */
- private void scrollToRow(int rowIndex, ScrollDestination destination,
- int paddingPx) throws IllegalArgumentException {
- int maxsize = escalator.getBody().getRowCount() - 1;
-
- if (rowIndex < 0) {
- throw new IllegalArgumentException("Row index (" + rowIndex
- + ") is below zero!");
- }
-
- if (rowIndex > maxsize) {
- throw new IllegalArgumentException("Row index (" + rowIndex
- + ") is above maximum (" + maxsize + ")!");
- }
-
- escalator.scrollToRow(rowIndex, destination, paddingPx);
- }
-
- /**
- * Scrolls to the beginning of the very first row.
- */
- public void scrollToStart() {
- scrollToRow(0, ScrollDestination.START);
- }
-
- /**
- * Scrolls to the end of the very last row.
- */
- public void scrollToEnd() {
- scrollToRow(escalator.getBody().getRowCount() - 1,
- ScrollDestination.END);
- }
-
- /**
- * Sets the vertical scroll offset.
- *
- * @param px
- * the number of pixels this grid should be scrolled down
- */
- public void setScrollTop(double px) {
- escalator.setScrollTop(px);
- }
-
- /**
- * Gets the vertical scroll offset
- *
- * @return the number of pixels this grid is scrolled down
- */
- public double getScrollTop() {
- return escalator.getScrollTop();
- }
-
- /**
- * Gets the horizontal scroll offset
- *
- * @return the number of pixels this grid is scrolled to the right
- */
- public double getScrollLeft() {
- return escalator.getScrollLeft();
- }
-
- private static final Logger getLogger() {
- return Logger.getLogger(Grid.class.getName());
- }
-
- /**
- * Sets the number of rows that should be visible in Grid's body, while
- * {@link #getHeightMode()} is {@link HeightMode#ROW}.
- * <p>
- * If Grid is currently not in {@link HeightMode#ROW}, the given value is
- * remembered, and applied once the mode is applied.
- *
- * @param rows
- * The height in terms of number of rows displayed in Grid's
- * body. If Grid doesn't contain enough rows, white space is
- * displayed instead.
- * @throws IllegalArgumentException
- * if {@code rows} is zero or less
- * @throws IllegalArgumentException
- * if {@code rows} is {@link Double#isInifinite(double)
- * infinite}
- * @throws IllegalArgumentException
- * if {@code rows} is {@link Double#isNaN(double) NaN}
- *
- * @see #setHeightMode(HeightMode)
- */
- public void setHeightByRows(double rows) throws IllegalArgumentException {
- escalator.setHeightByRows(rows);
- }
-
- /**
- * Gets the amount of rows in Grid's body that are shown, while
- * {@link #getHeightMode()} is {@link HeightMode#ROW}.
- * <p>
- * By default, it is {@value Escalator#DEFAULT_HEIGHT_BY_ROWS}.
- *
- * @return the amount of rows that should be shown in Grid's body, while in
- * {@link HeightMode#ROW}.
- * @see #setHeightByRows(double)
- */
- public double getHeightByRows() {
- return escalator.getHeightByRows();
- }
-
- /**
- * Defines the mode in which the Grid widget's height is calculated.
- * <p>
- * If {@link HeightMode#CSS} is given, Grid will respect the values given
- * via {@link #setHeight(String)}, and behave as a traditional Widget.
- * <p>
- * If {@link HeightMode#ROW} is given, Grid will make sure that the body
- * will display as many rows as {@link #getHeightByRows()} defines.
- * <em>Note:</em> If headers/footers are inserted or removed, the widget
- * will resize itself to still display the required amount of rows in its
- * body. It also takes the horizontal scrollbar into account.
- *
- * @param heightMode
- * the mode in to which Grid should be set
- */
- public void setHeightMode(HeightMode heightMode) {
- /*
- * This method is a workaround for the fact that Vaadin re-applies
- * widget dimensions (height/width) on each state change event. The
- * original design was to have setHeight an setHeightByRow be equals,
- * and whichever was called the latest was considered in effect.
- *
- * But, because of Vaadin always calling setHeight on the widget, this
- * approach doesn't work.
- */
-
- escalator.setHeightMode(heightMode);
- }
-
- /**
- * Returns the current {@link HeightMode} the Grid is in.
- * <p>
- * Defaults to {@link HeightMode#CSS}.
- *
- * @return the current HeightMode
- */
- public HeightMode getHeightMode() {
- return escalator.getHeightMode();
- }
-
- private Set<String> getConsumedEventsForRenderer(Renderer<?> renderer) {
- Set<String> events = new HashSet<String>();
- if (renderer instanceof ComplexRenderer) {
- Collection<String> consumedEvents = ((ComplexRenderer<?>) renderer)
- .getConsumedEvents();
- if (consumedEvents != null) {
- events.addAll(consumedEvents);
- }
- }
- return events;
- }
-
- @Override
- public void onBrowserEvent(Event event) {
- super.onBrowserEvent(event);
-
- EventTarget target = event.getEventTarget();
-
- if (!Element.is(target)) {
- return;
- }
-
- Element e = Element.as(target);
- RowContainer container = escalator.findRowContainer(e);
- Cell cell;
- boolean isGrid = Util.findWidget(e, null) == this;
- if (container == null) {
- cell = activeCellHandler.getActiveCell();
- container = activeCellHandler.container;
- } else {
- cell = container.getCell(e);
- }
-
- if (isGrid) {
- if (handleEditorRowEvent(event, container, cell)) {
- return;
- }
-
- if (handleHeaderDefaultRowEvent(event, container, cell)) {
- return;
- }
-
- if (handleRendererEvent(event, container, cell)) {
- return;
- }
-
- if (handleNavigationEvent(event, container, cell)) {
- return;
- }
-
- if (handleActiveCellEvent(event, container, cell)) {
- return;
- }
- }
- }
-
- private boolean handleEditorRowEvent(Event event, RowContainer container,
- Cell cell) {
- if (editorRow.getState() != State.INACTIVE) {
- if (event.getTypeInt() == Event.ONKEYDOWN
- && event.getKeyCode() == EditorRow.KEYCODE_HIDE) {
- editorRow.cancel();
- }
- return true;
- }
- if (editorRow.isEnabled()) {
- if (event.getTypeInt() == Event.ONDBLCLICK) {
- if (container == escalator.getBody() && cell != null) {
- editorRow.editRow(cell.getRow());
- return true;
- }
- } else if (event.getTypeInt() == Event.ONKEYDOWN
- && event.getKeyCode() == EditorRow.KEYCODE_SHOW) {
- editorRow.editRow(activeCellHandler.activeRow);
- return true;
- }
- }
- return false;
- }
-
- private boolean handleRendererEvent(Event event, RowContainer container,
- Cell cell) {
-
- if (container == escalator.getBody() && cell != null) {
- GridColumn<?, T> gridColumn = getColumnFromVisibleIndex(cell
- .getColumn());
- boolean enterKey = event.getType().equals(BrowserEvents.KEYDOWN)
- && event.getKeyCode() == KeyCodes.KEY_ENTER;
- boolean doubleClick = event.getType()
- .equals(BrowserEvents.DBLCLICK);
-
- if (gridColumn.getRenderer() instanceof ComplexRenderer) {
- ComplexRenderer<?> cplxRenderer = (ComplexRenderer<?>) gridColumn
- .getRenderer();
- if (cplxRenderer.getConsumedEvents().contains(event.getType())) {
- if (cplxRenderer.onBrowserEvent(cell, event)) {
- return true;
- }
- }
-
- // Calls onActivate if KeyDown and Enter or double click
- if ((enterKey || doubleClick) && cplxRenderer.onActivate(cell)) {
- return true;
- }
- }
- }
- return false;
- }
-
- private boolean handleActiveCellEvent(Event event, RowContainer container,
- Cell cell) {
- Collection<String> navigation = activeCellHandler.getNavigationEvents();
- if (navigation.contains(event.getType())) {
- activeCellHandler.handleNavigationEvent(event, cell);
- }
- return false;
- }
-
- private boolean handleNavigationEvent(Event event, RowContainer unused,
- Cell cell) {
- if (!event.getType().equals(BrowserEvents.KEYDOWN)) {
- // Only handle key downs
- return false;
- }
-
- int newRow = -1;
- RowContainer container = escalator.getBody();
- switch (event.getKeyCode()) {
- case KeyCodes.KEY_HOME:
- if (container.getRowCount() > 0) {
- newRow = 0;
- }
- break;
- case KeyCodes.KEY_END:
- if (container.getRowCount() > 0) {
- newRow = container.getRowCount() - 1;
- }
- break;
- case KeyCodes.KEY_PAGEUP: {
- Range range = escalator.getVisibleRowRange();
- if (!range.isEmpty()) {
- int firstIndex = getFirstVisibleRowIndex();
- newRow = firstIndex - range.length();
- if (newRow < 0) {
- newRow = 0;
- }
- }
- break;
- }
- case KeyCodes.KEY_PAGEDOWN: {
- Range range = escalator.getVisibleRowRange();
- if (!range.isEmpty()) {
- int lastIndex = getLastVisibleRowIndex();
- newRow = lastIndex + range.length();
- if (newRow >= container.getRowCount()) {
- newRow = container.getRowCount() - 1;
- }
- }
- break;
- }
- default:
- return false;
- }
-
- scrollToRow(newRow);
-
- return true;
- }
-
- private Point rowEventTouchStartingPoint;
-
- private boolean handleHeaderDefaultRowEvent(Event event,
- RowContainer container, final Cell cell) {
- if (container != escalator.getHeader()) {
- return false;
- }
- if (!getHeader().getRow(cell.getRow()).isDefault()) {
- return false;
- }
- if (!getColumn(cell.getColumn()).isSortable()) {
- // Only handle sorting events if the column is sortable
- return false;
- }
-
- if (BrowserEvents.TOUCHSTART.equals(event.getType())) {
- if (event.getTouches().length() > 1) {
- return false;
- }
-
- event.preventDefault();
-
- Touch touch = event.getChangedTouches().get(0);
- rowEventTouchStartingPoint = new Point(touch.getClientX(),
- touch.getClientY());
-
- sorter.sortAfterDelay(GridConstants.LONG_TAP_DELAY, cell, true);
-
- return true;
-
- } else if (BrowserEvents.TOUCHMOVE.equals(event.getType())) {
- if (event.getTouches().length() > 1) {
- return false;
- }
-
- event.preventDefault();
-
- Touch touch = event.getChangedTouches().get(0);
- double diffX = Math.abs(touch.getClientX()
- - rowEventTouchStartingPoint.getX());
- double diffY = Math.abs(touch.getClientY()
- - rowEventTouchStartingPoint.getY());
-
- // Cancel long tap if finger strays too far from
- // starting point
- if (diffX > GridConstants.LONG_TAP_THRESHOLD
- || diffY > GridConstants.LONG_TAP_THRESHOLD) {
- sorter.cancelDelayedSort();
- }
-
- return true;
-
- } else if (BrowserEvents.TOUCHEND.equals(event.getType())) {
- if (event.getTouches().length() > 1) {
- return false;
- }
-
- if (sorter.isDelayedSortScheduled()) {
- // Not a long tap yet, perform single sort
- sorter.cancelDelayedSort();
- sorter.sort(cell, false);
- }
-
- return true;
-
- } else if (BrowserEvents.TOUCHCANCEL.equals(event.getType())) {
- if (event.getTouches().length() > 1) {
- return false;
- }
-
- sorter.cancelDelayedSort();
-
- return true;
-
- } else if (BrowserEvents.CLICK.equals(event.getType())) {
-
- sorter.sort(cell, event.getShiftKey());
-
- // Click events should go onward to active cell logic
- return false;
- } else {
- return false;
- }
- }
-
- @Override
- public com.google.gwt.user.client.Element getSubPartElement(String subPart) {
- // Parse SubPart string to type and indices
- String[] splitArgs = subPart.split("\\[");
-
- String type = splitArgs[0];
- int[] indices = new int[splitArgs.length - 1];
- for (int i = 0; i < indices.length; ++i) {
- String tmp = splitArgs[i + 1];
- indices[i] = Integer.parseInt(tmp.substring(0, tmp.length() - 1));
- }
-
- // Get correct RowContainer for type from Escalator
- RowContainer container = null;
- if (type.equalsIgnoreCase("header")) {
- container = escalator.getHeader();
- } else if (type.equalsIgnoreCase("cell")) {
- // If wanted row is not visible, we need to scroll there.
- Range visibleRowRange = escalator.getVisibleRowRange();
- if (indices.length > 0 && !visibleRowRange.contains(indices[0])) {
- try {
- scrollToRow(indices[0]);
- } catch (IllegalArgumentException e) {
- getLogger().log(Level.SEVERE, e.getMessage());
- }
- // Scrolling causes a lazy loading event. No element can
- // currently be retrieved.
- return null;
- }
- container = escalator.getBody();
- } else if (type.equalsIgnoreCase("footer")) {
- container = escalator.getFooter();
- }
-
- if (null != container) {
- if (indices.length == 0) {
- // No indexing. Just return the wanted container element
- return DOM.asOld(container.getElement());
- } else {
- try {
- return DOM.asOld(getSubPart(container, indices));
- } catch (Exception e) {
- getLogger().log(Level.SEVERE, e.getMessage());
- }
- }
- }
- return null;
- }
-
- private Element getSubPart(RowContainer container, int[] indices) {
- // Scroll wanted column to view if able
- if (indices.length > 1
- && escalator.getColumnConfiguration().getFrozenColumnCount() <= indices[1]) {
- escalator.scrollToColumn(indices[1], ScrollDestination.ANY, 0);
- }
-
- Element targetElement = container.getRowElement(indices[0]);
- for (int i = 1; i < indices.length && targetElement != null; ++i) {
- targetElement = (Element) targetElement.getChild(indices[i]);
- }
- return targetElement;
- }
-
- @Override
- public String getSubPartName(com.google.gwt.user.client.Element subElement) {
- // Containers and matching SubPart types
- List<RowContainer> containers = Arrays.asList(escalator.getHeader(),
- escalator.getBody(), escalator.getFooter());
- List<String> containerType = Arrays.asList("header", "cell", "footer");
-
- for (int i = 0; i < containers.size(); ++i) {
- RowContainer container = containers.get(i);
- boolean containerRow = (subElement.getTagName().equalsIgnoreCase(
- "tr") && subElement.getParentElement() == container
- .getElement());
- if (containerRow) {
- // Wanted SubPart is row that is a child of containers root
- // To get indices, we use a cell that is a child of this row
- subElement = DOM.asOld(subElement.getFirstChildElement());
- }
-
- Cell cell = container.getCell(subElement);
- if (cell != null) {
- // Skip the column index if subElement was a child of root
- return containerType.get(i) + "[" + cell.getRow()
- + (containerRow ? "]" : "][" + cell.getColumn() + "]");
- }
- }
- return null;
- }
-
- private void setSelectColumnRenderer(
- final Renderer<Boolean> selectColumnRenderer) {
- if (this.selectColumnRenderer == selectColumnRenderer) {
- return;
- }
-
- if (this.selectColumnRenderer != null) {
- removeColumnSkipSelectionColumnCheck(selectionColumn);
- activeCellHandler.offsetRangeBy(-1);
- }
-
- this.selectColumnRenderer = selectColumnRenderer;
-
- if (selectColumnRenderer != null) {
- activeCellHandler.offsetRangeBy(1);
- selectionColumn = new SelectionColumn(selectColumnRenderer);
-
- // FIXME: this needs to be done elsewhere, requires design...
- selectionColumn.setWidth(25);
- addColumnSkipSelectionColumnCheck(selectionColumn, 0);
- selectionColumn.initDone();
- } else {
- selectionColumn = null;
- refreshBody();
- }
- }
-
- /**
- * Accesses the package private method Widget#setParent()
- *
- * @param widget
- * The widget to access
- * @param parent
- * The parent to set
- */
- static native final void setParent(Widget widget, Widget parent)
- /*-{
- widget.@com.google.gwt.user.client.ui.Widget::setParent(Lcom/google/gwt/user/client/ui/Widget;)(parent);
- }-*/;
-
- /**
- * Sets the current selection model.
- * <p>
- * This function will call {@link SelectionModel#setGrid(Grid)}.
- *
- * @param selectionModel
- * a selection model implementation.
- * @throws IllegalArgumentException
- * if selection model argument is null
- */
- public void setSelectionModel(SelectionModel<T> selectionModel) {
-
- if (selectionModel == null) {
- throw new IllegalArgumentException("Selection model can't be null");
- }
-
- if (selectColumnRenderer != null
- && selectColumnRenderer instanceof ComplexRenderer) {
- ((ComplexRenderer<?>) selectColumnRenderer).destroy();
- }
-
- this.selectionModel = selectionModel;
- selectionModel.setGrid(this);
- setSelectColumnRenderer(this.selectionModel
- .getSelectionColumnRenderer());
- }
-
- /**
- * Gets a reference to the current selection model.
- *
- * @return the currently used SelectionModel instance.
- */
- public SelectionModel<T> getSelectionModel() {
- return selectionModel;
- }
-
- /**
- * Sets current selection mode.
- * <p>
- * This is a shorthand method for {@link Grid#setSelectionModel}.
- *
- * @param mode
- * a selection mode value
- * @see {@link SelectionMode}.
- */
- public void setSelectionMode(SelectionMode mode) {
- SelectionModel<T> model = mode.createModel();
- setSelectionModel(model);
- }
-
- /**
- * Test if a row is selected.
- *
- * @param row
- * a row object
- * @return true, if the current selection model considers the provided row
- * object selected.
- */
- public boolean isSelected(T row) {
- return selectionModel.isSelected(row);
- }
-
- /**
- * Select a row using the current selection model.
- * <p>
- * Only selection models implementing {@link SelectionModel.Single} and
- * {@link SelectionModel.Multi} are supported; for anything else, an
- * exception will be thrown.
- *
- * @param row
- * a row object
- * @return <code>true</code> iff the current selection changed
- * @throws IllegalStateException
- * if the current selection model is not an instance of
- * {@link SelectionModel.Single} or {@link SelectionModel.Multi}
- */
- @SuppressWarnings("unchecked")
- public boolean select(T row) {
- if (selectionModel instanceof SelectionModel.Single<?>) {
- return ((SelectionModel.Single<T>) selectionModel).select(row);
- } else if (selectionModel instanceof SelectionModel.Multi<?>) {
- return ((SelectionModel.Multi<T>) selectionModel).select(row);
- } else {
- throw new IllegalStateException("Unsupported selection model");
- }
- }
-
- /**
- * Deselect a row using the current selection model.
- * <p>
- * Only selection models implementing {@link SelectionModel.Single} and
- * {@link SelectionModel.Multi} are supported; for anything else, an
- * exception will be thrown.
- *
- * @param row
- * a row object
- * @return <code>true</code> iff the current selection changed
- * @throws IllegalStateException
- * if the current selection model is not an instance of
- * {@link SelectionModel.Single} or {@link SelectionModel.Multi}
- */
- @SuppressWarnings("unchecked")
- public boolean deselect(T row) {
- if (selectionModel instanceof SelectionModel.Single<?>) {
- return ((SelectionModel.Single<T>) selectionModel).deselect(row);
- } else if (selectionModel instanceof SelectionModel.Multi<?>) {
- return ((SelectionModel.Multi<T>) selectionModel).deselect(row);
- } else {
- throw new IllegalStateException("Unsupported selection model");
- }
- }
-
- /**
- * Gets last selected row from the current SelectionModel.
- * <p>
- * Only selection models implementing {@link SelectionModel.Single} are
- * valid for this method; for anything else, use the
- * {@link Grid#getSelectedRows()} method.
- *
- * @return a selected row reference, or null, if no row is selected
- * @throws IllegalStateException
- * if the current selection model is not an instance of
- * {@link SelectionModel.Single}
- */
- public T getSelectedRow() {
- if (selectionModel instanceof SelectionModel.Single<?>) {
- return ((SelectionModel.Single<T>) selectionModel).getSelectedRow();
- } else {
- throw new IllegalStateException(
- "Unsupported selection model; can not get single selected row");
- }
- }
-
- /**
- * Gets currently selected rows from the current selection model.
- *
- * @return a non-null collection containing all currently selected rows.
- */
- public Collection<T> getSelectedRows() {
- return selectionModel.getSelectedRows();
- }
-
- @Override
- public HandlerRegistration addSelectionChangeHandler(
- final SelectionChangeHandler<T> handler) {
- return addHandler(handler, SelectionChangeEvent.getType());
- }
-
- /**
- * Sets the current sort order using the fluid Sort API. Read the
- * documentation for {@link Sort} for more information.
- *
- * @param s
- * a sort instance
- */
- public void sort(Sort s) {
- setSortOrder(s.build());
- }
-
- /**
- * Sorts the Grid data in ascending order along one column.
- *
- * @param column
- * a grid column reference
- */
- public <C> void sort(GridColumn<C, T> column) {
- sort(column, SortDirection.ASCENDING);
- }
-
- /**
- * Sorts the Grid data along one column.
- *
- * @param column
- * a grid column reference
- * @param direction
- * a sort direction value
- */
- public <C> void sort(GridColumn<C, T> column, SortDirection direction) {
- sort(Sort.by(column, direction));
- }
-
- /**
- * Sets the sort order to use. Setting this causes the Grid to re-sort
- * itself.
- *
- * @param order
- * a sort order list. If set to null, the sort order is cleared.
- */
- public void setSortOrder(List<SortOrder> order) {
- setSortOrder(order, SortEventOriginator.API);
- }
-
- private void setSortOrder(List<SortOrder> order,
- SortEventOriginator originator) {
- if (order != sortOrder) {
- sortOrder.clear();
- if (order != null) {
- sortOrder.addAll(order);
- }
- }
- sort(originator);
- }
-
- /**
- * Get a copy of the current sort order array.
- *
- * @return a copy of the current sort order array
- */
- public List<SortOrder> getSortOrder() {
- return Collections.unmodifiableList(sortOrder);
- }
-
- /**
- * Finds the sorting order for this column
- */
- private SortOrder getSortOrder(GridColumn<?, ?> column) {
- for (SortOrder order : getSortOrder()) {
- if (order.getColumn() == column) {
- return order;
- }
- }
- return null;
- }
-
- /**
- * Register a GWT event handler for a sorting event. This handler gets
- * called whenever this Grid needs its data source to provide data sorted in
- * a specific order.
- *
- * @param handler
- * a sort event handler
- * @return the registration for the event
- */
- public HandlerRegistration addSortHandler(SortEventHandler<T> handler) {
- return addHandler(handler, SortEvent.getType());
- }
-
- /**
- * Register a GWT event handler for a data available event. This handler
- * gets called whenever the {@link DataSource} for this Grid has new data
- * available.
- * <p>
- * This handle will be fired with the current available data after
- * registration is done.
- *
- * @param handler
- * a data available event handler
- * @return the registartion for the event
- */
- public HandlerRegistration addDataAvailableHandler(
- final DataAvailableHandler handler) {
- // Deferred call to handler with current row range
- Scheduler.get().scheduleFinally(new ScheduledCommand() {
- @Override
- public void execute() {
- handler.onDataAvailable(new DataAvailableEvent(
- currentDataAvailable));
- }
- });
- return addHandler(handler, DataAvailableEvent.TYPE);
- }
-
- /**
- * Register a BodyKeyDownHandler to this Grid. The event for this handler is
- * fired when a KeyDown event occurs while active cell is in the Body of
- * this Grid.
- *
- * @param handler
- * the key handler to register
- * @return the registration for the event
- */
- public HandlerRegistration addBodyKeyDownHandler(BodyKeyDownHandler handler) {
- return addHandler(handler, keyDown.getAssociatedType());
- }
-
- /**
- * Register a BodyKeyUpHandler to this Grid. The event for this handler is
- * fired when a KeyUp event occurs while active cell is in the Body of this
- * Grid.
- *
- * @param handler
- * the key handler to register
- * @return the registration for the event
- */
- public HandlerRegistration addBodyKeyUpHandler(BodyKeyUpHandler handler) {
- return addHandler(handler, keyUp.getAssociatedType());
- }
-
- /**
- * Register a BodyKeyPressHandler to this Grid. The event for this handler
- * is fired when a KeyPress event occurs while active cell is in the Body of
- * this Grid.
- *
- * @param handler
- * the key handler to register
- * @return the registration for the event
- */
- public HandlerRegistration addBodyKeyPressHandler(
- BodyKeyPressHandler handler) {
- return addHandler(handler, keyPress.getAssociatedType());
- }
-
- /**
- * Register a HeaderKeyDownHandler to this Grid. The event for this handler
- * is fired when a KeyDown event occurs while active cell is in the Header
- * of this Grid.
- *
- * @param handler
- * the key handler to register
- * @return the registration for the event
- */
- public HandlerRegistration addHeaderKeyDownHandler(
- HeaderKeyDownHandler handler) {
- return addHandler(handler, keyDown.getAssociatedType());
- }
-
- /**
- * Register a HeaderKeyUpHandler to this Grid. The event for this handler is
- * fired when a KeyUp event occurs while active cell is in the Header of
- * this Grid.
- *
- * @param handler
- * the key handler to register
- * @return the registration for the event
- */
- public HandlerRegistration addHeaderKeyUpHandler(HeaderKeyUpHandler handler) {
- return addHandler(handler, keyUp.getAssociatedType());
- }
-
- /**
- * Register a HeaderKeyPressHandler to this Grid. The event for this handler
- * is fired when a KeyPress event occurs while active cell is in the Header
- * of this Grid.
- *
- * @param handler
- * the key handler to register
- * @return the registration for the event
- */
- public HandlerRegistration addHeaderKeyPressHandler(
- HeaderKeyPressHandler handler) {
- return addHandler(handler, keyPress.getAssociatedType());
- }
-
- /**
- * Register a FooterKeyDownHandler to this Grid. The event for this handler
- * is fired when a KeyDown event occurs while active cell is in the Footer
- * of this Grid.
- *
- * @param handler
- * the key handler to register
- * @return the registration for the event
- */
- public HandlerRegistration addFooterKeyDownHandler(
- FooterKeyDownHandler handler) {
- return addHandler(handler, keyDown.getAssociatedType());
- }
-
- /**
- * Register a FooterKeyUpHandler to this Grid. The event for this handler is
- * fired when a KeyUp event occurs while active cell is in the Footer of
- * this Grid.
- *
- * @param handler
- * the key handler to register
- * @return the registration for the event
- */
- public HandlerRegistration addFooterKeyUpHandler(FooterKeyUpHandler handler) {
- return addHandler(handler, keyUp.getAssociatedType());
- }
-
- /**
- * Register a FooterKeyPressHandler to this Grid. The event for this handler
- * is fired when a KeyPress event occurs while active cell is in the Footer
- * of this Grid.
- *
- * @param handler
- * the key handler to register
- * @return the registration for the event
- */
- public HandlerRegistration addFooterKeyPressHandler(
- FooterKeyPressHandler handler) {
- return addHandler(handler, keyPress.getAssociatedType());
- }
-
- /**
- * Apply sorting to data source.
- */
- private void sort(SortEventOriginator originator) {
- refreshHeader();
- fireEvent(new SortEvent<T>(this,
- Collections.unmodifiableList(sortOrder), originator));
- }
-
- private int getLastVisibleRowIndex() {
- int lastRowIndex = escalator.getVisibleRowRange().getEnd();
- int footerTop = escalator.getFooter().getElement().getAbsoluteTop();
- Element lastRow;
-
- do {
- lastRow = escalator.getBody().getRowElement(--lastRowIndex);
- } while (lastRow.getAbsoluteBottom() > footerTop);
-
- return lastRowIndex;
- }
-
- private int getFirstVisibleRowIndex() {
- int firstRowIndex = escalator.getVisibleRowRange().getStart();
- int headerBottom = escalator.getHeader().getElement()
- .getAbsoluteBottom();
- Element firstRow = escalator.getBody().getRowElement(firstRowIndex);
-
- while (firstRow.getAbsoluteTop() < headerBottom) {
- firstRow = escalator.getBody().getRowElement(++firstRowIndex);
- }
-
- return firstRowIndex;
- }
-
- /**
- * Adds a scroll handler to this grid
- *
- * @param handler
- * the scroll handler to add
- * @return a handler registration for the registered scroll handler
- */
- public HandlerRegistration addScrollHandler(ScrollHandler handler) {
- return addHandler(handler, ScrollEvent.TYPE);
- }
- }
|