org.eclipse.scout.rt.ui.rap.basic.table.RwtScoutTable.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.scout.rt.ui.rap.basic.table.RwtScoutTable.java

Source

/*******************************************************************************
 * Copyright (c) 2011 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.ui.rap.basic.table;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicReference;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.scout.commons.CompareUtility;
import org.eclipse.scout.commons.StringUtility;
import org.eclipse.scout.commons.beans.IPropertyObserver;
import org.eclipse.scout.commons.dnd.TransferObject;
import org.eclipse.scout.commons.holders.Holder;
import org.eclipse.scout.commons.job.JobEx;
import org.eclipse.scout.rt.client.ClientSyncJob;
import org.eclipse.scout.rt.client.ui.IDNDSupport;
import org.eclipse.scout.rt.client.ui.IEventHistory;
import org.eclipse.scout.rt.client.ui.action.keystroke.IKeyStroke;
import org.eclipse.scout.rt.client.ui.action.menu.IMenu;
import org.eclipse.scout.rt.client.ui.basic.table.IHeaderCell;
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.RowIndexComparator;
import org.eclipse.scout.rt.client.ui.basic.table.TableEvent;
import org.eclipse.scout.rt.client.ui.basic.table.TableListener;
import org.eclipse.scout.rt.client.ui.basic.table.columns.IColumn;
import org.eclipse.scout.rt.client.ui.basic.table.customizer.ICustomColumn;
import org.eclipse.scout.rt.ui.rap.RwtMenuUtility;
import org.eclipse.scout.rt.ui.rap.basic.RwtScoutComposite;
import org.eclipse.scout.rt.ui.rap.basic.table.celleditor.RwtScoutTableCellEditor;
import org.eclipse.scout.rt.ui.rap.ext.MenuAdapterEx;
import org.eclipse.scout.rt.ui.rap.ext.table.TableEx;
import org.eclipse.scout.rt.ui.rap.ext.table.TableViewerEx;
import org.eclipse.scout.rt.ui.rap.ext.table.util.TableRolloverSupport;
import org.eclipse.scout.rt.ui.rap.extension.UiDecorationExtensionPoint;
import org.eclipse.scout.rt.ui.rap.form.fields.AbstractRwtScoutDndSupport;
import org.eclipse.scout.rt.ui.rap.keystroke.IRwtKeyStroke;
import org.eclipse.scout.rt.ui.rap.keystroke.RwtKeyStroke;
import org.eclipse.scout.rt.ui.rap.util.HtmlTextUtility;
import org.eclipse.scout.rt.ui.rap.util.RwtUtility;
import org.eclipse.scout.rt.ui.rap.util.UiRedrawHandler;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.internal.widgets.MarkupValidator;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;

/**
 * knownIssues - multi column sorting is not supported, unable to get any key
 * mask in the selection event.
 * <p>
 * - multi line support in headers is not supported by rwt.
 * <p>
 * - multi line support in row texts is not supported so far. Might probably be done by customized table rows.
 * 
 * @since 3.8.0
 */
@SuppressWarnings("restriction")
public class RwtScoutTable extends RwtScoutComposite<ITable> implements IRwtScoutTableForPatch {
    private P_ScoutTableListener m_scoutTableListener;
    private UiRedrawHandler m_redrawHandler;

    private Listener m_autoResizeColumnListener;
    private Listener m_columnListener = new P_TableColumnListener();
    private SelectionListener m_columnSortListener = new P_ColumnSortListener();
    private TableColumnManager m_uiColumnManager = new TableColumnManager();
    private RwtScoutTableCellEditor m_uiCellEditorComposite;
    private int[] m_uiColumnOrder;
    private TableViewer m_uiViewer;
    private IRwtKeyStroke[] m_uiKeyStrokes;
    private ClientSyncJob m_storeColumnWidthsJob;

    private RwtScoutColumnModel m_columnModel = null;

    private String m_variant = "";

    private AbstractTableKeyboardNavigationSupport m_keyboardNavigationSupport;

    public RwtScoutTable() {
    }

    public RwtScoutTable(String variant) {
        m_variant = variant;
    }

    @Override
    protected void initializeUi(Composite parent) {
        m_redrawHandler = new UiRedrawHandler(parent);
        int style;
        if (getScoutObject() != null && getScoutObject().isMultiSelect()) {
            style = SWT.MULTI;
        } else {
            style = SWT.SINGLE;
        }
        style |= SWT.V_SCROLL | SWT.H_SCROLL | SWT.FULL_SELECTION;
        TableEx table = getUiEnvironment().getFormToolkit().createTable(parent, style);
        if (StringUtility.hasText(m_variant)) {
            table.setData(RWT.CUSTOM_VARIANT, m_variant);
        }
        table.setData(RWT.MARKUP_ENABLED, Boolean.TRUE);
        table.setData(MarkupValidator.MARKUP_VALIDATION_DISABLED, Boolean.TRUE);
        table.setLinesVisible(true);
        table.setHeaderVisible(true);
        table.setTouchEnabled(RwtUtility.getBrowserInfo().isTablet() || RwtUtility.getBrowserInfo().isMobile());
        new TableRolloverSupport(table);
        TableViewer viewer = new TableViewerEx(table);
        ColumnViewerToolTipSupport.enableFor(viewer);
        viewer.setUseHashlookup(true);
        setUiTableViewer(viewer);
        setUiField(table);
        //cell editing support
        m_uiCellEditorComposite = new RwtScoutTableCellEditor(this);

        //columns
        initializeUiColumns();

        RwtScoutTableModel tableModel = createUiTableModel();
        viewer.setContentProvider(tableModel);
        viewer.setInput(tableModel);

        // ui listeners
        viewer.addSelectionChangedListener(new P_RwtSelectionListener());
        P_RwtTableListener rwtTableListener = new P_RwtTableListener();
        table.addListener(SWT.MouseDown, rwtTableListener);
        table.addListener(SWT.MouseUp, rwtTableListener);
        table.addListener(SWT.MouseDoubleClick, rwtTableListener);
        table.addListener(SWT.MenuDetect, rwtTableListener);
        table.addListener(SWT.Resize, rwtTableListener);
        getUiEnvironment().addKeyStroke(table, new RwtKeyStroke((int) ' ') {

            @Override
            public void handleUiAction(Event e) {
                handleUiToggleAcction(e);
            }
        }, false);
    }

