blob: 3080f664b17fb342c95e154f50ee8a3f7a4c6a06 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 BSI Business Systems Integration AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BSI Business Systems Integration AG - initial API and implementation
******************************************************************************/
package org.eclipse.scout.rt.client.ui.basic.table;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EventListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.scout.commons.BooleanUtility;
import org.eclipse.scout.commons.CollectionUtility;
import org.eclipse.scout.commons.CompareUtility;
import org.eclipse.scout.commons.CompositeObject;
import org.eclipse.scout.commons.ConfigurationUtility;
import org.eclipse.scout.commons.EventListenerList;
import org.eclipse.scout.commons.HTMLUtility;
import org.eclipse.scout.commons.ITypeWithClassId;
import org.eclipse.scout.commons.OptimisticLock;
import org.eclipse.scout.commons.StringUtility;
import org.eclipse.scout.commons.annotations.ConfigOperation;
import org.eclipse.scout.commons.annotations.ConfigProperty;
import org.eclipse.scout.commons.annotations.Order;
import org.eclipse.scout.commons.annotations.Replace;
import org.eclipse.scout.commons.beans.AbstractPropertyObserver;
import org.eclipse.scout.commons.dnd.TextTransferObject;
import org.eclipse.scout.commons.dnd.TransferObject;
import org.eclipse.scout.commons.exception.ProcessingException;
import org.eclipse.scout.commons.holders.Holder;
import org.eclipse.scout.commons.logger.IScoutLogger;
import org.eclipse.scout.commons.logger.ScoutLogManager;
import org.eclipse.scout.rt.client.services.common.icon.IIconProviderService;
import org.eclipse.scout.rt.client.ui.ClientUIPreferences;
import org.eclipse.scout.rt.client.ui.IDNDSupport;
import org.eclipse.scout.rt.client.ui.IEventHistory;
import org.eclipse.scout.rt.client.ui.MouseButton;
import org.eclipse.scout.rt.client.ui.action.ActionUtility;
import org.eclipse.scout.rt.client.ui.action.IAction;
import org.eclipse.scout.rt.client.ui.action.IActionVisitor;
import org.eclipse.scout.rt.client.ui.action.keystroke.IKeyStroke;
import org.eclipse.scout.rt.client.ui.action.keystroke.KeyStroke;
import org.eclipse.scout.rt.client.ui.action.menu.IMenu;
import org.eclipse.scout.rt.client.ui.action.menu.MenuSeparator;
import org.eclipse.scout.rt.client.ui.action.menu.root.IContextMenu;
import org.eclipse.scout.rt.client.ui.action.menu.root.ITableContextMenu;
import org.eclipse.scout.rt.client.ui.action.menu.root.internal.TableContextMenu;
import org.eclipse.scout.rt.client.ui.basic.cell.Cell;
import org.eclipse.scout.rt.client.ui.basic.cell.ICell;
import org.eclipse.scout.rt.client.ui.basic.table.columnfilter.ColumnFilterMenu;
import org.eclipse.scout.rt.client.ui.basic.table.columnfilter.DefaultTableColumnFilterManager;
import org.eclipse.scout.rt.client.ui.basic.table.columnfilter.ITableColumnFilter;
import org.eclipse.scout.rt.client.ui.basic.table.columnfilter.ITableColumnFilterManager;
import org.eclipse.scout.rt.client.ui.basic.table.columns.AbstractBooleanColumn;
import org.eclipse.scout.rt.client.ui.basic.table.columns.AbstractColumn;
import org.eclipse.scout.rt.client.ui.basic.table.columns.AbstractStringColumn;
import org.eclipse.scout.rt.client.ui.basic.table.columns.IBooleanColumn;
import org.eclipse.scout.rt.client.ui.basic.table.columns.IColumn;
import org.eclipse.scout.rt.client.ui.basic.table.columns.IContentAssistColumn;
import org.eclipse.scout.rt.client.ui.basic.table.customizer.AddCustomColumnMenu;
import org.eclipse.scout.rt.client.ui.basic.table.customizer.ITableCustomizer;
import org.eclipse.scout.rt.client.ui.basic.table.customizer.ModifyCustomColumnMenu;
import org.eclipse.scout.rt.client.ui.basic.table.customizer.RemoveCustomColumnMenu;
import org.eclipse.scout.rt.client.ui.basic.table.internal.InternalTableRow;
import org.eclipse.scout.rt.client.ui.basic.table.menus.CopyWidthsOfColumnsMenu;
import org.eclipse.scout.rt.client.ui.basic.table.menus.OrganizeColumnsMenu;
import org.eclipse.scout.rt.client.ui.basic.table.menus.ResetColumnsMenu;
import org.eclipse.scout.rt.client.ui.form.fields.IFormField;
import org.eclipse.scout.rt.client.ui.form.fields.booleanfield.IBooleanField;
import org.eclipse.scout.rt.client.ui.form.fields.tablefield.AbstractTableField;
import org.eclipse.scout.rt.client.ui.profiler.DesktopProfiler;
import org.eclipse.scout.rt.shared.data.basic.table.AbstractTableRowData;
import org.eclipse.scout.rt.shared.data.form.fields.tablefield.AbstractTableFieldBeanData;
import org.eclipse.scout.rt.shared.data.form.fields.tablefield.AbstractTableFieldData;
import org.eclipse.scout.rt.shared.services.common.code.ICode;
import org.eclipse.scout.rt.shared.services.common.exceptionhandler.IExceptionHandlerService;
import org.eclipse.scout.rt.shared.services.lookup.BatchLookupCall;
import org.eclipse.scout.rt.shared.services.lookup.BatchLookupResultCache;
import org.eclipse.scout.rt.shared.services.lookup.IBatchLookupService;
import org.eclipse.scout.rt.shared.services.lookup.ILookupCall;
import org.eclipse.scout.rt.shared.services.lookup.ILookupRow;
import org.eclipse.scout.rt.shared.services.lookup.LocalLookupCall;
import org.eclipse.scout.service.SERVICES;
/**
* Columns are defined as inner classes<br>
* for every inner column class there is a generated getXYColumn method directly
* on the table
*/
public abstract class AbstractTable extends AbstractPropertyObserver implements ITable {
private static final IScoutLogger LOG = ScoutLogManager.getLogger(AbstractTable.class);
private boolean m_initialized;
private final OptimisticLock m_initLock;
private ColumnSet m_columnSet;
/**
* synchronized list
*/
private final List<ITableRow> m_rows;
private final Object m_cachedRowsLock;
private List<ITableRow> m_cachedRows;
private final HashMap<CompositeObject, ITableRow> m_deletedRows;
private List<ITableRow/* ordered by rowIndex */> m_selectedRows = new ArrayList<ITableRow>();
private Map<Class<?>, Class<? extends IMenu>> m_menuReplacementMapping;
private ITableUIFacade m_uiFacade;
private final List<ITableRowFilter> m_rowFilters;
private String m_userPreferenceContext;
// batch mutation
private boolean m_autoDiscardOnDelete;
private boolean m_sortEnabled;
private boolean m_sortValid;
private boolean m_initialMultiLineText;
private int m_tableChanging;
private List<TableEvent> m_tableEventBuffer = new ArrayList<TableEvent>();
private final HashSet<P_CellLookup> m_cellLookupBuffer = new HashSet<P_CellLookup>();
private HashSet<ITableRow> m_rowDecorationBuffer = new HashSet<ITableRow>();
// key stroke buffer for select-as-you-type
private final KeyStrokeBuffer m_keyStrokeBuffer;
private final EventListenerList m_listenerList = new EventListenerList();
//cell editing
private P_CellEditorContext m_editContext;
private Set<ITableRow> m_rowValidty;
//checkable table
private IBooleanColumn m_checkableColumn;
//auto filter
private final Object m_cachedFilteredRowsLock;
private List<ITableRow> m_cachedFilteredRows;
private IEventHistory<TableEvent> m_eventHistory;
// only do one action at a time
private boolean m_actionRunning;
public AbstractTable() {
this(true);
}
public AbstractTable(boolean callInitializer) {
if (DesktopProfiler.getInstance().isEnabled()) {
DesktopProfiler.getInstance().registerTable(this);
}
m_rowValidty = new HashSet<ITableRow>();
m_cachedRowsLock = new Object();
m_cachedFilteredRowsLock = new Object();
m_rows = Collections.synchronizedList(new ArrayList<ITableRow>(1));
m_deletedRows = new HashMap<CompositeObject, ITableRow>();
m_keyStrokeBuffer = new KeyStrokeBuffer(500L);
m_rowFilters = new ArrayList<ITableRowFilter>(1);
m_initLock = new OptimisticLock();
m_actionRunning = false;
//add single observer listener
addTableListener(new P_TableListener());
if (callInitializer) {
callInitializer();
}
}
protected void callInitializer() {
initConfig();
}
@Override
public String classId() {
return ConfigurationUtility.getAnnotatedClassIdWithFallback(getClass()) + ITypeWithClassId.ID_CONCAT_SYMBOL + getContainer().classId();
}
/*
* Configuration
*/
/**
* Configures the title of this table. The title of the table is rarely used because a table is usually surrounded by
* an {@link AbstractTableField} having its own title / label.
* <p>
* Subclasses can override this method. Default is {@code null}.
*
* @return Title of this table.
*/
@ConfigProperty(ConfigProperty.TEXT)
@Order(10)
protected String getConfiguredTitle() {
return null;
}
/**
* Configures the default icon for this table. The default icon is used for each row in the table.
* <p>
* Subclasses can override this method. Default is {@code null}.
*
* @return the ID (name) of the icon
* @see IIconProviderService
*/
@ConfigProperty(ConfigProperty.ICON_ID)
@Order(20)
protected String getConfiguredDefaultIconId() {
return null;
}
/**
* Configures whether only one row can be selected at once in this table.
* <p>
* Subclasses can override this method. Default is {@code true}.
*
* @return {@code true} if more then one row in this table can be selected at once, {@code false} otherwise.
*/
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(30)
protected boolean getConfiguredMultiSelect() {
return true;
}
/**
* Configures whether only one row can be checked in this table. This configuration is only useful if
* {@link #getConfiguredCheckable()} is {@code true} .
* <p>
* Subclasses can override this method. Default is {@code true}.
*
* @return {@code true} if more then one row in this table can be checked, {@code false} otherwise.
*/
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(32)
protected boolean getConfiguredMultiCheck() {
return true;
}
/**
* Configures the default menu that is used on the ENTER (action key) or the double click on a table row.
* <p>
* Subclasses can override this method. Default is {@code null}.
*
* @return The default menu to use.
*/
@ConfigProperty(ConfigProperty.MENU_CLASS)
@Order(35)
protected Class<? extends IMenu> getConfiguredDefaultMenu() {
return null;
}
/**
* Interception method used for customizing the default menu. Should be used by the framework only.
*
* @since 3.8.1
*/
protected Class<? extends IMenu> getDefaultMenuInternal() {
return getConfiguredDefaultMenu();
}
/**
* Configures whether deleted rows are automatically erased or cached for later processing (service deletion).
* <p>
* Subclasses can override this method. Default is {@code false}.
*
* @return {@code true} if deleted rows are automatically erased, {@code false} if deleted nodes are cached for later
* processing.
*/
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(50)
protected boolean getConfiguredAutoDiscardOnDelete() {
return false;
}
/**
* Configures whether sort is enabled for this table. If sort is enabled, the table rows are sorted based on their
* sort index (see {@link AbstractColumn#getConfiguredSortIndex()}) and the user might change the sorting at run time.
* If sort is disabled, the table rows are not sorted and the user cannot change the sorting.
* <p>
* Subclasses can override this method. Default is {@code true}.
*
* @return {@code true} if sort is enabled, {@code false} otherwise.
*/
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(60)
protected boolean getConfiguredSortEnabled() {
return true;
}
/**
* Configures whether the header row is visible. The header row contains the titles of each column.
* <p>
* Subclasses can override this method. Default is {@code true}.
*
* @return {@code true} if the header row is visible, {@code false} otherwise.
*/
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(70)
protected boolean getConfiguredHeaderVisible() {
return true;
}
/**
* Configures whether the columns are auto resized. If true, all columns are resized so that the table never needs
* horizontal scrolling. This is especially useful for tables inside a form.
* <p>
* Subclasses can override this method. Default is {@code false}.
*
* @return {@code true} if the columns are auto resized, {@code false} otherwise.
* @see {@link AbstractColumn#getConfiguredWidth()}
*/
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(80)
protected boolean getConfiguredAutoResizeColumns() {
return false;
}
/**
* Configures whether the table supports multiline text. If multiline text is supported and a string column has set
* the {@link AbstractStringColumn#getConfiguredTextWrap()} property to true, the text is wrapped and uses two or more
* lines.
* <p>
* Subclasses can override this method. Default is {@code false}. If the method is not overridden and at least one
* string column has set the {@link AbstractStringColumn#getConfiguredTextWrap()} to true, the multiline property is
* set automatically to true.
*
* @return {@code true} if the table supports multiline text, {@code false} otherwise.
*/
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(90)
protected boolean getConfiguredMultilineText() {
return false;
}
/**
* Configures the row height hint. This is a hint for the UI if and only if it is not capable of having variable table
* row height based on cell contents (such as rap/rwt or swt).
* <p>
* This property is interpreted in different manner for each GUI port:
* <ul>
* <li>Swing: The property is ignored.
* <li>SWT: Used as the maximal row height.
* <li>rap/rwt: Used as the fixed row height in multiline tables.
* </ul>
* This hint defines the table row height in pixels being used as the fixed row height for all table rows of this
* table.
* </p>
* Subclasses can override this method. Default is {@code -1}.
*
* @return Table row height hint in pixels.
*/
@ConfigProperty(ConfigProperty.INTEGER)
@Order(92)
protected int getConfiguredRowHeightHint() {
return -1;
}
/**
* Configures whether the table is checkable.
* <p>
* Subclasses can override this method. Default is {@code false}.
*
* @return {@code true} if the table is checkable, {@code false} otherwise.
*/
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(100)
protected boolean getConfiguredCheckable() {
return false;
}
/**
* Configures the checkable column. The checkable column represents the check state of the row, i.e. if it is checked
* or not. If no checkable column is configured, only the row itself represents if the row was checked or not.
* <p>
* Subclasses can override this method. Default is {@code null}.
*
* @return A column class extending {@link AbstractBooleanColumn} that represents the row check state.
*/
@ConfigProperty(ConfigProperty.TABLE_COLUMN)
@Order(102)
protected Class<? extends AbstractBooleanColumn> getConfiguredCheckableColumn() {
return null;
}
/**
* Configures the drop support of this table.
* <p>
* Subclasses can override this method. Default is {@code 0} (no drop support).
*
* @return {@code 0} for no support or one or more of {@link IDNDSupport#TYPE_FILE_TRANSFER},
* {@link IDNDSupport#TYPE_IMAGE_TRANSFER}, {@link IDNDSupport#TYPE_JAVA_ELEMENT_TRANSFER} or
* {@link IDNDSupport#TYPE_TEXT_TRANSFER} (e.g. {@code TYPE_TEXT_TRANSFER | TYPE_FILE_TRANSFER}).
*/
@ConfigProperty(ConfigProperty.DRAG_AND_DROP_TYPE)
@Order(190)
protected int getConfiguredDropType() {
return 0;
}
/**
* Configures the drag support of this table.
* <p>
* Subclasses can override this method. Default is {@code 0} (no drag support).
*
* @return {@code 0} for no support or one or more of {@link IDNDSupport#TYPE_FILE_TRANSFER},
* {@link IDNDSupport#TYPE_IMAGE_TRANSFER}, {@link IDNDSupport#TYPE_JAVA_ELEMENT_TRANSFER} or
* {@link IDNDSupport#TYPE_TEXT_TRANSFER} (e.g. {@code TYPE_TEXT_TRANSFER | TYPE_FILE_TRANSFER}).
*/
@ConfigProperty(ConfigProperty.DRAG_AND_DROP_TYPE)
@Order(190)
protected int getConfiguredDragType() {
return 0;
}
/**
* Configures whether the keyboard can be used for navigation in table. When activated, the user can click on a column
* in the table. Now starting to type some letters, the row matching the typed letters in the column will be selected.
* <p>
* Subclasses can override this method. Default is {@code true}.
*
* @return {@code true} if the keyboard navigation is supported, {@code false} otherwise.
*/
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(200)
protected boolean getConfiguredKeyboardNavigation() {
return true;
}
/**
* Configures whether the table always scrolls to the selection. When activated and the selection in a table changes,
* the table is scrolled to the selection so that the selected row is visible.
* <p>
* Subclasses can override this method. Default is {@code false}.
*
* @return {@code true} if the table scrolls to the selection, {@code false} otherwise.
*/
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(230)
protected boolean getConfiguredScrollToSelection() {
return false;
}
/**
* Called after a drag operation was executed on one or several table rows.
* <p>
* Subclasses can override this method. The default does nothing.
*
* @param rows
* Table rows that were dragged (unmodifiable list).
* @return A transferable object representing the given rows.
* @throws ProcessingException
*/
@ConfigOperation
@Order(10)
protected TransferObject execDrag(List<ITableRow> rows) throws ProcessingException {
return null;
}
/**
* Called after a drop operation was executed on the table.
* <p>
* Subclasses can override this method. The default does nothing.
*
* @param row
* Table row on which the transferable object was dropped (row may be null for empty space drop).
* @param t
* Transferable object that was dropped on the row.
* @throws ProcessingException
*/
@ConfigOperation
@Order(20)
protected void execDrop(ITableRow row, TransferObject t) throws ProcessingException {
}
/**
* Called by a <code>CTRL-C</code> event on the table to copy the given rows into the clipboard.
* <p>
* Subclasses can override this method. The default creates a {@link TextTransferObject} of the table content (HTML
* table).
*
* @param rows
* The selected table rows to copy.
* @return A transferable object representing the given rows or null to not populate the clipboard.
* @throws ProcessingException
*/
@ConfigOperation
@Order(30)
protected TransferObject execCopy(List<? extends ITableRow> rows) throws ProcessingException {
if (!CollectionUtility.hasElements(rows)) {
return null;
}
StringBuilder plainText = new StringBuilder();
StringBuilder htmlText = new StringBuilder("<html>");
// Adding the following MS-office specific style information will cause Excel
// to put all line-break-delimited entries of a <td> cell into a single Excel cell,
// instead of one sub-cell for each <br />
htmlText.append("<head><style type=\"text/css\"> br {mso-data-placement:same-cell;} </style></head>");
htmlText.append("<body><table border=\"0\">");
List<IColumn<?>> columns = getColumnSet().getVisibleColumns();
Pattern patternHtmlCheck = Pattern.compile(".*?<\\s*html.*?>.*", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
Pattern patternBodyContent = Pattern.compile("<\\s*body.*?>(.*?)<\\s*/\\s*body\\s*>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
boolean firstRow = true;
for (ITableRow row : rows) {
// text/html
htmlText.append("<tr>");
// text/plain
if (!firstRow) {
plainText.append(System.getProperty("line.separator"));
}
boolean firstColumn = true;
for (IColumn<?> column : columns) {
String text;
ICell cell = row.getCell(column);
if (column instanceof IBooleanColumn) {
boolean value = BooleanUtility.nvl(((IBooleanColumn) column).getValue(row), false);
text = value ? "X" : "";
}
else {
text = StringUtility.emptyIfNull(cell.getText());
}
// text/plain
if (!firstColumn) {
plainText.append("\t");
}
plainText.append(StringUtility.emptyIfNull(StringUtility.unwrapText(text)));
// text/html
String html = null;
if (patternHtmlCheck.matcher(text).matches()) {
// ensure proper HTML and extract body content
Matcher matcher = patternBodyContent.matcher(HTMLUtility.cleanupHtml(text, false, false, null));
if (matcher.find()) {
html = matcher.group(1);
}
}
if (html == null) {
html = StringUtility.htmlEncode(text);
}
if (!cell.isHtmlEnabled()) {
html = StringUtility.htmlEncode(html);
}
htmlText.append("<td>");
htmlText.append(html);
htmlText.append("</td>");
firstColumn = false;
}
htmlText.append("</tr>");
firstRow = false;
}
htmlText.append("</table></body></html>");
TextTransferObject transferObject = new TextTransferObject(plainText.toString(), htmlText.toString());
return transferObject;
}
/**
* Called after the table content changed, rows were added, removed or changed.
* <p>
* Subclasses can override this method. The default does nothing.
*
* @throws ProcessingException
*/
@ConfigOperation
@Order(40)
protected void execContentChanged() throws ProcessingException {
}
/**
* Called after {@link AbstractColumn#execDecorateCell(Cell,ITableRow)} on the column to decorate the cell.
* <p>
* Subclasses can override this method. The default does nothing.
*
* @throws ProcessingException
*/
@ConfigOperation
@Order(50)
protected void execDecorateCell(Cell view, ITableRow row, IColumn<?> col) throws ProcessingException {
}
/**
* Called during initialization of this table, after the columns were initialized.
* <p>
* Subclasses can override this method. The default does nothing.
*
* @throws ProcessingException
*/
@ConfigOperation
@Order(60)
protected void execInitTable() throws ProcessingException {
}
/**
* Called when this table is disposed, after the columns were disposed.
* <p>
* Subclasses can override this method. The default does nothing.
*
* @throws ProcessingException
*/
@ConfigOperation
@Order(70)
protected void execDisposeTable() throws ProcessingException {
}
/**
* @deprecated use {@link #execRowClick(ITableRow, MouseButton)} instead. Will be removed with V5.0.
*/
@Deprecated
protected void execRowClick(ITableRow row) throws ProcessingException {
}
/**
* Called when the user clicks on a row in this table.
* <p>
* Subclasses can override this method. The default fires a {@link TableEvent#TYPE_ROW_CLICK} event.
*
* @param Row
* that was clicked (never null).
* @param mouseButton
* the mouse button ({@link MouseButton}) which triggered this method
* @throws ProcessingException
*/
@ConfigOperation
@Order(80)
protected void execRowClick(ITableRow row, MouseButton mouseButton) throws ProcessingException {
TableEvent e = new TableEvent(this, TableEvent.TYPE_ROW_CLICK, CollectionUtility.arrayList(row));
fireTableEventInternal(e);
execRowClick(row);
}
/**
* Called when the row has been activated.
* <p>
* Subclasses can override this method. The default opens the configured default menu or if no default menu is
* configured, fires a {@link TableEvent#TYPE_ROW_ACTION} event.
*
* @param Row
* that was activated (never null).
* @throws ProcessingException
*/
@ConfigOperation
@Order(90)
protected void execRowAction(ITableRow row) throws ProcessingException {
Class<? extends IMenu> defaultMenuType = getDefaultMenuInternal();
if (defaultMenuType != null) {
try {
runMenu(defaultMenuType);
}
catch (ProcessingException ex) {
SERVICES.getService(IExceptionHandlerService.class).handleException(ex);
}
catch (Throwable t) {
SERVICES.getService(IExceptionHandlerService.class).handleException(createNewUnexpectedProcessingException(t));
}
}
else {
TableEvent e = new TableEvent(this, TableEvent.TYPE_ROW_ACTION, CollectionUtility.arrayList(row));
fireTableEventInternal(e);
}
}
/**
* Called when one or more rows are selected.
* <p>
* Subclasses can override this method. The default does nothing.
*
* @param rows
* a unmodifiable list of selected rows.
* that were selected.
* @throws ProcessingException
*/
@ConfigOperation
@Order(100)
protected void execRowsSelected(List<? extends ITableRow> rows) throws ProcessingException {
}
/**
* Called when the row is going to be decorated.
* <p>
* Subclasses can override this method. The default does nothing.
*
* @param row
* that is going to be decorated.
* @throws ProcessingException
*/
@ConfigOperation
@Order(110)
protected void execDecorateRow(ITableRow row) throws ProcessingException {
}
/**
* Called when a hyperlink is used within the table. The hyperlink's table row is the selected row and the column is
* the context column ({@link #getContextColumn()}).
* <p>
* Subclasses can override this method. The default does nothing.
*
* @param url
* Hyperlink to process.
* @param path
* Path of URL ({@link URL#getPath()}).
* @param local
* {@code true} if the url is not a valid external url but a local model url (http://local/...)
* @throws ProcessingException
*/
@ConfigOperation
@Order(120)
protected void execHyperlinkAction(URL url, String path, boolean local) throws ProcessingException {
}
/**
* This method is called during initializing the table and is thought to add header menus to the given list of menus.
* Menus added in this method should be of menu type {@link ITableMenu.TableMenuType#Header}
*
* @param menuList
* a live list of the menus. Add additional header menus to this list optionally add some separators at the
* end.
*/
protected void execCreateHeaderMenus(List<IMenu> menuList) {
// header menus
if (getTableCustomizer() != null) {
menuList.add(new AddCustomColumnMenu(this));
menuList.add(new ModifyCustomColumnMenu(this));
menuList.add(new RemoveCustomColumnMenu(this));
}
if (menuList.size() > 0) {
menuList.add(new MenuSeparator());
}
menuList.add(new ResetColumnsMenu(this));
menuList.add(new OrganizeColumnsMenu(this));
menuList.add(new ColumnFilterMenu(this));
menuList.add(new CopyWidthsOfColumnsMenu(this));
menuList.add(new MenuSeparator());
}
protected List<Class<? extends IMenu>> getDeclaredMenus() {
Class[] dca = ConfigurationUtility.getDeclaredPublicClasses(getClass());
List<Class<IMenu>> filtered = ConfigurationUtility.filterClasses(dca, IMenu.class);
List<Class<? extends IMenu>> foca = ConfigurationUtility.sortFilteredClassesByOrderAnnotation(filtered, IMenu.class);
return ConfigurationUtility.removeReplacedClasses(foca);
}
private List<? extends Class<? extends IColumn>> getConfiguredColumns() {
Class[] dca = ConfigurationUtility.getDeclaredPublicClasses(getClass());
List<Class<? extends IColumn>> foca = ConfigurationUtility.sortFilteredClassesByOrderAnnotation(CollectionUtility.arrayList(dca), IColumn.class);
return ConfigurationUtility.removeReplacedClasses(foca);
}
private List<? extends Class<? extends IKeyStroke>> getConfiguredKeyStrokes() {
Class[] dca = ConfigurationUtility.getDeclaredPublicClasses(getClass());
List<Class<IKeyStroke>> fca = ConfigurationUtility.filterClasses(dca, IKeyStroke.class);
return ConfigurationUtility.removeReplacedClasses(fca);
}
protected void initConfig() {
m_eventHistory = createEventHistory();
m_uiFacade = createUIFacade();
setTitle(getConfiguredTitle());
setAutoDiscardOnDelete(getConfiguredAutoDiscardOnDelete());
setSortEnabled(getConfiguredSortEnabled());
setDefaultIconId(getConfiguredDefaultIconId());
setHeaderVisible(getConfiguredHeaderVisible());
setAutoResizeColumns(getConfiguredAutoResizeColumns());
setCheckable(getConfiguredCheckable());
setMultiCheck(getConfiguredMultiCheck());
setMultiSelect(getConfiguredMultiSelect());
setInitialMultilineText(getConfiguredMultilineText());
setMultilineText(getConfiguredMultilineText());
setRowHeightHint(getConfiguredRowHeightHint());
setKeyboardNavigation(getConfiguredKeyboardNavigation());
setDragType(getConfiguredDragType());
setDropType(getConfiguredDropType());
setScrollToSelection(getConfiguredScrollToSelection());
if (getTableCustomizer() == null) {
setTableCustomizer(createTableCustomizer());
}
// columns
createColumnsInternal();
// menus
List<IMenu> menuList = new ArrayList<IMenu>();
List<Class<? extends IMenu>> ma = getDeclaredMenus();
Map<Class<?>, Class<? extends IMenu>> replacements = ConfigurationUtility.getReplacementMapping(ma);
if (!replacements.isEmpty()) {
m_menuReplacementMapping = replacements;
}
for (Class<? extends IMenu> clazz : ma) {
try {
IMenu menu = ConfigurationUtility.newInnerInstance(this, clazz);
menuList.add(menu);
}
catch (Throwable t) {
String className = "null";
if (clazz != null) {
className = clazz.getName();
}
SERVICES.getService(IExceptionHandlerService.class).handleException(new ProcessingException("error creating instance of class '" + className + "'.", t));
}
}
try {
injectMenusInternal(menuList);
}
catch (Exception e) {
LOG.error("error occured while dynamically contributing menus.", e);
}
execCreateHeaderMenus(menuList);
//set container on menus
for (IMenu menu : menuList) {
menu.setContainerInternal(this);
}
ITableContextMenu contextMenu = new TableContextMenu(this, menuList);
setContextMenu(contextMenu);
// key strokes
List<IKeyStroke> ksList = new ArrayList<IKeyStroke>();
List<? extends Class<? extends IKeyStroke>> ksArray = getConfiguredKeyStrokes();
for (Class<? extends IKeyStroke> clazz : ksArray) {
try {
IKeyStroke ks = ConfigurationUtility.newInnerInstance(this, clazz);
ksList.add(ks);
}
catch (Throwable t) {
String className = "null";
if (clazz != null) {
className = clazz.getName();
}
SERVICES.getService(IExceptionHandlerService.class).handleException(new ProcessingException("error creating instance of class '" + className + "'.", t));
}
}
//add ENTER key stroke when default menu is used or execRowAction has an override
Class<? extends IMenu> defaultMenuType = getDefaultMenuInternal();
if (defaultMenuType != null || ConfigurationUtility.isMethodOverwrite(AbstractTable.class, "execRowAction", new Class[]{ITableRow.class}, this.getClass())) {
ksList.add(new KeyStroke("ENTER") {
@Override
protected void execAction() throws ProcessingException {
fireRowAction(getSelectedRow());
}
});
}
setKeyStrokes(ksList);
// add Convenience observer for drag & drop callbacks and event history
addTableListener(new TableAdapter() {
@Override
public void tableChanged(TableEvent e) {
//event history
IEventHistory<TableEvent> h = getEventHistory();
if (h != null) {
h.notifyEvent(e);
}
//dnd
switch (e.getType()) {
case TableEvent.TYPE_ROWS_DRAG_REQUEST: {
if (e.getDragObject() == null) {
try {
e.setDragObject(execDrag(e.getRows()));
}
catch (ProcessingException ex) {
SERVICES.getService(IExceptionHandlerService.class).handleException(ex);
}
}
break;
}
case TableEvent.TYPE_ROW_DROP_ACTION: {
if (e.getDropObject() != null) {
try {
execDrop(e.getFirstRow(), e.getDropObject());
}
catch (ProcessingException ex) {
SERVICES.getService(IExceptionHandlerService.class).handleException(ex);
}
}
break;
}
case TableEvent.TYPE_ROWS_COPY_REQUEST: {
if (e.getCopyObject() == null) {
try {
e.setCopyObject(execCopy(e.getRows()));
}
catch (ProcessingException ex) {
SERVICES.getService(IExceptionHandlerService.class).handleException(ex);
}
}
break;
}
case TableEvent.TYPE_ALL_ROWS_DELETED:
case TableEvent.TYPE_ROWS_DELETED:
case TableEvent.TYPE_ROWS_INSERTED:
case TableEvent.TYPE_ROWS_UPDATED: {
try {
execContentChanged();
}
catch (ProcessingException ex) {
SERVICES.getService(IExceptionHandlerService.class).handleException(ex);
}
break;
}
}
}
});
}
private void initColumnsInternal() {
for (IColumn<?> c : getColumnSet().getColumns()) {
try {
c.initColumn();
}
catch (Throwable t) {
LOG.error("column " + c, t);
}
}
getColumnSet().initialize();
}
private void disposeColumnsInternal() {
for (IColumn<?> c : getColumnSet().getColumns()) {
try {
c.disposeColumn();
}
catch (Throwable t) {
LOG.error("column " + c, t);
}
}
}
private void createColumnsInternal() {
List<? extends Class<? extends IColumn>> ca = getConfiguredColumns();
ArrayList<IColumn<?>> colList = new ArrayList<IColumn<?>>();
for (Class<? extends IColumn> clazz : ca) {
try {
IColumn<?> column = ConfigurationUtility.newInnerInstance(this, clazz);
colList.add(column);
}
catch (Exception e) {
String className = "null";
if (clazz != null) {
className = clazz.getName();
}
SERVICES.getService(IExceptionHandlerService.class).handleException(new ProcessingException("error creating instance of class '" + className + "'.", e));
}
}
try {
injectColumnsInternal(colList);
}
catch (Exception e) {
LOG.error("error occured while dynamically contribute columns.", e);
}
List<IColumn<?>> completeList = new ArrayList<IColumn<?>>();
completeList.addAll(colList);
m_columnSet = new ColumnSet(this, completeList);
if (getConfiguredCheckableColumn() != null) {
AbstractBooleanColumn checkableColumn = getColumnSet().getColumnByClass(getConfiguredCheckableColumn());
setCheckableColumn(checkableColumn);
}
}
/**
* Override this internal method only in order to make use of dynamic fields<br>
* Used to manage column list and add/remove columns
*
* @param columnList
* live and mutable list of configured columns, not yet initialized
*/
protected void injectColumnsInternal(List<IColumn<?>> columnList) {
ITableCustomizer c = getTableCustomizer();
if (c != null) {
c.injectCustomColumns(columnList);
}
}
/**
* Override this internal method only in order to make use of dynamic menus<br>
* Used to manage menu list and add/remove menus
*
* @param menuList
* live and mutable list of configured menus
*/
protected void injectMenusInternal(List<IMenu> menuList) {
}
protected ITableUIFacade createUIFacade() {
return new P_TableUIFacade();
}
/*
* Runtime
*/
@Override
public String getUserPreferenceContext() {
return m_userPreferenceContext;
}
@Override
public void setUserPreferenceContext(String context) {
m_userPreferenceContext = context;
if (isTableInitialized()) {
//re-initialize
try {
initTable();
}
catch (ProcessingException e) {
LOG.error("Failed re-initializing table " + getClass().getName(), e);
}
}
}
/**
* This is the init of the runtime model after the table and columns are built
* and configured
*/
@Override
public final void initTable() throws ProcessingException {
try {
if (m_initLock.acquire()) {
try {
setTableChanging(true);
//
initTableInternal();
ActionUtility.initActions(getMenus());
execInitTable();
}
finally {
setTableChanging(false);
}
}
}
finally {
m_initialized = true;
m_initLock.release();
}
}
protected void initTableInternal() throws ProcessingException {
initColumnsInternal();
if (getColumnFilterManager() == null) {
setColumnFilterManager(createColumnFilterManager());
}
}
@Override
public final void disposeTable() {
try {
disposeTableInternal();
execDisposeTable();
}
catch (Throwable t) {
LOG.warn(getClass().getName(), t);
}
}
protected void disposeTableInternal() throws ProcessingException {
disposeColumnsInternal();
}
@Override
public void doHyperlinkAction(ITableRow row, IColumn<?> col, URL url) throws ProcessingException {
if (!m_actionRunning) {
try {
m_actionRunning = true;
if (row != null) {
selectRow(row);
}
if (col != null) {
setContextColumn(col);
}
execHyperlinkAction(url, url.getPath(), url != null && url.getHost().equals(LOCAL_URL_HOST));
}
finally {
m_actionRunning = false;
}
}
}
@Override
public List<ITableRowFilter> getRowFilters() {
return CollectionUtility.arrayList(m_rowFilters);
}
@Override
public void addRowFilter(ITableRowFilter filter) {
if (filter != null) {
//avoid duplicate add
if (!m_rowFilters.contains(filter)) {
m_rowFilters.add(filter);
applyRowFilters();
}
}
}
@Override
public void removeRowFilter(ITableRowFilter filter) {
if (filter != null) {
if (m_rowFilters.remove(filter)) {
applyRowFilters();
}
}
}
@Override
public void applyRowFilters() {
applyRowFiltersInternal();
fireRowFilterChanged();
}
private void applyRowFiltersInternal() {
for (ITableRow row : m_rows) {
applyRowFiltersInternal((InternalTableRow) row);
}
}
private void applyRowFiltersInternal(InternalTableRow row) {
row.setFilterAcceptedInternal(true);
if (m_rowFilters.size() > 0) {
for (ITableRowFilter filter : m_rowFilters) {
if (!filter.accept(row)) {
row.setFilterAcceptedInternal(false);
/*
* ticket 95770
*/
if (isSelectedRow(row)) {
deselectRow(row);
}
break;
}
}
}
}
@Override
public String getTitle() {
return propertySupport.getPropertyString(PROP_TITLE);
}
@Override
public void setTitle(String s) {
propertySupport.setPropertyString(PROP_TITLE, s);
}
@Override
public boolean isAutoResizeColumns() {
return propertySupport.getPropertyBool(PROP_AUTO_RESIZE_COLUMNS);
}
@Override
public void setAutoResizeColumns(boolean b) {
propertySupport.setPropertyBool(PROP_AUTO_RESIZE_COLUMNS, b);
}
@Override
public ColumnSet getColumnSet() {
return m_columnSet;
}
@Override
public int getColumnCount() {
return getColumnSet().getColumnCount();
}
@Override
public List<IColumn<?>> getColumns() {
return getColumnSet().getColumns();
}
@Override
public List<String> getColumnNames() {
List<String> columnNames = new ArrayList<String>(getColumnCount());
for (IColumn col : getColumns()) {
columnNames.add(col.getHeaderCell().getText());
}
return columnNames;
}
@Override
public int getVisibleColumnCount() {
return getColumnSet().getVisibleColumnCount();
}
@Override
public IHeaderCell getVisibleHeaderCell(int visibleColumnIndex) {
return getHeaderCell(getColumnSet().getVisibleColumn(visibleColumnIndex));
}
@Override
public IHeaderCell getHeaderCell(int columnIndex) {
return getHeaderCell(getColumnSet().getColumn(columnIndex));
}
@Override
public IHeaderCell getHeaderCell(IColumn<?> col) {
return col.getHeaderCell();
}
@Override
public ICell getVisibleCell(int rowIndex, int visibleColumnIndex) {
return getVisibleCell(getRow(rowIndex), visibleColumnIndex);
}
@Override
public ICell getVisibleCell(ITableRow row, int visibleColumnIndex) {
return getCell(row, getColumnSet().getVisibleColumn(visibleColumnIndex));
}
@Override
public ICell getCell(int rowIndex, int columnIndex) {
return getCell(getRow(rowIndex), getColumnSet().getColumn(columnIndex));
}
@Override
public ICell getSummaryCell(int rowIndex) {
return getSummaryCell(getRow(rowIndex));
}
@Override
public ICell getSummaryCell(ITableRow row) {
List<IColumn<?>> a = getColumnSet().getSummaryColumns();
if (a.size() == 0) {
IColumn<?> col = getColumnSet().getFirstDefinedVisibileColumn();
if (col != null) {
a = CollectionUtility.<IColumn<?>> arrayList(col);
}
}
if (a.isEmpty()) {
return new Cell();
}
else if (a.size() == 1) {
Cell cell = new Cell(getCell(row, a.get(0)));
if (cell.getIconId() == null) {
// use icon of row
cell.setIconId(row.getIconId());
}
return cell;
}
else {
Cell cell = new Cell(getCell(row, a.get(0)));
if (cell.getIconId() == null) {
// use icon of row
cell.setIconId(row.getIconId());
}
StringBuilder b = new StringBuilder();
for (IColumn<?> c : a) {
if (b.length() > 0) {
b.append(" ");
}
b.append(getCell(row, c).getText());
}
cell.setText(b.toString());
return cell;
}
}
@Override
public ICell getCell(ITableRow row, IColumn<?> col) {
row = resolveRow(row);
if (row == null || col == null) {
return null;
}
return row.getCell(col.getColumnIndex());
}
/**
* Note that this is not a java bean method and thus not thread-safe
*/
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return isCellEditable(getRow(rowIndex), getColumnSet().getColumn(columnIndex));
}
/**
* Note that this is not a java bean method and thus not thread-safe
*/
@Override
public boolean isCellEditable(ITableRow row, int visibleColumnIndex) {
return isCellEditable(row, getColumnSet().getVisibleColumn(visibleColumnIndex));
}
/**
* Note that this is not a java bean method and thus not thread-safe
*/
@Override
public boolean isCellEditable(ITableRow row, IColumn<?> column) {
return row != null && column != null && column.isVisible() && column.isCellEditable(row);
}
@Override
public Object getProperty(String name) {
return propertySupport.getProperty(name);
}
@Override
public void setProperty(String name, Object value) {
propertySupport.setProperty(name, value);
}
@Override
public boolean hasProperty(String name) {
return propertySupport.hasProperty(name);
}
@Override
public boolean isCheckable() {
return propertySupport.getPropertyBool(PROP_CHECKABLE);
}
@Override
public void setCheckable(boolean b) {
propertySupport.setPropertyBool(PROP_CHECKABLE, b);
}
@Override
public void setDragType(int dragType) {
propertySupport.setPropertyInt(PROP_DRAG_TYPE, dragType);
}
@Override
public int getDragType() {
return propertySupport.getPropertyInt(PROP_DRAG_TYPE);
}
@Override
public void setDropType(int dropType) {
propertySupport.setPropertyInt(PROP_DROP_TYPE, dropType);
}
@Override
public int getDropType() {
return propertySupport.getPropertyInt(PROP_DROP_TYPE);
}
@Override
public boolean isMultilineText() {
return propertySupport.getPropertyBool(PROP_MULTILINE_TEXT);
}
@Override
public void setMultilineText(boolean on) {
propertySupport.setPropertyBool(PROP_MULTILINE_TEXT, on);
}
@Override
public int getRowHeightHint() {
return propertySupport.getPropertyInt(PROP_ROW_HEIGHT_HINT);
}
@Override
public void setRowHeightHint(int h) {
propertySupport.setPropertyInt(PROP_ROW_HEIGHT_HINT, h);
}
@Override
public boolean isInitialMultilineText() {
return m_initialMultiLineText;
}
@Override
public void setInitialMultilineText(boolean on) {
m_initialMultiLineText = on;
}
@Override
public boolean hasKeyboardNavigation() {
return propertySupport.getPropertyBool(PROP_KEYBOARD_NAVIGATION);
}
@Override
public void setKeyboardNavigation(boolean on) {
propertySupport.setPropertyBool(PROP_KEYBOARD_NAVIGATION, on);
}
@Override
public boolean isMultiSelect() {
return propertySupport.getPropertyBool(PROP_MULTI_SELECT);
}
@Override
public void setMultiSelect(boolean b) {
propertySupport.setPropertyBool(PROP_MULTI_SELECT, b);
}
@Override
public boolean isMultiCheck() {
return propertySupport.getPropertyBool(PROP_MULTI_CHECK);
}
@Override
public void setMultiCheck(boolean b) {
propertySupport.setPropertyBool(PROP_MULTI_CHECK, b);
}
@Override
public IBooleanColumn getCheckableColumn() {
return m_checkableColumn;
}
@Override
public void setCheckableColumn(IBooleanColumn checkableColumn) {
m_checkableColumn = checkableColumn;
}
@Override
public boolean isAutoDiscardOnDelete() {
return m_autoDiscardOnDelete;
}
@Override
public void setAutoDiscardOnDelete(boolean on) {
m_autoDiscardOnDelete = on;
}
@Override
public boolean isTableInitialized() {
return m_initialized;
}
@Override
public boolean isTableChanging() {
return m_tableChanging > 0;
}
@Override
public void setTableChanging(boolean b) {
// use a stack counter because setTableChanging might be called in nested
// loops
if (b) {
m_tableChanging++;
if (m_tableChanging == 1) {
// 0 --> 1
propertySupport.setPropertiesChanging(true);
}
}
else {
// all calls to further methods are wrapped into a try-catch block so that the change counters are adjusted correctly
if (m_tableChanging > 0) {
Throwable saveEx = null;
if (m_tableChanging == 1) {
try {
//will be going to zero, but process decorations here, so events are added to the event buffer
processDecorationBuffer();
if (!m_sortValid) {
sort();
}
}
catch (Throwable t) {
saveEx = t;
}
}
m_tableChanging--;
if (m_tableChanging == 0) {
try {
processEventBuffer();
}
catch (Throwable t) {
if (saveEx == null) {
saveEx = t;
}
}
try {
propertySupport.setPropertiesChanging(false);
}
catch (Throwable t) {
if (saveEx == null) {
saveEx = t;
}
}
}
if (saveEx == null) {
return;
}
else if (saveEx instanceof RuntimeException) {
throw (RuntimeException) saveEx;
}
else if (saveEx instanceof Error) {
throw (Error) saveEx;
}
}
}
}
@Override
public List<IKeyStroke> getKeyStrokes() {
return CollectionUtility.arrayList(propertySupport.<IKeyStroke> getPropertyList(PROP_KEY_STROKES));
}
@Override
public void setKeyStrokes(List<? extends IKeyStroke> keyStrokes0) {
propertySupport.setPropertyList(PROP_KEY_STROKES, CollectionUtility.arrayListWithoutNullElements(keyStrokes0));
}
@Override
public void requestFocus() {
fireRequestFocus();
}
@Override
public void requestFocusInCell(IColumn<?> column, ITableRow row) {
if (isCellEditable(row, column)) {
fireRequestFocusInCell(column, row);
}
}
@Override
public ITableRowDataMapper createTableRowDataMapper(Class<? extends AbstractTableRowData> rowType) throws ProcessingException {
return execCreateTableRowDataMapper(rowType);
}
/**
* Creates a {@link TableRowDataMapper} that is used for reading and writing data from the given
* {@link AbstractTableRowData} type.
*
* @param rowType
* @return
* @throws ProcessingException
* @since 3.8.2
*/
@ConfigOperation
@Order(130)
protected ITableRowDataMapper execCreateTableRowDataMapper(Class<? extends AbstractTableRowData> rowType) throws ProcessingException {
return new TableRowDataMapper(rowType, getColumnSet());
}
@Override
public void exportToTableBeanData(AbstractTableFieldBeanData target) throws ProcessingException {
ITableRowDataMapper rowMapper = createTableRowDataMapper(target.getRowType());
for (int i = 0, ni = getRowCount(); i < ni; i++) {
ITableRow row = getRow(i);
if (rowMapper.acceptExport(row)) {
AbstractTableRowData rowData = target.addRow();
rowMapper.exportTableRowData(row, rowData);
}
}
List<ITableRow> deletedRows = getDeletedRows();
for (ITableRow delRow : deletedRows) {
if (rowMapper.acceptExport(delRow)) {
AbstractTableRowData rowData = target.addRow();
rowMapper.exportTableRowData(delRow, rowData);
rowData.setRowState(AbstractTableRowData.STATUS_DELETED);
}
}
}
@Override
public void importFromTableBeanData(AbstractTableFieldBeanData source) throws ProcessingException {
importFromTableRowBeanData(CollectionUtility.arrayList(source.getRows()), source.getRowType());
}
public void importFromTableRowBeanData(List<? extends AbstractTableRowData> rowDatas, Class<? extends AbstractTableRowData> rowType) throws ProcessingException {
discardAllDeletedRows();
clearValidatedValuesOnAllColumns();
clearAllRowsValidity();
int deleteCount = 0;
List<ITableRow> newRows = new ArrayList<ITableRow>();
ITableRowDataMapper mapper = createTableRowDataMapper(rowType);
for (int i = 0, ni = rowDatas.size(); i < ni; i++) {
AbstractTableRowData rowData = rowDatas.get(i);
if (rowData.getRowState() != AbstractTableFieldData.STATUS_DELETED && mapper.acceptImport(rowData)) {
ITableRow newTableRow = new TableRow(getColumnSet());
mapper.importTableRowData(newTableRow, rowData);
newRows.add(newTableRow);
}
else {
deleteCount++;
}
}
replaceRows(newRows);
if (deleteCount > 0) {
try {
setTableChanging(true);
//
for (int i = 0, ni = rowDatas.size(); i < ni; i++) {
AbstractTableRowData rowData = rowDatas.get(i);
if (rowData.getRowState() == AbstractTableFieldData.STATUS_DELETED && mapper.acceptImport(rowData)) {
ITableRow newTableRow = new TableRow(getColumnSet());
mapper.importTableRowData(newTableRow, rowData);
newTableRow.setStatus(ITableRow.STATUS_NON_CHANGED);
ITableRow addedRow = addRow(newTableRow);
deleteRow(addedRow);
}
}
}
finally {
setTableChanging(false);
}
}
}
@Override
public void extractTableData(AbstractTableFieldData target) throws ProcessingException {
for (int i = 0, ni = getRowCount(); i < ni; i++) {
ITableRow row = getRow(i);
int newRowIndex = target.addRow();
for (int j = 0, nj = row.getCellCount(); j < nj; j++) {
target.setValueAt(newRowIndex, j, row.getCellValue(j));
}
target.setRowState(newRowIndex, row.getStatus());
}
for (ITableRow delRow : getDeletedRows()) {
int newRowIndex = target.addRow();
for (int j = 0, nj = delRow.getCellCount(); j < nj; j++) {
target.setValueAt(newRowIndex, j, delRow.getCellValue(j));
}
target.setRowState(newRowIndex, AbstractTableFieldData.STATUS_DELETED);
}
target.setValueSet(true);
}
@Override
@SuppressWarnings("unchecked")
public void updateTable(AbstractTableFieldData source) throws ProcessingException {
if (source.isValueSet()) {
clearValidatedValuesOnAllColumns();
clearAllRowsValidity();
discardAllDeletedRows();
int deleteCount = 0;
List<ITableRow> newRows = new ArrayList<ITableRow>();
for (int i = 0, ni = source.getRowCount(); i < ni; i++) {
int importState = source.getRowState(i);
if (importState != AbstractTableFieldData.STATUS_DELETED) {
ITableRow newTableRow = new TableRow(getColumnSet());
for (int j = 0, nj = source.getColumnCount(); j < nj; j++) {
if (j < getColumnCount()) {
getColumnSet().getColumn(j).setValue(newTableRow, source.getValueAt(i, j));
}
else {
newTableRow.setCellValue(j, source.getValueAt(i, j));
}
}
newTableRow.setStatus(importState);
newRows.add(newTableRow);
}
else {
deleteCount++;
}
}
replaceRows(newRows);
if (deleteCount > 0) {
try {
setTableChanging(true);
//
for (int i = 0, ni = source.getRowCount(); i < ni; i++) {
int importState = source.getRowState(i);
if (importState == AbstractTableFieldData.STATUS_DELETED) {
ITableRow newTableRow = new TableRow(getColumnSet());
for (int j = 0, nj = source.getColumnCount(); j < nj; j++) {
if (j < getColumnCount()) {
getColumnSet().getColumn(j).setValue(newTableRow, source.getValueAt(i, j));
}
else {
newTableRow.setCellValue(j, source.getValueAt(i, j));
}
}
newTableRow.setStatus(ITableRow.STATUS_NON_CHANGED);
ITableRow addedRow = addRow(newTableRow);
deleteRow(addedRow);
}
}
}
finally {
setTableChanging(false);
}
}
}
}
@Override
public void setMenus(List<? extends IMenu> menus) {
getContextMenu().setChildActions(menus);
}
@Override
public void addMenu(IMenu menu) {
List<IMenu> menus = getMenus();
menus.add(menu);
setMenus(menus);
}
protected void setContextMenu(ITableContextMenu contextMenu) {
propertySupport.setProperty(PROP_CONTEXT_MENU, contextMenu);
}
@Override
public ITableContextMenu getContextMenu() {
return (ITableContextMenu) propertySupport.getProperty(PROP_CONTEXT_MENU);
}
@Override
public List<IMenu> getMenus() {
return getContextMenu().getChildActions();
}
@Override
public <T extends IMenu> T getMenu(final Class<T> menuType) throws ProcessingException {
IContextMenu contextMenu = getContextMenu();
if (contextMenu != null) {
final Holder<T> resultHolder = new Holder<T>();
contextMenu.acceptVisitor(new IActionVisitor() {
@SuppressWarnings("unchecked")
@Override
public int visit(IAction action) {
if (menuType.isAssignableFrom(action.getClass())) {
resultHolder.setValue((T) action);
return CANCEL;
}
return CONTINUE;
}
});
return resultHolder.getValue();
}
return null;
}
@SuppressWarnings("deprecation")
@Override
public boolean runMenu(Class<? extends IMenu> menuType) throws ProcessingException {
Class<? extends IMenu> c = getReplacingMenuClass(menuType);
for (IMenu m : getMenus()) {
if (m.getClass() == c) {
if (!m.isEnabledProcessingAction()) {
return false;
}
if ((!m.isInheritAccessibility()) || isEnabled()) {
m.prepareAction();
m.aboutToShow();
if (m.isVisible() && m.isEnabled()) {
m.doAction();
return true;
}
else {
return false;
}
}
}
}
return false;
}
/**
* Checks whether the menu with the given class has been replaced by another menu. If so, the replacing
* menu's class is returned. Otherwise the given class itself.
*
* @param c
* @return Returns the possibly available replacing menu class for the given class.
* @see Replace
* @since 3.8.2
*/
private <T extends IMenu> Class<? extends T> getReplacingMenuClass(Class<T> c) {
if (m_menuReplacementMapping != null) {
@SuppressWarnings("unchecked")
Class<? extends T> replacingMenuClass = (Class<? extends T>) m_menuReplacementMapping.get(c);
if (replacingMenuClass != null) {
return replacingMenuClass;
}
}
return c;
}
/**
* factory to manage table column filters
* <p>
* default creates a {@link DefaultTableColumnFilterManager}
*/
protected ITableColumnFilterManager createColumnFilterManager() {
return new DefaultTableColumnFilterManager(this);
}
/**
* factory to manage custom columns
* <p>
* default creates null
*/
protected ITableCustomizer createTableCustomizer() {
return null;
}
/*
* Row handling methods. Operate on a Row instance.
*/
public ITableRow createRow() throws ProcessingException {
return new P_TableRowBuilder().createRow();
}
public ITableRow createRow(Object rowValues) throws ProcessingException {
return new P_TableRowBuilder().createRow(rowValues);
}
public List<ITableRow> createRowsByArray(Object dataArray) throws ProcessingException {
return new P_TableRowBuilder().createRowsByArray(dataArray);
}
public List<ITableRow> createRowsByArray(Object dataArray, int rowStatus) throws ProcessingException {
return new P_TableRowBuilder().createRowsByArray(dataArray, rowStatus);
}
/**
* Performance note:<br>
* Since the matrix may contain large amount of data, the Object[][] can be passed as new
* AtomicReference<Object>(Object[][])
* so that the further processing can set the content of the holder to null while processing.
*/
public List<ITableRow> createRowsByMatrix(Object dataMatrixOrReference) throws ProcessingException {
return new P_TableRowBuilder().createRowsByMatrix(dataMatrixOrReference);
}
/**
* Performance note:<br>
* Since the matrix may contain large amount of data, the Object[][] can be passed as new
* AtomicReference<Object>(Object[][])
* so that the further processing can set the content of the holder to null while processing.
*/
public List<ITableRow> createRowsByMatrix(Object dataMatrixOrReference, int rowStatus) throws ProcessingException {
return new P_TableRowBuilder().createRowsByMatrix(dataMatrixOrReference, rowStatus);
}
public List<ITableRow> createRowsByCodes(Collection<? extends ICode<?>> codes) throws ProcessingException {
return new P_TableRowBuilder().createRowsByCodes(codes);
}
/**
* Performance note:<br>
* Since the matrix may contain large amount of data, the Object[][] can be passed as new
* AtomicReference<Object>(Object[][])
* so that the further processing can set the content of the holder to null while processing.
*/
@Override
public void replaceRowsByMatrix(Object dataMatrixOrReference) throws ProcessingException {
replaceRows(createRowsByMatrix(dataMatrixOrReference));
}
@Override
public void replaceRowsByArray(Object dataArray) throws ProcessingException {
replaceRows(createRowsByArray(dataArray));
}
@Override
public void replaceRows(List<? extends ITableRow> newRows) throws ProcessingException {
/*
* There are two ways to replace: (1) Completely replace all rows by
* discarding all rows and adding new rows when - autoDiscardOnDelete=true
* (2) Replace rows by applying insert/update/delete on existing rows by
* primary key match when - autoDiscardOnDelete=false
*/
if (isAutoDiscardOnDelete()) {
replaceRowsCase1(newRows);
}
else {
replaceRowsCase2(newRows);
}
}
private void replaceRowsCase1(List<? extends ITableRow> newRows) throws ProcessingException {
try {
setTableChanging(true);
//
ArrayList<CompositeObject> selectedKeys = new ArrayList<CompositeObject>();
for (ITableRow r : getSelectedRows()) {
selectedKeys.add(new CompositeObject(getRowKeys(r)));
}
discardAllRows();
addRows(newRows, false);
// restore selection
ArrayList<ITableRow> selectedRows = new ArrayList<ITableRow>();
if (selectedKeys.size() > 0) {
for (ITableRow r : m_rows) {
if (selectedKeys.remove(new CompositeObject(getRowKeys(r)))) {
selectedRows.add(r);
if (selectedKeys.size() == 0) {
break;
}
}
}
}
selectRows(selectedRows, false);
}
finally {
setTableChanging(false);
}
}
private void replaceRowsCase2(List<? extends ITableRow> newRows) throws ProcessingException {
try {
setTableChanging(true);
//
int[] oldToNew = new int[getRowCount()];
int[] newToOld = new int[newRows.size()];
Arrays.fill(oldToNew, -1);
Arrays.fill(newToOld, -1);
HashMap<CompositeObject, Integer> newRowIndexMap = new HashMap<CompositeObject, Integer>();
for (int i = newRows.size() - 1; i >= 0; i--) {
newRowIndexMap.put(new CompositeObject(getRowKeys(newRows.get(i))), Integer.valueOf(i));
}
int mappedCount = 0;
for (int i = 0, ni = getRowCount(); i < ni; i++) {
ITableRow existingRow = m_rows.get(i);
Integer newIndex = newRowIndexMap.remove(new CompositeObject(getRowKeys(existingRow)));
if (newIndex != null) {
oldToNew[i] = newIndex.intValue();
newToOld[newIndex.intValue()] = i;
mappedCount++;
}
}
List<ITableRow> updatedRows = new ArrayList<ITableRow>(mappedCount);
for (int i = 0; i < oldToNew.length; i++) {
if (oldToNew[i] >= 0) {
ITableRow oldRow = getRow(i);
ITableRow newRow = newRows.get(oldToNew[i]);
try {
oldRow.setRowChanging(true);
//
oldRow.setEnabled(newRow.isEnabled());
oldRow.setStatus(newRow.getStatus());
for (int columnIndex = 0; columnIndex < getColumnCount(); columnIndex++) {
if (columnIndex < newRow.getCellCount()) {
oldRow.getCellForUpdate(columnIndex).updateFrom(newRow.getCell(columnIndex));
}
else {
// reset the visible values
oldRow.getCellForUpdate(columnIndex).setText(null);
oldRow.getCellForUpdate(columnIndex).setValue(null);
}
}
}
finally {
oldRow.setRowPropertiesChanged(false);
oldRow.setRowChanging(false);
}
//
updatedRows.add(oldRow);
}
}
List<ITableRow> deletedRows = new ArrayList<ITableRow>(getRowCount() - mappedCount);
for (int i = 0; i < oldToNew.length; i++) {
if (oldToNew[i] < 0) {
deletedRows.add(m_rows.get(i));
}
}
List<ITableRow> insertedRows = new ArrayList<ITableRow>(newRows.size() - mappedCount);
int[] insertedRowIndexes = new int[newRows.size() - mappedCount];
int index = 0;
for (int i = 0; i < newToOld.length; i++) {
if (newToOld[i] < 0) {
insertedRows.add(newRows.get(i));
insertedRowIndexes[index] = i;
index++;
}
}
//
updateRows(updatedRows);
deleteRows(deletedRows);
addRows(insertedRows, false, insertedRowIndexes);
}
finally {
setTableChanging(false);
}
}
@Override
public void updateRow(ITableRow row) {
if (row != null) {
updateRows(CollectionUtility.arrayList(row));
}
}
@Override
public void updateAllRows() {
updateRows(getRows());
}
@Override
public void setRowState(ITableRow row, int rowState) throws ProcessingException {
setRowState(CollectionUtility.arrayList(row), rowState);
}
@Override
public void setAllRowState(int rowState) throws ProcessingException {
setRowState(getRows(), rowState);
}
@Override
public void setRowState(Collection<? extends ITableRow> rows, int rowState) throws ProcessingException {
try {
setTableChanging(true);
//
for (ITableRow row : rows) {
row.setStatus(rowState);
}
}
finally {
setTableChanging(false);
}
}
@Override
public void updateRows(Collection<? extends ITableRow> rows) {
try {
setTableChanging(true);
//
List<ITableRow> resolvedRowList = new ArrayList<ITableRow>(rows.size());
for (ITableRow row : rows) {
ITableRow resolvedRow = resolveRow(row);
if (resolvedRow != null) {
resolvedRowList.add(resolvedRow);
updateRowImpl(resolvedRow);
}
}
if (resolvedRowList.size() > 0) {
fireRowsUpdated(resolvedRowList);
}
if (getColumnSet().getSortColumnCount() > 0) {
// restore order of rows according to sort criteria
if (isTableChanging()) {
m_sortValid = false;
}
else {
sort();
}
}
}
finally {
setTableChanging(false);
}
}
private void updateRowImpl(ITableRow row) {
if (row != null) {
/*
* do NOT use ITableRow#setRowChanging, this might cause a stack overflow
*/
for (IColumn<?> col : getColumns()) {
if (col instanceof AbstractColumn<?>) {
((AbstractColumn<?>) col).validateColumnValue(row);
}
}
enqueueDecorationTasks(row);
}
}
@Override
public int getRowCount() {
return m_rows.size();
}
@Override
public int getDeletedRowCount() {
return m_deletedRows.size();
}
@Override
public int getSelectedRowCount() {
return m_selectedRows.size();
}
@Override
public ITableRow getSelectedRow() {
return CollectionUtility.firstElement(m_selectedRows);
}
@Override
public List<ITableRow> getSelectedRows() {
return CollectionUtility.arrayList(m_selectedRows);
}
@Override
public boolean isSelectedRow(ITableRow row) {
row = resolveRow(row);
if (row == null) {
return false;
}
else {
return m_selectedRows.contains(row);
}
}
@Override
public void selectRow(int rowIndex) {
selectRow(getRow(rowIndex));
}
@Override
public void selectRow(ITableRow row) {
selectRow(row, false);
}
@Override
public void selectRow(ITableRow row, boolean append) {
selectRows(CollectionUtility.arrayList(row), append);
}
@Override
public void selectRows(List<? extends ITableRow> rows) {
selectRows(rows, false);
}
@Override
public void selectRows(List<? extends ITableRow> rows, boolean append) {
rows = resolveRows(rows);
TreeSet<ITableRow> newSelection = new TreeSet<ITableRow>(new RowIndexComparator());
if (append) {
newSelection.addAll(m_selectedRows);
newSelection.addAll(rows);
}
else {
newSelection.addAll(rows);
}
// check selection count with multiselect
if (newSelection.size() > 1 && !isMultiSelect()) {
ITableRow first = newSelection.first();
newSelection.clear();
newSelection.add(first);
}
if (!CollectionUtility.equalsCollection(m_selectedRows, newSelection, true)) {
m_selectedRows = new ArrayList<ITableRow>(newSelection);
// notify menus
List<ITableRow> notificationCopy = CollectionUtility.arrayList(m_selectedRows);
fireRowsSelected(notificationCopy);
}
}
@Override
public void selectFirstRow() {
selectRow(getRow(0));
}
@Override
public void selectNextRow() {
ITableRow row = getSelectedRow();
if (row != null && row.getRowIndex() + 1 < getRowCount()) {
selectRow(getRow(row.getRowIndex() + 1));
}
else if (row == null && getRowCount() > 0) {
selectRow(0);
}
}
@Override
public void selectPreviousRow() {
ITableRow row = getSelectedRow();
if (row != null && row.getRowIndex() - 1 >= 0) {
selectRow(getRow(row.getRowIndex() - 1));
}
else if (row == null && getRowCount() > 0) {
selectRow(getRowCount() - 1);
}
}
@Override
public void selectLastRow() {
selectRow(getRow(getRowCount() - 1));
}
@Override
public void deselectRow(ITableRow row) {
if (row != null) {
deselectRows(CollectionUtility.arrayList(row));
}
}
@Override
public void deselectRows(List<? extends ITableRow> rows) {
rows = resolveRows(rows);
if (CollectionUtility.hasElements(rows)) {
TreeSet<ITableRow> newSelection = new TreeSet<ITableRow>(new RowIndexComparator());
newSelection.addAll(m_selectedRows);
if (newSelection.removeAll(rows)) {
m_selectedRows = new ArrayList<ITableRow>(newSelection);
fireRowsSelected(m_selectedRows);
}
}
}
@Override
public void selectAllRows() {
selectRows(getRows(), false);
}
@Override
public void deselectAllRows() {
selectRow(null, false);
}
@Override
public void selectAllEnabledRows() {
List<ITableRow> newList = new ArrayList<ITableRow>();
for (int i = 0, ni = getRowCount(); i < ni; i++) {
ITableRow row = getRow(i);
if (row.isEnabled()) {
newList.add(row);
}
else if (isSelectedRow(row)) {
newList.add(row);
}
}
selectRows(newList, false);
}
@Override
public void deselectAllEnabledRows() {
List<ITableRow> selectedRows = getSelectedRows();
ArrayList<ITableRow> newList = new ArrayList<ITableRow>();
for (ITableRow selectedRow : selectedRows) {
if (selectedRow.isEnabled()) {
newList.add(selectedRow);
}
}
deselectRows(newList);
}
@Override
public Collection<ITableRow> getCheckedRows() {
final List<ITableRow> checkedRows = new ArrayList<ITableRow>();
for (ITableRow row : getRows()) {
if (row.isChecked()) {
checkedRows.add(row);
}
}
return checkedRows;
}
@Override
public void checkRow(int row, boolean value) throws ProcessingException {
checkRow(getRow(row), value);
}
@Override
public void checkRow(ITableRow row, boolean value) throws ProcessingException {
if (!row.isEnabled()) {
return;
}
if (!isMultiCheck() && value && getCheckedRows().size() > 0) {
uncheckAllRows();
}
row.setChecked(value);
if (getCheckableColumn() != null) {
getCheckableColumn().setValue(row, value);
}
}
@Override
public void checkRows(Collection<? extends ITableRow> rows, boolean value) throws ProcessingException {
rows = resolveRows(rows);
// check checked count with multicheck
if (rows.size() > 1 && !isMultiCheck()) {
ITableRow first = CollectionUtility.firstElement(rows);
first.setChecked(value);
}
else {
for (ITableRow row : rows) {
checkRow(row, value);
}
}
}
@Override
public void checkAllRows() throws ProcessingException {
try {
setTableChanging(true);
for (int i = 0; i < getRowCount(); i++) {
checkRow(i, true);
}
}
finally {
setTableChanging(false);
}
}
@Override
public void uncheckAllRows() throws ProcessingException {
try {
setTableChanging(true);
for (int i = 0; i < getRowCount(); i++) {
checkRow(i, false);
}
}
finally {
setTableChanging(false);
}
}
@Override
public String getDefaultIconId() {
String iconId = propertySupport.getPropertyString(PROP_DEFAULT_ICON);
if (iconId != null && iconId.length() == 0) {
iconId = null;
}
return iconId;
}
@Override
public void setDefaultIconId(String iconId) {
propertySupport.setPropertyString(PROP_DEFAULT_ICON, iconId);
}
@Override
public boolean isEnabled() {
return propertySupport.getPropertyBool(PROP_ENABLED);
}
@Override
public final void setEnabled(boolean b) {
boolean changed = propertySupport.setPropertyBool(PROP_ENABLED, b);
if (changed) {
//update the state of all current cell beans that are out there
try {
setTableChanging(true);
//
for (ITableRow row : getRows()) {
enqueueDecorationTasks(row);
}
}
finally {
setTableChanging(false);
}
}
}
@Override
public boolean isScrollToSelection() {
return propertySupport.getPropertyBool(PROP_SCROLL_TO_SELECTION);
}
@Override
public void setScrollToSelection(boolean b) {
propertySupport.setPropertyBool(PROP_SCROLL_TO_SELECTION, b);
}
@Override
public void scrollToSelection() {
fireTableEventInternal(new TableEvent(this, TableEvent.TYPE_SCROLL_TO_SELECTION));
}
/**
* @return a copy of a row<br>
* when the row is changed it has to be applied to the table using
* modifyRow(row);
*/
@Override
public ITableRow getRow(int rowIndex) {
return CollectionUtility.getElement(getRows(), rowIndex);
}
@Override
public List<ITableRow> getRows() {
//lazy create list in getter, make sure to be thread-safe since getters may be called from "wild" threads
synchronized (m_cachedRowsLock) {
if (m_cachedRows == null) {
//this code must be thread-safe
m_cachedRows = CollectionUtility.arrayList(m_rows);
}
return m_cachedRows;
}
}
@Override
public List<ITableRow> getFilteredRows() {
List<ITableRow> rows = getRows();
if (m_rowFilters.size() > 0) {
//lazy create list in getter, make sure to be thread-safe since getters may be called from "wild" threads
synchronized (m_cachedFilteredRowsLock) {
if (m_cachedFilteredRows == null) {
//this code must be thread-safe
if (m_rowFilters.size() > 0) {
List<ITableRow> filteredRows = new ArrayList<ITableRow>(getRowCount());
for (ITableRow row : rows) {
if (row != null && row.isFilterAccepted()) {
filteredRows.add(row);
}
}
m_cachedFilteredRows = filteredRows;
}
else {
m_cachedFilteredRows = CollectionUtility.emptyArrayList();
}
}
return m_cachedFilteredRows;
}
}
else {
return rows;
}
}
@Override
public int getFilteredRowCount() {
if (m_rowFilters.size() > 0) {
return getFilteredRows().size();
}
else {
return getRowCount();
}
}
@Override
public ITableRow getFilteredRow(int index) {
if (m_rowFilters.size() > 0) {
ITableRow row = null;
List<ITableRow> filteredRows = getFilteredRows();
if (index >= 0 && index < filteredRows.size()) {
row = filteredRows.get(index);
}
return row;
}
else {
return getRow(index);
}
}
@Override
public int getFilteredRowIndex(ITableRow row) {
return getFilteredRows().indexOf(row);
}
@Override
public List<ITableRow> getNotDeletedRows() {
List<ITableRow> notDeletedRows = new ArrayList<ITableRow>();
for (ITableRow row : getRows()) {
if (row.getStatus() != ITableRow.STATUS_DELETED) {
notDeletedRows.add(row);
}
}
return notDeletedRows;
}
@Override
public int getNotDeletedRowCount() {
return getNotDeletedRows().size();
}
@Override
public Object[][] getTableData() {
Object[][] data = new Object[getRowCount()][getColumnCount()];
for (int r = 0; r < getRowCount(); r++) {
for (int c = 0; c < getColumnCount(); c++) {
data[r][c] = getRow(r).getCellValue(c);
}
}
return data;
}
@Override
public Object[][] exportTableRowsAsCSV(List<? extends ITableRow> rows, List<? extends IColumn> columns, boolean includeLineForColumnNames, boolean includeLineForColumnTypes, boolean includeLineForColumnFormat) {
return TableUtility.exportRowsAsCSV(rows, columns, includeLineForColumnNames, includeLineForColumnTypes, includeLineForColumnFormat);
}
@Override
public List<ITableRow> getRows(int[] rowIndexes) {
if (rowIndexes == null) {
return CollectionUtility.emptyArrayList();
}
List<ITableRow> result = new ArrayList<ITableRow>(rowIndexes.length);
for (int rowIndex : rowIndexes) {
ITableRow row = getRow(rowIndex);
if (row != null) {
result.add(row);
}
}
return result;
}
/**
* @return a copy of a deleted row<br>
* when the row is changed it has to be applied to the table using
* modifyRow(row);
*/
@Override
public List<ITableRow> getDeletedRows() {
return CollectionUtility.arrayList(m_deletedRows.values());
}
@Override
public int getInsertedRowCount() {
int count = 0;
for (ITableRow row : getRows()) {
if (row.getStatus() == ITableRow.STATUS_INSERTED) {
count++;
}
}
return count;
}
@Override
public List<ITableRow> getInsertedRows() {
List<ITableRow> rowList = new ArrayList<ITableRow>();
for (ITableRow row : getRows()) {
if (row.getStatus() == ITableRow.STATUS_INSERTED) {
rowList.add(row);
}
}
return rowList;
}
@Override
public int getUpdatedRowCount() {
int count = 0;
for (ITableRow row : getRows()) {
if (row.getStatus() == ITableRow.STATUS_UPDATED) {
count++;
}
}
return count;
}
@Override
public List<ITableRow> getUpdatedRows() {
List<ITableRow> rowList = new ArrayList<ITableRow>();
for (ITableRow row : getRows()) {
if (row.getStatus() == ITableRow.STATUS_UPDATED) {
rowList.add(row);
}
}
return rowList;
}
/**
* Convenience to add row by data only
*/
@Override
public ITableRow addRowByArray(Object dataArray) throws ProcessingException {
if (dataArray == null) {
return null;
}
List<ITableRow> result = addRowsByMatrix(new Object[]{dataArray});
return CollectionUtility.firstElement(result);
}
@Override
public List<ITableRow> addRowsByMatrix(Object dataMatrix) throws ProcessingException {
return addRowsByMatrix(dataMatrix, ITableRow.STATUS_INSERTED);
}
@Override
public List<ITableRow> addRowsByMatrix(Object dataMatrix, int rowStatus) throws ProcessingException {
return addRows(createRowsByMatrix(dataMatrix, rowStatus));
}
@Override
public List<ITableRow> addRowsByArray(Object dataArray) throws ProcessingException {
return addRowsByArray(dataArray, ITableRow.STATUS_INSERTED);
}
@Override
public List<ITableRow> addRowsByArray(Object dataArray, int rowStatus) throws ProcessingException {
return addRows(createRowsByArray(dataArray, rowStatus));
}
@Override
public ITableRow addRow(ITableRow newRow) throws ProcessingException {
return addRow(newRow, false);
}
@Override
public ITableRow addRow(ITableRow newRow, boolean markAsInserted) throws ProcessingException {
List<ITableRow> addedRows = addRows(CollectionUtility.arrayList(newRow), markAsInserted);
return CollectionUtility.firstElement(addedRows);
}
@Override
public List<ITableRow> addRows(List<? extends ITableRow> newRows) throws ProcessingException {
return addRows(newRows, false);
}
@Override
public List<ITableRow> addRows(List<? extends ITableRow> newRows, boolean markAsInserted) throws ProcessingException {
return addRows(newRows, markAsInserted, null);
}
@Override
public List<ITableRow> addRows(List<? extends ITableRow> newRows, boolean markAsInserted, int[] insertIndexes) throws ProcessingException {
if (newRows == null) {
return CollectionUtility.emptyArrayList();
}
try {
setTableChanging(true);
//
int oldRowCount = m_rows.size();
List<ITableRow> newIRows = new ArrayList<ITableRow>(newRows.size());
for (ITableRow newRow : newRows) {
newIRows.add(addRowImpl(newRow, markAsInserted));
}
fireRowsInserted(newIRows);
if (getColumnSet().getSortColumnCount() > 0) {
// restore order of rows according to sort criteria
if (isTableChanging()) {
m_sortValid = false;
}
else {
sort();
}
}
else if (insertIndexes != null) {
ITableRow[] sortArray = new ITableRow[m_rows.size()];
// add new rows that have a given sortIndex
for (int i = 0; i < insertIndexes.length; i++) {
sortArray[insertIndexes[i]] = newIRows.get(i);
}
int sortArrayIndex = 0;
// add existing rows
for (int i = 0; i < oldRowCount; i++) {
// find next empty slot
while (sortArray[sortArrayIndex] != null) {
sortArrayIndex++;
}
sortArray[sortArrayIndex] = m_rows.get(i);
}
// add new rows that have no given sortIndex
for (int i = insertIndexes.length; i < newIRows.size(); i++) {
// find next empty slot
while (sortArray[sortArrayIndex] != null) {
sortArrayIndex++;
}
sortArray[sortArrayIndex] = newIRows.get(i);
}
sortInternal(Arrays.asList(sortArray));
}
return newIRows;
}
finally {
setTableChanging(false);
}
}
private ITableRow addRowImpl(ITableRow newRow, boolean markAsInserted) throws ProcessingException {
if (markAsInserted) {
newRow.setStatus(ITableRow.STATUS_INSERTED);
}
InternalTableRow newIRow = new InternalTableRow(this, newRow);
for (IColumn<?> col : getColumns()) {
if (col instanceof AbstractColumn<?>) {
((AbstractColumn<?>) col).validateColumnValue(newIRow);
}
}
wasEverValid(newIRow);
synchronized (m_cachedRowsLock) {
m_cachedRows = null;
int newIndex = m_rows.size();
newIRow.setRowIndex(newIndex);
newIRow.setTableInternal(this);
m_rows.add(newIRow);
}
enqueueDecorationTasks(newIRow);
return newIRow;
}
@Override
public void moveRow(int sourceIndex, int targetIndex) {
moveRowImpl(sourceIndex, targetIndex);
}
/**
* move the movingRow to the location just before the target row
*/
@Override
public void moveRowBefore(ITableRow movingRow, ITableRow targetRow) {
movingRow = resolveRow(movingRow);
targetRow = resolveRow(targetRow);
if (movingRow != null && targetRow != null) {
moveRowImpl(movingRow.getRowIndex(), targetRow.getRowIndex());
}
}
/**
* move the movingRow to the location just after the target row
*/
@Override
public void moveRowAfter(ITableRow movingRow, ITableRow targetRow) {
movingRow = resolveRow(movingRow);
targetRow = resolveRow(targetRow);
if (movingRow != null && targetRow != null) {
moveRowImpl(movingRow.getRowIndex(), targetRow.getRowIndex() + 1);
}
}
private void moveRowImpl(int sourceIndex, int targetIndex) {
if (sourceIndex < 0) {
sourceIndex = 0;
}
if (sourceIndex >= getRowCount()) {
sourceIndex = getRowCount() - 1;
}
if (targetIndex < 0) {
targetIndex = 0;
}
if (targetIndex >= getRowCount()) {
targetIndex = getRowCount() - 1;
}
if (targetIndex != sourceIndex) {
synchronized (m_cachedRowsLock) {
m_cachedRows = null;
ITableRow row = m_rows.remove(sourceIndex);
m_rows.add(targetIndex, row);
}
// update row indexes
int min = Math.min(sourceIndex, targetIndex);
int max = Math.max(sourceIndex, targetIndex);
ITableRow[] changedRows = new ITableRow[max - min + 1];
for (int i = min; i <= max; i++) {
changedRows[i - min] = getRow(i);
((InternalTableRow) changedRows[i - min]).setRowIndex(i);
}
fireRowOrderChanged();
// rebuild selection
selectRows(getSelectedRows(), false);
}
}
@Override
public void deleteRow(int rowIndex) {
deleteRows(new int[]{rowIndex});
}
@Override
public void deleteRows(int[] rowIndexes) {
List<ITableRow> rowList = new ArrayList<ITableRow>();
for (int i = 0; i < rowIndexes.length; i++) {
ITableRow row = getRow(rowIndexes[i]);
if (row != null) {
rowList.add(row);
}
}
deleteRows(rowList);
}
@Override
public void deleteRow(ITableRow row) {
if (row != null) {
deleteRows(CollectionUtility.arrayList(row));
}
}
@Override
public void deleteAllRows() {
deleteRows(getRows());
}
@Override
public void deleteRows(Collection<? extends ITableRow> rows) {
List<ITableRow> existingRows = getRows();
//peformance quick-check
if (rows != existingRows) {
rows = resolveRows(rows);
}
if (CollectionUtility.hasElements(rows)) {
try {
setTableChanging(true);
//
int rowCountBefore = getRowCount();
int min = getRowCount();
int max = 0;
for (ITableRow row : rows) {
min = Math.min(min, row.getRowIndex());
max = Math.max(max, row.getRowIndex());
}
List<ITableRow> deletedRows = new ArrayList<ITableRow>(rows);
// remove from selection
deselectRows(deletedRows);
//delete impl
//peformance quick-check
if (rows == existingRows) {
//remove all of them
synchronized (m_cachedRowsLock) {
m_rows.clear();
m_cachedRows = null;
}
clearValidatedValuesOnAllColumns();
clearAllRowsValidity();
for (int i = deletedRows.size() - 1; i >= 0; i--) {
ITableRow candidateRow = deletedRows.get(i);
if (candidateRow != null) {
deleteRowImpl(candidateRow);
}
}
}
else {
for (int i = deletedRows.size() - 1; i >= 0; i--) {
ITableRow candidateRow = deletedRows.get(i);
if (candidateRow != null) {
// delete regardless if index is right
boolean removed = false;
synchronized (m_cachedRowsLock) {
removed = m_rows.remove(candidateRow);
if (removed) {
m_cachedRows = null;
}
}
if (removed) {
clearValidatedValueOnColumns(candidateRow);
clearRowValidity(candidateRow);
deleteRowImpl(candidateRow);
}
}
}
}
// get affected rows
List<ITableRow> selectionRows = new ArrayList<ITableRow>(getSelectedRows());
int minAffectedIndex = Math.max(min - 1, 0);
ITableRow[] affectedRows = new ITableRow[getRowCount() - minAffectedIndex];
for (int i = minAffectedIndex; i < getRowCount(); i++) {
affectedRows[i - minAffectedIndex] = getRow(i);
((InternalTableRow) affectedRows[i - minAffectedIndex]).setRowIndex(i);
selectionRows.remove(getRow(i));
}
if (rowCountBefore == deletedRows.size()) {
fireAllRowsDeleted(deletedRows);
}
else {
fireRowsDeleted(deletedRows);
}
selectRows(selectionRows, false);
}
finally {
setTableChanging(false);
}
}
}
private void deleteRowImpl(ITableRow row) {
if (!(row instanceof InternalTableRow)) {
return;
}
InternalTableRow internalRow = (InternalTableRow) row;
if (isAutoDiscardOnDelete()) {
internalRow.setTableInternal(null);
// don't manage deleted rows any further
}
else if (internalRow.getStatus() == ITableRow.STATUS_INSERTED) {
internalRow.setTableInternal(null);
// it was new and now it is gone, no further action required
}
else {
internalRow.setStatus(ITableRow.STATUS_DELETED);
m_deletedRows.put(new CompositeObject(getRowKeys(internalRow)), internalRow);
}
}
private void clearValidatedValuesOnAllColumns() {
for (IColumn column : getColumnSet().getColumns()) {
if (column instanceof AbstractColumn<?>) {
((AbstractColumn) column).clearValidatedValues();
}
}
}
private void clearValidatedValueOnColumns(ITableRow row) {
for (IColumn column : getColumnSet().getColumns()) {
if (column instanceof AbstractColumn<?>) {
((AbstractColumn) column).clearValidatedValue(row);
}
}
}
@Override
public void discardRow(int rowIndex) {
discardRows(new int[]{rowIndex});
}
@Override
public void discardRows(int[] rowIndexes) {
List<ITableRow> rowList = new ArrayList<ITableRow>();
for (int rIndex : rowIndexes) {
ITableRow row = getRow(rIndex);
if (row != null) {
rowList.add(row);
}
}
discardRows(rowList);
}
@Override
public void discardRow(ITableRow row) {
if (row != null) {
discardRows(CollectionUtility.arrayList(row));
}
}
@Override
public void discardAllRows() {
discardRows(getRows());
}
/**
* discard is the same as delete with the exception that discarded rows are
* not collected in the deletedRows list
*/
@Override
public void discardRows(Collection<? extends ITableRow> rows) {
try {
setTableChanging(true);
//
for (ITableRow row : rows) {
((InternalTableRow) row).setStatus(ITableRow.STATUS_INSERTED);
}
deleteRows(rows);
}
finally {
setTableChanging(false);
}
}
@Override
public void discardAllDeletedRows() {
for (Iterator<ITableRow> it = m_deletedRows.values().iterator(); it.hasNext();) {
((InternalTableRow) it.next()).setTableInternal(null);
}
m_deletedRows.clear();
}
@Override
public void discardDeletedRow(ITableRow deletedRow) {
if (deletedRow != null) {
discardDeletedRows(CollectionUtility.arrayList(deletedRow));
}
}
@Override
public void discardDeletedRows(Collection<? extends ITableRow> deletedRows) {
if (deletedRows != null) {
for (ITableRow row : deletedRows) {
m_deletedRows.remove(new CompositeObject(getRowKeys(row)));
((InternalTableRow) row).setTableInternal(null);
}
}
}
@Override
public void setContextColumn(IColumn<?> col) {
propertySupport.setProperty(PROP_CONTEXT_COLUMN, col);
}
@Override
public IColumn<?> getContextColumn() {
return (IColumn<?>) propertySupport.getProperty(PROP_CONTEXT_COLUMN);
}
@Override
public List<Object> getRowKeys(int rowIndex) {
ITableRow row = getRow(rowIndex);
return getRowKeys(row);
}
@Override
public List<Object> getRowKeys(ITableRow row) {
if (row != null) {
return row.getKeyValues();
}
return CollectionUtility.emptyArrayList();
}
@Override
public ITableRow findRowByKey(List<?> keys) {
List<IColumn<?>> keyColumns = getColumnSet().getKeyColumns();
if (keyColumns.size() == 0) {
keyColumns = getColumnSet().getColumns();
}
for (ITableRow row : m_rows) {
boolean match = true;
if (CollectionUtility.hasElements(keys)) {
for (int i = 0; i < keyColumns.size() && i < keys.size(); i++) {
if (!CompareUtility.equals(keyColumns.get(i).getValue(row), keys.get(i))) {
match = false;
break;
}
}
}
if (match) {
return row;
}
}
return null;
}
@Override
public ITableColumnFilterManager getColumnFilterManager() {
return (ITableColumnFilterManager) propertySupport.getProperty(PROP_COLUMN_FILTER_MANAGER);
}
@Override
public void setColumnFilterManager(ITableColumnFilterManager m) {
propertySupport.setProperty(PROP_COLUMN_FILTER_MANAGER, m);
}
@Override
public ITableCustomizer getTableCustomizer() {
return (ITableCustomizer) propertySupport.getProperty(PROP_TABLE_CUSTOMIZER);
}
@Override
public void setTableCustomizer(ITableCustomizer c) {
propertySupport.setProperty(PROP_TABLE_CUSTOMIZER, c);
}
@Override
public ITypeWithClassId getContainer() {
return (ITypeWithClassId) propertySupport.getProperty(PROP_CONTAINER);
}
/**
* do not use this internal method unless you are implementing a container that holds and controls an {@link ITable}
*/
public void setContainerInternal(ITypeWithClassId container) {
propertySupport.setProperty(PROP_CONTAINER, container);
}
@Override
public boolean isSortEnabled() {
return m_sortEnabled;
}
@Override
public void setSortEnabled(boolean b) {
m_sortEnabled = b;
}
@Override
public void sort() {
try {
if (isSortEnabled()) {
// Consider any active sort-column, not only explicit ones.
// This is to support reverse (implicit) sorting of columns, meaning that multiple column sort is done
// without CTRL-key held. In contrast to explicit multiple column sort, the first clicked column
// is the least significant sort column.
List<IColumn<?>> sortCols = getColumnSet().getSortColumns();
if (sortCols.size() > 0) {
// first make sure decorations and lookups are up-to-date
processDecorationBuffer();
List<ITableRow> a = new ArrayList<ITableRow>(getRows());
Collections.sort(a, new TableRowComparator(sortCols));
sortInternal(a);
}
}
}
finally {
m_sortValid = true;
}
}
@Override
public void sort(List<? extends ITableRow> rowsInNewOrder) {
List<ITableRow> resolvedRows = resolveRows(rowsInNewOrder);
if (resolvedRows.size() == rowsInNewOrder.size()) {
sortInternal(resolvedRows);
}
else {
// check which rows could not be mapped
ArrayList<ITableRow> list = new ArrayList<ITableRow>();
list.addAll(m_rows);
list.removeAll(resolvedRows);
ArrayList<ITableRow> sortedList = new ArrayList<ITableRow>();
sortedList.addAll(resolvedRows);
sortedList.addAll(list);
sortInternal(sortedList);
}
}
private void sortInternal(List<? extends ITableRow> resolvedRows) {
int i = 0;
for (ITableRow row : resolvedRows) {
((InternalTableRow) row).setRowIndex(i);
i++;
}
synchronized (m_cachedRowsLock) {
m_cachedRows = null;
m_rows.clear();
m_rows.addAll(resolvedRows);
}
//sort selection without firing an event
if (m_selectedRows != null && m_selectedRows.size() > 0) {
TreeSet<ITableRow> newSelection = new TreeSet<ITableRow>(new RowIndexComparator());
newSelection.addAll(m_selectedRows);
m_selectedRows = new ArrayList<ITableRow>(newSelection);
}
fireRowOrderChanged();
}
@SuppressWarnings("unchecked")
@Override
public void resetColumnConfiguration() {
discardAllRows();
//
try {
setTableChanging(true);
// save displayable state
HashMap<String, Boolean> displayableState = new HashMap<String, Boolean>();
for (IColumn<?> col : getColumns()) {
displayableState.put(col.getColumnId(), col.isDisplayable());
}
// reset columns
disposeColumnsInternal();
createColumnsInternal();
initColumnsInternal();
// re-apply displayable
for (IColumn<?> col : getColumns()) {
if (displayableState.get(col.getColumnId()) != null) {
col.setDisplayable(displayableState.get(col.getColumnId()));
}
}
// re-apply existing filters to new columns
ITableColumnFilterManager filterManager = getColumnFilterManager();
if (filterManager != null && filterManager.getFilters() != null) {
for (IColumn<?> col : getColumns()) {
for (ITableColumnFilter filter : filterManager.getFilters()) {
if (filter.getColumn().getColumnId().equals(col.getColumnId())) {
filter.setColumn(col);
}
}
filterManager.refresh();
}
}
fireTableEventInternal(new TableEvent(this, TableEvent.TYPE_COLUMN_STRUCTURE_CHANGED));
}
finally {
setTableChanging(false);
}
}
@Override
public void resetColumnVisibilities() {
resetColumns(true, false, false, false);
}
@Override
public void resetColumnOrder() {
resetColumns(false, true, false, false);
}
@Override
public void resetColumnSortOrder() {
resetColumns(false, false, true, false);
}
@Override
public void resetColumnWidths() {
resetColumns(false, false, false, true);
}
@Override
public void resetDisplayableColumns() {
resetColumns(true, true, true, true);
}
@Override
public void resetColumns(boolean visibility, boolean order, boolean sorting, boolean widths) {
try {
setTableChanging(true);
//
try {
if (sorting) {
m_sortValid = false;
}
resetColumnsInternal(visibility, order, sorting, widths);
execResetColumns(visibility, order, sorting, widths);
}
catch (Throwable t) {
LOG.error("reset columns " + visibility + "," + order + "," + sorting + "," + widths, t);
}
}
finally {
setTableChanging(false);
}
}
private void resetColumnsInternal(boolean visibility, boolean order, boolean sorting, boolean widths) {
ClientUIPreferences env = ClientUIPreferences.getInstance();
env.removeAllTableColumnPreferences(this, visibility, order, sorting, widths);
//Visibilities
if (visibility) {
ArrayList<IColumn<?>> list = new ArrayList<IColumn<?>>();
for (IColumn<?> col : getColumnSet().getAllColumnsInUserOrder()) {
if (col.isDisplayable()) {
boolean configuredVisible = ((AbstractColumn<?>) col).isInitialVisible();
if (configuredVisible) {
list.add(col);
}
}
}
getColumnSet().setVisibleColumns(list);
}
//Order
if (order) {
TreeMap<CompositeObject, IColumn<?>> orderMap = new TreeMap<CompositeObject, IColumn<?>>();
int index = 0;
for (IColumn<?> col : getColumns()) {
if (col.isDisplayable() && col.isVisible()) {
orderMap.put(new CompositeObject(col.getViewOrder(), index), col);
index++;
}
}
getColumnSet().setVisibleColumns(orderMap.values());
}
//Sorting
if (sorting) {
TreeMap<CompositeObject, IColumn<?>> sortMap = new TreeMap<CompositeObject, IColumn<?>>();
int index = 0;
for (IColumn<?> col : getColumns()) {
if (col.getInitialSortIndex() >= 0) {
sortMap.put(new CompositeObject(col.getInitialSortIndex(), index), col);
}
index++;
}
//
getColumnSet().clearSortColumns();
getColumnSet().clearPermanentHeadSortColumns();
getColumnSet().clearPermanentTailSortColumns();
for (IColumn<?> col : sortMap.values()) {
if (col.isInitialAlwaysIncludeSortAtBegin()) {
getColumnSet().addPermanentHeadSortColumn(col, col.isInitialSortAscending());
}
else if (col.isInitialAlwaysIncludeSortAtEnd()) {
getColumnSet().addPermanentTailSortColumn(col, col.isInitialSortAscending());
}
else {
getColumnSet().addSortColumn(col, col.isInitialSortAscending());
}
}
}
//Widths
if (widths) {
for (IColumn<?> col : getColumns()) {
if (col.isDisplayable()) {
col.setWidth(col.getInitialWidth());
}
}
}
}
/**
* Affects columns with lookup calls or code types<br>
* cells that have changed values fetch new texts/decorations from the lookup
* service in one single batch call lookup (performance optimization)
*/
private void processDecorationBuffer() {
/*
* 1. process lookup service calls
*/
try {
BatchLookupCall batchCall = null;
ArrayList<ITableRow> tableRowList = null;
ArrayList<Integer> columnIndexList = null;
if (m_cellLookupBuffer.size() > 0) {
batchCall = new BatchLookupCall();
tableRowList = new ArrayList<ITableRow>();
columnIndexList = new ArrayList<Integer>();
BatchLookupResultCache lookupResultCache = new BatchLookupResultCache();
for (P_CellLookup lookup : m_cellLookupBuffer) {
ITableRow row = lookup.getRow();
if (row.getTable() == AbstractTable.this) {
IContentAssistColumn<?, ?> col = lookup.getColumn();
ILookupCall<?> call = col.prepareLookupCall(row);
if (call != null && call.getKey() != null) {
//split: local vs remote
if (call instanceof LocalLookupCall) {
List<ILookupRow<?>> result = lookupResultCache.getDataByKey(call);
applyLookupResult((InternalTableRow) row, col.getColumnIndex(), result);
}
else {
tableRowList.add(row);
columnIndexList.add(Integer.valueOf(col.getColumnIndex()));
batchCall.addLookupCall(call);
}
}
else {
applyLookupResult((InternalTableRow) row, col.getColumnIndex(), new ArrayList<ILookupRow<?>>(0));
}
}
}
}
m_cellLookupBuffer.clear();
//
if (batchCall != null && tableRowList != null && columnIndexList != null && !batchCall.isEmpty()) {
ITableRow[] tableRows = tableRowList.toArray(new ITableRow[tableRowList.size()]);
List<List<ILookupRow<?>>> resultArray;
IBatchLookupService service = SERVICES.getService(IBatchLookupService.class);
resultArray = service.getBatchDataByKey(batchCall);
for (int i = 0; i < tableRows.length; i++) {
applyLookupResult((InternalTableRow) tableRows[i], ((Number) columnIndexList.get(i)).intValue(), resultArray.get(i));
}
}
}
catch (ProcessingException e) {
SERVICES.getService(IExceptionHandlerService.class).handleException(e);
}
finally {
m_cellLookupBuffer.clear();
}
/*
* 2. update row decorations
*/
HashSet<ITableRow> set = m_rowDecorationBuffer;
m_rowDecorationBuffer = new HashSet<ITableRow>();
for (ITableRow row : set) {
if (row.getTable() == AbstractTable.this) {
applyRowDecorationsImpl(row);
}
}
/*
* check row filters
*/
if (m_rowFilters.size() > 0) {
boolean filterChanged = false;
for (ITableRow row : set) {
if (row.getTable() == AbstractTable.this) {
if (row instanceof InternalTableRow) {
InternalTableRow irow = (InternalTableRow) row;
boolean oldFlag = irow.isFilterAccepted();
applyRowFiltersInternal(irow);
boolean newFlag = irow.isFilterAccepted();
filterChanged = filterChanged || (oldFlag != newFlag);
}
}
}
if (filterChanged) {
fireRowFilterChanged();
}
}
}
private int m_processEventBufferLoopDetection;
/**
* Fire events in form of one batch<br>
* fire all buffered events<br>
* coalesce all TableEvents of same type and sort according to their type
*/
private void processEventBuffer() {
//loop detection
try {
m_processEventBufferLoopDetection++;
if (m_processEventBufferLoopDetection > 100) {
LOG.error("LOOP DETECTION in " + getClass() + ". see stack trace for more details.", new Exception("LOOP DETECTION"));
return;
}
//
List<TableEvent> list = m_tableEventBuffer;
m_tableEventBuffer = new ArrayList<TableEvent>();
if (list.size() > 0) {
HashMap<Integer, List<TableEvent>> coalesceMap = new HashMap<Integer, List<TableEvent>>();
for (TableEvent e : list) {
List<TableEvent> subList = coalesceMap.get(e.getType());
if (subList == null) {
subList = new ArrayList<TableEvent>();
coalesceMap.put(e.getType(), subList);
}
subList.add(e);
}
Map<Integer, TableEvent> sortedCoalescedMap = new TreeMap<Integer, TableEvent>();
for (Map.Entry<Integer, List<TableEvent>> entry : coalesceMap.entrySet()) {
int type = entry.getKey();
List<TableEvent> subList = entry.getValue();
int lastIndex = subList.size() - 1;
switch (type) {
case TableEvent.TYPE_ALL_ROWS_DELETED: {
ArrayList<TableEvent> singleList = new ArrayList<TableEvent>(1);
singleList.add(subList.get(lastIndex));// use last
sortedCoalescedMap.put(10, coalesceTableEvents(singleList, false, true));
break;
}
case TableEvent.TYPE_ROWS_DELETED: {
sortedCoalescedMap.put(20, coalesceTableEvents(subList, false, true));// merge
break;
}
case TableEvent.TYPE_ROWS_INSERTED: {
sortedCoalescedMap.put(30, coalesceTableEvents(subList, true, false));// merge
break;
}
case TableEvent.TYPE_ROWS_UPDATED: {
sortedCoalescedMap.put(40, coalesceTableEvents(subList, true, false));// merge
break;
}
case TableEvent.TYPE_COLUMN_HEADERS_UPDATED: {
sortedCoalescedMap.put(60, coalesceTableEvents(subList, false, false));// merge
break;
}
case TableEvent.TYPE_COLUMN_ORDER_CHANGED: {
sortedCoalescedMap.put(70, coalesceTableEvents(subList, false, false));// merge
break;
}
case TableEvent.TYPE_COLUMN_STRUCTURE_CHANGED: {
sortedCoalescedMap.put(80, subList.get(lastIndex));// use last
break;
}
case TableEvent.TYPE_ROW_ORDER_CHANGED: {
sortedCoalescedMap.put(90, subList.get(lastIndex));// use last
break;
}
case TableEvent.TYPE_ROWS_DRAG_REQUEST: {
sortedCoalescedMap.put(100, subList.get(lastIndex));// use last
break;
}
case TableEvent.TYPE_ROW_DROP_ACTION: {
sortedCoalescedMap.put(110, subList.get(lastIndex));// use last
break;
}
case TableEvent.TYPE_ROW_ACTION: {
sortedCoalescedMap.put(160, subList.get(lastIndex));// use last
break;
}
case TableEvent.TYPE_ROWS_SELECTED: {
sortedCoalescedMap.put(170, subList.get(lastIndex));// use last
break;
}
case TableEvent.TYPE_SCROLL_TO_SELECTION: {
sortedCoalescedMap.put(180, subList.get(lastIndex));// use last
break;
}
default: {
sortedCoalescedMap.put(-type, subList.get(lastIndex));// use last
}
}
}
// fire the batch and set tree to changing, otherwise a listener might trigger another events that then are processed before all other listeners received that batch
try {
setTableChanging(true);
//
fireTableEventBatchInternal(new ArrayList<TableEvent>(sortedCoalescedMap.values()));
}
finally {
setTableChanging(false);
}
}
}
finally {
m_processEventBufferLoopDetection--;
}
}
private TableEvent coalesceTableEvents(List<TableEvent> list, boolean includeExistingRows, boolean includeRemovedRows) {
if (list.size() == 1) {
return list.get(0);
}
else {
TableEvent last = list.get(list.size() - 1);
TableEvent ce = new TableEvent(last.getTable(), last.getType());
//
ce.setSortInMemoryAllowed(last.isSortInMemoryAllowed());
ce.setDragObject(last.getDragObject());
ce.setDropObject(last.getDropObject());
ce.setCopyObject(last.getCopyObject());
ce.addPopupMenus(last.getPopupMenus());
//columns
Set<IColumn<?>> colList = new LinkedHashSet<IColumn<?>>();
for (TableEvent t : list) {
if (t.getColumns() != null) {
colList.addAll(t.getColumns());
}
}
ce.setColumns(colList);
//rows
List<ITableRow> rowList = new ArrayList<ITableRow>();
for (TableEvent t : list) {
if (t.getRowCount() > 0) {
for (ITableRow row : t.getRows()) {
if (row.getTable() == AbstractTable.this && includeExistingRows) {
rowList.add(row);
}
else if (row.getTable() != AbstractTable.this && includeRemovedRows) {
rowList.add(row);
}
}
}
}
ce.setRows(rowList);
//
return ce;
}
}
/**
* do decoration and filtering later
*/
private void enqueueDecorationTasks(ITableRow row) {
if (row != null) {
for (int i = 0; i < row.getCellCount(); i++) {
IColumn<?> column = getColumnSet().getColumn(i);
// lookups
if (column instanceof IContentAssistColumn) {
IContentAssistColumn<?, ?> assistColumn = (IContentAssistColumn<?, ?>) column;
if (assistColumn.getLookupCall() != null) {
m_cellLookupBuffer.add(new P_CellLookup(row, assistColumn));
}
}
}
m_rowDecorationBuffer.add(row);
}
}
/*
* does not use setTableChanging()
*/
private void applyRowDecorationsImpl(ITableRow tableRow) {
// disable row changed trigger on row
try {
tableRow.setRowChanging(true);
//
// row decorator on table
this.decorateRow(tableRow);
// row decorator on columns
ColumnSet cset = getColumnSet();
for (int c = 0; c < tableRow.getCellCount(); c++) {
// cell decorator on column
IColumn<?> col = cset.getColumn(c);
col.decorateCell(tableRow);
// cell decorator on table
this.decorateCell(tableRow, col);
}
}
catch (Throwable t) {
LOG.error("Error occured while applying row decoration", t);
}
finally {
tableRow.setRowPropertiesChanged(false);
tableRow.setRowChanging(false);
}
}
private void applyLookupResult(InternalTableRow tableRow, int columnIndex, List<ILookupRow<?>> result) {
// disable row changed trigger on row
try {
tableRow.setRowChanging(true);
//
Cell cell = (Cell) tableRow.getCell(columnIndex);
if (result.size() == 1) {
cell.setText(result.get(0).getText());
cell.setTooltipText(result.get(0).getTooltipText());
}
else if (result.size() > 1) {
StringBuffer buf = new StringBuffer();
StringBuffer bufTooltip = new StringBuffer();
for (int i = 0; i < result.size(); i++) {
if (i > 0) {
if (isMultilineText()) {
buf.append("\n");
bufTooltip.append("\n");
}
else {
buf.append(", ");
bufTooltip.append(", ");
}
}
ILookupRow<?> row = result.get(i);
buf.append(row.getText());
bufTooltip.append(row.getTooltipText());
}
cell.setText(buf.toString());
cell.setTooltipText(bufTooltip.toString());
}
else {
cell.setText("");
cell.setTooltipText("");
}
}
finally {
tableRow.setRowPropertiesChanged(false);
tableRow.setRowChanging(false);
}
}
@Override
public void tablePopulated() {
if (m_tableEventBuffer.isEmpty()) {
synchronized (m_cachedFilteredRowsLock) {
m_cachedFilteredRows = null;
}
fireTableEventInternal(new TableEvent(this, TableEvent.TYPE_TABLE_POPULATED, null));
}
}
@Override
public ITableRow resolveRow(ITableRow row) {
if (row == null) {
return null;
}
if (!(row instanceof InternalTableRow)) {
throw new IllegalArgumentException("only accept InternalTableRow, not " + (row != null ? row.getClass() : null));
}
// check owner
if (row.getTable() == this) {
return row;
}
else {
return null;
}
}
@Override
public List<ITableRow> resolveRows(Collection<? extends ITableRow> rows) {
if (rows == null) {
rows = CollectionUtility.emptyArrayList();
}
List<ITableRow> resolvedRows = new ArrayList<ITableRow>(rows.size());
for (ITableRow row : rows) {
if (resolveRow(row) == row) {
resolvedRows.add(row);
}
else {
LOG.warn("could not resolve row " + row);
}
}
return resolvedRows;
}
@Override
public boolean isHeaderVisible() {
return propertySupport.getPropertyBool(PROP_HEADER_VISIBLE);
}
@Override
public void setHeaderVisible(boolean b) {
propertySupport.setPropertyBool(PROP_HEADER_VISIBLE, b);
}
@Override
public final void decorateCell(ITableRow row, IColumn<?> col) {
Cell cell = row.getCellForUpdate(col.getColumnIndex());
decorateCellInternal(cell, row, col);
try {
execDecorateCell(cell, row, col);
}
catch (ProcessingException e) {
SERVICES.getService(IExceptionHandlerService.class).handleException(e);
}
catch (Throwable t) {
SERVICES.getService(IExceptionHandlerService.class).handleException(createNewUnexpectedProcessingException(t));
}
}
public boolean wasEverValid(ITableRow row) {
if (!m_rowValidty.contains(row)) {
for (IColumn<?> col : getColumns()) {
if (row.getCell(col).getErrorStatus() != null) {
return false;
}
}
m_rowValidty.add(row);
}
return true;
}
private void clearRowValidity(ITableRow row) {
m_rowValidty.remove(row);
}
private void clearAllRowsValidity() {
m_rowValidty.clear();
}
protected void decorateCellInternal(Cell view, ITableRow row, IColumn<?> col) {
}
@Override
public final void decorateRow(ITableRow row) {
decorateRowInternal(row);
try {
execDecorateRow(row);
}
catch (ProcessingException e) {
SERVICES.getService(IExceptionHandlerService.class).handleException(e);
}
catch (Throwable t) {
SERVICES.getService(IExceptionHandlerService.class).handleException(createNewUnexpectedProcessingException(t));
}
}
protected void decorateRowInternal(ITableRow row) {
// icon
if (row.getIconId() == null) {
String s = getDefaultIconId();
if (s != null) {
row.setIconId(s);
}
}
}
/**
* Called when the columns are reset.
* <p>
* Subclasses can override this method. The default does nothing.
*
* @param visiblity
* {@code true} if the visibility is reset.
* @param order
* {@code true} if the order is reset.
* @param sorting
* {@code true} if the sorting is reset.
* @param widths
* {@code true} if the column widths are reset.
* @throws ProcessingException
*/
@ConfigOperation
@Order(90)
protected void execResetColumns(boolean visibility, boolean order, boolean sorting, boolean widths) throws ProcessingException {
}
/**
* Model Observer
*/
@Override
public void addTableListener(TableListener listener) {
m_listenerList.add(TableListener.class, listener);
}
@Override
public void removeTableListener(TableListener listener) {
m_listenerList.remove(TableListener.class, listener);
}
@Override
public void addUITableListener(TableListener listener) {
m_listenerList.insertAtFront(TableListener.class, listener);
}
protected IEventHistory<TableEvent> createEventHistory() {
return new DefaultTableEventHistory(5000L);
}
@Override
public IEventHistory<TableEvent> getEventHistory() {
return m_eventHistory;
}
private void fireRowsInserted(List<? extends ITableRow> rows) {
synchronized (m_cachedFilteredRowsLock) {
m_cachedFilteredRows = null;
}
fireTableEventInternal(new TableEvent(this, TableEvent.TYPE_ROWS_INSERTED, rows));
}
private void fireRowsUpdated(List<? extends ITableRow> rows) {
synchronized (m_cachedFilteredRowsLock) {
m_cachedFilteredRows = null;
}
fireTableEventInternal(new TableEvent(this, TableEvent.TYPE_ROWS_UPDATED, rows));
}
/**
* Request to reload/replace table data with refreshed data
*/
private void fireRowsDeleted(List<? extends ITableRow> rows) {
synchronized (m_cachedFilteredRowsLock) {
m_cachedFilteredRows = null;
}
fireTableEventInternal(new TableEvent(this, TableEvent.TYPE_ROWS_DELETED, rows));
}
private void fireAllRowsDeleted(List<? extends ITableRow> rows) {
synchronized (m_cachedFilteredRowsLock) {
m_cachedFilteredRows = null;
}
fireTableEventInternal(new TableEvent(this, TableEvent.TYPE_ALL_ROWS_DELETED, rows));
}
private void fireRowsSelected(List<? extends ITableRow> rows) {
fireTableEventInternal(new TableEvent(this, TableEvent.TYPE_ROWS_SELECTED, rows));
}
private void fireRowClick(ITableRow row, MouseButton mouseButton) {
if (row != null) {
try {
interceptRowClickSingleObserver(row);
execRowClick(row, mouseButton);
}
catch (ProcessingException ex) {
SERVICES.getService(IExceptionHandlerService.class).handleException(ex);
}
catch (Throwable t) {
SERVICES.getService(IExceptionHandlerService.class).handleException(createNewUnexpectedProcessingException(t));
}
}
}
protected void interceptRowClickSingleObserver(ITableRow row) throws ProcessingException {
// single observer for checkable tables
// if row click is targetted to cell editor, do not interpret click as check/uncheck event
if (row.isEnabled() && isEnabled()) {
IColumn<?> ctxCol = getContextColumn();
if (isCellEditable(row, ctxCol)) {
//cell-level checkbox
if (ctxCol instanceof IBooleanColumn) {
//editable boolean columns consume this click
IFormField field = ctxCol.prepareEdit(row);
if (field instanceof IBooleanField) {
IBooleanField bfield = (IBooleanField) field;
bfield.setChecked(!bfield.isChecked());
ctxCol.completeEdit(row, field);
}
}
else {
//other editable columns have no effect HERE, the ui will open an editor
}
}
else {
//row-level checkbox
if (isCheckable()) {
row.setChecked(!row.isChecked());
}
}
}
}
private void fireRowAction(ITableRow row) {
if (!m_actionRunning) {
try {
m_actionRunning = true;
if (row != null) {
try {
execRowAction(row);
}
catch (ProcessingException ex) {
SERVICES.getService(IExceptionHandlerService.class).handleException(ex);
}
catch (Throwable t) {
SERVICES.getService(IExceptionHandlerService.class).handleException(createNewUnexpectedProcessingException(t));
}
}
}
finally {
m_actionRunning = false;
}
}
}
private void fireRowOrderChanged() {
synchronized (m_cachedFilteredRowsLock) {
m_cachedFilteredRows = null;
}
fireTableEventInternal(new TableEvent(this, TableEvent.TYPE_ROW_ORDER_CHANGED, getRows()));
}
private void fireRequestFocus() {
fireTableEventInternal(new TableEvent(this, TableEvent.TYPE_REQUEST_FOCUS));
}
private void fireRequestFocusInCell(IColumn<?> column, ITableRow row) {
TableEvent e = new TableEvent(this, TableEvent.TYPE_REQUEST_FOCUS_IN_CELL);
e.setColumns(CollectionUtility.hashSet(column));
e.setRows(CollectionUtility.arrayList(row));
fireTableEventInternal(e);
}
private void fireRowFilterChanged() {
synchronized (m_cachedFilteredRowsLock) {
m_cachedFilteredRows = null;
}
fireTableEventInternal(new TableEvent(this, TableEvent.TYPE_ROW_FILTER_CHANGED));
}
private TransferObject fireRowsDragRequest() {
List<ITableRow> rows = getSelectedRows();
if (CollectionUtility.hasElements(rows)) {
TableEvent e = new TableEvent(this, TableEvent.TYPE_ROWS_DRAG_REQUEST, rows);
fireTableEventInternal(e);
return e.getDragObject();
}
else {
return null;
}
}
private void fireRowDropAction(ITableRow row, TransferObject dropData) {
List<ITableRow> rows = null;
if (row != null) {
rows = CollectionUtility.arrayList(row);
}
TableEvent e = new TableEvent(this, TableEvent.TYPE_ROW_DROP_ACTION, rows);
e.setDropObject(dropData);
fireTableEventInternal(e);
}
private TransferObject fireRowsCopyRequest() {
List<ITableRow> rows = getSelectedRows();
if (CollectionUtility.hasElements(rows)) {
TableEvent e = new TableEvent(this, TableEvent.TYPE_ROWS_COPY_REQUEST, rows);
fireTableEventInternal(e);
return e.getCopyObject();
}
else {
return null;
}
}
/**
* Called before the header menus are displayed.
* <p>
* Subclasses can override this method. The default add menus for add, modifying and removing custom column and menus
* for reseting, organizing and filtering the columns.
*
* @param e
* Table event of type {@link TableEvent#TYPE_HEADER_POPUP}.
* @throws ProcessingException
* @Deprecated use {@link #execCreateHeaderMenus(List)} instead.
*/
@SuppressWarnings("deprecation")
@ConfigOperation
@Order(100)
@Deprecated
protected void execAddHeaderMenus(TableEvent e) throws ProcessingException {
if (getTableCustomizer() != null) {
if (e.getPopupMenuCount() > 0) {
e.addPopupMenu(new MenuSeparator());
}
for (IMenu m : new IMenu[]{new AddCustomColumnMenu(this), new ModifyCustomColumnMenu(this), new RemoveCustomColumnMenu(this)}) {
m.prepareAction();
if (m.isVisible()) {
e.addPopupMenu(m);
}
}
}
if (e.getPopupMenuCount() > 0) {
e.addPopupMenu(new MenuSeparator());
}
for (IMenu m : new IMenu[]{new ResetColumnsMenu(this), new OrganizeColumnsMenu(this), new ColumnFilterMenu(this), new CopyWidthsOfColumnsMenu(this)}) {
m.prepareAction();
if (m.isVisible()) {
e.addPopupMenu(m);
}
}
}
// main handler
protected void fireTableEventInternal(TableEvent e) {
if (isTableChanging()) {
// buffer the event for later batch firing
m_tableEventBuffer.add(e);
}
else {
//Ensure all editor values have been applied.
// getUIFacade().completeCellEditFromUI();
EventListener[] listeners = m_listenerList.getListeners(TableListener.class);
if (listeners != null && listeners.length > 0) {
for (int i = 0; i < listeners.length; i++) {
try {
((TableListener) listeners[i]).tableChanged(e);
}
catch (Throwable t) {
LOG.error("fire " + e, t);
}
}
}
}
}
// batch handler
private void fireTableEventBatchInternal(List<? extends TableEvent> batch) {
if (CollectionUtility.hasElements(batch)) {
EventListener[] listeners = m_listenerList.getListeners(TableListener.class);
if (listeners != null && listeners.length > 0) {
for (int i = 0; i < listeners.length; i++) {
((TableListener) listeners[i]).tableChangedBatch(batch);
}
}
}
}
protected boolean handleKeyStroke(String keyName, char keyChar) {
if (keyName == null) {
return false;
}
keyName = keyName.toLowerCase();
// check if there is no menu keystroke with that name
for (IMenu m : getMenus()) {
if (m.getKeyStroke() != null && m.getKeyStroke().equalsIgnoreCase(keyName)) {
return false;
}
}
// check if there is no keystroke with that name (ticket 78234)
for (IKeyStroke k : getKeyStrokes()) {
if (k.getKeyStroke() != null && k.getKeyStroke().equalsIgnoreCase(keyName)) {
return false;
}
}
if (keyChar > ' ' && (!keyName.contains("control")) && (!keyName.contains("ctrl")) && (!keyName.contains("alt"))) {
// select first/next line with corresponding character
String newText = "" + Character.toLowerCase(keyChar);
m_keyStrokeBuffer.append(newText);
String prefix = m_keyStrokeBuffer.getText();
IColumn<?> col = getContextColumn();
if (col == null) {
List<IColumn<?>> sortCols = getColumnSet().getSortColumns();
if (sortCols.size() > 0) {
col = CollectionUtility.lastElement(sortCols);
}
else {
TreeMap<CompositeObject, IColumn<?>> sortMap = new TreeMap<CompositeObject, IColumn<?>>();
int index = 0;
for (IColumn<?> c : getColumnSet().getVisibleColumns()) {
if (c.getDataType() == String.class) {
sortMap.put(new CompositeObject(1, index), c);
}
else if (c.getDataType() == Boolean.class) {
sortMap.put(new CompositeObject(3, index), c);
}
else {
sortMap.put(new CompositeObject(2, index), c);
}
index++;
}
if (sortMap.size() > 0) {
col = sortMap.get(sortMap.firstKey());
}
}
}
if (col != null) {
int colIndex = col.getColumnIndex();
String pattern = StringUtility.toRegExPattern(prefix.toLowerCase());
pattern = pattern + ".*";
if (LOG.isInfoEnabled()) {
LOG.info("finding regex:" + pattern + " in column " + getColumnSet().getColumn(colIndex).getHeaderCell().getText());
}
// loop over values and find matching one
int rowCount = getRowCount();
ITableRow selRow = getSelectedRow();
int startIndex = 0;
if (selRow != null) {
if (prefix.length() <= 1) {
startIndex = selRow.getRowIndex() + 1;
}
else {
startIndex = selRow.getRowIndex();
}
}
for (int i = 0; i < rowCount; i++) {
ITableRow row = m_rows.get((startIndex + i) % rowCount);
String text = row.getCell(colIndex).getText();
if (text != null && text.toLowerCase().matches(pattern)) {
// handled
selectRow(row, false);
return true;
}
}
}
}
return false;
}
@Override
public ITableUIFacade getUIFacade() {
return m_uiFacade;
}
private ProcessingException createNewUnexpectedProcessingException(Throwable t) {
return new ProcessingException("Unexpected", t);
}
/*
* UI Notifications
*/
protected class P_TableUIFacade implements ITableUIFacade {
private int m_uiProcessorCount = 0;
protected void pushUIProcessor() {
m_uiProcessorCount++;
}
protected void popUIProcessor() {
m_uiProcessorCount--;
}
@Override
public boolean isUIProcessing() {
return m_uiProcessorCount > 0;
}
@Override
public void fireRowClickFromUI(ITableRow row, MouseButton mouseButton) {
try {
pushUIProcessor();
//
row = resolveRow(row);
if (row != null) {
fireRowClick(resolveRow(row), mouseButton);
}
}
finally {
popUIProcessor();
}
}
@Override
public void fireRowActionFromUI(ITableRow row) {
try {
pushUIProcessor();
//
row = resolveRow(row);
if (row != null) {
fireRowAction(row);
}
}
finally {
popUIProcessor();
}
}
@Override
public boolean fireKeyTypedFromUI(String keyStrokeText, char keyChar) {
try {
pushUIProcessor();
//
return handleKeyStroke(keyStrokeText, keyChar);
}
finally {
popUIProcessor();
}
}
@Override
public void fireVisibleColumnsChangedFromUI(Collection<IColumn<?>> visibleColumns) {
try {
pushUIProcessor();
//
getColumnSet().setVisibleColumns(visibleColumns);
ClientUIPreferences.getInstance().setAllTableColumnPreferences(AbstractTable.this);
}
finally {
popUIProcessor();
}
}
@Override
public void fireColumnMovedFromUI(IColumn<?> c, int toViewIndex) {
try {
pushUIProcessor();
//
c = getColumnSet().resolveColumn(c);
if (c != null) {
getColumnSet().moveColumnToVisibleIndex(c.getColumnIndex(), toViewIndex);
ClientUIPreferences.getInstance().setAllTableColumnPreferences(AbstractTable.this);
}
}
finally {
popUIProcessor();
}
}
@Override
public void setColumnWidthFromUI(IColumn<?> c, int newWidth) {
try {
pushUIProcessor();
//
c = getColumnSet().resolveColumn(c);
if (c != null) {
c.setWidthInternal(newWidth);
ClientUIPreferences.getInstance().setAllTableColumnPreferences(AbstractTable.this);
}
}
finally {
popUIProcessor();
}
}
@Override
public void fireHeaderSortFromUI(IColumn<?> c, boolean multiSort) {
try {
pushUIProcessor();
//
if (isSortEnabled()) {
c = getColumnSet().resolveColumn(c);
if (c != null) {
getColumnSet().handleSortEvent(c, multiSort);
ClientUIPreferences.getInstance().setAllTableColumnPreferences(AbstractTable.this);
sort();
}
}
}
finally {
popUIProcessor();
}
}
@Override
public void setSelectedRowsFromUI(List<? extends ITableRow> rows) {
try {
pushUIProcessor();
//
Set<ITableRow> requestedRows = new HashSet<ITableRow>(resolveRows(rows));
List<ITableRow> validRows = new ArrayList<ITableRow>();
// add existing selected rows that are masked by filter
for (ITableRow row : getSelectedRows()) {
if (!row.isFilterAccepted()) {
validRows.add(row);
}
}
// remove all filtered from requested
requestedRows.removeAll(validRows);
// add remainder
for (ITableRow row : requestedRows) {
validRows.add(row);
}
selectRows(validRows, false);
}
finally {
popUIProcessor();
}
}
@Override
public TransferObject fireRowsDragRequestFromUI() {
try {
pushUIProcessor();
return fireRowsDragRequest();
}
finally {
popUIProcessor();
}
}
@Override
public void fireRowDropActionFromUI(ITableRow row, TransferObject dropData) {
try {
pushUIProcessor();
row = resolveRow(row);
fireRowDropAction(row, dropData);
}
finally {
popUIProcessor();
}
}
@Override
public TransferObject fireRowsCopyRequestFromUI() {
try {
pushUIProcessor();
return fireRowsCopyRequest();
}
finally {
popUIProcessor();
}
}
@Override
public void fireHyperlinkActionFromUI(ITableRow row, IColumn<?> col, URL url) {
try {
pushUIProcessor();
//
doHyperlinkAction(resolveRow(row), col, url);
}
catch (ProcessingException e) {
SERVICES.getService(IExceptionHandlerService.class).handleException(e);
}
finally {
popUIProcessor();
}
}
@Override
public void setContextColumnFromUI(IColumn<?> col) {
try {
pushUIProcessor();
//
if (col != null && col.getTable() != AbstractTable.this) {
col = null;
}
setContextColumn(col);
}
finally {
popUIProcessor();
}
}
@Override
public IFormField prepareCellEditFromUI(ITableRow row, IColumn<?> col) {
try {
pushUIProcessor();
//
m_editContext = null;
row = resolveRow(row);
if (row != null && col != null) {
try {
// ensure the editable row to be selected.
// This is crucial if the cell's value is changed right away in @{link IColumn#prepareEdit(ITableRow)}, e.g. in @{link AbstractBooleanColumn}
row.getTable().selectRow(row);
IFormField f = col.prepareEdit(row);
if (f != null) {
m_editContext = new P_CellEditorContext(row, col, f);
}
return f;
}
catch (ProcessingException e) {
SERVICES.getService(IExceptionHandlerService.class).handleException(e);
}
catch (Throwable t) {
SERVICES.getService(IExceptionHandlerService.class).handleException(new ProcessingException("Unexpected", t));
}
}
}
finally {
popUIProcessor();
}
return null;
}
@Override
public void completeCellEditFromUI() {
try {
pushUIProcessor();
//
if (m_editContext != null) {
try {
m_editContext.getColumn().completeEdit(m_editContext.getRow(), m_editContext.getFormField());
}
catch (ProcessingException e) {
SERVICES.getService(IExceptionHandlerService.class).handleException(e);
}
catch (Throwable t) {
SERVICES.getService(IExceptionHandlerService.class).handleException(new ProcessingException("Unexpected", t));
}
finally {
m_editContext = null;
}
}
}
finally {
popUIProcessor();
}
}
@Override
public void cancelCellEditFromUI() {
try {
pushUIProcessor();
//
m_editContext = null;
}
finally {
popUIProcessor();
}
}
}
private static class P_CellLookup {
private final ITableRow m_row;
private final IContentAssistColumn<?, ?> m_column;
public P_CellLookup(ITableRow row, IContentAssistColumn<?, ?> col) {
m_row = row;
m_column = col;
}
public ITableRow getRow() {
return m_row;
}
public IContentAssistColumn<?, ?> getColumn() {
return m_column;
}
}// end private class
private class P_TableRowBuilder extends AbstractTableRowBuilder<Object> {
@Override
protected ITableRow createEmptyTableRow() {
return new TableRow(getColumnSet());
}
}
private class P_TableListener extends TableAdapter {
@Override
public void tableChanged(TableEvent e) {
switch (e.getType()) {
case TableEvent.TYPE_ROWS_SELECTED: {
// single observer exec
try {
execRowsSelected(e.getRows());
}
catch (ProcessingException ex) {
SERVICES.getService(IExceptionHandlerService.class).handleException(ex);
}
catch (Throwable t) {
SERVICES.getService(IExceptionHandlerService.class).handleException(new ProcessingException("Unexpected", t));
}
break;
}
}
}
}
private static class P_CellEditorContext {
private final ITableRow m_row;
private final IColumn<?> m_column;
private final IFormField m_formField;
public P_CellEditorContext(ITableRow row, IColumn<?> col, IFormField f) {
m_row = row;
m_column = col;
m_formField = f;
}
public ITableRow getRow() {
return m_row;
}
public IColumn<?> getColumn() {
return m_column;
}
public IFormField getFormField() {
return m_formField;
}
}
}