/******************************************************************************* * Copyright (c) 2007, 2014 compeople AG and others. * 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: * compeople AG - initial API and implementation *******************************************************************************/ package org.eclipse.riena.internal.ui.ridgets.swt; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.viewers.ColumnLayoutData; import org.eclipse.jface.viewers.ColumnPixelData; import org.eclipse.jface.viewers.ColumnWeightData; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.swt.SWT; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Item; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.riena.ui.common.ISortableByColumn; import org.eclipse.riena.ui.ridgets.IColumnFormatter; import org.eclipse.riena.ui.ridgets.IGroupedTreeTableRidget; import org.eclipse.riena.ui.ridgets.listener.ClickEvent; import org.eclipse.riena.ui.ridgets.swt.ColumnFormatter; import org.eclipse.riena.ui.ridgets.swt.SortableComparator; import org.eclipse.riena.ui.swt.facades.SWTFacade; import org.eclipse.riena.ui.swt.utils.SwtUtilities; /** * Ridget for SWT @link {@link Tree} widgets, that shows a tree with multiple columns. */ public class TreeTableRidget extends TreeRidget implements IGroupedTreeTableRidget { private static final Listener GROUPED_ITEM_ERASER = new GroupedTableEraseListener(); private final ColumnSortListener sortListener; private boolean isSortedAscending; private int sortedColumn; private final Map<Integer, Boolean> sortableColumnsMap; private final Map<Integer, Comparator<Object>> comparatorMap; private final Map<Integer, IColumnFormatter> formatterMap; private ColumnLayoutData[] columnWidths; private boolean isGroupingEnabled; public TreeTableRidget() { sortListener = new ColumnSortListener(); isSortedAscending = true; sortedColumn = -1; sortableColumnsMap = new HashMap<Integer, Boolean>(); comparatorMap = new HashMap<Integer, Comparator<Object>>(); formatterMap = new HashMap<Integer, IColumnFormatter>(); } @Override protected void bindUIControl() { super.bindUIControl(); final Tree control = getUIControl(); if (control != null) { for (final TreeColumn column : control.getColumns()) { column.addSelectionListener(sortListener); } applyComparator(); applyGrouping(); } } @Override protected ClickEvent createClickEvent(final MouseEvent e) { final Tree tree = (Tree) e.widget; final int colIndex = SwtUtilities.findColumn(tree, new Point(e.x, e.y)); // x = 0 gets us an item even not using SWT.FULL_SELECTION final Item item = tree.getItem(new Point(0, e.y)); final Object rowData = item != null ? item.getData() : null; final ClickEvent event = new ClickEvent(this, e.button, colIndex, rowData); return event; } @Override protected void unbindUIControl() { super.unbindUIControl(); final Tree control = getUIControl(); if (control != null) { for (final TreeColumn column : control.getColumns()) { column.removeSelectionListener(sortListener); } final SWTFacade facade = SWTFacade.getDefault(); facade.removeEraseItemListener(control, GROUPED_ITEM_ERASER); } } @Override protected IColumnFormatter[] getColumnFormatters(final int numColumns) { Assert.isLegal(numColumns >= 0); final IColumnFormatter[] result = new IColumnFormatter[numColumns]; for (int i = 0; i < numColumns; i++) { final IColumnFormatter columnFormatter = formatterMap.get(Integer.valueOf(i)); if (columnFormatter != null) { result[i] = columnFormatter; } } return result; } @Override protected void applyColumnWidths(final Tree control) { ColumnUtils.applyColumnWidths(control, columnWidths); } // ITreeTableRidget methods // ///////////////////////// public void bindToModel(final Object[] treeRoots, final Class<? extends Object> treeElementClass, final String childrenAccessor, final String parentAccessor, final String[] valueAccessors, final String[] columnHeaders) { final String noEnablementAccessor = null; final String noVisibilityAccessor = null; final String noImageAccessor = null; final String noOpenImageAccessor = null; super.bindToModel(treeRoots, treeElementClass, childrenAccessor, parentAccessor, valueAccessors, columnHeaders, noEnablementAccessor, noVisibilityAccessor, noImageAccessor, noOpenImageAccessor); } // IGroupedTableRidget methods // //////////////////////////// public boolean isGroupingEnabled() { return isGroupingEnabled; } public void setGroupingEnabled(final boolean grouping) { final boolean oldValue = isGroupingEnabled; isGroupingEnabled = grouping; if (oldValue != isGroupingEnabled) { firePropertyChange(IGroupedTreeTableRidget.PROPERTY_GROUPING_ENABLED, oldValue, isGroupingEnabled); applyGrouping(); } } // ISortableByColumn methods // ////////////////////////// public int getSortedColumn() { final boolean isSorted = sortedColumn != -1 && isColumnSortable(sortedColumn); return isSorted ? sortedColumn : -1; } public boolean isColumnSortable(final int columnIndex) { checkColumnRange(columnIndex); boolean result = false; final Integer key = Integer.valueOf(columnIndex); final Boolean sortable = sortableColumnsMap.get(columnIndex); if (sortable == null || Boolean.TRUE.equals(sortable)) { result = comparatorMap.get(key) != null; } return result; } public boolean isSortedAscending() { return getSortedColumn() != -1 && isSortedAscending; } public void setColumnSortable(final int columnIndex, final boolean sortable) { checkColumnRange(columnIndex); final Integer key = Integer.valueOf(columnIndex); final Boolean newValue = Boolean.valueOf(sortable); Boolean oldValue = sortableColumnsMap.put(key, newValue); if (oldValue == null) { oldValue = Boolean.TRUE; } if (!newValue.equals(oldValue)) { firePropertyChange(ISortableByColumn.PROPERTY_COLUMN_SORTABILITY, null, columnIndex); } } /** * {@inheritDoc} * <p> * Implementation note: if the array is non-null, its elements must be {@link ColumnPixelData} or {@link ColumnWeightData} instances. * * @throws RuntimeException * if an unsupported array element is encountered */ public void setColumnWidths(final Object[] widths) { columnWidths = ColumnUtils.copyWidths(widths); final Tree control = getUIControl(); if (control != null) { applyColumnWidths(control); } } public void setComparator(final int columnIndex, final Comparator<Object> compi) { checkColumnRange(columnIndex); final Integer key = Integer.valueOf(columnIndex); if (compi != null) { comparatorMap.put(key, compi); } else { comparatorMap.remove(key); } if (columnIndex == sortedColumn) { applyComparator(); } } public void setSortedAscending(final boolean ascending) { if (isSortedAscending != ascending) { final boolean oldSortedAscending = isSortedAscending; isSortedAscending = ascending; applyComparator(); firePropertyChange(ISortableByColumn.PROPERTY_SORT_ASCENDING, oldSortedAscending, isSortedAscending); } } public void setSortedColumn(final int columnIndex) { if (columnIndex != -1) { checkColumnRange(columnIndex); } if (sortedColumn != columnIndex) { final int oldSortedColumn = sortedColumn; sortedColumn = columnIndex; applyComparator(); firePropertyChange(ISortableByColumn.PROPERTY_SORTED_COLUMN, oldSortedColumn, sortedColumn); } } public void setColumnFormatter(final int columnIndex, final IColumnFormatter formatter) { checkColumnRange(columnIndex); if (formatter != null) { Assert.isLegal(formatter instanceof ColumnFormatter, "formatter must sublass ColumnFormatter"); //$NON-NLS-1$ } final Integer key = Integer.valueOf(columnIndex); formatterMap.put(key, formatter); } // helping methods // //////////////// private void applyComparator() { final TreeViewer viewer = getViewer(); if (viewer != null) { final Tree tree = viewer.getTree(); tree.setRedraw(false); try { Comparator<Object> compi = null; if (sortedColumn != -1) { final Integer key = Integer.valueOf(sortedColumn); compi = comparatorMap.get(key); } if (compi != null) { final TreeColumn column = tree.getColumn(sortedColumn); tree.setSortColumn(column); final int direction = isSortedAscending ? SWT.UP : SWT.DOWN; tree.setSortDirection(direction); final SortableComparator sortableComparator = new SortableComparator(this, compi); viewer.setComparator(new TableComparator(sortableComparator)); } else { viewer.setComparator(null); tree.setSortColumn(null); tree.setSortDirection(SWT.NONE); } // we have to update the expanded / collapsed icons viewer.refresh(); } finally { tree.setRedraw(true); } } } private void applyGrouping() { final Tree control = getUIControl(); if (control != null) { control.setRedraw(false); try { final SWTFacade facade = SWTFacade.getDefault(); facade.removeEraseItemListener(control, GROUPED_ITEM_ERASER); if (isGroupingEnabled) { facade.addEraseItemListener(control, GROUPED_ITEM_ERASER); } } finally { control.setRedraw(true); control.redraw(); } } } private void checkColumnRange(final int columnIndex) { final Tree tree = getUIControl(); // tree may be null if unbound final int range = tree.getColumnCount(); final String msg = "columnIndex out of range (0 - " + range + " ): " + columnIndex; //$NON-NLS-1$ //$NON-NLS-2$ Assert.isLegal(-1 < columnIndex, msg); Assert.isLegal(columnIndex < range, msg); } // helping classes // //////////////// /** * Selection listener for table headers that changes the sort order of a column according to the information stored in the ridget. */ private final class ColumnSortListener extends SelectionAdapter { @Override public void widgetSelected(final SelectionEvent e) { final TreeColumn column = (TreeColumn) e.widget; final int columnIndex = column.getParent().indexOf(column); final int direction = column.getParent().getSortDirection(); if (columnIndex == sortedColumn) { if (direction == SWT.UP) { setSortedAscending(false); } else if (direction == SWT.DOWN) { setSortedColumn(-1); } } else if (isColumnSortable(columnIndex)) { setSortedColumn(columnIndex); if (direction == SWT.NONE) { setSortedAscending(true); } } column.getParent().showSelection(); } } /** * Listener for EraseItem events that is responsible for greated the "grouped" look in tree tables. * <p> * Implementation note: this works by registering this class an an EraseEListener and indicating we will be repsonsible from drawing the cells content. We * do not register a PaintListener, meaning that we do NOT paint anything. * * @see '<a href= "http://www.eclipse.org/articles/article.php?file=Article-CustomDrawingTableAndTreeItems/index.html" >Custom Drawing Table and Tree * Items</a>' */ private final static class GroupedTableEraseListener implements Listener { /* * Called EXTREMELY frequently. Must be as efficient as possible. */ public void handleEvent(final Event event) { final TreeItem item = (TreeItem) event.item; /* * let SWT draw the cell content if: (a) the item has no children or (b) we are in the first column */ if (item.getItemCount() == 0 || event.index == 0) { return; } // indicate we are responsible for drawing the cell's content event.detail &= ~SWT.FOREGROUND; } } }