    @Override
    public boolean isUiDisposed() {
        return getUiField() == null || getUiField().isDisposed();
    }

    protected RwtScoutTableModel createUiTableModel() {
        return new RwtScoutTableModel(getScoutObject(), this, m_uiColumnManager);
    }

    protected RwtScoutColumnModel getUiColumnModel() {
        if (m_columnModel == null) {
            m_columnModel = new RwtScoutColumnModel(getScoutObject(), this, m_uiColumnManager);
        }
        return m_columnModel;
    }

    @Override
    public TableColumnManager getUiColumnManager() {
        return m_uiColumnManager;
    }

    @Override
    public void initializeUiColumns() {
        m_redrawHandler.pushControlChanging();
        try {
            for (TableColumn col : getUiField().getColumns()) {
                col.dispose();
            }
            /*
             * bug: rwt table first column can not be aligned nor an image can be set.
             * see also RwtScoutTableCellEditor
             */
            TableColumn dummyCol = new TableColumn(getUiField(), SWT.LEFT);
            dummyCol.setWidth(0);
            dummyCol.setResizable(false);
            dummyCol.setMoveable(false);
            boolean sortEnabled = false;
            IColumn<?>[] scoutColumnsOrdered;
            if (getScoutObject() != null) {
                scoutColumnsOrdered = getScoutObject().getColumnSet().getVisibleColumns();
                sortEnabled = getScoutObject().isSortEnabled();
            } else {
                scoutColumnsOrdered = new IColumn[0];
            }
            if (m_uiColumnManager == null) {
                m_uiColumnManager = new TableColumnManager();
            }
            m_uiColumnManager.initialize(scoutColumnsOrdered);
            boolean multilineHeaders = false;
            for (IColumn<?> scoutColumn : scoutColumnsOrdered) {
                IHeaderCell cell = scoutColumn.getHeaderCell();
                String cellText = cell.getText();
                if (cellText == null) {
                    cellText = "";
                }
                boolean isHtml = HtmlTextUtility.isTextWithHtmlMarkup(cellText);
                if (!isHtml && cellText.indexOf("\n") >= 0) {
                    multilineHeaders = true;
                }
                if (isHtml) {
                    multilineHeaders = true;
                    cellText = getUiEnvironment().adaptHtmlCell(RwtScoutTable.this, cellText);
                }
                int style = RwtUtility.getHorizontalAlignment(cell.getHorizontalAlignment());
                TableColumn rwtCol = new TableColumn(getUiField(), style);
                TableViewerColumn rwtViewerCol = new TableViewerColumn(getUiTableViewer(), rwtCol);
                rwtViewerCol.setLabelProvider(getUiColumnModel());
                rwtCol.setData(KEY_SCOUT_COLUMN, scoutColumn);
                rwtCol.setMoveable(true);
                rwtCol.setToolTipText(cell.getTooltipText());
                updateHeaderText(rwtCol, scoutColumn);
                rwtCol.setWidth(scoutColumn.getWidth());
                if (scoutColumn.isFixedWidth()) {
                    rwtCol.setResizable(false);
                }
                if (cell.isSortActive()) {
                    getUiField().setSortColumn(rwtCol);
                    getUiField().setSortDirection(cell.isSortAscending() ? SWT.UP : SWT.DOWN);
                }
                if (sortEnabled) {
                    rwtCol.addSelectionListener(m_columnSortListener);
                }
                rwtCol.addListener(SWT.Move, m_columnListener);
                rwtCol.addListener(SWT.Resize, m_columnListener);
            }
            //multiline header settings
            if (multilineHeaders) {
                getUiField().setData("multiLineHeader", Boolean.TRUE);
            }
            m_uiColumnOrder = getUiField().getColumnOrder();
            //update cell editors
            m_uiCellEditorComposite.initializeUi();
        } finally {
            m_redrawHandler.popControlChanging();
        }
    }

    @Override
    protected void attachScout() {
        super.attachScout();
        if (getScoutObject() == null) {
            return;
        }
        if (m_scoutTableListener == null) {
            m_scoutTableListener = new P_ScoutTableListener();
            getScoutObject().addUITableListener(m_scoutTableListener);
        }
        setHeaderVisibleFromScout(getScoutObject().isHeaderVisible());
        setSelectionFromScout(getScoutObject().getSelectedRows());
        setKeyStrokeFormScout();
        setRowHeightFromScout();
        setKeyboardNavigationFromScout();
        updateAutoResizeColumnsFromScout();
        attachDndSupport();
        handleEventsFromRecentHistory();
    }

    private void handleEventsFromRecentHistory() {
        final IEventHistory<TableEvent> h = getScoutObject().getEventHistory();
        if (h == null) {
            return;
        }

        getUiEnvironment().getDisplay().asyncExec(new Runnable() {
            @Override
            public void run() {
                for (TableEvent e : h.getRecentEvents()) {
                    handleScoutTableEventInUi(e);
                }
            }
        });
    }

    protected void attachDndSupport() {
        if (UiDecorationExtensionPoint.getLookAndFeel().isDndSupportEnabled()) {
            new P_DndSupport(getScoutObject(), getScoutObject(), getUiField());
        }
    }

