net.karlmartens.ui.widget.Table.java Source code

Java tutorial

Introduction

Here is the source code for net.karlmartens.ui.widget.Table.java

Source

/**
 *   Copyright 2011 Karl Martens
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *       
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 *   net.karlmartens.ui, is a library of UI widgets
 */
package net.karlmartens.ui.widget;

import java.util.Arrays;
import java.util.BitSet;
import java.util.Comparator;

import net.karlmartens.platform.function.Criteria;
import net.karlmartens.platform.function.Function;
import net.karlmartens.platform.util.ArraySupport;
import net.karlmartens.platform.util.NullSafe;
import net.karlmartens.platform.util.NumberStringComparator;
import net.karlmartens.ui.Images;

import org.eclipse.jface.action.GroupMarker;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
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.Menu;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.TypedListener;

import de.kupzog.ktable.KTable;
import de.kupzog.ktable.KTableCellEditor;
import de.kupzog.ktable.KTableCellRenderer;
import de.kupzog.ktable.KTableCellResizeListener;
import de.kupzog.ktable.KTableDefaultModel;
import de.kupzog.ktable.KTableModel;
import de.kupzog.ktable.SWTX;
import de.kupzog.ktable.renderers.DefaultCellRenderer;

public final class Table extends Composite {

    public static final String DATA_COLUMN = "TimeSeriesTable.Column";

    public static final String GROUP_COMMAND = "Table.Group.Command";
    public static final String GROUP_EDIT = "Table.Group.Edit";
    public static final String GROUP_DATA = "Table.Group.Data";
    public static final String GROUP_VISIBLE_COLUMNS = "Table.Group.VisibleColumns";

    public static final int SORT_DESCENDING = -1;
    public static final int SORT_NONE = 0;
    public static final int SORT_ASCENDING = 1;

    private final TableColumnManager _columnManager;
    private final CellSelectionManager _cellSelectionManager;

    private final Image _imageAscending;
    private final Image _imageDecending;
    private final KTableImpl _table;
    private final TableListener _listener;

    private boolean _isActive = true;
    private boolean _showHeader = false;
    private int _rowHeight;

    private int _fixedColumnCount = 0;
    private int _fixedHeaderColumnCount = 0;
    private int _fixedRowCount = 0;
    private int _fixedHeaderRowCount = 0;
    private int _itemCount = 0;
    private TableItem[] _items = new TableItem[0];
    private int _columnCount = 0;
    private TableColumn[] _columns = new TableColumn[0];

    public Table(Composite parent) {
        this(parent, SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI);
    }

    public Table(Composite parent, int style) {
        super(parent, checkStyle(style));
        setLayout(new FillLayout());

        _listener = new TableListener();
        updateFontData();

        _imageAscending = Images.ASCENDING.createImage();
        _imageDecending = Images.DECENDING.createImage();

        _table = new KTableImpl(this, style | SWTX.AUTO_SCROLL | SWTX.MARK_FOCUS_HEADERS);
        _table.setBackground(getBackground());
        _table.setForeground(getForeground());
        _table.setModel(_model);

        updatePreferredSize();

        _cellSelectionManager = new CellSelectionManager(this);
        _columnManager = new TableColumnManager(this, _table);

        final PassthoughEventListener passthroughListener = new PassthoughEventListener(this);
        passthroughListener.addSource(_table);

        hookControls();
    }

    public boolean isFocusControl() {
        checkWidget();
        if (_table.isFocusControl())
            return true;

        return super.isFocusControl();
    }

    @Override
    public Point computeSize(int wHint, int hHint, boolean changed) {
        checkWidget();

        final Rectangle available = getParent().getClientArea();
        final Point preferred = _table.computeSize(wHint, hHint, changed);

        int width = 0;
        if (wHint != SWT.DEFAULT) {
            width = wHint;
        } else {
            width = preferred.x;

            if ((_table.getStyle() & SWT.H_SCROLL) > 0)
                width = Math.min(preferred.x, available.width);
        }

        int height = 0;
        if (hHint != SWT.DEFAULT) {
            height = hHint;
        } else {
            height = preferred.y;

            if ((_table.getStyle() & SWT.V_SCROLL) > 0)
                height = Math.min(preferred.y, available.height);
        }

        return new Point(width, height);
    }

    @Override
    public void setBackground(Color color) {
        super.setBackground(color);
        _table.setBackground(color);
    }

    @Override
    public void setFont(Font font) {
        super.setFont(font);
        updateFontData();
    }

    @Override
    public void setForeground(Color color) {
        super.setForeground(color);
        _table.setForeground(color);
    }

    @Override
    public ScrollBar getHorizontalBar() {
        return _table.getHorizontalBar();
    }

    @Override
    public ScrollBar getVerticalBar() {
        return _table.getVerticalBar();
    }

    @Override
    public boolean setFocus() {
        return _table.forceFocus();
    }

    @Override
    public void redraw() {
        checkWidget();
        _table.redraw();
    }

    public void setHeaderVisible(boolean show) {
        checkWidget();
        _showHeader = show;
        redraw();
    }

    public int getFixedColumnCount() {
        checkWidget();
        return _fixedColumnCount;
    }

