Java tutorial
/* * BoundTable.java * * Created on July 24, 2007, 5:30 PM * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package cc.alcina.framework.gwt.client.gwittir.widget; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Element; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.HasClickHandlers; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyUpEvent; import com.google.gwt.event.shared.HandlerRegistration; 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.ClickListener; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlexTable; import com.google.gwt.user.client.ui.FocusListener; import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.HTMLTable; import com.google.gwt.user.client.ui.HTMLTable.Cell; import com.google.gwt.user.client.ui.HasFocus; import com.google.gwt.user.client.ui.HasHorizontalAlignment; import com.google.gwt.user.client.ui.ScrollListener; import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.SourcesClickEvents; import com.google.gwt.user.client.ui.SourcesTableEvents; import com.google.gwt.user.client.ui.TableListener; import com.google.gwt.user.client.ui.Widget; import com.totsp.gwittir.client.action.Action; import com.totsp.gwittir.client.beans.Binding; import com.totsp.gwittir.client.beans.Introspector; import com.totsp.gwittir.client.beans.Property; import com.totsp.gwittir.client.beans.SourcesPropertyChangeEvents; import com.totsp.gwittir.client.keyboard.KeyBinding; import com.totsp.gwittir.client.keyboard.KeyBindingException; import com.totsp.gwittir.client.keyboard.KeyboardController; import com.totsp.gwittir.client.keyboard.SuggestedKeyBinding; import com.totsp.gwittir.client.keyboard.Task; import com.totsp.gwittir.client.log.Level; import com.totsp.gwittir.client.ui.BoundWidget; import com.totsp.gwittir.client.ui.Button; import com.totsp.gwittir.client.ui.Checkbox; import com.totsp.gwittir.client.ui.Label; import com.totsp.gwittir.client.ui.table.AbstractTableWidget; import com.totsp.gwittir.client.ui.table.DataProvider; import com.totsp.gwittir.client.ui.table.Field; import com.totsp.gwittir.client.ui.table.HasChunks; import com.totsp.gwittir.client.ui.table.SortableDataProvider; import com.totsp.gwittir.client.ui.util.BoundWidgetTypeFactory; import com.totsp.gwittir.client.util.ListSorter; import cc.alcina.framework.common.client.util.AlcinaTopics; import cc.alcina.framework.common.client.util.Ax; import cc.alcina.framework.common.client.util.CommonUtils; import cc.alcina.framework.common.client.util.LooseContextInstance; import cc.alcina.framework.gwt.client.gwittir.GwittirBridge; import cc.alcina.framework.gwt.client.gwittir.HasBinding; import cc.alcina.framework.gwt.client.gwittir.provider.CollectionDataProvider; import cc.alcina.framework.gwt.client.gwittir.widget.EndRowButtonClickedEvent.EndRowButtonClickedHandler; import cc.alcina.framework.gwt.client.gwittir.widget.EndRowButtonClickedEvent.HasEndRowClickedHandlers; import cc.alcina.framework.gwt.client.logic.RenderContext; import cc.alcina.framework.gwt.client.objecttree.HasRenderContext; import cc.alcina.framework.gwt.client.widget.FlowPanelClickable; import cc.alcina.framework.gwt.client.widget.SpanPanel; /** * This is an option-rich table for use with objects implementing the * SourcesPropertyChangeEvents interfaces. * * @author <a href="mailto:cooper@screaming-penguin.com">Robert "kebernet" * Cooper</a> * @see com.totsp.gwittir.client.beans.SourcesPropertyChangeEvents * @author Nick Reddel * <p> * <b>Changes from gwittir.BoundTable</b> * </p> * <ul> * <li>changed to protected: allRowsHandle, columns, masks, rowHandles, * shiftDown, table, widgetCache , addRow(), * <li>add: HANDLES_AS_CHECKBOXES, searchingMessage, noContentMessage, * sortedColumn * <li>method changes: * <ul> * <li>ordering in init() {works better with the logic, i think} * <li>init(int) - support for handlesAsCheckboxes * <li>renderAll() - support for searchingmessage, nocontentmessage, * labels as HTML (see GridForm). <br> * Also show hide navrow if <=1 chunk, and render with display=none * until the end (keep browser from reflowing unnecessarily) * <li>first(), next(), previous(), last() - ignore if this.inChunk == * true (otherwise we get errors on setchunk when people press things * twice) * <li>Visual hints (up/down arrows) to indicate column sort status * </ul> * </ul> */ @SuppressWarnings({ "unchecked", "deprecation" }) public class BoundTableExt extends AbstractTableWidget implements HasChunks, HasBinding, HasRenderContext, HasEndRowClickedHandlers { private static BoundTableExt activeTable = null; /** * A placholder for no mask options (0) */ public static final int NONE_MASK = 0; /** * Renderer the table inside a scroll panel. * * <p> * If the table has a DataProvider, it will use the "Google Reader" * get-next-chunk-on-max-scroll operation. * </p> */ public static final int SCROLL_MASK = 1; /** * Renderers a heading row on the table using the labels on the Column * objects. */ public static final int HEADER_MASK = 2; /** * Lets the user have multiple rows in the "selected" state at a time. */ public static final int MULTIROWSELECT_MASK = 4; /** * Turns off row selection and styling. */ public static final int SELECT_ROW_MASK = 8; /** * Turns off selected column stying. */ public static final int NO_SELECT_COL_MASK = 16; /** * Turns off cell selection stying. */ public static final int NO_SELECT_CELL_MASK = 32; /** * Tells the table to render a spacing row in between bound rows. */ public static final int SPACER_ROW_MASK = 64; /** * If this table has a DataProvider AND it is not scrolling, this supresses * the first, previous, next and last buttons at the bottom of the table. */ public static final int NO_NAV_ROW_MASK = 128; /** * Enables sorting on the table when a header row is clicked. * * If this table has a DataProvider, it must be a SortableDataProvider for * this to work. */ public static final int SORT_MASK = 256; /** * Enables the click in widget insertion. Note: This will use the default * widget type for the model object from the BoundWidgetTypeFactory * * @see com.totsp.gwittir.client.ui.util.BoundWidgetTypeFactory */ public static final int INSERT_WIDGET_MASK = 512; /** * Determines whether buttons for row handles/selection should be present. */ public static final int ROW_HANDLE_MASK = 1024; public static final int MULTI_REQUIRES_SHIFT = 2048; public static final int HANDLES_AS_CHECKBOXES = 4096; public static final int END_ROW_BUTTON = 8192; private static final String DEFAULT_STYLE = "default"; private static final String NAV_STYLE = "nav"; static int setCounter = 0; private Binding topBinding; protected Button allRowsHandle; private Collection value; private DataProvider provider; protected FlexTable table; private HashMap<SourcesClickEvents, ClickListener> clickListeners = new HashMap<SourcesClickEvents, ClickListener>(); private HashMap<HasFocus, FocusListener> focusListeners = new HashMap<HasFocus, FocusListener>(); private HashMap<Integer, String> selectedRowStyles; protected List rowHandles; /* <Button> */ private Map bindingCache = new HashMap(); private Map externalKeyBindings = new HashMap(); private Map keyBindings = new HashMap(); protected Map widgetCache = new HashMap(); private ScrollPanel scroll; private String selectedCellLastStyle; private String selectedColLastStyle = BoundTableExt.DEFAULT_STYLE; private String selectedRowLastStyle = BoundTableExt.DEFAULT_STYLE; private Timer cleanUpCaches = new Timer() { public void run() { if (value != null) { for (Iterator it = new ArrayList(widgetCache.keySet()).iterator(); it.hasNext();) { Object o = it.next(); if (!value.contains(o)) { widgetCache.remove(o); } } for (Iterator it = new ArrayList(bindingCache.keySet()).iterator(); it.hasNext();) { Object o = it.next(); if (!value.contains(o)) { bindingCache.remove(o); } } } } }; private Widget base; private boolean[] ascending; protected Field[] columns; private boolean inChunk = false; protected boolean shiftDown = false; private int currentChunk = -1; private int lastScrollPosition; protected int masks; private int numberOfChunks; private int selectedCellRowLastIndex = -1; private int selectedColLastIndex = -1; private int selectedRowLastIndex = -1; private PropertyChangeListener collectionPropertyChangeListener; private List<SourcesPropertyChangeEvents> listenedToByCollectionChangeListener = new ArrayList<SourcesPropertyChangeEvents>(); private RenderContext renderContext; private ClickHandler rowSelectHandler = new ClickHandler() { @Override public void onClick(ClickEvent event) { Object o = getObjectForEvent(event); setSelected(Collections.singletonList(o)); } }; private EventingSimplePanel esp; private String searchingMessage = "Searching..."; private String noContentMessage = "No matching results found"; private Collection lastRendered; private Iterator rowIterator = null; private int sortedColumn = -1; /** Creates a new instance of BoundTable */ public BoundTableExt() { super(); this.init(0); } /** * Creates a new instance of Bound table with the indicated options value. * * @param masks * int value containing the sum of the *_MASK options for the * table. */ public BoundTableExt(int masks) { super(); this.init(masks); } /** * Creates a new instance of Bound table with the indicated options value. * * @param typeFactory * A BoundWidget type factory used to create the widgets that * appear in the table. * @param masks * int value containing the sum of the *_MASK options for the * table. */ public BoundTableExt(int masks, BoundWidgetTypeFactory typeFactory) { super(); this.factory = typeFactory; this.init(masks); } /** * Creates a new instance of a table using a Collection as a data set. * * @param typeFactory * A BoundWidget type factory used to create the widgets that * appear in the table. * @param masks * int value containing the sum of the *_MASK options for the * table. * @param cols * The Column objects for the table. */ public BoundTableExt(int masks, BoundWidgetTypeFactory typeFactory, Field[] cols) { super(); this.setColumns(cols); this.factory = typeFactory; this.init(masks); } /** * Creates a new instance of a table using a Collection as a data set. * * @param typeFactory * A BoundWidget type factory used to create the widgets that * appear in the table. * @param masks * int value containing the sum of the *_MASK options for the * table. * @param cols * The Column objects for the table. * @param value * A collection containing SourcesPropertyChangeEvents objects to * render in the table. */ public BoundTableExt(int masks, BoundWidgetTypeFactory typeFactory, Field[] cols, Collection value) { super(); this.setColumns(cols); this.value = value; this.factory = typeFactory; this.init(masks); } /** * Creates a new instance of BoundTable * * @param typeFactory * A BoundWidget type factory used to create the widgets that * appear in the table. * @param masks * int value containing the sum of the *_MASK options for the * table. * @param cols * The Column objects for the table. * @param provider * Instance of DataProvider to get chunked data from. */ public BoundTableExt(int masks, BoundWidgetTypeFactory typeFactory, Field[] cols, DataProvider provider) { super(); this.setColumns(cols); this.provider = provider; this.factory = typeFactory; this.init(masks); } /** * Creates a new instance of a table using a Collection as a data set. * * @param masks * int value containing the sum of the *_MASK options for the * table. * @param cols * The Column objects for the table. */ public BoundTableExt(int masks, Field[] cols) { super(); this.setColumns(cols); this.init(masks); } /** * Creates a new instance of a table using a Collection as a data set. * * @param masks * int value containing the sum of the *_MASK options for the * table. * @param cols * The Column objects for the table. * @param value * A collection containing SourcesPropertyChangeEvents objects to * render in the table. */ public BoundTableExt(int masks, Field[] cols, Collection value) { super(); this.setColumns(cols); this.value = value; this.init(masks); } /** * Creates a new instance of BoundTable * * @param masks * int value containing the sum of the *_MASK options for the * table. * @param cols * The Column objects for the table. * @param provider * Instance of DataProvider to get chunked data from. */ public BoundTableExt(int masks, Field[] cols, DataProvider provider) { super(); this.setColumns(cols); this.provider = provider; this.init(masks); } /** * Adds a colleciton of Bindables to the table * * @param c * A collection containing SourcesPropertyChangeEvents objects. */ public void add(Collection c) { for (Iterator it = c.iterator(); it.hasNext();) { this.add((SourcesPropertyChangeEvents) it.next()); } } /** * Adds a new SourcesPropertyChangeEvents object to the table. * * @param o * An object of type SourcesPropertyChangeEvents. */ public void add(SourcesPropertyChangeEvents o) { if (this.value.add(o)) { this.addRow(o); } } public HandlerRegistration addClickHandler(ClickHandler handler) { return this.table.addClickHandler(handler); } public void addCollectionPropertyChangeListener(PropertyChangeListener collectionPropertyChangeListener) { this.collectionPropertyChangeListener = collectionPropertyChangeListener; } @Override public HandlerRegistration addEndRowClickedHandler(EndRowButtonClickedHandler handler) { return addHandler(handler, EndRowButtonClickedEvent.getType()); } public void addKeyBinding(KeyBinding binding) throws KeyBindingException { if (this.getActive()) { KeyboardController.INSTANCE.register(binding, this); } this.addKeyBinding(binding, (Object) this); } public void addKeyBinding(KeyBinding binding, Action action) throws KeyBindingException { if (this.getActive()) { KeyboardController.INSTANCE.register(binding, action); } this.addKeyBinding(binding, (Object) action); } public void addKeyBinding(KeyBinding binding, BoundWidget widget) throws KeyBindingException { if (this.getActive()) { KeyboardController.INSTANCE.register(binding, widget); } this.addKeyBinding(binding, (Object) widget); } public void addKeyBinding(KeyBinding binding, Task task) throws KeyBindingException { if (this.getActive()) { KeyboardController.INSTANCE.register(binding, task); } this.addKeyBinding(binding, (Object) task); } @Override public void addStyleName(String style) { this.base.addStyleName(style); } public void addTableListener(TableListener listener) { this.table.addTableListener(listener); } /** * Clears the table and cleans up all bindings and listeners. */ public void clear() { for (SourcesPropertyChangeEvents spce : listenedToByCollectionChangeListener) { spce.removePropertyChangeListener(collectionPropertyChangeListener); } listenedToByCollectionChangeListener.clear(); this.topBinding.unbind(); this.topBinding.getChildren().clear(); if (this.rowHandles != null) { this.rowHandles.clear(); } if (this.keyBindings != null) { this.keyBindings.clear(); } for (Iterator it = this.focusListeners.entrySet().iterator(); it.hasNext();) { Entry entry = (Entry) it.next(); ((HasFocus) entry.getKey()).removeFocusListener((FocusListener) entry.getValue()); } for (Iterator it = this.clickListeners.entrySet().iterator(); it.hasNext();) { Entry entry = (Entry) it.next(); ((SourcesClickEvents) entry.getKey()).removeClickListener((ClickListener) entry.getValue()); } lastRendered = null; createTable(); if (this.selectedRowStyles != null) { this.selectedRowStyles.clear(); } this.clearSelectedCol(); this.selectedCellLastStyle = BoundTableExt.DEFAULT_STYLE; this.selectedColLastIndex = -1; this.selectedColLastStyle = BoundTableExt.DEFAULT_STYLE; this.selectedRowLastIndex = -1; this.selectedRowLastStyle = BoundTableExt.DEFAULT_STYLE; this.selectedCellRowLastIndex = -1; this.cleanUpCaches.schedule(50); } /** * Causes the table to go to the first chunk of data, if a data provider is * used. */ public void first() { if (this.inChunk) { return; } this.currentChunk = 0; this.inChunk = true; this.provider.getChunk(this, this.getCurrentChunk()); } public boolean getActive() { return BoundTableExt.activeTable == this; } /** * Returns the Binding object used by this table. * * @return The Binding object for this table. */ public Binding getBinding() { return this.topBinding; } public int getCellCount(int row) { return this.table.getCellCount(row); } public HTMLTable.CellFormatter getCellFormatter() { return this.table.getCellFormatter(); } public int getCellPadding() { return this.table.getCellPadding(); } public int getCellSpacing() { return this.table.getCellSpacing(); } public HTMLTable.ColumnFormatter getColumnFormatter() { return this.table.getColumnFormatter(); } /** * Returns the Columns used in this table. * * @return Column[] used for rendering this table. */ public Field[] getColumns() { Field[] ret = new Field[this.columns.length]; for (int i = 0; i < ret.length; i++) { ret[i] = this.columns[i]; } return columns; } /** * Returns the current fetched chunk from the data provider. * * @return int index of the current chunk. */ public int getCurrentChunk() { return currentChunk; } public FlexTable.FlexCellFormatter getFlexCellFormatter() { return this.table.getFlexCellFormatter(); } public String getHTML(int row, int column) { return this.table.getHTML(row, column); } public String getNoContentMessage() { return noContentMessage; } /** * Returns the number of available chunks (passed in from the DataProvider) * * @return int number of chunks available from the DataProvider */ public int getNumberOfChunks() { return numberOfChunks; } public Object getObjectForEvent(ClickEvent event) { Cell cell = table.getCellForEvent(event); if (cell != null) { Iterator itr = value.iterator(); for (int i = 1; i < cell.getRowIndex() && itr.hasNext(); i++) { itr.next(); } return itr.hasNext() ? itr.next() : null; } return null; } @Override public LooseContextInstance getRenderContext() { return this.renderContext; } public int getRowCount() { return this.table.getRowCount(); } public HTMLTable.RowFormatter getRowFormatter() { return this.table.getRowFormatter(); } /** * Returns a List containing the current selected row objects. * * @return List of Bindables from the selected rows. */ public List getSelected() { ArrayList selected = new ArrayList(); HashSet realIndexes = new HashSet(); if (this.selectedRowStyles != null) { for (Iterator it = this.selectedRowStyles.keySet().iterator(); it.hasNext();) { realIndexes.add(calculateRowToObjectOffset((Integer) it.next())); } } else if (this.selectedRowLastIndex != -1) { realIndexes.add(calculateRowToObjectOffset(new Integer(this.selectedRowLastIndex))); } int i = 0; for (Iterator it = this.value.iterator(); it.hasNext(); i++) { if (realIndexes.contains(new Integer(i))) { selected.add(it.next()); } else { it.next(); } } return selected; } public int getSelectedRowIndex() { return this.selectedRowLastIndex; } @Override public String getStyleName() { return this.base.getStyleName(); } @Override public String getTitle() { return this.table.getTitle(); } public Object getValue() { return value; } public BoundWidget getWidget(int row, int col) { return (BoundWidget) this.table.getWidget(row, col); } public void handleKeyupEvent(KeyUpEvent event) { int delta = 0; if (event.getNativeKeyCode() == KeyCodes.KEY_UP) { delta = -1; } if (event.getNativeKeyCode() == KeyCodes.KEY_DOWN) { delta = 1; } if (delta != 0) { boolean hasHeader = (masks & BoundTableExt.HEADER_MASK) > 0; int row = selectedRowLastIndex + delta; if (row == -2) { row = -1; } row = row % value.size(); setSelectedRow(row); int row2 = Math.min(value.size() - 1, row + 4); final Element row3 = getRow(table.getElement(), row2); event.stopPropagation(); event.preventDefault(); } } /** * Method called by the DataProvider to initialize the first chunk and pass * in the to total number of chunks available. * * @param c * Data for Chunk index 0 * @param numberOfChunks * The total number of available chunks of data. */ public void init(Collection c, int numberOfChunks) { this.numberOfChunks = numberOfChunks; this.currentChunk = 0; this.inChunk = false; this.setValue(c); } /** * Causes the table to render the last chunk of data. */ public void last() { if (this.inChunk) { return; } if ((this.numberOfChunks - 1) >= 0) { this.currentChunk = this.numberOfChunks - 1; this.inChunk = true; this.provider.getChunk(this, currentChunk); } } /** * Causes the table to render the next chunk of data. */ public void next() { if (this.inChunk) { return; } if ((this.currentChunk + 1) < this.numberOfChunks) { this.inChunk = true; this.provider.getChunk(this, ++currentChunk); } } /** * Causes teh table to render the previous chunk of data. */ public void previous() { if (this.inChunk) { return; } if ((this.getCurrentChunk() - 1) >= 0) { inChunk = true; this.provider.getChunk(this, --currentChunk); } } public void renderBottom() { if ((this.provider != null) && ((this.masks & BoundTableExt.SCROLL_MASK) == 0) && ((this.masks & BoundTableExt.NO_NAV_ROW_MASK) == 0) && numberOfChunks > 1) { int row = this.table.getRowCount(); this.table.setWidget(row, 0, this.createNavWidget()); this.table.getFlexCellFormatter().setColSpan(row, 0, this.columns.length); table.getCellFormatter().setHorizontalAlignment(row, 0, HasHorizontalAlignment.ALIGN_CENTER); } setVisible(true); } public boolean renderCheck() { try { RenderContext.get().pushContext(renderContext); if (this.value == null || this.value.isEmpty()) { this.clear(); HTML l = new HTML(inChunk && !(this.provider instanceof CollectionDataProvider) ? searchingMessage : noContentMessage); l.setStyleName("no-content"); this.table.setWidget(0, 0, l); return false; } return true; } finally { RenderContext.get().pop(); } } public void renderTop() { setVisible(false); this.clear(); int startColumn = 0; if ((this.masks & BoundTableExt.ROW_HANDLE_MASK) > 0) { this.table.setWidget(0, 0, this.allRowsHandle); startColumn = 1; } if ((this.masks & BoundTableExt.HEADER_MASK) > 0) { for (int i = 0; i < this.columns.length; i++) { this.table.setWidget(0, i + startColumn, new HTML(this.columns[i].getLabel())); } if (this.provider instanceof SortableDataProvider && (this.masks & BoundTableExt.SORT_MASK) > 0) { SortableDataProvider sdp = (SortableDataProvider) this.provider; String[] sortableProperties = sdp.getSortableProperties(); for (int i = 0; (i < sortableProperties.length); i++) { for (int index = 0; index < this.columns.length; index++) { if (sortableProperties[i].equals(this.columns[index].getPropertyName())) { this.table.getCellFormatter().addStyleName(0, index + startColumn, "sortable"); } } } } this.table.getRowFormatter().setStyleName(0, "header"); } if ((this.masks & BoundTableExt.END_ROW_BUTTON) > 0) { this.table.setWidget(0, this.columns.length + startColumn, new HTML("\u00A0")); } if (sortedColumn != -1) { if ((this.masks & BoundTableExt.HEADER_MASK) > 0) { table.getCellFormatter().addStyleName(0, sortedColumn + startColumn, this.ascending[sortedColumn] ? "ascending" : "descending"); } } rowIterator = this.value == null ? null : this.value.iterator(); } public void setActive(boolean active) { if ((BoundTableExt.activeTable == this) & !active) { BoundTableExt.activeTable = null; this.changes.firePropertyChange("active", true, false); } else if (BoundTableExt.activeTable != this) { if (BoundTableExt.activeTable != null) { BoundTableExt.activeTable.setActive(false); } BoundTableExt.activeTable = this; this.changes.firePropertyChange("active", false, true); } } public void setBorderWidth(int width) { this.table.setBorderWidth(width); } public void setCellPadding(int padding) { this.table.setCellPadding(padding); } public void setCellSpacing(int spacing) { this.table.setCellSpacing(spacing); } /** * Called by the DataProvider to pass in a requested chunk of data. THIS * METHOD MUST BE CALLED ASYNCRONOUSLY. * * @param c * The next requested chunk of SourcesPropertyChangeEvents * objects. */ public void setChunk(Collection c) { if (!this.inChunk) { throw new RuntimeException("This method MUST becalled asyncronously!"); // edge - if a user presses the 'next' button twice // now handled with an inchunk check for all nav actions } if ((masks & BoundTableExt.SCROLL_MASK) > 0) { this.add(c); } else { this.setValue(c); } if (((masks & BoundTableExt.SCROLL_MASK) > 0) && (this.scroll.getVerticalScrollPosition() >= this.lastScrollPosition)) { this.scroll.setVerticalScrollPosition(this.lastScrollPosition); } this.inChunk = false; } /** * Sets Column[] object for use on the table. Note, this will foce a re-init * of the table. * * @param columns * Column[] to use to render the table. */ public void setColumns(Field[] columns) { this.columns = new Field[columns.length]; for (int i = 0; i < columns.length; i++) { this.columns[i] = columns[i]; } if ((this.masks & BoundTableExt.SORT_MASK) > 0) { this.ascending = new boolean[this.columns.length]; } this.renderAll(); } public void setDataProvider(DataProvider provider) { this.provider = provider; this.inChunk = true; this.provider.init(this); } @Override public void setHeight(String height) { this.base.setHeight(height); } public void setNoContentMessage(String noContentMessage) { this.noContentMessage = noContentMessage; // TODO - searching indicator } @Override public void setPixelSize(int width, int height) { this.table.setPixelSize(width, height); } /** * Sets the indicated items in the list to "selected" state. * * @param selected * A List of Bindables to set as the Selected value. */ public void setSelected(List selected) { int i = 0; this.clearSelectedRows(); for (Iterator it = this.topBinding.getChildren().iterator(); it.hasNext(); i++) { SourcesPropertyChangeEvents b = ((Binding) ((Binding) it.next()).getChildren().get(0)) .getRight().object; if (selected.contains(b)) { this.setSelectedRow(calculateObjectToRowOffset(i)); if (this.table.getWidget(calculateObjectToRowOffset(i), 0) instanceof HasFocus) { ((HasFocus) this.table.getWidget(calculateObjectToRowOffset(i), 0)).setFocus(true); } } } } @Override public void setSize(String width, String height) { this.base.setSize(width, height); } @Override public void setStyleName(String style) { this.base.setStyleName(style); } @Override public void setStyleName(String style, boolean add) { this.base.setStyleName(style, add); } public void setValue(Object value) { Collection old = this.value; this.value = (Collection) value; this.changes.firePropertyChange("value", old, this.value); boolean active = this.getActive(); this.setActive(false); this.renderAll(); this.setActive(active); } @Override public void setWidth(String width) { this.base.setWidth(width); } /** * Sorts the table based on the value of the property in the specified * column index. * * If using a SortableDataProvider, this will throw a runtime exception if * the column denoted by the index is not a supported sortable column. * * @param index * index of the column to sort the table on. */ public void sortColumn(int index) { ascending[index] = !ascending[index]; if (this.provider == null) { ArrayList sort = new ArrayList(); sort.addAll(value); try { ListSorter.sortOnProperty(sort, columns[index].getPropertyName(), ascending[index]); } catch (Exception e) { LOG.log(Level.INFO, "Exception during sort", e); } value.clear(); for (Iterator it = sort.iterator(); it.hasNext();) { value.add(it.next()); } setValue(value); } else if (this.provider instanceof SortableDataProvider) { SortableDataProvider sdp = (SortableDataProvider) this.provider; boolean canSort = false; String[] sortableProperties = sdp.getSortableProperties(); for (int i = 0; (i < sortableProperties.length) && !canSort; i++) { if (sortableProperties[i].equals(this.columns[index].getPropertyName())) { canSort = true; } } if (!canSort) { AlcinaTopics.notifyDevWarning(new RuntimeException( CommonUtils.formatJ("Field %s is not a" + " sortable field from data provider %s.", this.columns[index].getPropertyName(), this.provider.getClass().getName()))); return; } sortedColumn = index; sdp.sortOnProperty(this, this.columns[index].getPropertyName(), this.ascending[index]); } int startColumn = ((masks & BoundTableExt.ROW_HANDLE_MASK) > 0) ? 1 : 0; if ((this.masks & BoundTableExt.HEADER_MASK) > 0) { table.getCellFormatter().setStyleName(0, index + startColumn, this.ascending[index] ? "ascending" : "descending"); } } private void addKeyBinding(KeyBinding binding, Object object) { this.externalKeyBindings.put(binding, object); } private void addSelectedClickListener(final SourcesClickEvents widget, final int objectNumber, final int col) { ClickListener l = new ClickListener() { public void onClick(Widget sender) { setActive(true); int row = calculateObjectToRowOffset(objectNumber); handleSelect(true, row, col); } }; widget.addClickListener(l); clickListeners.put(widget, l); } private void addSelectedFocusListener(final HasFocus widget, final int objectNumber, final int col) { FocusListener l = new FocusListener() { public void onFocus(Widget sender) { setActive(true); int row = calculateObjectToRowOffset(objectNumber); // GWT.log("Focus row: " + row + " object: " + objectNumber // + " col: " + col, null); // GWT.log("SelectedRowLastIndex " + selectedRowLastIndex, // null); handleSelect(row != selectedRowLastIndex, row, col); } public void onLostFocus(Widget sender) { } }; widget.addFocusListener(l); focusListeners.put(widget, l); } private void clearSelectedCell() { if ((this.selectedColLastIndex != -1) && (this.selectedCellRowLastIndex != -1)) { this.getCellFormatter().setStyleName(this.selectedCellRowLastIndex, this.selectedColLastIndex, this.selectedCellLastStyle); } this.selectedColLastIndex = -1; this.selectedCellRowLastIndex = -1; } private void clearSelectedCol() { if (this.selectedColLastIndex != -1) { this.getColumnFormatter().setStyleName(this.selectedColLastIndex, this.selectedColLastStyle); } } private void clearSelectedRows() { this.clearSelectedCol(); this.clearSelectedCell(); List old = this.getSelected(); if ((this.masks & BoundTableExt.INSERT_WIDGET_MASK) > 0) { List removeRows = new ArrayList(); if (this.selectedRowStyles != null) { removeRows.addAll(this.selectedRowStyles.keySet()); } else { removeRows.add(new Integer(this.selectedRowLastIndex)); } for (int i = removeRows.size() - 1; i >= 0; i--) { // GWT.log("Removing nested: " + removeRows.get(i), null); this.removeNestedWidget(((Integer) removeRows.get(i)).intValue()); } } if ((this.masks & BoundTableExt.MULTIROWSELECT_MASK) > 0) { for (Iterator it = this.selectedRowStyles.entrySet().iterator(); it.hasNext();) { Entry entry = (Entry) it.next(); int row = ((Integer) entry.getKey()).intValue(); this.table.getRowFormatter().removeStyleName(row, "selected"); } this.selectedRowStyles.clear(); } else if ((this.masks & BoundTableExt.SELECT_ROW_MASK) != 0) { if (this.selectedRowLastIndex != -1) { this.table.getRowFormatter().removeStyleName(this.selectedRowLastIndex, "selected"); this.selectedRowLastStyle = BoundTableExt.DEFAULT_STYLE; } } this.selectedRowLastIndex = -1; this.selectedCellRowLastIndex = -1; this.changes.firePropertyChange("selected", old, this.getSelected()); } private Widget createNavWidget() { Grid p = new Grid(1, 5); p.setStyleName(BoundTableExt.NAV_STYLE); Button b = new Button("<<", new ClickListener() { public void onClick(Widget sender) { first(); } }); b.setStyleName(BoundTableExt.NAV_STYLE); if (this.getCurrentChunk() == 0) { b.setEnabled(false); } p.setWidget(0, 0, b); b = new Button("<", new ClickListener() { public void onClick(Widget sender) { previous(); } }); b.setStyleName(BoundTableExt.NAV_STYLE); if (this.getCurrentChunk() == 0) { b.setEnabled(false); } p.setWidget(0, 1, b); b = new Button(">", new ClickListener() { public void onClick(Widget sender) { next(); } }); b.setStyleName(BoundTableExt.NAV_STYLE); if (this.getCurrentChunk() == (this.getNumberOfChunks() - 1)) { b.setEnabled(false); } Label l = new Label((this.getCurrentChunk() + 1) + " / " + this.getNumberOfChunks()); p.setWidget(0, 2, l); p.getCellFormatter().setHorizontalAlignment(0, 2, HasHorizontalAlignment.ALIGN_CENTER); p.setWidget(0, 3, b); b = new Button(">>", new ClickListener() { public void onClick(Widget sender) { last(); } }); b.setStyleName(BoundTableExt.NAV_STYLE); if (this.getCurrentChunk() == (this.getNumberOfChunks() - 1)) { b.setEnabled(false); } p.setWidget(0, 4, b); return p; } private void createTable() { String oldStyleNames = ""; if (this.table != null) { oldStyleNames = this.table.getStyleName(); } this.table = createTableImpl(); if ((this.masks & BoundTableExt.SELECT_ROW_MASK) > 0) { this.table.addClickHandler(rowSelectHandler); } this.table.setCellPadding(0); this.table.setCellSpacing(0); table.addTableListener(new TableListener() { public void onCellClicked(SourcesTableEvents sender, int row, int cell) { setActive(true); int startColumn = ((masks & BoundTableExt.ROW_HANDLE_MASK) > 0) ? 1 : 0; if (startColumn == 0) { handleSelect(true, row, cell); } if (((masks & BoundTableExt.SORT_MASK) > 0) && ((masks & BoundTableExt.HEADER_MASK) > 0) && (row == 0) && !(BoundTableExt.this.value == null || BoundTableExt.this.value.isEmpty())) { sortColumn(cell - startColumn); } } }); this.base = this.table; this.setStyleName("gwittir-BoundTable", true); if (Ax.notBlank(oldStyleNames)) { this.setStyleName(oldStyleNames); } if (++setCounter == 5) { // should be number 5 in seq int debug = 3; } esp.setWidget(this.table); } private void handleSelect(boolean toggleRow, int row, int col) { int calcRow = row; // GWT.log( "Toggle row "+ toggleRow, null ); // GWT.log( " ON "+row+", "+col, new RuntimeException() ); if ((this.masks & BoundTableExt.INSERT_WIDGET_MASK) > 0) { if (((this.selectedRowStyles == null) && (this.selectedRowLastIndex != -1) && (this.selectedRowLastIndex == (row - 1))) || ((this.selectedRowStyles != null) && this.selectedRowStyles.containsKey(new Integer(row - 1)))) { return; } calcRow = row - this.selectedRowsBeforeRow(row); } if (calcRow < 0) { throw new RuntimeException("Row base is negative!"); } if ((((masks & BoundTableExt.SPACER_ROW_MASK) == 0) && ((((masks & BoundTableExt.HEADER_MASK) > 0) && (calcRow > 0)) || ((masks & BoundTableExt.HEADER_MASK) == 0))) || (((masks & BoundTableExt.HEADER_MASK) > 0) & ((calcRow % 2) != 0)) || (((masks & BoundTableExt.HEADER_MASK) == 0) && ((calcRow % 2) != 1))) { // GWT.log( "Inside" , null); if ((toggleRow && (((masks & BoundTableExt.MULTIROWSELECT_MASK) == 0) && (row != this.selectedCellRowLastIndex))) || (((masks & BoundTableExt.MULTIROWSELECT_MASK) > 0) && toggleRow)) { // if( toggleRow || (masks & BoundTable.MULTIROWSELECT_MASK) == // 0){ row = setSelectedRow(row); } setSelectedCell(row, col); setSelectedCol(col); this.selectedCellRowLastIndex = row; } } private void init(int masksValue) { renderContext = RenderContext.get().snapshot(); // GWT.log( "Init "+ +masksValue + " :: "+((masksValue & // BoundTable.MULTI_REQUIRES_SHIFT) > 0), null); final BoundTableExt instance = this; this.topBinding = new Binding(); this.masks = masksValue; this.factory = (this.factory == null) ? new BoundWidgetTypeFactory(true) : this.factory; if (((this.masks & BoundTableExt.SORT_MASK) > 0) && (this.columns != null)) { this.ascending = new boolean[this.columns.length]; } if ((this.masks & BoundTableExt.MULTIROWSELECT_MASK) > 0) { this.selectedRowStyles = new HashMap(); } if (((this.masks & BoundTableExt.ROW_HANDLE_MASK) > 0) && ((this.masks & BoundTableExt.MULTIROWSELECT_MASK) > 0)) { this.allRowsHandle = new Button(" ", new ClickListener() { public void onClick(Widget sender) { if ((getSelected() != null) && (getSelected().size() == 0)) { setSelected(new ArrayList((Collection) getValue())); } else { setSelected(new ArrayList()); } } }); this.allRowsHandle.setStyleName("rowHandle"); this.allRowsHandle.setHeight("100%"); this.allRowsHandle.setWidth("100%"); if ((this.masks & BoundTableExt.MULTIROWSELECT_MASK) == 0) { this.allRowsHandle.setEnabled(false); } } if ((this.masks & BoundTableExt.ROW_HANDLE_MASK) > 0) { this.rowHandles = new ArrayList(); } esp = new EventingSimplePanel(); createTable(); if ((masks & BoundTableExt.SCROLL_MASK) > 0) { if ("".isEmpty()) { throw new UnsupportedOperationException(); } this.scroll = new ScrollPanel(); this.scroll.setWidget(table); super.initWidget(esp); scroll.addScrollListener(new ScrollListener() { public void onScroll(Widget widget, int scrollLeft, int scrollTop) { // GWT.log("HasProvider: " + (provider != null), null); if ((provider != null) && (inChunk == false) && (scrollTop >= (table.getOffsetHeight() - scroll.getOffsetHeight()))) { // GWT.log("Scroll Event fired. ", null); lastScrollPosition = scrollTop - 1; next(); } } }); } else { super.initWidget(esp); } this.value = (this.value == null) ? new ArrayList() : this.value; this.columns = (this.columns == null) ? new Field[0] : this.columns; this.setStyleName("gwittir-BoundTable"); if ((masks & BoundTableExt.HANDLES_AS_CHECKBOXES) > 0) { this.addStyleName("handles-as-checkboxes"); } if ((this.provider != null) && (this.getCurrentChunk() == -1)) { this.inChunk = true; this.provider.init(this); } this.addPropertyChangeListener("selected", new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent propertyChangeEvent) { if (getAction() != null) { getAction().execute(instance); } } }); this.addPropertyChangeListener("active", new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent propertyChangeEvent) { boolean newActive = ((Boolean) propertyChangeEvent.getNewValue()).booleanValue(); if ((masks & BoundTableExt.ROW_HANDLE_MASK) > 0 && (masks & BoundTableExt.HANDLES_AS_CHECKBOXES) == 0) { for (int i = 0; i < rowHandles.size(); i++) { ((Button) rowHandles.get(i)) .setText((newActive && (i <= 8)) ? Integer.toString(i + 1) : " "); } } for (Iterator it = keyBindings.entrySet().iterator(); it.hasNext();) { Entry entry = (Entry) it.next(); handleBinding(newActive, entry); } for (Iterator it = externalKeyBindings.entrySet().iterator(); it.hasNext();) { Entry entry = (Entry) it.next(); handleBinding(newActive, entry); } } private void handleBinding(boolean newActive, Entry entry) { KeyBinding kb = (KeyBinding) entry.getKey(); Object execute = entry.getValue(); if (newActive) { BoundTableExt.LOG.log(Level.SPAM, "Registering " + kb, null); try { if (execute instanceof Task) { KeyboardController.INSTANCE.register(kb, (Task) execute); } else if (execute instanceof Action) { KeyboardController.INSTANCE.register(kb, (Action) execute); } else if (execute instanceof BoundWidget) { KeyboardController.INSTANCE.register(kb, (BoundWidget) execute); } } catch (KeyBindingException kbe) { BoundTableExt.LOG.log(Level.DEBUG, "Unable to register" + kb, kbe); } } else { boolean result = KeyboardController.INSTANCE.unregister(kb); BoundTableExt.LOG.log(Level.SPAM, "Unregistering " + kb + " " + result, null); } } }); } private void insertNestedWidget(int row) { // GWT.log( "Inserting nested for row "+row, null); Integer realIndex = this.calculateRowToObjectOffset(new Integer(row)); // GWT.log( "RealIndex: "+ realIndex, null ); int i = 0; SourcesPropertyChangeEvents o = null; for (Iterator it = this.topBinding.getChildren().iterator(); it.hasNext(); i++) { if (realIndex.intValue() == i) { o = ((Binding) ((Binding) it.next()).getChildren().get(0)).getRight().object; break; } else { it.next(); } } BoundWidget widget = (BoundWidget) this.factory.getWidgetProvider(Introspector.INSTANCE.resolveClass(o)) .get(); widget.setModel(o); this.table.insertRow(row + 1); this.table.setWidget(row + 1, 0, (Widget) widget); this.table.getFlexCellFormatter().setColSpan(row + 1, 0, this.columns.length + 1); this.table.getCellFormatter().setStyleName(row + 1, 0, "expanded"); this.modifySelectedIndexes(row, +1); } private void modifySelectedIndexes(int fromRow, int modifier) { if (this.selectedRowLastIndex > fromRow) { this.selectedRowLastIndex += modifier; } if (this.selectedCellRowLastIndex > fromRow) { this.selectedCellRowLastIndex += modifier; } if (this.selectedRowStyles == null) { return; } HashMap newSelectedRowStyles = new HashMap(); for (Iterator it = this.selectedRowStyles.entrySet().iterator(); it.hasNext();) { Entry entry = (Entry) it.next(); Integer entryRow = (Integer) entry.getKey(); if (entryRow.intValue() > fromRow) { newSelectedRowStyles.put(new Integer(entryRow.intValue() + modifier), entry.getValue()); } else { newSelectedRowStyles.put(entryRow, entry.getValue()); } } this.selectedRowStyles = newSelectedRowStyles; } private void removeNestedWidget(int row) { this.modifySelectedIndexes(row, -1); this.table.removeRow(row + 1); } private void renderRows(int numberOfRows) { for (; rowIterator != null && rowIterator.hasNext() && --numberOfRows != 0;) { this.addRow((SourcesPropertyChangeEvents) rowIterator.next()); } } private int selectedRowsBeforeRow(int row) { // GWT.log( "=======Selected rows before "+row, null); // GWT.log( "=======lastRow "+this.selectedRowLastIndex, null ); if (this.selectedRowStyles == null) { return ((this.selectedRowLastIndex == -1) || (this.selectedRowLastIndex >= row)) ? 0 : 1; } int count = 0; for (Iterator it = this.selectedRowStyles.keySet().iterator(); it.hasNext();) { if (((Integer) it.next()).intValue() < row) { count++; } } return count; } private void setSelectedCell(int row, int col) { if ((((this.masks & BoundTableExt.HEADER_MASK) > 0) && (row == 0)) || ((this.masks & BoundTableExt.NO_SELECT_CELL_MASK) > 0)) { return; } if ((this.selectedColLastIndex != -1) && (this.selectedCellRowLastIndex != -1)) { this.getCellFormatter().setStyleName(this.selectedCellRowLastIndex, this.selectedColLastIndex, this.selectedCellLastStyle); } this.selectedCellLastStyle = table.getCellFormatter().getStyleName(row, col); if ((this.selectedCellLastStyle == null) || (this.selectedCellLastStyle.length() == 0)) { this.selectedCellLastStyle = BoundTableExt.DEFAULT_STYLE; } table.getCellFormatter().setStyleName(row, col, "selected"); } private void setSelectedCol(int col) { clearSelectedCol(); this.selectedColLastIndex = col; if ((this.masks & BoundTableExt.NO_SELECT_COL_MASK) == 0) { this.selectedColLastStyle = table.getColumnFormatter().getStyleName(col); if ((this.selectedColLastStyle == null) || (this.selectedColLastStyle.length() == 0)) { this.selectedColLastStyle = BoundTableExt.DEFAULT_STYLE; } table.getColumnFormatter().setStyleName(col, "selected"); } } private int setSelectedRow(int row) { if (((this.masks & BoundTableExt.HEADER_MASK) > 0) && (row == 0)) { return row; } List old = this.getSelected(); if ((this.masks & BoundTableExt.MULTIROWSELECT_MASK) > 0) { if ((((masks & BoundTableExt.MULTI_REQUIRES_SHIFT) > 0) == shiftDown)) { // TOGGLE ROW. if (this.selectedRowStyles.containsKey(new Integer(row))) { // Handle Widget remove on Multirow this.getRowFormatter().setStyleName(row, (String) this.selectedRowStyles.remove(new Integer(row))); if ((this.masks & BoundTableExt.INSERT_WIDGET_MASK) > 0) { this.removeNestedWidget(row); } } else { String lastStyle = table.getRowFormatter().getStyleName(row); lastStyle = ((lastStyle == null) || (lastStyle.length() == 0)) ? BoundTableExt.DEFAULT_STYLE : lastStyle; this.selectedRowStyles.put(new Integer(row), lastStyle); this.getRowFormatter().addStyleName(row, "selected"); if ((this.masks & BoundTableExt.INSERT_WIDGET_MASK) > 0) { this.insertNestedWidget(row); } } } else { // Set selected and toggle all others GWT.log("clearing all rows", null); for (Integer i : (Integer[]) this.selectedRowStyles.keySet() .toArray(new Integer[this.selectedRowStyles.keySet().size()])) { if (i == row) { continue; } GWT.log("Clearing " + i, null); // Handle Widget remove on Multirow this.getRowFormatter().removeStyleName(i, "selected"); if ((this.masks & BoundTableExt.INSERT_WIDGET_MASK) > 0) { this.removeNestedWidget(row); } } if (!this.selectedRowStyles.containsKey(row)) { String lastStyle = table.getRowFormatter().getStyleName(row); lastStyle = ((lastStyle == null) || (lastStyle.length() == 0)) ? BoundTableExt.DEFAULT_STYLE : lastStyle; this.selectedRowStyles.put(row, lastStyle); this.getRowFormatter().addStyleName(row, "selected"); if ((this.masks & BoundTableExt.INSERT_WIDGET_MASK) > 0) { this.insertNestedWidget(row); } } } } else { if ((this.masks & BoundTableExt.SELECT_ROW_MASK) != 0) { if (this.selectedRowLastIndex != -1) { this.getRowFormatter().removeStyleName(this.selectedRowLastIndex, "selected"); if ((this.masks & BoundTableExt.INSERT_WIDGET_MASK) > 0) { this.removeNestedWidget(this.selectedRowLastIndex); if (this.selectedRowLastIndex < row) { row--; } } } String currentStyle = table.getRowFormatter().getStyleName(row); if ((currentStyle == null) || !currentStyle.equals("selected")) { this.selectedRowLastStyle = currentStyle; } if ((this.selectedRowLastStyle == null) || (this.selectedRowLastStyle.length() == 0)) { this.selectedRowLastStyle = BoundTableExt.DEFAULT_STYLE; } table.getRowFormatter().addStyleName(row, "selected"); if ((this.masks & BoundTableExt.INSERT_WIDGET_MASK) > 0) { this.insertNestedWidget(row); } } } this.selectedRowLastIndex = (this.selectedRowLastIndex == row) ? (-1) : row; this.changes.firePropertyChange("selected", old, this.getSelected()); return row; } protected void addRow(final SourcesPropertyChangeEvents o) { int row = table.getRowCount(); if (((((masks & BoundTableExt.HEADER_MASK) > 0) && (row >= 2)) || (((masks & BoundTableExt.HEADER_MASK) == 0) && (row >= 1))) && ((masks & BoundTableExt.SPACER_ROW_MASK) > 0)) { table.setWidget(row, 0, new Label("")); table.getFlexCellFormatter().setColSpan(row, 0, this.columns.length); table.getRowFormatter().setStyleName(row, "spacer"); row++; } Binding bindingRow = new Binding(); topBinding.getChildren().add(bindingRow); int count = topBinding.getChildren().size(); final Widget handle; int startColumn = 0; if ((this.masks & BoundTableExt.ROW_HANDLE_MASK) > 0) { if ((this.masks & BoundTableExt.HANDLES_AS_CHECKBOXES) > 0) { handle = new Checkbox(); } else { handle = new Button( (this.getActive() && (rowHandles.size() < 9)) ? Integer.toString(this.rowHandles.size() + 1) : " "); } handle.setStyleName("rowHandle"); ((HasFocus) handle).addFocusListener(new FocusListener() { public void onFocus(Widget sender) { if (shiftDown) { return; } setActive(true); List newSelected = null; if ((masks & BoundTableExt.MULTIROWSELECT_MASK) > 0) { newSelected = new ArrayList(getSelected()); if (newSelected.contains(o)) { newSelected.remove(o); } else { newSelected.add(o); } } else { newSelected = new ArrayList(); newSelected.add(o); } setSelected(newSelected); } public void onLostFocus(Widget sender) { } }); ((SourcesClickEvents) handle).addClickListener(new ClickListener() { public void onClick(Widget sender) { setActive(true); List newSelected = null; if ((masks & BoundTableExt.MULTIROWSELECT_MASK) > 0) { newSelected = new ArrayList(getSelected()); if (newSelected.contains(o)) { newSelected.remove(o); } else { newSelected.add(o); } } else { newSelected = new ArrayList(); newSelected.add(o); } if (handle != null) { ((HasFocus) handle).setFocus(true); } if (handle != null) { ((HasFocus) handle).setFocus(true); } setSelected(newSelected); } }); startColumn++; this.rowHandles.add(handle); this.table.setWidget(row, 0, handle); } else { handle = null; } if (count < 10) { SuggestedKeyBinding kb = new SuggestedKeyBinding(Integer.toString(count).charAt(0), false, true, false); Task task = new Task() { public void run() { List newSelected = new ArrayList(getSelected()); if (newSelected.contains(o)) { newSelected.remove(o); } else { newSelected.add(o); } setSelected(newSelected); if (handle != null) { ((HasFocus) handle).setFocus(true); } } }; this.keyBindings.put(kb, task); if (this.getActive()) { try { KeyboardController.INSTANCE.register(kb, task); } catch (KeyBindingException kbe) { BoundTableExt.LOG.log(Level.DEBUG, "Unable to register" + kb, kbe); } } } for (int col = 0; col < this.columns.length; col++) { Widget widget = (Widget) createCellWidget(bindingRow, col, o); try { table.setWidget(row, col + startColumn, widget); if (widget instanceof HasFocus) { addSelectedFocusListener((HasFocus) widget, topBinding.getChildren().size() - 1, col + startColumn); } if (widget instanceof SourcesClickEvents) { addSelectedClickListener((SourcesClickEvents) widget, topBinding.getChildren().size() - 1, col + startColumn); } if (this.columns[col].getWidgetStyleName() != null) { widget.addStyleName(this.columns[col].getWidgetStyleName()); } if (this.columns[col].getStyleName() != null) { table.getCellFormatter().setStyleName(row, col + startColumn, this.columns[col].getStyleName()); } } catch (RuntimeException e) { BoundTableExt.LOG.log(Level.ERROR, widget + "", e); } } if ((this.masks & BoundTableExt.END_ROW_BUTTON) > 0) { EndRowButton endRowButton = new EndRowButton(); table.setWidget(row, this.columns.length + startColumn, endRowButton); int f_row = row; endRowButton.addClickHandler(e -> { EndRowButtonClickedEvent.fire(BoundTableExt.this, f_row, o); }); } if (collectionPropertyChangeListener != null) { o.addPropertyChangeListener(collectionPropertyChangeListener); listenedToByCollectionChangeListener.add(o); } boolean odd = (this.calculateRowToObjectOffset(new Integer(row)).intValue() % 2) != 0; this.table.getRowFormatter().setStyleName(row, odd ? "odd" : "even"); bindingRow.setLeft(); } protected int calculateObjectToRowOffset(int row) { if ((masks & BoundTableExt.SPACER_ROW_MASK) > 0) { row += row; } if ((masks & BoundTableExt.HEADER_MASK) > 0) { row++; } // GWT.log( "Row before: "+ row, null); if ((masks & BoundTableExt.INSERT_WIDGET_MASK) > 0) { // if( (masks & BoundTable.MULTIROWSELECT_MASK) > 0){ // row += this.selectedRowsBeforeObjectRow(row); // } else { row += this.selectedRowsBeforeRow(row); // } } // GWT.log( "Row after "+ row, null); return row; } protected Integer calculateRowToObjectOffset(Integer rowNumber) { int row = rowNumber.intValue(); if ((masks & BoundTableExt.HEADER_MASK) > 0) { row -= 1; } if ((masks & BoundTableExt.SPACER_ROW_MASK) > 0) { row -= (row / 2); } // GWT.log( "Selected rows before row "+row+" "+ // this.selectedRowsBeforeRow( row ), null ); if (((masks & BoundTableExt.INSERT_WIDGET_MASK) > 0) && ((masks & BoundTableExt.MULTIROWSELECT_MASK) > 0)) { // GWT.log( "At"+ row+ // " Removing: "+this.selectedRowsBeforeRow(row), null ); row -= this.selectedRowsBeforeRow(row); } // GWT.log( "Returning object instance index: "+row, null); return new Integer(row); } protected BoundWidget createCellWidget(Binding rowBinding, int colIndex, SourcesPropertyChangeEvents target) { final BoundWidget widget; Field col = this.columns[colIndex]; BoundWidget[] rowWidgets = (BoundWidget[]) widgetCache.get(target); if (rowWidgets == null) { rowWidgets = new BoundWidget[this.columns.length]; widgetCache.put(target, rowWidgets); } if (rowWidgets[colIndex] != null) { widget = rowWidgets[colIndex]; // BoundTable.LOG.log(Level.SPAM, // "Using cache widget for " + target + "." + // col.getPropertyName(), null); } else { if (col.getCellProvider() != null) { widget = col.getCellProvider().get(); } else { final Property p = GwittirBridge.get().getProperty(target, col.getPropertyName()); widget = (BoundWidget) this.factory.getWidgetProvider(col.getPropertyName(), p.getType()).get(); // TODO Figure out some way to make this read only. } rowWidgets[colIndex] = widget; // BoundTable.LOG.log(Level.SPAM, // "Creating widget for " + target + "." + col.getPropertyName(), // null); } Binding[] bindings = (Binding[]) this.bindingCache.get(target); if (bindings == null) { bindings = new Binding[this.columns.length]; this.bindingCache.put(target, bindings); } if (bindings[colIndex] == null) { bindings[colIndex] = new Binding(widget, "value", col.getValidator(), col.getFeedback(), target, col.getPropertyName(), null, null); // BoundTable.LOG.log(Level.SPAM, // "Created binding " + bindings[colIndex], null); } widget.setModel(target); rowBinding.getChildren().add(bindings[colIndex]); return widget; } protected FlexTable createTableImpl() { return new FlexTable(); } protected native Element getRow(Element elem, int row)/*-{ return elem.rows[row]; }-*/; @Override protected void onAttach() { super.onAttach(); this.renderAll(); ensureBound(true); } private void ensureBound(boolean bound) { if (bound) { if (!topBinding.isBound()) { topBinding.bind(); } } else { if (topBinding.isBound()) { topBinding.unbind(); } } } @Override protected void onDetach() { ensureBound(false); super.onDetach(); this.setActive(false); } protected void renderAll() { if (value != null && value == lastRendered) { return; } if (columns == null) { return; } if (this.topBinding == null) { // Used to check that init() has fired. return; } if (!renderCheck()) { return; } try { RenderContext.get().pushContext(renderContext); renderTop(); renderRows(Integer.MAX_VALUE); renderBottom(); lastRendered = value; } finally { RenderContext.get().pop(); } } private class EventingSimplePanel extends SimplePanel { EventingSimplePanel() { super(); sinkEvents(Event.MOUSEEVENTS); sinkEvents(Event.FOCUSEVENTS); sinkEvents(Event.KEYEVENTS); } @Override public void onBrowserEvent(Event evt) { if (DOM.eventGetShiftKey(evt)) { shiftDown = true; } else { shiftDown = false; } super.onBrowserEvent(evt); } } protected static class EndRowButton extends Composite implements HasClickHandlers { private FlowPanelClickable fpc; public EndRowButton() { fpc = new FlowPanelClickable(); initWidget(fpc); SpanPanel inner = new SpanPanel(); fpc.add(inner); setStyleName("end-row-button"); } @Override public HandlerRegistration addClickHandler(ClickHandler handler) { return fpc.addClickHandler(handler); } } }