    @Override
    protected void detachScout() {
        super.detachScout();
        removeAutoResizeColumnListener();
        if (getScoutObject() == null) {
            return;
        }
        if (m_scoutTableListener != null) {
            getScoutObject().removeTableListener(m_scoutTableListener);
            m_scoutTableListener = null;
        }
    }

    @Override
    public TableEx getUiField() {
        return (TableEx) super.getUiField();
    }

    @Override
    public TableViewer getUiTableViewer() {
        return m_uiViewer;
    }

    @Override
    public void setUiTableViewer(TableViewer uiViewer) {
        m_uiViewer = uiViewer;
    }

    @Override
    public ITableRow getUiSelectedRow() {
        ITableRow[] rows = getUiSelectedRows();
        if (rows.length > 0) {
            return rows[0];
        }
        return null;
    }

    @Override
    public ITableRow[] getUiSelectedRows() {
        StructuredSelection uiSelection = (StructuredSelection) getUiTableViewer().getSelection();
        TreeSet<ITableRow> sortedRows = new TreeSet<ITableRow>(new RowIndexComparator());
        if (uiSelection != null && !uiSelection.isEmpty()) {
            for (Object o : uiSelection.toArray()) {
                ITableRow row = (ITableRow) o;
                sortedRows.add(row);
            }
        }
        return sortedRows.toArray(new ITableRow[sortedRows.size()]);
    }

    protected void setKeyStrokeFormScout() {
        // remove old
        if (m_uiKeyStrokes != null) {
            for (IRwtKeyStroke rwtKeyStroke : m_uiKeyStrokes) {
                getUiEnvironment().removeKeyStroke(getUiField(), rwtKeyStroke);
            }
        }
        // add new
        ArrayList<IRwtKeyStroke> newRwtKeyStrokes = new ArrayList<IRwtKeyStroke>();
        IKeyStroke[] scoutKeyStrokes = getScoutObject().getKeyStrokes();
        for (IKeyStroke scoutKeyStroke : scoutKeyStrokes) {
            if (scoutKeyStroke.isEnabled()) {
                IRwtKeyStroke[] rwtStrokes = RwtUtility.getKeyStrokes(scoutKeyStroke, getUiEnvironment());
                for (IRwtKeyStroke rwtStroke : rwtStrokes) {
                    getUiEnvironment().addKeyStroke(getUiField(), rwtStroke, false);
                    newRwtKeyStrokes.add(rwtStroke);
                }
            }
        }
        m_uiKeyStrokes = newRwtKeyStrokes.toArray(new IRwtKeyStroke[newRwtKeyStrokes.size()]);
    }

    protected void setRowHeightFromScout() {
        int h = getScoutObject().getRowHeightHint();
        if (h >= 0) {
            getUiField().setData(RWT.CUSTOM_ITEM_HEIGHT, h);
        } else {
            int defaultTableRowHeight = UiDecorationExtensionPoint.getLookAndFeel().getTableRowHeight();
            if (defaultTableRowHeight >= 0) {
                getUiField().setData(RWT.CUSTOM_ITEM_HEIGHT, defaultTableRowHeight);
            }
        }
        if (isCreated()) {
            getUiTableViewer().refresh();
        }
    }

    protected void setKeyboardNavigationFromScout() {
        if (getScoutObject().hasKeyboardNavigation()) {
            if (m_keyboardNavigationSupport == null) {
                m_keyboardNavigationSupport = new P_KeyBoardNavigationSupport(getUiField());
            }
        } else {
            if (m_keyboardNavigationSupport != null) {
                m_keyboardNavigationSupport.dispose();
                m_keyboardNavigationSupport = null;
            }
        }
    }

    private void updateAutoResizeColumnsFromScout() {
        if (getUiField() != null && !getUiField().getParent().isDisposed()) {
            Composite parent = getUiField().getParent();
            if (getScoutObject().isAutoResizeColumns()) {
                if (m_autoResizeColumnListener == null) {
                    m_autoResizeColumnListener = new P_RwtResizeListener();
                    parent.addListener(SWT.Resize, m_autoResizeColumnListener);
                }
                //If the parent has already been resized no event will be fired anymore.
                //So it is necessary to request an auto resizing of the columns manually. (Bugzilla 355855)
                scheduleHandleAutoResizeColumn();
            }
        } else {
            removeAutoResizeColumnListener();
        }
    }

    private void scheduleHandleAutoResizeColumn() {
        getUiField().getDisplay().asyncExec(new Runnable() {
            @Override
            public void run() {
                handleAutoSizeColumns();
            }
        });
    }

    private void removeAutoResizeColumnListener() {
        if (m_autoResizeColumnListener == null) {
            return;
        }

        Composite parent = getUiField().getParent();
        parent.removeListener(SWT.Resize, m_autoResizeColumnListener);
        m_autoResizeColumnListener = null;
    }

    /**
     * scout property observer
     */
    @Override
    protected void handleScoutPropertyChange(String propName, Object newValue) {
        if (propName.equals(ITable.PROP_HEADER_VISIBLE)) {
            setHeaderVisibleFromScout(((Boolean) newValue).booleanValue());
        } else if (propName.equals(ITable.PROP_KEY_STROKES)) {
            setKeyStrokeFormScout();
        } else if (propName.equals(ITable.PROP_ROW_HEIGHT_HINT)) {
            setRowHeightFromScout();
        } else if (propName.equals(ITable.PROP_KEYBOARD_NAVIGATION)) {
            setKeyboardNavigationFromScout();
        } else if (propName.equals(ITable.PROP_AUTO_RESIZE_COLUMNS)) {
            updateAutoResizeColumnsFromScout();
        } else if (propName.equals(ITable.PROP_SCROLL_TO_SELECTION)) {
            updateScrollToSelectionFromScout();
        }
    }

