| package org.eclipse.scout.rt.ui.rap.basic.table; |
| |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.jface.viewers.ColumnLabelProvider; |
| import org.eclipse.jface.viewers.ViewerCell; |
| import org.eclipse.rap.rwt.RWT; |
| import org.eclipse.scout.commons.CollectionUtility; |
| import org.eclipse.scout.commons.HTMLUtility; |
| import org.eclipse.scout.commons.StringUtility; |
| 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.ui.basic.cell.ICell; |
| import org.eclipse.scout.rt.client.ui.basic.table.ITable; |
| import org.eclipse.scout.rt.client.ui.basic.table.ITableRow; |
| import org.eclipse.scout.rt.client.ui.basic.table.columns.IColumn; |
| import org.eclipse.scout.rt.client.ui.basic.table.columns.IProposalColumn; |
| import org.eclipse.scout.rt.client.ui.basic.table.columns.IStringColumn; |
| import org.eclipse.scout.rt.shared.AbstractIcons; |
| import org.eclipse.scout.rt.ui.rap.RwtIcons; |
| import org.eclipse.scout.rt.ui.rap.extension.UiDecorationExtensionPoint; |
| import org.eclipse.scout.rt.ui.rap.util.HtmlTextUtility; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.Font; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.TableItem; |
| |
| public class RwtScoutColumnModel extends ColumnLabelProvider { |
| |
| public static final String EDITABLE_VARIANT_PREFIX = "EDITABLE_CELL_VARIANT_"; |
| public static final String EDITABLE_VARIANT_CELL_MARKER = "EDITABLE"; |
| |
| private static final IScoutLogger LOG = ScoutLogManager.getLogger(RwtScoutColumnModel.class); |
| private static final long serialVersionUID = 1L; |
| |
| private final ITable m_scoutTable; |
| private final RwtScoutTable m_uiTable; |
| private final TableColumnManager m_columnManager; |
| private final Image m_imgCheckboxFalse; |
| private final Image m_imgCheckboxTrue; |
| private final Color m_disabledForegroundColor; |
| |
| private volatile Map<ITableRow, Map<IColumn<?>, P_CachedCell>> m_cachedCells; |
| |
| public RwtScoutColumnModel(ITable scoutTable, RwtScoutTable uiTable, TableColumnManager columnManager) { |
| m_scoutTable = scoutTable; |
| m_uiTable = uiTable; |
| m_columnManager = columnManager; |
| m_imgCheckboxTrue = getUiTable().getUiEnvironment().getIcon(RwtIcons.CheckboxYes); |
| m_imgCheckboxFalse = getUiTable().getUiEnvironment().getIcon(RwtIcons.CheckboxNo); |
| m_disabledForegroundColor = getUiTable().getUiEnvironment().getColor(UiDecorationExtensionPoint.getLookAndFeel().getColorForegroundDisabled()); |
| rebuildCache(); |
| } |
| |
| protected ITable getScoutTable() { |
| return m_scoutTable; |
| } |
| |
| public RwtScoutTable getUiTable() { |
| return m_uiTable; |
| } |
| |
| @Override |
| public void update(ViewerCell cell) { |
| ITableRow row = (ITableRow) cell.getElement(); |
| int columnIndex = cell.getColumnIndex(); |
| |
| cell.setText(getColumnText(row, columnIndex)); |
| cell.setImage(getColumnImage(row, columnIndex)); |
| cell.setBackground(getBackground(row, columnIndex)); |
| cell.setForeground(getForeground(row, columnIndex)); |
| cell.setFont(getFont(row, columnIndex)); |
| |
| // Encode the information of which cell is editable into the custom variant of the 'TableItem' to display a visual marker for editable cells. |
| // This is a workaround because there is no actual RAP support to set an individual variant on table-cells (the CSS-element 'Table-Cell' applies to all cells of the table). |
| // The marker itself is added in the JavaScript patch 'EditableCellMarkerPatch.js' which patches 'GridRow.js'. |
| if (cell.getColumnIndex() == 1) { |
| cell.getItem().setData(RWT.CUSTOM_VARIANT, createTableRowVariant(row)); // A 'TableItem' represents not a single cell but the whole row instead. Therefore, the encoding is only done for the first cell being updated. |
| } |
| } |
| |
| public String getColumnText(ITableRow element, int columnIndex) { |
| ICell cell = getCell(element, columnIndex); |
| if (cell == null) { |
| return ""; |
| } |
| |
| String text = cell.getText(); |
| if (text == null) { |
| text = ""; |
| } |
| else if (HtmlTextUtility.isTextWithHtmlMarkup(text)) { |
| text = getUiTable().getUiEnvironment().adaptHtmlCell(getUiTable(), text); |
| text = getUiTable().getUiEnvironment().convertLinksInHtmlCell(getUiTable(), text); |
| } |
| else { |
| boolean multiline = isMultiline(text); |
| if (!multiline) { |
| text = replaceLineBreaksInMultilineText(text); |
| } |
| boolean isMultilineTable = getScoutTable().isMultilineText(); |
| boolean markupEnabled = Boolean.TRUE.equals(getUiTable().getUiField().getData(RWT.MARKUP_ENABLED)); |
| |
| if (markupEnabled || multiline) { |
| boolean replaceBreakableChars = true; |
| IColumn<?> column = m_columnManager.getColumnByModelIndex(columnIndex - 1); |
| if (column instanceof IStringColumn && isMultilineTable) { |
| IStringColumn stringColumn = (IStringColumn) column; |
| replaceBreakableChars = !stringColumn.isTextWrap(); |
| } |
| text = HtmlTextUtility.transformPlainTextToHtml(text, replaceBreakableChars); |
| } |
| } |
| return text; |
| } |
| |
| private boolean isMultiline(String text) { |
| return isMultilineText(text) && getScoutTable().isMultilineText(); |
| } |
| |
| private boolean isMultilineText(String text) { |
| return text.indexOf("\n") >= 0; |
| } |
| |
| private String replaceLineBreaksInMultilineText(String text) { |
| if (isMultilineText(text)) { |
| text = StringUtility.replaceNewLines(text, " "); |
| } |
| return text; |
| } |
| |
| public Image getColumnImage(ITableRow element, int columnIndex) { |
| int[] columnOrder = getUiTable().getUiField().getColumnOrder(); |
| if (columnOrder.length <= 1) { |
| return null; |
| } |
| IColumn col = m_columnManager.getColumnByModelIndex(columnIndex - 1); |
| ICell cell = getCell(element, columnIndex); |
| //checkbox |
| Image checkBoxImage = null; |
| if (columnOrder[1] == columnIndex && getUiTable().getScoutObject() != null && getUiTable().getScoutObject().isCheckable()) { |
| if (element.isChecked()) { |
| checkBoxImage = m_imgCheckboxTrue; |
| } |
| else { |
| checkBoxImage = m_imgCheckboxFalse; |
| } |
| } |
| else if (col != null && cell != null && col.getDataType() == Boolean.class && (!(col instanceof IProposalColumn) || ((IProposalColumn) col).getLookupCall() == null)) { |
| Boolean b = (Boolean) cell.getValue(); |
| if (b != null && b.booleanValue()) { |
| checkBoxImage = m_imgCheckboxTrue; |
| } |
| else { |
| checkBoxImage = m_imgCheckboxFalse; |
| } |
| } |
| |
| //deco |
| String iconId = null; |
| if (cell != null && cell.getErrorStatus() != null && cell.getErrorStatus().getSeverity() == IStatus.ERROR) { |
| iconId = AbstractIcons.StatusError; |
| } |
| else if (cell != null && cell.getIconId() != null) { |
| iconId = cell.getIconId(); |
| } |
| else if (columnOrder[1] == columnIndex) { |
| iconId = element.getIconId(); |
| } |
| Image decoImage = getUiTable().getUiEnvironment().getIcon(iconId); |
| //merge |
| if (checkBoxImage != null && decoImage != null) { |
| //TODO rap/rwt: new GC(Image) is not possible since in rwt an image does not implement Drawable. |
| return checkBoxImage; |
| } |
| if (checkBoxImage != null) { |
| return checkBoxImage; |
| } |
| if (decoImage != null) { |
| return decoImage; |
| } |
| return null; |
| } |
| |
| public Color getBackground(ITableRow element, int columnIndex) { |
| if (columnIndex > 0) { |
| ICell cell = getCell(element, columnIndex); |
| if (cell != null) { |
| return getUiTable().getUiEnvironment().getColor(cell.getBackgroundColor()); |
| } |
| } |
| return null; |
| } |
| |
| public Color getForeground(ITableRow element, int columnIndex) { |
| if (columnIndex > 0) { |
| ICell cell = getCell(element, columnIndex); |
| if (cell != null) { |
| Color col = getUiTable().getUiEnvironment().getColor(cell.getForegroundColor()); |
| if (col == null) { |
| if (!element.isEnabled() || !cell.isEnabled()) { |
| col = m_disabledForegroundColor; |
| } |
| } |
| return col; |
| } |
| } |
| return null; |
| } |
| |
| public Font getFont(ITableRow element, int columnIndex) { |
| if (columnIndex > 0) { |
| ICell cell = getCell(element, columnIndex); |
| if (cell != null) { |
| return getUiTable().getUiEnvironment().getFont(cell.getFont(), getUiTable().getUiField().getFont()); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public String getToolTipText(Object element) { |
| Display display = getUiTable().getUiEnvironment().getDisplay(); |
| Point cursorOnTable = display.map(null, getUiTable().getUiField(), display.getCursorLocation()); |
| ViewerCell uiCell = getUiTable().getUiTableViewer().getCell(cursorOnTable); |
| String text = ""; |
| if (uiCell != null) { |
| int columnIndex = uiCell.getColumnIndex(); |
| |
| ICell cell = getCell(element, columnIndex); |
| if (cell != null) { |
| text = cell.getTooltipText(); |
| if (text == null) { |
| text = cell.getText(); |
| if (HtmlTextUtility.isTextWithHtmlMarkup(text)) { |
| //Tooltips don't support html -> convert to plain text |
| text = HTMLUtility.getPlainText(text); |
| } |
| if (text == null || text.indexOf("\n") <= 0) { |
| text = ""; |
| } |
| } |
| } |
| text = StringUtility.wrapWord(text, 80); |
| } |
| return text; |
| } |
| |
| public void consumeColumnModelEvent(RwtScoutTableEvent uiTableEvent) { |
| rebuildCache(); |
| } |
| |
| protected ICell getCell(Object row, int colIndex) { |
| IColumn<?> column = m_columnManager.getColumnByModelIndex(colIndex - 1); |
| if (column != null) { |
| Map<ITableRow, Map<IColumn<?>, P_CachedCell>> currentCache = m_cachedCells; |
| if (currentCache == null || currentCache.get(row) == null) { |
| rebuildCache(); |
| } |
| |
| P_CachedCell cachedCell = m_cachedCells.get(row).get(column); |
| if (cachedCell != null) { |
| return cachedCell.m_cell; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Creates a custom variant to be set as {@link TableItem}'s data to identify editable cells of that row. This |
| * identifier is used in <code>GridRow.js</code> to install a visual marker for editable cells.<br/> |
| * Format: the cells are delimited with a '_' and editable cells are marked with the literal 'EDITABLE'. |
| * |
| * @param row |
| * the current row. |
| * @return the variant for that row. |
| */ |
| protected String createTableRowVariant(ITableRow row) { |
| StringBuilder builder = new StringBuilder(); |
| |
| Map<IColumn<?>, P_CachedCell> cachedRow = m_cachedCells.get(row); |
| int[] visibleColumnIndexes = getScoutTable().getColumnSet().getVisibleColumnIndexes(); |
| boolean isEditable = false; |
| for (int i = 0; i < visibleColumnIndexes.length; i++) { |
| if (i > 0) { |
| builder.append('_'); |
| } |
| |
| IColumn<?> col = getScoutTable().getColumnSet().getColumn(visibleColumnIndexes[i]); |
| if (col != null && cachedRow != null) { |
| P_CachedCell cachedCell = cachedRow.get(col); |
| if (cachedCell != null && cachedCell.m_isEditable) { |
| builder.append(EDITABLE_VARIANT_CELL_MARKER); |
| isEditable = true; |
| } |
| } |
| } |
| |
| if (isEditable) { |
| builder.insert(0, EDITABLE_VARIANT_PREFIX); |
| return builder.toString(); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Determines whether the visual marker for a cell is to be displayed. |
| * |
| * @return <code>true</code> if the cell is editable and not of the type {@link Boolean}. |
| */ |
| protected boolean isEditableIconNeededInScoutThread(final ITableRow row, final IColumn<?> column) { |
| if (column == null || row == null) { |
| return false; |
| } |
| |
| if (column.getDataType().isAssignableFrom(Boolean.class)) { |
| return false; |
| } |
| |
| ITable scoutTable = getScoutTable(); |
| if (scoutTable == null) { |
| return false; |
| } |
| return scoutTable.isCellEditable(row, column); |
| } |
| |
| private void rebuildCache() { |
| if (getScoutTable() == null) { |
| m_cachedCells = CollectionUtility.emptyHashMap(); |
| return; |
| } |
| |
| final Holder<Map<ITableRow, Map<IColumn<?>, P_CachedCell>>> cacheResultHolder = new Holder<Map<ITableRow, Map<IColumn<?>, P_CachedCell>>>(); |
| Runnable r = new Runnable() { |
| @Override |
| public void run() { |
| List<ITableRow> rows = getScoutTable().getRows(); |
| Map<ITableRow, Map<IColumn<?>, P_CachedCell>> tmp = new HashMap<ITableRow, Map<IColumn<?>, P_CachedCell>>(rows.size()); |
| for (ITableRow scoutRow : rows) { |
| List<IColumn<?>> visibleColumns = getScoutTable().getColumnSet().getVisibleColumns(); |
| Map<IColumn<?>, P_CachedCell> cells = new HashMap<IColumn<?>, P_CachedCell>(visibleColumns.size()); |
| for (IColumn<?> col : visibleColumns) { |
| ICell cell = getScoutTable().getCell(scoutRow, col); |
| boolean isCellEditable = isEditableIconNeededInScoutThread(scoutRow, col); |
| cells.put(col, new P_CachedCell(cell, isCellEditable)); |
| } |
| tmp.put(scoutRow, cells); |
| } |
| cacheResultHolder.setValue(tmp); |
| } |
| }; |
| |
| try { |
| getUiTable().getUiEnvironment().invokeScoutLater(r, -1).join(); |
| m_cachedCells = cacheResultHolder.getValue(); |
| } |
| catch (InterruptedException e) { |
| LOG.warn("Interrupted while waiting for the model.", e); |
| } |
| } |
| |
| private static final class P_CachedCell { |
| private final ICell m_cell; |
| private final boolean m_isEditable; |
| |
| private P_CachedCell(ICell cell, boolean isEditable) { |
| m_cell = cell; |
| m_isEditable = isEditable; |
| } |
| } |
| } |