org.eclipse.scout.rt.ui.swt.basic.table.SwtScoutTable.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.scout.rt.ui.swt.basic.table.SwtScoutTable.java

Source

/*******************************************************************************
 * 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.ui.swt.basic.table;

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.CellEditor;
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.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.cell.ICell;
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.shared.security.CopyToClipboardPermission;
import org.eclipse.scout.rt.shared.services.common.security.ACCESS;
import org.eclipse.scout.rt.ui.swt.ISwtEnvironment;
import org.eclipse.scout.rt.ui.swt.SwtMenuUtility;
import org.eclipse.scout.rt.ui.swt.basic.SwtScoutComposite;
import org.eclipse.scout.rt.ui.swt.basic.table.celleditor.SwtScoutTableCellEditor;
import org.eclipse.scout.rt.ui.swt.ext.table.TableEx;
import org.eclipse.scout.rt.ui.swt.ext.table.util.TableRolloverSupport;
import org.eclipse.scout.rt.ui.swt.extension.UiDecorationExtensionPoint;
import org.eclipse.scout.rt.ui.swt.form.fields.AbstractSwtScoutDndSupport;
import org.eclipse.scout.rt.ui.swt.form.fields.ISwtScoutDndSupport;
import org.eclipse.scout.rt.ui.swt.keystroke.ISwtKeyStroke;
import org.eclipse.scout.rt.ui.swt.util.SwtTransferObject;
import org.eclipse.scout.rt.ui.swt.util.SwtUtility;
import org.eclipse.scout.rt.ui.swt.util.UiRedrawHandler;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MenuAdapter;
import org.eclipse.swt.events.MenuDetectEvent;
import org.eclipse.swt.events.MenuDetectListener;
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.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;

/**
 * <h3>SwtScoutTable</h3> ...
 * 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 swt.
 * <p>
 * - multi line support in row texts is not supported so far. Might probably be done by customized table rows.
 * 
 * @since 1.0.0 28.03.2008
 */
public class SwtScoutTable extends SwtScoutComposite<ITable> implements ISwtScoutTable {
    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_columnManager = new TableColumnManager();
    private SwtScoutTableCellEditor m_cellEditorComposite;
    private int[] m_uiColumnOrder;
    private Menu m_contextMenu;
    private Menu m_headerMenu;
    private TableViewer m_viewer;
    private ISwtKeyStroke[] m_keyStrokes;
    private ClientSyncJob m_storeColumnWidthsJob;
    private TableKeyboardNavigationSupport m_keyboardNavigationSupport;
    private TableMouseMoveSelectionSupport m_mouseMoveSelectionSupport;
    private ISwtScoutDndSupport m_dndSupport;
    private P_MultilineTooltipListener m_multiLineTooltipListener = null;
    private static SwtScoutTable s_activeMultlineTooltipTable = null;

    public SwtScoutTable() {
    }

    @Override
    protected void initializeSwt(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 | SWT.BORDER;
        TableEx table = getEnvironment().getFormToolkit().createTable(parent, style, getScoutObject());
        table.setLinesVisible(false);
        table.setHeaderVisible(true);
        new TableRolloverSupport(table);
        table.addDisposeListener(new DisposeListener() {
            @Override
            public void widgetDisposed(DisposeEvent e) {
                if (m_headerMenu != null && !m_headerMenu.isDisposed()) {
                    m_headerMenu.dispose();
                }
                if (m_contextMenu != null && !m_contextMenu.isDisposed()) {
                    m_contextMenu.dispose();
                }
            }
        });
        TableViewer viewer = new TableViewer(table);
        viewer.setUseHashlookup(true);
        setSwtTableViewer(viewer);
        setSwtField(table);

        //cell editing support
        m_cellEditorComposite = new SwtScoutTableCellEditor(this);

        // header menu
        m_headerMenu = new Menu(viewer.getTable().getShell(), SWT.POP_UP);
        table.addMenuDetectListener(new P_SwtHeaderMenuDetectListener());

        //columns
        initializeColumns();

        SwtScoutTableModel tableModel = createTableModel();
        viewer.setContentProvider(tableModel);
        viewer.setLabelProvider(tableModel);
        viewer.setInput(tableModel);

        // ui listeners
        viewer.addSelectionChangedListener(new P_SwtSelectionListener());
        P_SwtTableListener swtTableListener = new P_SwtTableListener();
        table.addListener(SWT.MouseDown, swtTableListener);
        table.addListener(SWT.MouseUp, swtTableListener);
        table.addListener(SWT.MouseDoubleClick, swtTableListener);
        table.addListener(SWT.MenuDetect, swtTableListener);
        table.addListener(SWT.KeyUp, swtTableListener);

        // context menu
        Menu contextMenu = new Menu(viewer.getTable().getShell(), SWT.POP_UP);
        contextMenu.addMenuListener(new P_ContextMenuListener());
        m_contextMenu = contextMenu;

        // CTRL-C listener to copy selected rows into clipboard)
        table.addKeyListener(new KeyAdapter() {

            @Override
            public void keyPressed(KeyEvent e) {
                if ((e.stateMask & SWT.MOD1) == 0 || e.keyCode != ((int) 'c')) {
                    return;
                }

                // CTRL-C event
                TransferObject scoutTransferable = handleSwtCopyRequest();
                if (scoutTransferable == null) {
                    return;
                }

                SwtTransferObject[] swtTransferables = SwtUtility.createSwtTransferables(scoutTransferable);
                if (swtTransferables.length == 0) {
                    return;
                }

                Clipboard clipboard = new Clipboard(getEnvironment().getDisplay());
                try {
                    Transfer[] dataTypes = new Transfer[swtTransferables.length];
                    Object[] data = new Object[swtTransferables.length];
                    for (int i = 0; i < swtTransferables.length; i++) {
                        dataTypes[i] = swtTransferables[i].getTransfer();
                        data[i] = swtTransferables[i].getData();
                    }
                    clipboard.setContents(data, dataTypes);
                } finally {
                    clipboard.dispose();
                }
            }
        });
        // Multiline tooltip
        checkTooltipListener();
    }