    /**
     * scout table observer
     */
    protected boolean isHandleScoutTableEvent(TableEvent[] a) {
        for (TableEvent element : a) {
            switch (element.getType()) {
            case TableEvent.TYPE_REQUEST_FOCUS:
            case TableEvent.TYPE_REQUEST_FOCUS_IN_CELL:
            case TableEvent.TYPE_ROWS_INSERTED:
            case TableEvent.TYPE_ROWS_UPDATED:
            case TableEvent.TYPE_ROWS_DELETED:
            case TableEvent.TYPE_ALL_ROWS_DELETED:
            case TableEvent.TYPE_ROW_ORDER_CHANGED:
            case TableEvent.TYPE_ROW_FILTER_CHANGED:
            case TableEvent.TYPE_COLUMN_ORDER_CHANGED:
            case TableEvent.TYPE_COLUMN_HEADERS_UPDATED:
            case TableEvent.TYPE_COLUMN_STRUCTURE_CHANGED:
            case TableEvent.TYPE_ROWS_SELECTED:
            case TableEvent.TYPE_SCROLL_TO_SELECTION: {
                return true;
            }
            }
        }
        return false;
    }

    protected void handleScoutTableEventInUi(TableEvent e) {
        if (isUiDisposed()) {
            return;
        }
        RwtScoutTableEvent uiTableEvent = null;
        /*
         * check the scout observer to filter all events that are used here
         * @see isHandleScoutTableEvent()
         */
        switch (e.getType()) {
        case TableEvent.TYPE_REQUEST_FOCUS: {
            getUiField().setFocus();
            break;
        }
        case TableEvent.TYPE_REQUEST_FOCUS_IN_CELL: {
            //start editing
            int swtCol = -1;
            TableColumn[] swtColumns = getUiField().getColumns();
            for (int c = 0; c < swtColumns.length; c++) {
                if (swtColumns[c].getData(KEY_SCOUT_COLUMN) == e.getFirstColumn()) {
                    swtCol = c;
                    break;
                }
            }
            ITableRow scoutRow = e.getFirstRow();
            if (scoutRow != null && swtCol >= 0) {
                getUiTableViewer().editElement(scoutRow, swtCol);
            }
            break;
        }
        case TableEvent.TYPE_SCROLL_TO_SELECTION: {
            scrollToSelection();
            break;
        }
        case TableEvent.TYPE_ROWS_INSERTED:
        case TableEvent.TYPE_ROWS_UPDATED:
        case TableEvent.TYPE_ROWS_DELETED:
        case TableEvent.TYPE_ALL_ROWS_DELETED:
        case TableEvent.TYPE_ROW_FILTER_CHANGED:
        case TableEvent.TYPE_ROW_ORDER_CHANGED: {
            uiTableEvent = new RwtScoutTableEvent();
            break;
        }
        case TableEvent.TYPE_COLUMN_ORDER_CHANGED:
            break;
        case TableEvent.TYPE_COLUMN_HEADERS_UPDATED:
            headerUpdateFromScout();
            break;
        case TableEvent.TYPE_COLUMN_STRUCTURE_CHANGED: {
            // re-install columns
            initializeUiColumns();
            if (getScoutObject().isAutoResizeColumns()) {
                handleAutoSizeColumns();
            }

            uiTableEvent = new RwtScoutTableEvent();
            break;
        }
        case TableEvent.TYPE_ROWS_SELECTED: {
            setSelectionFromScout(e.getRows());
            break;
        }
        }
        //
        if (uiTableEvent != null) {
            getUiColumnModel().consumeColumnModelEvent(uiTableEvent);
            ((RwtScoutTableModel) getUiTableViewer().getContentProvider()).consumeTableModelEvent(uiTableEvent);
            getUiTableViewer().refresh();
        }
        // refresh selection, indexes might have changed
        switch (e.getType()) {
        case TableEvent.TYPE_ROW_FILTER_CHANGED:
            // Update column title if filter changed (mark column as filtered)
            for (TableColumn swtCol : getUiField().getColumns()) {
                updateHeaderText(swtCol);
            }
            setSelectionFromScout(e.getTable().getSelectedRows());
            break;
        case TableEvent.TYPE_ROWS_INSERTED:
        case TableEvent.TYPE_ROWS_UPDATED:
        case TableEvent.TYPE_ROWS_DELETED:
        case TableEvent.TYPE_ALL_ROWS_DELETED:
        case TableEvent.TYPE_ROW_ORDER_CHANGED: {
            setSelectionFromScout(e.getTable().getSelectedRows());
            break;
        }
        }
    }

    private void updateHeaderText(TableColumn swtCol) {
        if (swtCol == null) {
            return;
        }
        Object data = swtCol.getData(KEY_SCOUT_COLUMN);
        if (data instanceof IColumn<?>) {
            updateHeaderText(swtCol, (IColumn<?>) data);
        }
    }

    private void updateHeaderText(TableColumn swtCol, IColumn<?> scoutCol) {
        updateHeaderText(swtCol, scoutCol, false);
    }

    private void updateHeaderText(TableColumn swtCol, IColumn<?> scoutCol, boolean indicateSortOrder) {
        IHeaderCell cell = scoutCol.getHeaderCell();
        String text = cell.getText();
        if (text == null) {
            text = "";
        }
        if (scoutCol instanceof ICustomColumn) {
            text = "[+] " + text;
        }
        if (scoutCol.isColumnFilterActive()) {
            text = "(*) " + text;
        }
        if (indicateSortOrder) {
            if (scoutCol.isSortAscending()) {
                text = "[a-z] " + text;
            } else {
                text = "[z-a] " + text;
            }
        }
        swtCol.setText(text);
    }

    protected void setHeaderVisibleFromScout(boolean headerVisible) {
        getUiField().setHeaderVisible(headerVisible);
    }