    public void setFixedColumnCount(int count) {
        checkWidget();
        if (count < 0)
            SWT.error(SWT.ERROR_INVALID_RANGE);

        _fixedColumnCount = count;

        updatePreferredSize();
        redraw();
    }

    public int getFixedHeaderColumnCount() {
        checkWidget();
        return _fixedHeaderColumnCount;
    }

    public void setFixedHeaderColumnCount(int count) {
        checkWidget();
        if (count < 0)
            SWT.error(SWT.ERROR_INVALID_RANGE);

        if (_fixedColumnCount < count)
            setFixedColumnCount(count);

        _fixedHeaderColumnCount = count;

        updatePreferredSize();
        redraw();
    }

    public int getFixedRowCount() {
        checkWidget();
        return _fixedRowCount;
    }

    public void setFixedRowCount(int count) {
        checkWidget();
        if (count < 0)
            SWT.error(SWT.ERROR_INVALID_RANGE);

        _fixedRowCount = count;

        updatePreferredSize();
        redraw();
    }

    public int getFixedHeaderRowCount() {
        checkWidget();
        return _fixedHeaderRowCount;
    }

    public void setFixedHeaderRowCount(int count) {
        checkWidget();
        if (count < 0)
            SWT.error(SWT.ERROR_INVALID_RANGE);

        if (_fixedRowCount < count)
            setFixedRowCount(count);

        _fixedHeaderRowCount = count;

        updatePreferredSize();
        redraw();
    }

    public int getItemCount() {
        checkWidget();
        return _itemCount;
    }

    private int _lastIndexOf = -1;

    public int indexOf(TableItem item) {
        checkWidget();
        checkNull(item);

        if (_lastIndexOf >= 1 && _lastIndexOf < _itemCount - 1) {
            if (_items[_lastIndexOf] == item)
                return _lastIndexOf;
            if (_items[_lastIndexOf + 1] == item)
                return ++_lastIndexOf;
            if (_items[_lastIndexOf - 1] == item)
                return --_lastIndexOf;
        }

        if (_lastIndexOf < _itemCount / 2) {
            for (int i = 0; i < _itemCount; i++) {
                if (_items[i] == item) {
                    _lastIndexOf = i;
                    return i;
                }
            }
        } else {
            for (int i = _itemCount - 1; i >= 0; i--) {
                if (_items[i] == item) {
                    _lastIndexOf = i;
                    return i;
                }
            }
        }

        return -1;
    }

    public TableItem getItem(int index) {
        checkWidget();
        checkRowIndex(index);
        return _items[index];
    }

    public TableItem getItem(Point point) {
        checkWidget();
        checkNull(point);

        final Point dPoint = this.toDisplay(point);
        final Point tPoint = _table.toControl(dPoint);
        final Point cell = _table.getCellForCoordinates(tPoint.x, tPoint.y);
        final int row = computeRow(cell.y);
        if (row < 0)
            return null;

        return _items[row];
    }

    public TableItem[] getItems() {
        checkWidget();
        final TableItem[] items = new TableItem[_itemCount];
        System.arraycopy(_items, 0, items, 0, items.length);
        return items;
    }

    public int getColumnCount() {
        checkWidget();
        return _columnCount;
    }

    public int indexOf(TableColumn column) {
        checkWidget();
        checkNull(column);

        for (int i = 0; i < _columnCount; i++) {
            if (_columns[i] == column) {
                return i;
            }
        }

        return -1;
    }

    public TableColumn getColumn(int index) {
        checkWidget();
        checkColumnIndex(index);
        return _columns[index];
    }

    public int[] getSelectionIndices() {
        checkWidget();

        final BitSet selectedRows = new BitSet();
        for (Point selection : _table.getCellSelection()) {
            if (_showHeader && selection.y < _table.getModel().getFixedHeaderRowCount())
                continue;

            selectedRows.set(computeRow(selection.y));
        }

        return ArraySupport.toArray(selectedRows);
    }

    public TableItem[] getSelection() {
        checkWidget();

        final int[] indices = getSelectionIndices();
        final TableItem[] selected = new TableItem[indices.length];
        for (int i = 0; i < indices.length; i++) {
            selected[i] = _items[indices[i]];
        }

        return selected;
    }

    public Rectangle getVisibleScrollableCells() {
        final KTableModel model = _table.getModel();

        final Rectangle rect = _table.getVisibleCells();
        final Point tableTopLeft = new Point(rect.x, rect.y);
        rect.y = computeRow(rect.y);

        // Required because KTable report more visible column when scrolled
        // all the way to the right.
        final int hCorrection = Math.min(_columnCount - (rect.x + rect.width), 0);
        rect.width += hCorrection;

        // Adjust height for fully visible rows
        final int rows = _table.getVisibleRowCount() - model.getFixedHeaderRowCount()
                - model.getFixedSelectableRowCount();
        rect.height = Math.min(rows, rect.height);

        // Adjust height for fully visible columns
        while (!_table.isCellFullyVisible(tableTopLeft.x + rect.width - 1, tableTopLeft.y + rect.height - 1)
                && rect.width > 0) {
            rect.width--;
        }

        return rect;
    }

    public void deselectAll() {
        checkWidget();
        _table.clearSelection();
        notifyListeners(SWT.Selection, new Event());
    }