    private synchronized void checkTooltipListener() {
        getSwtTableViewer().getTable().setToolTipText("");
        if (m_multiLineTooltipListener == null) {
            m_multiLineTooltipListener = new P_MultilineTooltipListener();
            getSwtTableViewer().getTable().addListener(SWT.Dispose, m_multiLineTooltipListener);
            getSwtTableViewer().getTable().addListener(SWT.KeyDown, m_multiLineTooltipListener);
            getSwtTableViewer().getTable().addListener(SWT.MouseMove, m_multiLineTooltipListener);
            getSwtTableViewer().getTable().addListener(SWT.MouseHover, m_multiLineTooltipListener);
        }
    }

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

    protected SwtScoutTableModel createTableModel() {
        return new SwtScoutTableModel(getScoutObject(), this, getEnvironment(), m_columnManager);
    }

    public TableColumnManager getColumnManager() {
        return m_columnManager;
    }

    public void initializeColumns() {

        m_redrawHandler.pushControlChanging();
        try {
            for (TableColumn col : getSwtField().getColumns()) {
                col.dispose();
            }
            /*
             * bug: swt table first column can not be aligned nor an image can be set.
             * see also SwtScoutTableCellEditor
             */
            TableColumn dummyCol = new TableColumn(getSwtField(), 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_columnManager == null) {
                m_columnManager = new TableColumnManager();
            }
            m_columnManager.initialize(scoutColumnsOrdered);
            for (IColumn<?> scoutColumn : scoutColumnsOrdered) {
                IHeaderCell cell = scoutColumn.getHeaderCell();
                int style = SwtUtility.getHorizontalAlignment(cell.getHorizontalAlignment());
                TableColumn swtCol = new TableColumn(getSwtField(), style);
                swtCol.setData(KEY_SCOUT_COLUMN, scoutColumn);
                swtCol.setMoveable(true);
                swtCol.setToolTipText(cell.getTooltipText());
                updateHeaderText(swtCol, scoutColumn);
                swtCol.setWidth(scoutColumn.getWidth());
                if (scoutColumn.isFixedWidth()) {
                    swtCol.setResizable(false);
                }
                if (cell.isSortActive()) {
                    getSwtField().setSortColumn(swtCol);
                    getSwtField().setSortDirection(cell.isSortAscending() ? SWT.UP : SWT.DOWN);
                }
                if (sortEnabled) {
                    swtCol.addSelectionListener(m_columnSortListener);
                }
                swtCol.addListener(SWT.Move, m_columnListener);
                swtCol.addListener(SWT.Resize, m_columnListener);
            }
            m_uiColumnOrder = getSwtField().getColumnOrder();
            //update cell editors
            m_cellEditorComposite.initialize();
        } 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());
        updateKeyStrokeFormScout();
        updateKeyboardNavigationFromScout();
        updateAutoResizeColumnsFromScout();