    @Override
    public void setEnabledFromScout(boolean enabledFromScout) {
        getUiField().setEnabled(!enabledFromScout);
        // <Workaround>
        // Because RAP seems to ignore the default ":disabled" state,
        // we apply a custom variant to all header cells. Otherwise
        // the "normal" style would be used for disabled cells.
        for (TableColumn column : getUiField().getColumns()) {
            column.setData(RWT.CUSTOM_VARIANT, (enabledFromScout ? null : VARIANT_TABLE_COLUMN_DISABLED));
        }
        // </Workaround>
    }

    protected void setSelectionFromScout(ITableRow[] selectedRows) {
        if (getUiField().isDisposed()) {
            return;
        }
        ITableRow[] uiSelection = getUiSelectedRows();
        if (CompareUtility.equals(uiSelection, selectedRows)) {
            // no change
            return;
        } else {
            if (selectedRows == null) {
                selectedRows = new ITableRow[0];
            }
            getUiTableViewer().setSelection(new StructuredSelection(selectedRows), true);
            updateScrollToSelectionFromScout();
        }
    }

    private void updateScrollToSelectionFromScout() {
        if (getScoutObject().isScrollToSelection()) {
            scrollToSelection();
        }
    }

    protected void scrollToSelection() {
        if (getUiField() != null && !getUiField().isDisposed()) {
            getUiField().showSelection();
        }
    }

    protected void setContextColumnFromUi(TableColumn uiColumn) {
        if (getScoutObject() != null) {
            //try to find correct location, since TableColumn has NO x,y and is not a Control!
            Point pDisp = getUiField().toDisplay(-getUiField().getHorizontalBar().getSelection(), 0);
            for (TableColumn c : getUiField().getColumns()) {
                if (c == uiColumn) {
                    break;
                }
                pDisp.x += c.getWidth();
            }
            getUiEnvironment().setPopupOwner(getUiField(),
                    new Rectangle(pDisp.x - 2, pDisp.y, 1, getUiField().getHeaderHeight()));
            // notify Scout
            IColumn scoutColumn = null;
            if (uiColumn != null) {
                scoutColumn = (IColumn<?>) uiColumn.getData(KEY_SCOUT_COLUMN);
            }
            final IColumn finalCol = scoutColumn;
            Runnable t = new Runnable() {
                @Override
                public void run() {
                    getScoutObject().getUIFacade().setContextColumnFromUI(finalCol);
                }
            };
            getUiEnvironment().invokeScoutLater(t, 0);
            // end notify
        }
    }

    private int getVisualCellIndex(TableItem tableItem, int columnIndex) {
        int visualCellIndex = columnIndex;
        final int[] columnOrder = tableItem.getParent().getColumnOrder();
        for (int element : columnOrder) {
            if (element == columnIndex) {
                visualCellIndex = columnIndex;
            }
        }
        return visualCellIndex;
    }

    protected void setSelectionFromUi(final StructuredSelection selection) {
        if (getUpdateUiFromScoutLock().isAcquired()) {
            return;
        }
        //
        if (getScoutObject() != null) {
            // notify Scout
            Runnable t = new Runnable() {
                @Override
                public void run() {
                    try {
                        addIgnoredScoutEvent(TableEvent.class, "" + TableEvent.TYPE_ROWS_SELECTED);
                        //
                        getScoutObject().getUIFacade()
                                .setSelectedRowsFromUI(RwtUtility.getItemsOfSelection(ITableRow.class, selection));
                    } finally {
                        removeIgnoredScoutEvent(TableEvent.class, "" + TableEvent.TYPE_ROWS_SELECTED);
                    }
                }
            };
            getUiEnvironment().invokeScoutLater(t, 0);
            // end notify
        }
    }

    protected void headerUpdateFromScout() {
        // Because SWT can only indicate one sort column, we will use the first (i.e. the column
        // with the lowest sort index) user sort column that is visible for that purpose. Further
        // sort columns will be indicated by a special header text (see updateHeaderText() method).
        int minSortIndex = -1;
        TableColumn minUiSortColumn = null;
        IColumn<?> minScoutSortColumn = null;
        for (TableColumn col : getUiField().getColumns()) {
            Object data = col.getData(KEY_SCOUT_COLUMN);
            if (data instanceof IColumn<?>) {
                IColumn<?> cell = (IColumn<?>) data;
                if (cell.isSortExplicit() && (minSortIndex == -1 || cell.getSortIndex() < minSortIndex)) {
                    minSortIndex = cell.getSortIndex();
                    minUiSortColumn = col;
                    minScoutSortColumn = cell;
                }
            }
        }

        if (minUiSortColumn != null && minScoutSortColumn != null) {
            getUiField().setSortColumn(minUiSortColumn);
            getUiField().setSortDirection(minScoutSortColumn.isSortAscending() ? SWT.UP : SWT.DOWN);
        } else {
            getUiField().setSortColumn(null);
        }
        for (TableColumn col : getUiField().getColumns()) {
            Object data = col.getData(KEY_SCOUT_COLUMN);
            if (data instanceof IColumn<?>) {
                IColumn<?> cell = (IColumn<?>) data;
                boolean indicateSortOrder = (cell.isSortExplicit() && cell != minScoutSortColumn);
                updateHeaderText(col, cell, indicateSortOrder);
            }
        }
    }

    protected void handleUiRowClick(final ITableRow row) {
        if (getScoutObject() != null) {
            if (row != null) {
                // notify Scout
                Runnable t = new Runnable() {
                    @Override
                    public void run() {
                        getScoutObject().getUIFacade().fireRowClickFromUI(row);
                    }
                };
                getUiEnvironment().invokeScoutLater(t, 0);
                // end notify
            }
        }
    }

    protected void handleUiRowAction(final ITableRow row) {
        if (getScoutObject() != null) {
            if (!getScoutObject().isCheckable() && row != null) {
                // notify Scout
                Runnable t = new Runnable() {
                    @Override
                    public void run() {
                        getScoutObject().getUIFacade().fireRowActionFromUI(row);
                    }
                };
                getUiEnvironment().invokeScoutLater(t, 0);
                // end notify
            }
        }
    }