    public void setSelection(TableItem[] items) {
        checkWidget();
        checkNull(items);

        final int[] indices = new int[items.length];
        int i = 0;
        for (TableItem item : items) {
            final int index = indexOf(item);
            if (index < 0)
                continue;

            indices[i++] = index;
        }

        final int[] result = new int[i];
        System.arraycopy(indices, 0, result, 0, i);
        setSelection(result);
    }

    public void setSelection(int[] indices) {
        checkWidget();
        checkNull(indices);

        final int width = _columnCount;
        final Point[] selections = new Point[indices.length * width];
        for (int i = 0; i < indices.length; i++) {
            for (int j = 0; j < width; j++) {
                selections[i * width + j] = new Point(j, indices[i]);
            }
        }

        setCellSelections(selections);
    }

    public void select(int[] indices) {
        checkWidget();
        checkNull(indices);

        final BitSet selected = new BitSet();
        for (int index : getSelectionIndices()) {
            selected.set(index);
        }

        for (int index : indices) {
            selected.set(index);
        }

        final int[] newSelection = new int[selected.cardinality()];
        int index = 0;
        for (int i = selected.nextSetBit(0); i >= 0; i = selected.nextSetBit(i + 1)) {
            newSelection[index++] = i;
        }
        setSelection(newSelection);
    }

    public void selectAll() {
        checkWidget();
        _cellSelectionManager.selectAll();
    }

    public Point[] getCellSelections() {
        checkWidget();
        final Point[] pts = _table.getCellSelection();
        final Point[] selection = new Point[pts.length];
        int index = 0;
        for (int i = 0; i < selection.length; i++) {
            final Point pt = pts[i];
            final Point selectionPt = new Point(pt.x, computeRow(pt.y));
            final TableColumn column = getColumn(selectionPt.x);
            final TableItem item = getItem(selectionPt.y);
            if (!column.isVisible() || !item.isVisible())
                continue;

            selection[index++] = selectionPt;
        }

        return Arrays.copyOf(selection, index);
    }

    public Point getFocusCell() {
        checkWidget();
        return _cellSelectionManager.getFocusCell();
    }

    public void setFocusCell(Point cell, boolean multi) {
        checkWidget();
        _cellSelectionManager.setFocusCell(cell, multi);
        setFocus();
    }

    public void setCellSelections(Point[] selected) {
        checkWidget();
        checkNull(selected);

        final BitSet previousRows = rowSelectionSet(_table.getCellSelection());

        final Point[] ktableCellSelection = new Point[selected.length];
        int i = 0;
        for (Point pt : selected) {
            ktableCellSelection[i++] = new Point(pt.x, computeKTableRow(pt.y));
        }

        _table.clearSelection();
        _table.setSelection(ktableCellSelection, false);
        _table.redraw();

        final BitSet currentRows = rowSelectionSet(ktableCellSelection);
        final BitSet changedRows = (BitSet) previousRows.clone();
        changedRows.xor(currentRows);
        if (!changedRows.isEmpty())
            notifyListeners(SWT.Selection, new Event());
    }

    private BitSet rowSelectionSet(Point[] pts) {
        final BitSet set = new BitSet(_itemCount + 1);
        for (Point pt : pts) {
            set.set(pt.y);
        }
        return set;
    }

    public void showSelection() {
        checkWidget();
        final TableItem[] items = getSelection();
        if (items.length == 0)
            return;

        showItem(items[0]);
    }

    public void showItem(TableItem item) {
        checkWidget();
        checkNull(item);

        final int index = indexOf(item);
        if (index < _fixedRowCount)
            return;

        final Rectangle r = getVisibleScrollableCells();
        if (r.y <= index && (r.y + r.height) > index)
            return;

        if (index < r.y) {
            scroll(new Point(r.x, index));
            return;
        }

        scroll(new Point(r.x, index - r.height + 1));
    }

    public void showColumn(int index) {
        checkWidget();
        checkColumnIndex(index);

        if (index < _fixedColumnCount)
            return;

        final Rectangle r = getVisibleScrollableCells();
        if (r.x <= index && (r.x + r.width) > index)
            return;

        if (index < r.x) {
            scroll(new Point(index, r.y));
            return;
        }

        scroll(new Point(index - r.width + 1, r.y));
    }

    public void scroll(Point cell) {
        checkWidget();
        checkNull(cell);

        _table.scroll(cell.x, computeKTableRow(cell.y));
    }

    public void setItemCount(int count) {
        checkWidget();
        final int c = Math.max(0, count);
        if (c == _itemCount)
            return;

        if (c > _itemCount) {
            for (int i = _itemCount; i < c; i++) {
                new TableItem(this, i);
            }
            return;
        }

        for (int i = c; i < _itemCount; i++) {
            final TableItem item = _items[i];
            if (item != null && !item.isDisposed())
                item.release();
            _items[i] = null;
        }

        final int length = Math.max(4, (c + 3) / 4 * 4);
        final TableItem[] newItems = new TableItem[length];
        System.arraycopy(_items, 0, newItems, 0, c);
        _items = newItems;
        _itemCount = c;
        updatePreferredSize();
        redraw();
    }