        if (m_dndSupport == null) {
            m_dndSupport = new P_DndSupport(getScoutObject(), getScoutObject(), getSwtField(), getEnvironment());
        }
        if (m_mouseMoveSelectionSupport == null
                && UiDecorationExtensionPoint.getLookAndFeel().isTableMouseMoveSelectionSupportEnabled()) {
            m_mouseMoveSelectionSupport = new P_MouseMoveSelectionSupport(getSwtField(), m_dndSupport);
        }

        //handle events from recent history
        final IEventHistory<TableEvent> h = getScoutObject().getEventHistory();
        if (h != null) {
            getEnvironment().getDisplay().asyncExec(new Runnable() {
                @Override
                public void run() {
                    for (TableEvent e : h.getRecentEvents()) {
                        handleScoutTableEventInSwt(e);
                    }
                }
            });
        }
    }

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

        removeAutoResizeColumnListener();
    }

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

    @Override
    public TableViewer getSwtTableViewer() {
        return m_viewer;
    }

    public void setSwtTableViewer(TableViewer viewer) {
        m_viewer = viewer;
    }

    public ITableRow getSwtSelectedRow() {
        ITableRow[] rows = getSwtSelectedRows();
        if (rows.length > 0) {
            return rows[0];
        }
        return null;
    }

    public ITableRow[] getSwtSelectedRows() {
        StructuredSelection uiSelection = (StructuredSelection) getSwtTableViewer().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 updateKeyStrokeFormScout() {
        // remove old
        if (m_keyStrokes != null) {
            for (ISwtKeyStroke swtKeyStroke : m_keyStrokes) {
                getEnvironment().removeKeyStroke(getSwtField(), swtKeyStroke);
            }
        }
        // add new
        ArrayList<ISwtKeyStroke> newSwtKeyStrokes = new ArrayList<ISwtKeyStroke>();
        IKeyStroke[] scoutKeyStrokes = getScoutObject().getKeyStrokes();
        for (IKeyStroke scoutKeyStroke : scoutKeyStrokes) {
            ISwtKeyStroke[] swtStrokes = SwtUtility.getKeyStrokes(scoutKeyStroke, getEnvironment());
            for (ISwtKeyStroke swtStroke : swtStrokes) {
                getEnvironment().addKeyStroke(getSwtField(), swtStroke);
                newSwtKeyStrokes.add(swtStroke);
            }
        }
        m_keyStrokes = newSwtKeyStrokes.toArray(new ISwtKeyStroke[newSwtKeyStrokes.size()]);
    }

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

    private void updateAutoResizeColumnsFromScout() {
        if (getSwtField() != null && !getSwtField().getParent().isDisposed()) {
            Composite parent = getSwtField().getParent();
            if (getScoutObject().isAutoResizeColumns()) {
                if (m_autoResizeColumnListener == null) {
                    m_autoResizeColumnListener = new P_SwtResizeListener();
                    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() {
        getSwtField().getDisplay().asyncExec(new Runnable() {
            @Override
            public void run() {
                handleAutoSizeColumns();
            }
        });
    }

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

        Composite parent = getSwtField().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)) {
            updateKeyStrokeFormScout();
        } else if (propName.equals(ITable.PROP_KEYBOARD_NAVIGATION)) {
            updateKeyboardNavigationFromScout();
        } 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 handleScoutTableEventInSwt(TableEvent e) {
        if (isDisposed()) {
            return;
        }
        SwtScoutTableEvent swtTableEvent = null;
        /*
         * check the scout observer to filter all events that are used here
         * @see isHandleScoutTableEvent()
         */
        switch (e.getType()) {
        case TableEvent.TYPE_REQUEST_FOCUS: {
            getSwtField().setFocus();
            break;
        }
        case TableEvent.TYPE_REQUEST_FOCUS_IN_CELL: {
            //start editing
            int swtCol = -1;
            TableColumn[] swtColumns = getSwtField().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) {
                getSwtTableViewer().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: {
            swtTableEvent = new SwtScoutTableEvent();
            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
            initializeColumns();
            if (getScoutObject().isAutoResizeColumns()) {
                handleAutoSizeColumns();
            }

            swtTableEvent = new SwtScoutTableEvent();
            break;
        }
        case TableEvent.TYPE_ROWS_SELECTED: {
            setSelectionFromScout(e.getRows());
            break;
        }
        }
        //
        if (swtTableEvent != null) {
            getSwtTableViewer().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 : getSwtField().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) {
        IHeaderCell cell = scoutCol.getHeaderCell();
        String text = cell.getText();
        if (text == null) {
            text = "";
        }
        if (scoutCol.isColumnFilterActive()) {
            text = "((" + text + "))";
        }
        swtCol.setText(text);
    }

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

    protected void scrollToSelection() {
        getSwtField().showSelection();
    }

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

    @Override
    public void setEnabledFromScout(boolean enabledFromScout) {
        getSwtField().setReadOnly(!enabledFromScout);
    }

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

            if (selectedRows.length > 0) {
                getSwtTableViewer().reveal(selectedRows[0]);
            }
        }
        //ticket 96051
        if (getScoutObject().isScrollToSelection()) {
            scrollToSelection();
        }
    }

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

    /**
     * @param p
     *          is the location of the Table control (i.e. not scrollbar adjusted)
     */
    private TableColumn getSwtColumnAt(Point p) {
        Table table = getSwtTableViewer().getTable();
        int x = p.x + getSwtField().getHorizontalBar().getSelection();
        int[] order = table.getColumnOrder();
        for (int index : order) {
            // loop over columns according current display-order
            TableColumn c = table.getColumn(index);
            if (c != null) {
                if (x >= 0 && x <= c.getWidth()) {
                    return c;
                }
                x = x - c.getWidth();
            }
        }
        return null;
    }

    protected void setSelectionFromSwt(final StructuredSelection selection) {
        if (getUpdateSwtFromScoutLock().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(SwtUtility.getItemsOfSelection(ITableRow.class, selection));
                    } finally {
                        removeIgnoredScoutEvent(TableEvent.class, "" + TableEvent.TYPE_ROWS_SELECTED);
                    }
                }
            };
            getEnvironment().invokeScoutLater(t, 0);
            // end notify
        }
    }

    protected void headerUpdateFromScout() {
        getSwtField().setSortColumn(null);

        for (TableColumn col : getSwtField().getColumns()) {
            Object data = col.getData(KEY_SCOUT_COLUMN);
            if (data instanceof IColumn<?>) {
                IColumn<?> cell = (IColumn<?>) data;
                if (cell.isSortExplicit()) {
                    getSwtField().setSortColumn(col);
                    getSwtField().setSortDirection(cell.isSortAscending() ? SWT.UP : SWT.DOWN);
                }
            }
        }
    }

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

    protected void handleSwtRowAction(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);
                    }
                };
                getEnvironment().invokeScoutLater(t, 0);
                // end notify
            }
        }
    }

    /**
     * TODO: this method is currently not used: When swt table supports styled or html cells, add a hyperlink listener
     * there
     */
    protected void handleSwtHyperlinkAction(final ITableRow row, final IColumn col, final URL url) {
        if (getScoutObject() != null && row != null) {
            // notify Scout
            Runnable t = new Runnable() {
                @Override
                public void run() {
                    getScoutObject().getUIFacade().fireHyperlinkActionFromUI(row, col, url);
                }
            };
            getEnvironment().invokeScoutLater(t, 0);
            // end notify
        }
    }

    /**
     * <p>
     * Distributes the table width to the columns considering column weights of model. <br/>
     * The initial widths of the columns are used as weights as well as the minimum widths. <br/>
     * Empty space will be distributed weighted.
     * </p>
     */
    protected void handleAutoSizeColumns() {
        if (getSwtField() == null || getSwtField().isDisposed()) {
            return;
        }

        int totalWidth = getSwtField().getClientArea().width;
        if (getSwtField().getVerticalBar() != null && getSwtField().getVerticalBar().getVisible()) {
            //      totalWidth -= getSwtField().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 : getSwtField().getColumns()) {
            if (col == null || col.isDisposed()) {
                continue;
            }
            actualWidth += col.getWidth();
            Object data = col.getData(SwtScoutTable.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;
        if (factor < 1) {
            factor = 1;
        }
        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 {
                //Column must not get smaller than initial width.
                int width = Math.max(totalWidth, entry.getValue());
                entry.getKey().setWidth(width);
            }
        }
    }

    protected void handleSwtColumnResized(TableColumn column) {
        if (column.isDisposed()) {
            return;
        }
        if (!column.getParent().isVisible()) {
            return;
        }
        if (getUpdateSwtFromScoutLock().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",
                        getEnvironment().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 TransferObject handleSwtCopyRequest() {
        if (getUpdateSwtFromScoutLock().isAcquired()) {
            return null;
        }
        final Holder<TransferObject> result = new Holder<TransferObject>(TransferObject.class, null);
        if (getScoutObject() != null) {
            // notify Scout
            Runnable t = new Runnable() {
                @Override
                public void run() {
                    if (!ACCESS.check(new CopyToClipboardPermission())) {
                        return;
                    }
                    TransferObject scoutTransferable = getScoutObject().getUIFacade().fireRowsCopyRequestFromUI();
                    result.setValue(scoutTransferable);
                }
            };
            try {
                getEnvironment().invokeScoutLater(t, 20000).join(20000);
            } catch (InterruptedException e) {
                //nop
            }
            // end notify
        }
        return result.getValue();
    }

    protected void handleSwtColumnMoved() {
        if (getUpdateSwtFromScoutLock().isAcquired()) {
            return;
        }
        int[] uiColumnOrder = getSwtField().getColumnOrder();
        // do not allow to reorder icon and dummy column
        if (uiColumnOrder[0] != 0) {
            getSwtField().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())) {
            getSwtTableViewer().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_columnManager.getOrderedColumns(truncatedColOrder);
        if (m_columnManager.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);
                    }
                }
            };
            getEnvironment().invokeScoutLater(t, 0);
            // end notify
        }
    }

    protected void handleKeyboardNavigationFromSwt(TableItem item) {
        if (getScoutObject().isCheckable()) {
            //nop
            return;
        }
        getSwtField().setSelection(item);
        Event selectionEvent = new Event();
        selectionEvent.type = SWT.DefaultSelection;
        selectionEvent.widget = getSwtField();
        for (Listener l : getSwtField().getListeners(SWT.DefaultSelection)) {
            l.handleEvent(selectionEvent);
        }
    }

    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 {
                            getUpdateSwtFromScoutLock().acquire();
                            //
                            handleScoutTableEventInSwt(e);
                        } finally {
                            getUpdateSwtFromScoutLock().release();
                        }
                    }
                };
                getEnvironment().invokeSwtLater(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 (isDisposed()) {
                            return;
                        }
                        m_redrawHandler.pushControlChanging();
                        try {
                            try {
                                getUpdateSwtFromScoutLock().acquire();
                                //
                                for (TableEvent element : filteredList) {
                                    handleScoutTableEventInSwt(element);
                                }
                            } finally {
                                getUpdateSwtFromScoutLock().release();
                            }
                        } finally {
                            m_redrawHandler.popControlChanging();
                        }
                    }
                };
                getEnvironment().invokeSwtLater(t);
            }
        }
    }// end P_ScoutTableListener

    private class P_SwtTableListener implements Listener {
        @Override
        public void handleEvent(Event event) {
            switch (event.type) {
            case SWT.MouseDown: {
                setContextColumnFromSwt(getSwtColumnAt(new Point(event.x, event.y)));
                if (getSwtField().getItem(new Point(event.x, event.y)) == null) {
                    getSwtTableViewer().setSelection(null);
                    setSelectionFromSwt(new StructuredSelection());
                }
                break;
            }
            case SWT.MouseUp: {
                if (event.count == 1) {
                    StructuredSelection selection = (StructuredSelection) getSwtTableViewer().getSelection();
                    if (selection.size() == 1) {
                        handleSwtRowClick((ITableRow) selection.getFirstElement());
                    }
                }
                break;
            }
            case SWT.MouseDoubleClick: {
                StructuredSelection selection = (StructuredSelection) getSwtTableViewer().getSelection();
                if (selection.size() == 1) {
                    handleSwtRowAction((ITableRow) selection.getFirstElement());
                }
                break;
            }
            case SWT.MenuDetect: {
                Point pt = getSwtField().getDisplay().map(null, getSwtField(), new Point(event.x, event.y));
                Rectangle clientArea = getSwtField().getClientArea();
                boolean header = clientArea.y <= pt.y && pt.y < clientArea.y + getSwtField().getHeaderHeight();
                getSwtField().setMenu(header ? m_headerMenu : m_contextMenu);
                break;
            }
            case SWT.KeyUp: {
                if (event.doit && getScoutObject().isCheckable()) {
                    if (event.stateMask == 0) {
                        switch (event.keyCode) {
                        case ' ':
                        case SWT.CR:
                            ITableRow[] selectedRows = SwtUtility.getItemsOfSelection(ITableRow.class,
                                    (StructuredSelection) getSwtTableViewer().getSelection());
                            if (selectedRows != null && selectedRows.length > 0) {
                                handleSwtRowClick(selectedRows[0]);
                            }
                            event.doit = false;
                            break;
                        }
                    }
                }
                break;
            }
            }
        }
    }

    private class P_SwtResizeListener implements Listener {
        @Override
        public void handleEvent(Event event) {
            //lazy column auto-fit
            if (getSwtField() != null && !getSwtField().isDisposed()) {
                scheduleHandleAutoResizeColumn();
            }
        }
    } // end class P_SwtResizeListener

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

    } // end class P_SwtSelectionListener

    private class P_TableColumnListener implements Listener {
        @Override
        public void handleEvent(Event event) {
            switch (event.type) {
            case SWT.Move:
                handleSwtColumnMoved();
                break;
            case SWT.Resize:
                if (event.widget instanceof TableColumn) {
                    handleSwtColumnResized((TableColumn) event.widget);
                }

            }
        }

    } // end class P_TableColumnMoveListener

    private class P_ColumnSortListener extends SelectionAdapter {
        @Override
        public void widgetSelected(SelectionEvent e) {
            /*
             * TODO multi column search does work with control mask. The selection
             * event on a table column does not provide any key mask nor right mouse
             * click.
             */
            TableColumn col = (TableColumn) e.getSource();
            setContextColumnFromSwt(col);
            final IColumn<?> newColumn = (IColumn<?>) col.getData(KEY_SCOUT_COLUMN);
            if (getScoutObject() != null) {
                Runnable job = new Runnable() {
                    @Override
                    public void run() {
                        // use always memory sort since SWT does not provide a keymask on
                        // selection events
                        getScoutObject().getUIFacade().fireHeaderSortFromUI(newColumn, false);
                    }
                };
                getEnvironment().invokeScoutLater(job, 0);
            }
        }
    } // end class P_ColumnSortListener

    private class P_ContextMenuListener extends MenuAdapter {
        @Override
        public void menuShown(MenuEvent e) {
            // clear all previous
            // Windows BUG: fires menu hide before the selection on the menu item is
            // propagated.
            if (m_contextMenu != null) {
                for (MenuItem item : m_contextMenu.getItems()) {
                    disposeMenuItem(item);
                }
            }

            final boolean emptySelection = getSwtTableViewer().getSelection().isEmpty();
            IMenu[] menus = SwtMenuUtility.collectMenus(getScoutObject(), emptySelection, !emptySelection,
                    getEnvironment());

            SwtMenuUtility.fillContextMenu(menus, m_contextMenu, getEnvironment());
        }

        private void disposeMenuItem(MenuItem item) {
            Menu menu = item.getMenu();
            if (menu != null) {
                for (MenuItem childItem : menu.getItems()) {
                    disposeMenuItem(childItem);
                }
                menu.dispose();
            }
            item.dispose();
        }

    } // end class P_ContextMenuListener

    private class P_SwtHeaderMenuDetectListener implements MenuDetectListener {

        @Override
        public void menuDetected(MenuDetectEvent event) {
            Table table = getSwtField();
            Point pTable = table.getDisplay().map(null, table, new Point(event.x, event.y));
            Rectangle clientArea = table.getClientArea();
            boolean header = clientArea.y <= pTable.y && pTable.y < clientArea.y + table.getHeaderHeight();
            if (!header) {
                return;
            }
            // clear all previous
            // Windows BUG: fires menu hide before the selection on the menu item is
            // propagated.
            if (m_headerMenu != null) {
                for (MenuItem item : m_headerMenu.getItems()) {
                    disposeMenuItem(item);
                }
            }
            setContextColumnFromSwt(getSwtColumnAt(pTable));
            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 = getEnvironment().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) {
                SwtMenuUtility.fillContextMenu(scoutMenusRef.get(), m_headerMenu, getEnvironment());
            }
        }

        private void disposeMenuItem(MenuItem item) {
            Menu menu = item.getMenu();
            if (menu != null) {
                for (MenuItem childItem : menu.getItems()) {
                    disposeMenuItem(childItem);
                }
                menu.dispose();
            }
            item.dispose();
        }

    } // end class P_HeaderMenuListener

    private class P_DndSupport extends AbstractSwtScoutDndSupport {
        public P_DndSupport(IPropertyObserver scoutObject, IDNDSupport scoutDndSupportable, Control control,
                ISwtEnvironment environment) {
            super(scoutObject, scoutDndSupportable, control, environment);
        }

        @Override
        protected boolean acceptDrag() {
            if (m_mouseMoveSelectionSupport != null) {
                return m_mouseMoveSelectionSupport.acceptDrag();
            }

            return super.acceptDrag();
        }

        @Override
        protected TransferObject handleSwtDragRequest() {
            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 {
                getEnvironment().invokeScoutLater(t, 20000).join(20000);
            } catch (InterruptedException e) {
                //nop
            }
            return result.getValue();
        }

        @Override
        protected void handleSwtDropAction(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);
                }
            };
            getEnvironment().invokeScoutLater(job, 200);
        }
    }// end class P_DndSupport

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

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

    private class P_MouseMoveSelectionSupport extends TableMouseMoveSelectionSupport {

        public P_MouseMoveSelectionSupport(Table table, ISwtScoutDndSupport dndSupport) {
            super(table, dndSupport);
        }

        @Override
        protected void selectingStopped() {
            //Selection event seems not to be fired properly after selecting the items so it's necessary to manually call setSelectionFromSwt
            StructuredSelection selection = (StructuredSelection) getSwtTableViewer().getSelection();
            setSelectionFromSwt(selection);
        }

    }

    protected class P_MultilineTooltipListener implements Listener {

        private Shell m_tooltip = null;
        private Label m_tooltipLabel = null;
        private Listener m_tooltipShellListener = null;

        @Override
        public void handleEvent(Event event) {

            disposeTooltip();
            switch (event.type) {
            case SWT.KeyDown:
            case SWT.MouseMove:
            case SWT.MouseHover: {
                // If any editor is active tooltip is not repainted.
                for (CellEditor editor : getSwtTableViewer().getCellEditors()) {
                    if (editor != null && editor.isActivated()) {
                        return;
                    }
                }

                Point eventMousePoint = getSwtTableViewer().getTable()
                        .toControl(getEnvironment().getDisplay().getCursorLocation());
                TableItem item = getSwtTableViewer().getTable().getItem(eventMousePoint);
                if (item != null) {
                    int columnIdx = 0;
                    int columnCount = getSwtTableViewer().getTable().getColumnCount();
                    Rectangle cellBounds;
                    do {
                        cellBounds = item.getBounds(columnIdx);
                        if (new Rectangle(cellBounds.x, cellBounds.y + 2, cellBounds.width, cellBounds.height - 4)
                                .contains(eventMousePoint.x, eventMousePoint.y)) {
                            break;
                        }
                        columnIdx++;
                    } while (columnIdx < columnCount);

                    // no cell found in the columns
                    if (columnIdx >= columnCount) {
                        break;
                    }

                    final ITableRow row = (ITableRow) item.getData();
                    IColumn<?>[] modelColumns = row.getTable().getColumns();
                    int invisibleColumnsBefore = 0;
                    for (int i = 0; i < columnIdx + invisibleColumnsBefore; i++) {
                        if (!modelColumns[i].isVisible()) {
                            invisibleColumnsBefore++;
                        }
                    }
                    // SWT index of colums starts at 1
                    final ICell modelCell = row.getCell(columnIdx - 1 + invisibleColumnsBefore);
                    String tooltipText = modelCell.getTooltipText();
                    if (StringUtility.isNullOrEmpty(tooltipText)) {
                        tooltipText = modelCell.getText();
                    }

                    // cell has no text to display in tooltip
                    if (!StringUtility.hasText(tooltipText)) {
                        break;
                    }

                    // "Disable" native tooltip
                    getSwtTableViewer().getTable().setToolTipText("");

                    m_tooltip = new Shell(getSwtTableViewer().getTable().getShell(), SWT.TOOL);
                    m_tooltip.setBackground(getSwtTableViewer().getTable().getParent().getShell().getDisplay()
                            .getSystemColor(SWT.COLOR_INFO_BACKGROUND));
                    m_tooltip.setFont(getSwtTableViewer().getTable().getFont());
                    FillLayout layout = new FillLayout();
                    layout.marginWidth = 6;
                    m_tooltip.setLayout(layout);
                    m_tooltipLabel = new Label(m_tooltip, SWT.WRAP);
                    m_tooltipLabel.setForeground(getSwtTableViewer().getTable().getParent().getShell().getDisplay()
                            .getSystemColor(SWT.COLOR_INFO_FOREGROUND));
                    m_tooltipLabel.setBackground(getSwtTableViewer().getTable().getParent().getShell().getDisplay()
                            .getSystemColor(SWT.COLOR_INFO_BACKGROUND));
                    m_tooltipLabel.setData("_TABLEITEM", item);
                    m_tooltipLabel.setFont(item.getFont());
                    m_tooltipLabel.setText(tooltipText);

                    /**
                     * Instead of listening to the mouse exit event on the label, which is not always fired we register a filter
                     * on the display. To ensure we get informed
                     * when mouse cursor leaves the tooltip.
                     **/
                    m_tooltipShellListener = new Listener() {

                        @Override
                        public void handleEvent(Event tooltipEvent) {
                            if ((m_tooltip != null) && (tooltipEvent.widget instanceof Control)
                                    && !m_tooltip.isDisposed()) {
                                Point mousePoint = ((Control) tooltipEvent.widget).toDisplay(tooltipEvent.x,
                                        tooltipEvent.y);
                                if (!m_tooltip.getBounds().contains(mousePoint)) {
                                    disposeTooltip();
                                }
                            }
                        }
                    };
                    getSwtTableViewer().getTable().getDisplay().addFilter(SWT.MouseMove, m_tooltipShellListener);
                    getSwtTableViewer().getTable().getDisplay().addFilter(SWT.MouseExit, m_tooltipShellListener);

                    Listener tooltipLabelListener = new Listener() {

                        @Override
                        public void handleEvent(final Event tooltipLabelEvent) {

                            switch (tooltipLabelEvent.type) {

                            case SWT.MouseDown: {
                                getSwtTableViewer().getTable().setFocus();
                                int button = tooltipLabelEvent.button;
                                if (button == 3 || modelCell.isEditable()) {
                                    disposeTooltip();
                                    if (s_activeMultlineTooltipTable != null
                                            && s_activeMultlineTooltipTable != SwtScoutTable.this) {
                                        s_activeMultlineTooltipTable.m_multiLineTooltipListener.disposeTooltip();
                                    }
                                }

                                if (modelCell.isEditable()) {
                                    Table table = getSwtTableViewer().getTable();
                                    tooltipLabelEvent.widget = table;
                                    Point tooltipEventMousePoint = table
                                            .toControl(getEnvironment().getDisplay().getCursorLocation());
                                    tooltipLabelEvent.x = tooltipEventMousePoint.x;
                                    tooltipLabelEvent.y = tooltipEventMousePoint.y;
                                    getSwtTableViewer().setSelection(new StructuredSelection(row), true);
                                    getSwtTableViewer().getTable().notifyListeners(SWT.MouseDown,
                                            tooltipLabelEvent);
                                }

                                if (!modelCell.isEditable() || button == 3) {
                                    Runnable scoutRun = new Runnable() {

                                        @Override
                                        public void run() {

                                            getScoutObject().selectRow(row);

                                        }

                                    };
                                    getEnvironment().invokeScoutLater(scoutRun, 0);
                                }
                                break;
                            }

                            case SWT.MouseExit: {
                                disposeTooltip();
                                break;
                            }

                            }

                        }

                    };
                    m_tooltipLabel.addListener(SWT.MouseDown, tooltipLabelListener);
                    m_tooltipLabel.addListener(SWT.MouseExit, tooltipLabelListener);

                    Point size = m_tooltip.computeSize(SWT.DEFAULT, SWT.DEFAULT);

                    //Show only if Text is longer than cell bounds.
                    if (cellBounds.width < size.x || cellBounds.height < size.y) {
                        Point pt = getSwtTableViewer().getTable().toDisplay(cellBounds.x - 1, cellBounds.y + 1);

                        // Max width is monitor...
                        Rectangle monitorBounds = m_tooltip.getMonitor().getBounds();
                        if (pt.x + size.x > monitorBounds.width) {
                            size = m_tooltip.computeSize(monitorBounds.width - pt.x, SWT.DEFAULT);
                        }

                        m_tooltip.setBounds(pt.x, pt.y, size.x, size.y);
                        if (s_activeMultlineTooltipTable != null
                                && s_activeMultlineTooltipTable != SwtScoutTable.this) {
                            s_activeMultlineTooltipTable.m_multiLineTooltipListener.disposeTooltip();
                        }
                        s_activeMultlineTooltipTable = SwtScoutTable.this;
                        m_tooltip.setVisible(true);
                        getSwtTableViewer().getTable().forceFocus();
                    }
                }
            }
            }
        }

        /**
         *
         */
        private synchronized void disposeTooltip() {
            if (m_tooltipShellListener != null) {
                getSwtTableViewer().getTable().getDisplay().removeFilter(SWT.MouseMove, m_tooltipShellListener);
                getSwtTableViewer().getTable().getDisplay().removeFilter(SWT.MouseExit, m_tooltipShellListener);
                m_tooltipShellListener = null;
            }
            if (m_tooltipLabel != null && !m_tooltipLabel.isDisposed()) {
                m_tooltipLabel.dispose();
            }
            m_tooltipLabel = null;
            if (m_tooltip != null && !m_tooltip.isDisposed()) {
                m_tooltip.dispose();
            }
            m_tooltip = null;
        }
    }

}