    protected void handleUiHyperlinkAction(String urlText) {
        if (getScoutObject() != null) {
            final URL url;
            try {
                url = new URL(urlText);
            } catch (MalformedURLException e) {
                //nop
                return;
            }
            // notify Scout
            Runnable t = new Runnable() {
                @Override
                public void run() {
                    ITable table = getScoutObject();
                    table.getUIFacade().fireHyperlinkActionFromUI(table.getSelectedRow(), table.getContextColumn(),
                            url);
                }
            };
            getUiEnvironment().invokeScoutLater(t, 0);
            // end notify
        }
    }

    /**
     * Distributes the table width to the columns considered column weights of
     * model. Empty space will be distributed weighted.
     */
    protected void handleAutoSizeColumns() {
        if (getUiField() == null || getUiField().isDisposed()) {
            return;
        }

        int totalWidth = getUiField().getClientArea().width;
        /* fixed in rwt
        if (getUiField().getVerticalBar() != null && getUiField().getVerticalBar().getVisible()) {
          totalWidth -= getUiField().getVerticalBar().getSize().x;
        }
        */
        if (totalWidth < 32) {
            //either not showing or not yet layouted
            return;
        }
        int totalWeight = 0;
        int actualWidth = 0;
        HashMap<TableColumn, Integer> columnWeights = new HashMap<TableColumn, Integer>();
        for (TableColumn col : getUiField().getColumns()) {
            if (col == null || col.isDisposed()) {
                continue;
            }
            actualWidth += col.getWidth();
            Object data = col.getData(RwtScoutTable.KEY_SCOUT_COLUMN);
            if (data instanceof IColumn<?> && !((IColumn<?>) data).isFixedWidth()) {
                int width = ((IColumn<?>) data).getInitialWidth();
                columnWeights.put(col, width);
                totalWeight += width;
            } else {
                totalWidth -= col.getWidth();
            }
        }

        //If the columns already have the correct size there is no need to recalculate it
        if (actualWidth == totalWidth) {
            return;
        }

        double factor = (double) totalWidth / (double) totalWeight;
        int i = 0;
        for (Entry<TableColumn, Integer> entry : columnWeights.entrySet()) {
            if (i < columnWeights.size() - 1) {
                int width = (int) (factor * entry.getValue().intValue());
                entry.getKey().setWidth(width);
                totalWidth -= width;
                i++;
            } else {
                entry.getKey().setWidth(totalWidth);
            }
        }
    }

    protected void handleUiColumnResized(TableColumn column) {
        if (column.isDisposed()) {
            return;
        }
        if (!column.getParent().isVisible()) {
            return;
        }
        if (getUpdateUiFromScoutLock().isAcquired()) {
            return;
        }
        //
        final int width = column.getWidth();
        final IColumn<?> scoutColumn = (IColumn<?>) column.getData(KEY_SCOUT_COLUMN);
        if (scoutColumn != null) {
            if (scoutColumn.getWidth() != width) {

                //Cancel already scheduled resize job to protect the model from too many resize events.
                if (m_storeColumnWidthsJob != null) {
                    m_storeColumnWidthsJob.cancel();
                }

                /*
                 * imo, 04.06.2009: added swt-side optimistic lock check Method
                 * autoSizeColumns indirectly calls this code by setting the width on a
                 * table column if this code calls scout using synchronizer it may
                 * invoke pending swt jobs while waiting, which can execute a dispose
                 * form job. After that job all columns are disposed. This results in
                 * WidgetDisposed exceptions on line with entry.getKey().setWidth(...)
                 */
                m_storeColumnWidthsJob = new ClientSyncJob("Store column widths",
                        getUiEnvironment().getClientSession()) {
                    @Override
                    protected IStatus runStatus(IProgressMonitor monitor) {
                        try {
                            addIgnoredScoutEvent(TableEvent.class, "" + TableEvent.TYPE_COLUMN_STRUCTURE_CHANGED);
                            //
                            getScoutObject().getUIFacade().setColumnWidthFromUI(scoutColumn, width);
                        } finally {
                            removeIgnoredScoutEvent(TableEvent.class,
                                    "" + TableEvent.TYPE_COLUMN_STRUCTURE_CHANGED);
                        }

                        return Status.OK_STATUS;
                    }
                };
                m_storeColumnWidthsJob.schedule(400);
            }
        }
    }

    protected void handleUiColumnMoved() {
        if (getUpdateUiFromScoutLock().isAcquired()) {
            return;
        }
        int[] uiColumnOrder = getUiField().getColumnOrder();
        // do not allow to reorder icon and dummy column
        if (uiColumnOrder[0] != 0) {
            getUiField().setColumnOrder(m_uiColumnOrder);
            return;
        }
        // if column with icon has changed position
        if (uiColumnOrder[1] != m_uiColumnOrder[1] && getScoutObject().getRowCount() > 0
                && StringUtility.hasText(getScoutObject().getRow(0).getIconId())) {
            getUiTableViewer().refresh();
        }
        int[] truncatedColOrder = new int[uiColumnOrder.length - 1];
        for (int i = 0; i < truncatedColOrder.length; i++) {
            truncatedColOrder[i] = uiColumnOrder[i + 1] - 1;
        }
        final IColumn<?>[] newOrder = m_uiColumnManager.getOrderedColumns(truncatedColOrder);
        if (m_uiColumnManager.applyNewOrder(newOrder)) {
            m_uiColumnOrder = uiColumnOrder;
            // notify Scout
            Runnable t = new Runnable() {
                @Override
                public void run() {
                    try {
                        addIgnoredScoutEvent(TableEvent.class, "" + TableEvent.TYPE_COLUMN_ORDER_CHANGED);
                        addIgnoredScoutEvent(TableEvent.class, "" + TableEvent.TYPE_COLUMN_STRUCTURE_CHANGED);
                        //
                        getScoutObject().getUIFacade().fireVisibleColumnsChangedFromUI(newOrder);
                    } finally {
                        removeIgnoredScoutEvent(TableEvent.class, "" + TableEvent.TYPE_COLUMN_ORDER_CHANGED);
                        removeIgnoredScoutEvent(TableEvent.class, "" + TableEvent.TYPE_COLUMN_STRUCTURE_CHANGED);
                    }
                }
            };
            getUiEnvironment().invokeScoutLater(t, 0);
            // end notify
        }
    }