    public void setColumnCount(int count) {
        checkWidget();
        if (count < _fixedColumnCount)
            SWT.error(SWT.ERROR_INVALID_RANGE);

        final int c = Math.max(0, count);
        if (c == _columnCount)
            return;

        if (c > _columnCount) {
            for (int i = _columnCount; i < c; i++) {
                new TableColumn(this, SWT.NONE, i);
            }
            return;
        }

        for (int i = c; i < _columnCount; i++) {
            final TableColumn column = _columns[i];
            if (column != null && !column.isDisposed()) {
                if (i == _lastSortColumnIndex)
                    setSortIndicator(-1, SORT_NONE);

                column.release();
            }

            _columns[i] = null;
        }

        final int length = Math.max(4, (c + 3) / 4 * 4);
        final TableColumn[] newColumns = new TableColumn[length];
        System.arraycopy(_columns, 0, newColumns, 0, c);
        _columns = newColumns;
        _columnCount = c;

        updatePreferredSize();
        redraw();
    }

    public void remove(int start, int end) {
        checkWidget();
        if (start < 0 || start > end || end >= _itemCount)
            SWT.error(SWT.ERROR_INVALID_RANGE);

        for (int i = end; i >= start; i--) {
            doRemove(i);
        }
        redraw();
    }

    public void remove(int[] indices) {
        checkWidget();
        checkNull(indices);
        if (indices.length == 0)
            return;

        final int[] idxs = new int[indices.length];
        System.arraycopy(indices, 0, idxs, 0, idxs.length);
        Arrays.sort(idxs);
        for (int i = idxs.length - 1; i >= 0; i--) {
            doRemove(idxs[i]);
        }
        redraw();
    }

    public void removeAll() {
        checkWidget();
        for (int i = 0; i < _itemCount; i++) {
            _items[i].release();
            _items[i] = null;
        }
        _itemCount = 0;
        updatePreferredSize();
        redraw();
    }

    public void clear(int index) {
        checkWidget();
        checkRowIndex(index);

        _items[index].clear();
        setSortIndicator(-1, SORT_NONE);
        redraw();
    }

    public void clearAll() {
        checkWidget();
        for (int i = 0; i < _itemCount; i++) {
            _items[i].clear();
        }
        setSortIndicator(-1, SORT_NONE);
        redraw();
    }

    public void moveColumn(int fromIndex, int toIndex) {
        checkWidget();
        checkColumnIndex(fromIndex);
        checkColumnIndex(toIndex);
        if (fromIndex == toIndex)
            return;

        if (!_columns[fromIndex].isMoveable() || !_columns[toIndex].isMoveable())
            return;

        final TableColumn t = _columns[fromIndex];
        _columns[fromIndex] = _columns[toIndex];
        _columns[toIndex] = t;

        for (int i = 0; i < _itemCount; i++) {
            _items[i].swapColumns(fromIndex, toIndex);
        }

        if (_lastSortColumnIndex == fromIndex) {
            _lastSortColumnIndex = toIndex;
        } else if (_lastSortColumnIndex == toIndex) {
            _lastSortColumnIndex = fromIndex;
        }

        _columns[fromIndex].notifyListeners(SWT.Move, new Event());
        _columns[toIndex].notifyListeners(SWT.Move, new Event());

        redraw();
    }

    public void sort(int index) {
        checkWidget();
        checkColumnIndex(index);

        final int firstRow = Math.max(0, Math.min(_fixedRowCount, _items.length));
        if (_itemCount <= firstRow + 1)
            return;

        if (index != _lastSortColumnIndex || _lastSortDirection == SORT_NONE) {
            sort(index, SORT_ASCENDING);
            return;
        }

        sort(index, _lastSortDirection * -1);
    }

    public void sort(int index, int direction) {
        checkWidget();
        checkColumnIndex(index);
        if (direction != SORT_ASCENDING && direction != SORT_DESCENDING)
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);

        final int firstRow = Math.max(0, Math.min(_fixedRowCount, _items.length));
        if (_itemCount <= firstRow + 1)
            return;

        final TableItem[] newItems = Arrays.copyOf(_items, _items.length);
        final Comparator<TableItem> comparator = new TableItemComparator(new NumberStringComparator(), index,
                direction);
        Arrays.sort(newItems, firstRow, _itemCount, comparator);