    protected void handleKeyboardNavigationFromUi(TableItem item) {
        getUiField().setSelection(item);
        Event selectionEvent = new Event();
        selectionEvent.type = SWT.DefaultSelection;
        selectionEvent.widget = getUiField();
        for (Listener l : getUiField().getListeners(SWT.DefaultSelection)) {
            l.handleEvent(selectionEvent);
        }
    }

    protected void handleUiToggleAcction(Event e) {
        if (e.doit && getScoutObject().isCheckable()) {
            if (e.stateMask == 0) {
                switch (e.keyCode) {
                case ' ':
                    ITableRow[] selectedRows = RwtUtility.getItemsOfSelection(ITableRow.class,
                            (StructuredSelection) getUiTableViewer().getSelection());
                    if (selectedRows != null && selectedRows.length > 0) {
                        handleUiRowClick(selectedRows[0]);
                    }
                    e.doit = false;
                    break;
                }
            }
        }
    }

    private class P_ScoutTableListener implements TableListener {
        @Override
        public void tableChanged(final TableEvent e) {
            if (isHandleScoutTableEvent(new TableEvent[] { e })) {
                if (isIgnoredScoutEvent(TableEvent.class, "" + e.getType())) {
                    return;
                }
                Runnable t = new Runnable() {
                    @Override
                    public void run() {
                        try {
                            getUpdateUiFromScoutLock().acquire();
                            //
                            handleScoutTableEventInUi(e);
                        } finally {
                            getUpdateUiFromScoutLock().release();
                        }
                    }
                };
                getUiEnvironment().invokeUiLater(t);
            }
        }

        @Override
        public void tableChangedBatch(final TableEvent[] a) {
            if (isHandleScoutTableEvent(a)) {
                final ArrayList<TableEvent> filteredList = new ArrayList<TableEvent>();
                for (int i = 0; i < a.length; i++) {
                    if (!isIgnoredScoutEvent(TableEvent.class, "" + a[i].getType())) {
                        filteredList.add(a[i]);
                    }
                }
                if (filteredList.size() == 0) {
                    return;
                }
                Runnable t = new Runnable() {
                    @Override
                    public void run() {
                        if (isUiDisposed()) {
                            return;
                        }
                        m_redrawHandler.pushControlChanging();
                        try {
                            try {
                                getUpdateUiFromScoutLock().acquire();
                                //
                                for (TableEvent element : filteredList) {
                                    handleScoutTableEventInUi(element);
                                }
                            } finally {
                                getUpdateUiFromScoutLock().release();
                            }
                        } finally {
                            m_redrawHandler.popControlChanging();
                        }
                    }
                };
                getUiEnvironment().invokeUiLater(t);
            }
        }
    }// end P_ScoutTableListener

    private Menu createMenu(boolean headerMenu) {
        if (getUiField().getMenu() != null) {
            getUiField().getMenu().dispose();
            getUiField().setMenu(null);
        }
        Menu contextMenu = new Menu(getUiField().getShell(), SWT.POP_UP);
        contextMenu.addMenuListener(new P_ContextMenuListener(headerMenu));
        getUiField().setMenu(contextMenu);

        return contextMenu;
    }

    private void createAndShowMenu(Point location) {
        Point pt = getUiField().getDisplay().map(null, getUiField(), location);
        Rectangle clientArea = getUiField().getClientArea();
        boolean header = clientArea.y <= pt.y && pt.y < clientArea.y + getUiField().getHeaderHeight();

        Menu menu = createMenu(header);
        menu.setLocation(location);
        menu.setVisible(true);
    }

    private class P_RwtTableListener implements Listener {
        private static final long serialVersionUID = 1L;

        private Boolean m_doubleClicked = Boolean.FALSE;

        @Override
        public void handleEvent(Event event) {
            Point eventPosition = new Point(event.x, event.y);
            switch (event.type) {
            case SWT.MouseDown: {
                setContextColumnFromUi(RwtUtility.getRwtColumnAt(getUiTableViewer().getTable(), eventPosition));
                if (getUiField().getItem(eventPosition) == null) {
                    getUiTableViewer().setSelection(null);
                    setSelectionFromUi(new StructuredSelection());
                }
                break;
            }
            case SWT.MouseUp: {
                synchronized (m_doubleClicked) {
                    if (m_doubleClicked == Boolean.TRUE) {
                        m_doubleClicked = Boolean.FALSE;
                        break;
                    }
                }
                StructuredSelection selection = (StructuredSelection) getUiTableViewer().getSelection();
                if (selection != null && selection.size() == 1) {
                    handleUiRowClick((ITableRow) selection.getFirstElement());
                }
                break;
            }
            case SWT.MouseDoubleClick: {
                synchronized (m_doubleClicked) {
                    m_doubleClicked = Boolean.TRUE;
                }
                StructuredSelection selection = (StructuredSelection) getUiTableViewer().getSelection();
                if (selection != null && selection.size() == 1) {
                    handleUiRowAction((ITableRow) selection.getFirstElement());
                }
                break;
            }
            case SWT.Resize: {
                //lazy column auto-fit
                if (getUiField() != null && !getUiField().isDisposed()) {
                    if (getScoutObject().isAutoResizeColumns()) {
                        scheduleHandleAutoResizeColumn();
                    }
                    updateScrollToSelectionFromScout();
                }
                break;
            }
            case SWT.MenuDetect: {
                createAndShowMenu(eventPosition);
                break;
            }
            }
        }
    }