        _items = newItems;
        setSortIndicator(index, direction);
        notifyListeners(SWT.Selection, new Event());
        redraw();
    }

    private IMenuManager _menuManager;

    public IMenuManager getMenuManager() {
        if (_menuManager == null) {
            final MenuManager mm = new MenuManager();
            mm.add(new GroupMarker(GROUP_COMMAND));
            mm.add(new Separator(GROUP_EDIT));
            mm.add(new Separator(GROUP_DATA));

            setMenu(mm.createContextMenu(this));
            _menuManager = mm;
        }
        return _menuManager;
    }

    public void setMenu(Menu menu) {
        checkWidget();
        _table.setMenu(menu);
    }

    public void addSelectionListener(SelectionListener listener) {
        checkWidget();
        checkNull(listener);

        final TypedListener tListener = new TypedListener(listener);
        addListener(SWT.Selection, tListener);
        addListener(SWT.DefaultSelection, tListener);
    }

    public void removeSelectionListener(SelectionListener listener) {
        checkWidget();
        checkNull(listener);

        final TypedListener tListener = new TypedListener(listener);
        removeListener(SWT.Selection, tListener);
        removeListener(SWT.DefaultSelection, tListener);
    }

    public void addColumnSortSupport() {
        _columnManager.enableColumnSort();
    }

    public void retainSelection(Runnable r) {
        _cellSelectionManager.retainSelection(r);
    }

    void updateFilteredItems() {
        Function<TableItem, Boolean> filter = Criteria.all();
        for (int i = 0; i < _columnCount; i++) {
            final Function<TableItem, Boolean> oFilter = _columns[i].getFilter();
            if (oFilter == null)
                continue;

            filter = Criteria.and(filter, oFilter);
        }

        for (int i = 0; i < _itemCount; i++) {
            final TableItem item = _items[i];
            item.setVisible(i < getFixedRowCount() || filter.apply(item));
        }
        redraw();
    }

    void createItem(TableColumn item, int index) {
        checkWidget();
        if (index < 0 || index > _columnCount)
            SWT.error(SWT.ERROR_INVALID_RANGE);

        if (_columns.length == _columnCount) {
            final TableColumn[] newColumns = new TableColumn[_columns.length + 4];
            System.arraycopy(_columns, 0, newColumns, 0, _columns.length);
            _columns = newColumns;
        }

        System.arraycopy(_columns, index, _columns, index + 1, _columnCount++ - index);
        _columns[index] = item;

        if (index == _lastSortColumnIndex)
            _lastSortColumnIndex++;

        updatePreferredSize();
    }

    void createItem(TableItem item, int index) {
        checkWidget();
        if (index < 0 || index > _itemCount)
            SWT.error(SWT.ERROR_INVALID_RANGE);

        if (_items.length == _itemCount) {
            final int length = Math.max(4, _items.length * 3 / 2);
            final TableItem[] newItems = new TableItem[length];
            System.arraycopy(_items, 0, newItems, 0, _items.length);
            _items = newItems;
        }

        System.arraycopy(_items, index, _items, index + 1, _itemCount++ - index);
        _items[index] = item;
        setSortIndicator(-1, SORT_NONE);
        updatePreferredSize();
    }

    Rectangle getBounds(TableItem item, int index) {
        checkWidget();
        checkNull(item);
        checkColumnIndex(index);

        final int mRow = indexOf(item);
        final int tRow = computeKTableRow(mRow);
        final Rectangle r = _table.getCellRect(index, tRow);
        final Point dPoint = _table.toDisplay(r.x, r.y);
        final Point pt = this.toControl(dPoint);
        return new Rectangle(pt.x, pt.y, r.width, r.height);
    }

    Rectangle getBounds(TableItem item) {
        checkWidget();
        checkNull(item);

        final int mRow = indexOf(item);
        final int tRow = computeKTableRow(mRow);
        Rectangle bounds = null;
        for (int i = 0; i < _columnCount; i++) {
            final Rectangle r = _table.getCellRect(i, tRow);
            if (bounds == null) {
                final Point dPoint = _table.toDisplay(r.x, r.y);
                final Point pt = this.toControl(dPoint);
                bounds = new Rectangle(pt.x, pt.y, 0, 0);
            }

            bounds.width += r.width;
            bounds.height = Math.max(bounds.height, r.height);
        }

        return bounds;
    }

    Rectangle getImageBounds(TableItem item, int index) {
        checkWidget();
        checkNull(item);
        checkColumnIndex(index);

        final Rectangle r = getBounds(item, index);
        r.width = 0;
        r.height = 0;

        final Image image = item.getImage(index);
        if (image == null)
            return r;

        final Rectangle imageBounds = image.getBounds();
        r.width = imageBounds.width;
        r.height = imageBounds.height;
        return r;
    }

    Composite getTableComposite() {
        checkWidget();
        return _table;
    }

    private Image _previousSortImage = null;
    private int _lastSortColumnIndex = -1;
    private int _lastSortDirection = SORT_NONE;

    void setSortIndicator(int index, int direction) {
        if (_lastSortColumnIndex >= 0 && _lastSortColumnIndex < _columnCount) {
            final TableColumn column = getColumn(_lastSortColumnIndex);
            column.setImage(_previousSortImage);
        }

        _previousSortImage = null;
        _lastSortColumnIndex = -1;
        _lastSortDirection = SORT_NONE;
        if (index < 0 || index >= _columnCount || (direction != SORT_ASCENDING && direction != SORT_DESCENDING))
            return;

        final TableColumn column = getColumn(index);
        _previousSortImage = column.getImage();
        _lastSortColumnIndex = index;
        _lastSortDirection = direction;

        final Image indicator;
        if (direction == SORT_ASCENDING) {
            indicator = _imageAscending;
        } else {
            indicator = _imageDecending;
        }
        column.setImage(indicator);
    }

    private void hookControls() {
        _table.addCellResizeListener(_listener);
        _table.addPaintListener(_listener);
        _table.addFocusListener(_listener);

        addControlListener(_listener);
        addPaintListener(_listener);
        addDisposeListener(_listener);
    }

    private void releaseControls() {
        _table.removeCellResizeListener(_listener);
        _table.removePaintListener(_listener);
        _table.removeFocusListener(_listener);

        removeControlListener(_listener);
        removePaintListener(_listener);
        removeDisposeListener(_listener);
    }

    private void doRemove(int index) {
        _items[index].release();

        System.arraycopy(_items, index + 1, _items, index, --_itemCount - index);
        _items[_itemCount] = null;
        updatePreferredSize();
    }

    private void updateFontData() {
        final GC gc = new GC(getShell());
        gc.setFont(getFont());
        _rowHeight = gc.getFontMetrics().getHeight() + 6;
        gc.dispose();
    }

    private void updatePreferredSize() {
        final int columns = Math.max(0, _columnCount);
        _table.setNumColsVisibleInPreferredSize(columns);
        _table.setNumRowsVisibleInPreferredSize(_itemCount);
    }

    private int computeRow(int ktableRow) {
        if (_showHeader)
            return ktableRow - _table.getModel().getFixedHeaderRowCount();

        return ktableRow;
    }

    private int computeKTableRow(int row) {
        if (_showHeader)
            return row + _table.getModel().getFixedHeaderRowCount();

        return row;
    }

    private static int checkStyle(int style) {
        final int mask = SWT.BORDER | SWT.MULTI;
        return style & mask;
    }

    private void checkColumnIndex(int index) {
        if (index < 0 || index >= _columnCount)
            SWT.error(SWT.ERROR_INVALID_RANGE);
    }

    private void checkRowIndex(int index) {
        if (index < 0 || index >= _itemCount)
            SWT.error(SWT.ERROR_INVALID_RANGE);
    }

    private void checkNull(Object o) {
        if (o == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
    }

    private final KTableModel _model = new KTableDefaultModel() {

        @Override
        public int getFixedHeaderRowCount() {
            return (_showHeader ? 1 : 0) + _fixedHeaderRowCount;
        }

        @Override
        public int getFixedSelectableRowCount() {
            return _fixedRowCount - _fixedHeaderRowCount;
        }

        @Override
        public int getFixedHeaderColumnCount() {
            return _fixedHeaderColumnCount;
        }

        @Override
        public int getFixedSelectableColumnCount() {
            return _fixedColumnCount - _fixedHeaderColumnCount;
        }

        @Override
        public boolean isColumnResizable(int col) {
            return getColumn(col).isVisible();
        }

        @Override
        public boolean isRowResizable(int row) {
            return false;
        }

        @Override
        public int getRowHeightMinimum() {
            return 0;
        }

        public int getColumnWidth(int col) {
            if (col < 0 || col >= _columnCount)
                return 0;

            final TableColumn column = getColumn(col);
            if (!column.isVisible())
                return 0;

            return column.getWidth();
        }

        public void setColumnWidth(int col, int value) {
            if (!isColumnResizable(col))
                return;

            final TableColumn column = getColumn(col);
            column.setWidth(value);
        }

        @Override
        public int getInitialColumnWidth(int column) {
            throw new UnsupportedOperationException();
        }

        @Override
        public int getInitialRowHeight(int row) {
            return _rowHeight;
        }

        public int getRowHeight(int row) {
            if (row == 0 && _showHeader)
                return _rowHeight;

            final int rIndex = computeRow(row);
            if (rIndex < 0 || rIndex >= _itemCount)
                return 0;

            final TableItem item = getItem(rIndex);
            if (item.isVisible()) {
                int height = _rowHeight;
                for (int i = 0; i < _columnCount; i++) {
                    final Image image = item.getImage(i);
                    if (image == null)
                        continue;

                    height = Math.max(height, image.getImageData().height + 4);
                }

                return height;
            }

            return 0;
        }

        @Override
        public Object doGetContentAt(int col, int row) {
            // This seems weird but it a KTable behaviour to ask for data that
            // doesn't exist.
            if (col < 0 || col >= _columnCount)
                return "";

            final TableColumn column = getColumn(col);
            if (_showHeader && row == 0)
                return column.getText();

            final int modelRow = computeRow(row);
            if (modelRow < 0 || modelRow >= _itemCount)
                return "";

            final TableItem item = getItem(modelRow);
            final String text = item.getText(col);
            if ((SWT.CHECK & column.getStyle()) > 0)
                return Boolean.valueOf(text);

            if (text == null)
                return "";

            return text;
        }

        @Override
        public KTableCellEditor doGetCellEditor(int col, int row) {
            // Not used
            return null;
        }

        @Override
        public void doSetContentAt(int col, int row, Object value) {
            // Not used
        }

        private final ImageFixedCellRenderer _headerRenderer = new ImageFixedCellRenderer(
                SWT.BOLD | DefaultCellRenderer.INDICATION_FOCUS_ROW);
        private final FixedCellRenderer _altHeaderRenderer = new FixedCellRenderer(
                SWT.BOLD | DefaultCellRenderer.INDICATION_FOCUS_ROW | DefaultCellRenderer.STYLE_FLAT);
        private final TextCellRenderer _renderer = new TextCellRenderer(DefaultCellRenderer.INDICATION_FOCUS);
        private final CheckableCellRenderer _checkRenderer = new CheckableCellRenderer(
                DefaultCellRenderer.INDICATION_FOCUS);
        private final HiddenCellRenderer _hiddenRenderer = new HiddenCellRenderer();

        @Override
        public KTableCellRenderer doGetCellRenderer(int col, int row) {
            if (_showHeader && row == 0) {
                _headerRenderer.setDefaultBackground(getBackground());
                _headerRenderer.setDefaultForeground(getForeground());
                _headerRenderer.setFont(getFont());
                _headerRenderer.setImage(null);
                _headerRenderer.setActive(_isActive);
                _headerRenderer.setFiltered(false);

                if (col < 0 || col >= _columnCount)
                    return _headerRenderer;

                final TableColumn column = getColumn(col);
                _headerRenderer.setFiltered(column.getFilter() != null);
                _headerRenderer.setImage(column.getImage());
                return _headerRenderer;
            }

            if (col < 0 || col >= _columnCount)
                return _hiddenRenderer;

            final TableColumn column = getColumn(col);
            if (!column.isVisible())
                return _hiddenRenderer;

            final int modelRow = computeRow(row);
            final TableItem item = getItem(modelRow);
            if (!item.isVisible())
                return _hiddenRenderer;

            final DefaultCellRenderer renderer;
            if (row < getFixedHeaderRowCount() || col < getFixedHeaderColumnCount()) {
                renderer = _altHeaderRenderer;
                _altHeaderRenderer.setActive(_isActive);
                _altHeaderRenderer.setImage(item.getImage(col));

                int alignment = SWTX.ALIGN_VERTICAL_CENTER;
                if (row < getFixedHeaderRowCount()) {
                    alignment |= SWTX.ALIGN_HORIZONTAL_CENTER;
                } else {
                    alignment |= SWTX.ALIGN_HORIZONTAL_LEFT;
                }
                renderer.setAlignment(alignment);
            } else if ((SWT.CHECK & column.getStyle()) > 0) {
                renderer = _checkRenderer;
                _checkRenderer.setActive(_isActive);
                renderer.setAlignment(SWTX.ALIGN_HORIZONTAL_CENTER | SWTX.ALIGN_VERTICAL_CENTER);
            } else {
                renderer = _renderer;
                _renderer.setActive(_isActive);
                _renderer.setImage(item.getImage(col));
                renderer.setAlignment(SWTX.ALIGN_HORIZONTAL_LEFT | SWTX.ALIGN_VERTICAL_CENTER);
            }

            applyStyle(column.getStyle(), renderer);

            if (modelRow < 0 || modelRow >= _itemCount)
                return renderer;

            renderer.setDefaultBackground(item.getBackground(col));
            renderer.setDefaultForeground(item.getForeground(col));
            renderer.setFont(item.getFont(col));
            applyStyle(item.getStyle(col), renderer);

            return renderer;
        }

        private void applyStyle(int style, DefaultCellRenderer renderer) {
            if ((style & SWT.LEFT) > 0) {
                renderer.setAlignment(SWTX.ALIGN_HORIZONTAL_LEFT | SWTX.ALIGN_VERTICAL_CENTER);
            } else if ((style & SWT.RIGHT) > 0) {
                renderer.setAlignment(SWTX.ALIGN_HORIZONTAL_RIGHT | SWTX.ALIGN_VERTICAL_CENTER);
            } else if ((style & SWT.CENTER) > 0) {
                renderer.setAlignment(SWTX.ALIGN_HORIZONTAL_CENTER | SWTX.ALIGN_VERTICAL_CENTER);
            }
        }

        @Override
        public int doGetRowCount() {
            final int rows = Math.max(0, _itemCount - _fixedRowCount);
            return getFixedRowCount() + rows;
        }

        @Override
        public int doGetColumnCount() {
            return _columnCount;
        }

        public Point doBelongsToCell(int col, int row) {
            if (col >= _fixedHeaderColumnCount && row >= _fixedHeaderRowCount)
                return super.doBelongsToCell(col, row);

            if (col <= 0 || row < 0)
                return super.doBelongsToCell(col, row);

            final Point pt = new Point(col, row);
            while (pt.x > 0) {
                for (int y = pt.y; y >= 0; y--) {
                    final Object o1 = doGetContentAt(pt.x, y);
                    final Object o2 = doGetContentAt(pt.x - 1, y);
                    if (!NullSafe.equals(o1, o2))
                        return pt;
                }

                pt.x--;
            }

            return pt;
        }
    };

    private final class TableListener
            implements ControlListener, KTableCellResizeListener, PaintListener, DisposeListener, FocusListener {

        @Override
        public void controlMoved(ControlEvent e) {
            // Ignore
        }

        @Override
        public void focusLost(FocusEvent e) {
            redraw();
        }

        @Override
        public void focusGained(FocusEvent e) {
            redraw();
        }

        @Override
        public void controlResized(ControlEvent e) {
            if (e.getSource() == Table.this) {
                final Rectangle ca = getClientArea();
                _table.setBounds(0, 0, ca.width, ca.height);
                redraw();
            }
        }

        @Override
        public void paintControl(PaintEvent e) {
            // Nothing to do
        }

        @Override
        public void columnResized(int col, int newWidth) {
            // Ignore
        }

        @Override
        public void rowResized(int row, int newHeight) {
            _items[computeRow(row)].notifyListeners(SWT.Resize, new Event());
        }

        @Override
        public void widgetDisposed(DisposeEvent e) {
            releaseControls();

            _imageAscending.dispose();
            _imageDecending.dispose();

            if (_menuManager != null) {
                _menuManager.dispose();
                _menuManager = null;
            }
        }
    }

    final class KTableImpl extends KTable {

        private boolean _ignoreMouseMove = false;

        private KTableImpl(Composite parent, int style) {
            super(parent, style);
        }

        public boolean isFocusControl() {
            return super.isFocusControl();
        }

        @Override
        public boolean setFocus() {
            return _table.forceFocus();
        }

        void setIgnoreMouseMove(boolean b) {
            _ignoreMouseMove = b;
        }

        @Override
        protected void onMouseMove(MouseEvent e) {
            if (_ignoreMouseMove)
                return;

            super.onMouseMove(e);
        }

        @Override
        protected void onMouseDoubleClick(MouseEvent e) {
            // Disable default double click event handling
        }

        @Override
        protected void onMouseDown(MouseEvent e) {
            // Disable default event handling

            if (e.button == 1) {
                setCapture(true);
                m_Capture = true;

                // Resize column?
                int columnIndex = getColumnForResize(e.x, e.y);
                if (columnIndex >= 0) {
                    m_ResizeColumnIndex = columnIndex;
                    m_ResizeColumnLeft = getColumnLeft(columnIndex);
                    return;
                }
            }
        }

        @Override
        protected void onKeyDown(KeyEvent e) {
            // Disable default even handling
        }

        @Override
        protected void doCalculations() {
            super.doCalculations();

            _isActive = isFocusControl();
            for (Control control : getChildren()) {
                if (_isActive)
                    break;

                _isActive |= control.isFocusControl();
            }
        }

        @Override
        public Point computeSize(int wHint, int hHint, boolean changed) {
            // This was duplicated from the KTable class because their implementation
            // had a defect that would always ask for the fixedHeaderColumnsWidth
            // when computing the desired table width.

            // start with margins
            int height = 1;
            int width = 1;

            if (m_Model != null) {
                // Determine height of header rows
                for (int i = 0; i < m_Model.getFixedHeaderRowCount(); i++) {
                    height += m_Model.getRowHeight(i);
                }

                // Add height of data rows to display
                int rowsVisible = 0;
                for (int i = m_Model.getFixedHeaderRowCount(); i < m_Model.getFixedHeaderRowCount()
                        + m_numRowsVisibleInPreferredSize && i < m_Model.getRowCount(); i++) {
                    height += m_Model.getRowHeight(i);
                    rowsVisible++;
                }

                // Make sure that there is room for m_numRowsVisibleInPreferredSize
                // rows, even if there are not that
                // many data rows currently available
                for (int i = rowsVisible; i < m_numRowsVisibleInPreferredSize; i++) {
                    height += m_preferredSizeDefaultRowHeight;
                }

                // Determine width of header columns
                for (int i = 0; i < m_Model.getFixedHeaderColumnCount(); i++) {
                    width += m_Model.getColumnWidth(i);
                }

                // Add width of data columns to display
                for (int i = m_Model.getFixedHeaderColumnCount(); i < m_Model.getFixedHeaderColumnCount()
                        + m_numColsVisibleInPreferredSize && i < m_Model.getColumnCount(); i++) {
                    width += m_Model.getColumnWidth(i);
                }
            }

            // Take scrollbars into account
            if (getHorizontalBar() != null) {
                height += getHorizontalBar().getSize().y;
            }
            if (getVerticalBar() != null) {
                width += getVerticalBar().getSize().x;
            }

            width += 2;

            return new Point(width, height);
        }

        public void redraw() {
            updateScrollbarVisibility();
            super.redraw();
        }

        protected void drawBottomSpace(GC gc) {
            Rectangle r = getClientArea();
            if (m_Model.getRowCount() > 0) {
                r.y += 1;

                for (int i = 0; i < getFixedRowCount(); i++)
                    r.y += m_Model.getRowHeight(i);

                for (int i = 0; i < m_RowsVisible; i++)
                    r.y += m_Model.getRowHeight(i + m_TopRow);
            }

            int lastColRight = getColumnRight(
                    Math.min(m_LeftColumn + m_ColumnsVisible, m_Model.getColumnCount() - 1));

            // draw simple background colored areas
            gc.setBackground(getBackground());
            gc.fillRectangle(r);
            gc.fillRectangle(lastColRight + 2, 0, r.width, r.height);

            gc.setForeground(m_Display.getSystemColor(SWT.COLOR_WHITE));
            gc.drawLine(1, r.y, lastColRight + 1, r.y);
            gc.drawLine(lastColRight + 1, 0, lastColRight + 1, r.y - 1);

            // draw left and top border line:
            if (m_Model.getRowCount() > 0) {
                if ((getStyle() & SWT.FLAT) == 0)
                    gc.setForeground(m_Display.getSystemColor(SWT.COLOR_WIDGET_DARK_SHADOW));
                else
                    gc.setForeground(m_Display.getSystemColor(SWT.COLOR_LIST_BACKGROUND));
                gc.drawLine(0, 0, 0, r.y - 1);
                gc.drawLine(0, 0, lastColRight, 0);
            }
        }
    }
}