    private class P_RwtResizeListener implements Listener {
        private static final long serialVersionUID = 1L;

        @Override
        public void handleEvent(Event event) {
            //lazy column auto-fit
            if (getUiField() != null && !getUiField().isDisposed()) {
                if (getScoutObject().isAutoResizeColumns()) {
                    scheduleHandleAutoResizeColumn();
                }
                updateScrollToSelectionFromScout();
            }
        }
    } // end class P_SwtResizeListener

    public class P_RwtSelectionListener implements ISelectionChangedListener {
        @Override
        public void selectionChanged(SelectionChangedEvent event) {
            setSelectionFromUi((StructuredSelection) event.getSelection());
        }
    }

    private class P_TableColumnListener implements Listener {
        private static final long serialVersionUID = 1L;

        @Override
        public void handleEvent(Event event) {
            switch (event.type) {
            case SWT.Move:
                handleUiColumnMoved();
                break;
            case SWT.Resize:
                if (event.widget instanceof TableColumn) {
                    handleUiColumnResized((TableColumn) event.widget);
                }

            }
        }

    } // end class P_TableColumnMoveListener

    private class P_ColumnSortListener extends SelectionAdapter {
        private static final long serialVersionUID = 1L;

        @Override
        public void widgetSelected(SelectionEvent e) {
            TableColumn col = (TableColumn) e.getSource();
            setContextColumnFromUi(col);
            final IColumn<?> newColumn = (IColumn<?>) col.getData(KEY_SCOUT_COLUMN);
            final boolean ctrlKeyPressed = ((e.stateMask & SWT.CONTROL) > 0);
            if (getScoutObject() != null) {
                Runnable job = new Runnable() {
                    @Override
                    public void run() {
                        getScoutObject().getUIFacade().fireHeaderSortFromUI(newColumn, ctrlKeyPressed);
                    }
                };
                getUiEnvironment().invokeScoutLater(job, 0);
            }
        }
    } // end class P_ColumnSortListener

    private class P_ContextMenuListener extends MenuAdapterEx {
        private boolean m_header;

        private static final long serialVersionUID = 1L;

        public P_ContextMenuListener(boolean header) {
            super(RwtScoutTable.this.getUiTableViewer().getTable(),
                    RwtScoutTable.this.getUiTableViewer().getTable());
            m_header = header;
        }

        @Override
        public void menuShown(MenuEvent e) {
            super.menuShown(e);

            IMenu[] menus = null;
            if (m_header) {
                menus = collectHeaderMenus();
            } else {
                final boolean emptySelection = getUiTableViewer().getSelection().isEmpty();
                menus = RwtMenuUtility.collectMenus(getScoutObject(), emptySelection, !emptySelection,
                        getUiEnvironment());
            }
            if (menus != null) {
                Menu menu = ((Menu) e.getSource());
                RwtMenuUtility.fillContextMenu(menus, getUiEnvironment(), menu);
            }
        }

        private IMenu[] collectHeaderMenus() {
            final AtomicReference<IMenu[]> scoutMenusRef = new AtomicReference<IMenu[]>();
            Runnable t = new Runnable() {
                @Override
                public void run() {
                    IMenu[] scoutMenus = getScoutObject().getUIFacade().fireHeaderPopupFromUI();
                    scoutMenusRef.set(scoutMenus);
                }
            };
            JobEx job = getUiEnvironment().invokeScoutLater(t, 1200);
            try {
                job.join(1200);
            } catch (InterruptedException ex) {
                //nop
            }
            // grab the actions out of the job, when the actions are providden
            // within the scheduled time the popup will be handled.
            if (scoutMenusRef.get() != null) {
                return scoutMenusRef.get();
            }

            return new IMenu[0];
        }
    }

    private class P_DndSupport extends AbstractRwtScoutDndSupport {
        public P_DndSupport(IPropertyObserver scoutObject, IDNDSupport scoutDndSupportable, Control control) {
            super(scoutObject, scoutDndSupportable, control, RwtScoutTable.this.getUiEnvironment());
        }

        @Override
        protected TransferObject handleUiDragRequest() {
            final Holder<TransferObject> result = new Holder<TransferObject>(TransferObject.class, null);
            Runnable t = new Runnable() {
                @Override
                public void run() {
                    TransferObject scoutTransferable = getScoutObject().getUIFacade().fireRowsDragRequestFromUI();
                    result.setValue(scoutTransferable);
                }
            };
            try {
                getUiEnvironment().invokeScoutLater(t, 20000).join(20000);
            } catch (InterruptedException e) {
                //nop
            }
            return result.getValue();
        }

        @Override
        protected void handleUiDropAction(DropTargetEvent event, final TransferObject scoutTransferObject) {
            Object dropTarget = event.item != null ? event.item.getData() : null;
            final ITableRow row = dropTarget instanceof ITableRow ? (ITableRow) dropTarget : null;
            Runnable job = new Runnable() {
                @Override
                public void run() {
                    getScoutObject().getUIFacade().fireRowDropActionFromUI(row, scoutTransferObject);
                }
            };
            getUiEnvironment().invokeScoutLater(job, 200);
        }
    }// end class P_DndSupport

    private class P_KeyBoardNavigationSupport extends AbstractTableKeyboardNavigationSupport {
        /**
         * @param table
         * @param delay
         */
        public P_KeyBoardNavigationSupport(TableEx table) {
            super(table);
        }

        @Override
        public void handleKeyboardNavigation(TableItem tableItem) {
            handleKeyboardNavigationFromUi(tableItem);
        }
    } // P_KeyBoardNavigationSupport

}