| /******************************************************************************* |
| * Copyright (c) 2013, 2020 Dirk Fauth and others. |
| * |
| * This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License 2.0 |
| * which is available at https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Dirk Fauth <dirk.fauth@googlemail.com> - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.nebula.widgets.nattable.examples._500_Layers._504_Viewport; |
| |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.nebula.widgets.nattable.NatTable; |
| import org.eclipse.nebula.widgets.nattable.command.AbstractLayerCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.config.AbstractUiBindingConfiguration; |
| import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration; |
| import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; |
| import org.eclipse.nebula.widgets.nattable.data.ExtendedReflectiveColumnPropertyAccessor; |
| import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor; |
| import org.eclipse.nebula.widgets.nattable.data.IDataProvider; |
| import org.eclipse.nebula.widgets.nattable.data.ListDataProvider; |
| import org.eclipse.nebula.widgets.nattable.dataset.person.PersonService; |
| import org.eclipse.nebula.widgets.nattable.dataset.person.PersonWithAddress; |
| import org.eclipse.nebula.widgets.nattable.examples.AbstractNatExample; |
| import org.eclipse.nebula.widgets.nattable.examples.runner.StandaloneNatExampleRunner; |
| import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider; |
| import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider; |
| import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider; |
| import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer; |
| import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer; |
| import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultColumnHeaderDataLayer; |
| import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer; |
| import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer; |
| import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer; |
| import org.eclipse.nebula.widgets.nattable.hideshow.ColumnHideShowLayer; |
| import org.eclipse.nebula.widgets.nattable.layer.AbstractLayer; |
| import org.eclipse.nebula.widgets.nattable.layer.AbstractLayerTransform; |
| import org.eclipse.nebula.widgets.nattable.layer.CompositeLayer; |
| import org.eclipse.nebula.widgets.nattable.layer.DataLayer; |
| import org.eclipse.nebula.widgets.nattable.layer.ILayer; |
| import org.eclipse.nebula.widgets.nattable.layer.LayerUtil; |
| import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; |
| import org.eclipse.nebula.widgets.nattable.painter.IOverlayPainter; |
| import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter; |
| import org.eclipse.nebula.widgets.nattable.painter.layer.CellLayerPainter; |
| import org.eclipse.nebula.widgets.nattable.print.command.MultiTurnViewportOffCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.print.command.MultiTurnViewportOnCommandHandler; |
| import org.eclipse.nebula.widgets.nattable.reorder.ColumnReorderLayer; |
| import org.eclipse.nebula.widgets.nattable.reorder.action.ColumnReorderDragMode; |
| import org.eclipse.nebula.widgets.nattable.reorder.command.ColumnReorderCommand; |
| import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; |
| import org.eclipse.nebula.widgets.nattable.selection.SelectionLayerPainter; |
| import org.eclipse.nebula.widgets.nattable.ui.action.AggregateDragMode; |
| import org.eclipse.nebula.widgets.nattable.ui.action.CellDragMode; |
| import org.eclipse.nebula.widgets.nattable.ui.binding.UiBindingRegistry; |
| import org.eclipse.nebula.widgets.nattable.ui.matcher.MouseEventMatcher; |
| import org.eclipse.nebula.widgets.nattable.ui.menu.HeaderMenuConfiguration; |
| import org.eclipse.nebula.widgets.nattable.util.ClientAreaAdapter; |
| import org.eclipse.nebula.widgets.nattable.util.GUIHelper; |
| import org.eclipse.nebula.widgets.nattable.viewport.SliderScroller; |
| import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.GC; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.layout.FillLayout; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Slider; |
| |
| /** |
| * Example showing how to implement NatTable that contains two horizontal split |
| * viewports in a grid. |
| */ |
| public class _5044_HorizontalSplitViewportGridExample extends AbstractNatExample { |
| |
| public static final int SPLIT_COLUMN_INDEX = 5; |
| |
| public static void main(String[] args) throws Exception { |
| StandaloneNatExampleRunner.run(600, 400, new _5044_HorizontalSplitViewportGridExample()); |
| } |
| |
| @Override |
| public String getDescription() { |
| return "This example shows a NatTable that contains two separately scrollable " |
| + "horzizontal split viewports in a grid."; |
| } |
| |
| @Override |
| public Control createExampleControl(Composite parent) { |
| // property names of the Person class |
| String[] propertyNames = { "firstName", "lastName", "gender", "married", "birthday", |
| "address.street", "address.housenumber", "address.postalCode", "address.city" }; |
| |
| // mapping from property to label, needed for column header labels |
| Map<String, String> propertyToLabelMap = new HashMap<>(); |
| propertyToLabelMap.put("firstName", "Firstname"); |
| propertyToLabelMap.put("lastName", "Lastname"); |
| propertyToLabelMap.put("gender", "Gender"); |
| propertyToLabelMap.put("married", "Married"); |
| propertyToLabelMap.put("birthday", "Birthday"); |
| propertyToLabelMap.put("address.street", "Street"); |
| propertyToLabelMap.put("address.housenumber", "Housenumber"); |
| propertyToLabelMap.put("address.postalCode", "Postal Code"); |
| propertyToLabelMap.put("address.city", "City"); |
| |
| IColumnPropertyAccessor<PersonWithAddress> columnPropertyAccessor = |
| new ExtendedReflectiveColumnPropertyAccessor<>(propertyNames); |
| |
| final BodyLayerStack<PersonWithAddress> bodyLayer = new BodyLayerStack<>( |
| PersonService.getPersonsWithAddress(50), columnPropertyAccessor); |
| |
| // build the row header layer |
| IDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider( |
| bodyLayer.getBodyDataProvider()); |
| DataLayer rowHeaderDataLayer = new DefaultRowHeaderDataLayer( |
| rowHeaderDataProvider); |
| final ILayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, |
| bodyLayer, bodyLayer.getSelectionLayer()); |
| |
| // build the column header layer |
| IDataProvider columnHeaderDataProvider = |
| new DefaultColumnHeaderDataProvider(propertyNames, propertyToLabelMap); |
| final DataLayer columnHeaderDataLayer = |
| new DefaultColumnHeaderDataLayer(columnHeaderDataProvider); |
| final AbstractLayer columnHeaderLayer = |
| new ColumnHeaderLayer(columnHeaderDataLayer, bodyLayer, bodyLayer.getSelectionLayer()); |
| |
| // Use this special layer painter that supports rendering of split |
| // viewports although the ColumnHeaderLayer is not split. Here is some |
| // custom calculation included that might not work correctly in case |
| // there are column groups or other spanning involved. |
| columnHeaderLayer.setLayerPainter(new CellLayerPainter() { |
| @Override |
| protected boolean isClipLeft(int position) { |
| // check position-1 because of the row header column count |
| // as the body is a composite layer, the default transformation |
| // for the grid is not working correctly |
| int index = LayerUtil.convertColumnPosition( |
| columnHeaderLayer, position - 1, columnHeaderDataLayer); |
| return (index > SPLIT_COLUMN_INDEX); |
| } |
| |
| @Override |
| protected void paintCell(ILayerCell cell, GC gc, IConfigRegistry configRegistry) { |
| ILayer layer = cell.getLayer(); |
| int columnPosition = cell.getColumnPosition(); |
| int rowPosition = cell.getRowPosition(); |
| ICellPainter cellPainter = layer.getCellPainter( |
| columnPosition, rowPosition, cell, configRegistry); |
| Rectangle adjustedCellBounds = layer |
| .getLayerPainter() |
| .adjustCellBounds(columnPosition, rowPosition, cell.getBounds()); |
| if (cellPainter != null) { |
| Rectangle originalClipping = gc.getClipping(); |
| |
| int startX = getStartXOfColumnPosition(columnPosition); |
| int startY = getStartYOfRowPosition(rowPosition); |
| |
| int endX = getStartXOfColumnPosition(cell.getOriginColumnPosition() + cell.getColumnSpan()); |
| int endY = getStartYOfRowPosition(cell.getOriginRowPosition() + cell.getRowSpan()); |
| |
| // correct position of first column in right region |
| // find the last visible column in left region |
| int viewportBorderX = bodyLayer.getViewportLayerLeft().getClientAreaWidth() |
| + rowHeaderLayer.getWidth(); |
| if (isClipLeft(columnPosition) && startX < viewportBorderX) { |
| startX = viewportBorderX; |
| } |
| if (!isClipLeft(columnPosition - 1) |
| && startX > viewportBorderX) { |
| startX = viewportBorderX; |
| } |
| if (isClipLeft(cell.getOriginColumnPosition() + cell.getColumnSpan()) |
| && endX < viewportBorderX) { |
| endX = viewportBorderX; |
| } |
| |
| Rectangle cellClipBounds = originalClipping.intersection( |
| new Rectangle(startX, startY, endX - startX, endY - startY)); |
| gc.setClipping(cellClipBounds.intersection(adjustedCellBounds)); |
| |
| cellPainter.paintCell(cell, gc, adjustedCellBounds, configRegistry); |
| |
| gc.setClipping(originalClipping); |
| } |
| } |
| }); |
| |
| // build the corner layer |
| IDataProvider cornerDataProvider = |
| new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider); |
| DataLayer cornerDataLayer = |
| new DataLayer(cornerDataProvider); |
| final ILayer cornerLayer = |
| new CornerLayer(cornerDataLayer, rowHeaderLayer, columnHeaderLayer); |
| |
| // build the grid layer |
| GridLayer gridLayer = |
| new GridLayer(bodyLayer, columnHeaderLayer, rowHeaderLayer, cornerLayer); |
| |
| // in order to make printing and exporting work correctly you need to |
| // register the following command handlers |
| gridLayer.registerCommandHandler( |
| new MultiTurnViewportOnCommandHandler( |
| bodyLayer.getViewportLayerLeft(), |
| bodyLayer.getViewportLayerRight())); |
| gridLayer.registerCommandHandler( |
| new MultiTurnViewportOffCommandHandler( |
| bodyLayer.getViewportLayerLeft(), |
| bodyLayer.getViewportLayerRight())); |
| |
| // Wrap NatTable in composite so we can slap on the external horizontal |
| // sliders |
| Composite composite = new Composite(parent, SWT.NONE); |
| GridLayout gridLayout = new GridLayout(1, false); |
| gridLayout.marginHeight = 0; |
| gridLayout.marginWidth = 0; |
| gridLayout.horizontalSpacing = 0; |
| gridLayout.verticalSpacing = 0; |
| composite.setLayout(gridLayout); |
| |
| NatTable natTable = new NatTable(composite, gridLayer, false); |
| GridData gridData = new GridData(); |
| gridData.horizontalAlignment = GridData.FILL; |
| gridData.verticalAlignment = GridData.FILL; |
| gridData.grabExcessHorizontalSpace = true; |
| gridData.grabExcessVerticalSpace = true; |
| natTable.setLayoutData(gridData); |
| |
| createSplitSliders(composite, rowHeaderLayer, |
| bodyLayer.getViewportLayerLeft(), |
| bodyLayer.getViewportLayerRight()); |
| |
| // add an IOverlayPainter to ensure the right border of the left |
| // viewport always |
| // this is necessary because the left border of layer stacks is not |
| // rendered by default |
| natTable.addOverlayPainter(new IOverlayPainter() { |
| |
| @Override |
| public void paintOverlay(GC gc, ILayer layer) { |
| Color beforeColor = gc.getForeground(); |
| gc.setForeground(GUIHelper.COLOR_GRAY); |
| int viewportBorderX = bodyLayer.getViewportLayerLeft().getWidth() |
| + rowHeaderLayer.getWidth() - 1; |
| gc.drawLine(viewportBorderX, 0, viewportBorderX, layer.getHeight() - 1); |
| gc.setForeground(beforeColor); |
| } |
| }); |
| |
| natTable.addConfiguration(new DefaultNatTableStyleConfiguration()); |
| natTable.addConfiguration(new HeaderMenuConfiguration(natTable)); |
| natTable.configure(); |
| |
| return composite; |
| } |
| |
| private void createSplitSliders( |
| Composite natTableParent, final ILayer rowHeaderLayer, |
| final ViewportLayer left, final ViewportLayer right) { |
| Composite sliderComposite = new Composite(natTableParent, SWT.NONE); |
| GridData gridData = new GridData(); |
| gridData.horizontalAlignment = GridData.FILL; |
| gridData.grabExcessHorizontalSpace = true; |
| gridData.grabExcessVerticalSpace = false; |
| gridData.heightHint = 17; |
| sliderComposite.setLayoutData(gridData); |
| |
| GridLayout gridLayout = new GridLayout(2, false); |
| gridLayout.marginHeight = 0; |
| gridLayout.marginWidth = 0; |
| gridLayout.horizontalSpacing = 0; |
| gridLayout.verticalSpacing = 0; |
| sliderComposite.setLayout(gridLayout); |
| |
| // Slider Left |
| // Need a composite here to set preferred size because Slider can't be |
| // subclassed. |
| Composite sliderLeftComposite = new Composite(sliderComposite, SWT.NONE) { |
| @Override |
| public Point computeSize(int wHint, int hHint, boolean changed) { |
| int width = ((ClientAreaAdapter) left.getClientAreaProvider()).getWidth(); |
| width += rowHeaderLayer.getWidth(); |
| return new Point(width, 17); |
| } |
| }; |
| sliderLeftComposite.setLayout(new FillLayout()); |
| gridData = new GridData(); |
| gridData.horizontalAlignment = GridData.BEGINNING; |
| gridData.verticalAlignment = GridData.BEGINNING; |
| sliderLeftComposite.setLayoutData(gridData); |
| |
| Slider sliderLeft = new Slider(sliderLeftComposite, SWT.HORIZONTAL); |
| gridData = new GridData(); |
| gridData.horizontalAlignment = GridData.FILL; |
| gridData.verticalAlignment = GridData.FILL; |
| sliderLeft.setLayoutData(gridData); |
| |
| left.setHorizontalScroller(new SliderScroller(sliderLeft)); |
| |
| // Slider Right |
| Slider sliderRight = new Slider(sliderComposite, SWT.HORIZONTAL); |
| gridData = new GridData(); |
| gridData.horizontalAlignment = GridData.FILL; |
| gridData.verticalAlignment = GridData.BEGINNING; |
| gridData.grabExcessHorizontalSpace = true; |
| gridData.grabExcessVerticalSpace = true; |
| sliderRight.setLayoutData(gridData); |
| |
| right.setHorizontalScroller(new SliderScroller(sliderRight)); |
| } |
| |
| /** |
| * The body layer stack that supports column hide/show, column reordering, |
| * selection and split viewports. |
| */ |
| class BodyLayerStack<T> extends AbstractLayerTransform { |
| |
| private final IDataProvider bodyDataProvider; |
| private final ColumnHideShowLayer columnHideShowLayer; |
| private final SelectionLayer selectionLayer; |
| private final ViewportLayer viewportLayerLeft; |
| private final ViewportLayer viewportLayerRight; |
| |
| public BodyLayerStack(List<T> values, IColumnPropertyAccessor<T> columnPropertyAccessor) { |
| this.bodyDataProvider = new ListDataProvider<>(values, columnPropertyAccessor); |
| DataLayer bodyDataLayer = new DataLayer(getBodyDataProvider()); |
| |
| // use our custom reorder drag mode configuration instead of the |
| // default to suppress the ability to move a column from one |
| // viewport to the other |
| ColumnReorderLayer columnReorderLayer = new ColumnReorderLayer(bodyDataLayer, false); |
| columnReorderLayer.addConfiguration(new AbstractUiBindingConfiguration() { |
| |
| @Override |
| public void configureUiBindings(UiBindingRegistry uiBindingRegistry) { |
| uiBindingRegistry.registerMouseDragMode( |
| MouseEventMatcher.columnHeaderLeftClick(SWT.NONE), |
| new AggregateDragMode( |
| new CellDragMode(), |
| new SplitViewportColumnReorderDragMode())); |
| } |
| }); |
| |
| this.columnHideShowLayer = new ColumnHideShowLayer(columnReorderLayer); |
| this.selectionLayer = new SelectionLayer(this.columnHideShowLayer); |
| |
| // use a cell layer painter that is configured for left clipping |
| // this ensures that the rendering works correctly for split |
| // viewports |
| this.selectionLayer.setLayerPainter(new SelectionLayerPainter(true, false)); |
| |
| // create a ViewportLayer for the left part of the table and |
| // configure it to only contain the first 5 columns |
| this.viewportLayerLeft = new ViewportLayer(this.selectionLayer) { |
| @Override |
| public int getMaxColumnPosition() { |
| return getNumberOfLeftColumns(); |
| } |
| }; |
| |
| // create a ViewportLayer for the right part of the table and |
| // configure it to only contain the last 4 columns |
| this.viewportLayerRight = new ViewportLayer(this.selectionLayer) { |
| @Override |
| public int getMinColumnPosition() { |
| return getNumberOfLeftColumns(); |
| } |
| }; |
| // as the min column position is calculated dynamically we need to |
| // set the minimum origin manually |
| int newMinOriginX = this.selectionLayer.getStartXOfColumnPosition(getNumberOfLeftColumns()); |
| this.viewportLayerRight.setMinimumOriginX(newMinOriginX); |
| |
| // create a CompositeLayer that contains both ViewportLayers |
| CompositeLayer compositeLayer = new CompositeLayer(2, 1); |
| compositeLayer.setChildLayer("REGION_A", getViewportLayerLeft(), 0, 0); |
| compositeLayer.setChildLayer("REGION_B", getViewportLayerRight(), 1, 0); |
| |
| // set the width of the left viewport to only showing 2 columns at |
| // the same time |
| int leftWidth = bodyDataLayer.getStartXOfColumnPosition(2); |
| |
| // as the CompositeLayer is setting a IClientAreaProvider for the |
| // composition |
| // we need to set a special ClientAreaAdapter after the creation of |
| // the CompositeLayer to support split viewports |
| ClientAreaAdapter leftClientAreaAdapter = |
| new ClientAreaAdapter(getViewportLayerLeft().getClientAreaProvider()); |
| leftClientAreaAdapter.setWidth(leftWidth); |
| getViewportLayerLeft().setClientAreaProvider(leftClientAreaAdapter); |
| |
| // register configuration to avoid reordering of columns between the |
| // split viewports |
| registerCommandHandler( |
| new SplitViewportColumnReorderCommandHandler(getViewportLayerLeft())); |
| |
| setUnderlyingLayer(compositeLayer); |
| } |
| |
| /** |
| * To support hide/show correctly the min/max column positions in the |
| * split viewports need to be calculated regarding the hide state. |
| * |
| * @return The number of visible columns in the left viewport. |
| */ |
| private int getNumberOfLeftColumns() { |
| int fixedColumns = SPLIT_COLUMN_INDEX; |
| for (int i = 0; i < (SPLIT_COLUMN_INDEX); i++) { |
| if (this.columnHideShowLayer.isColumnIndexHidden(i)) { |
| fixedColumns--; |
| } |
| } |
| return fixedColumns; |
| } |
| |
| public IDataProvider getBodyDataProvider() { |
| return this.bodyDataProvider; |
| } |
| |
| public SelectionLayer getSelectionLayer() { |
| return this.selectionLayer; |
| } |
| |
| public ViewportLayer getViewportLayerLeft() { |
| return this.viewportLayerLeft; |
| } |
| |
| public ViewportLayer getViewportLayerRight() { |
| return this.viewportLayerRight; |
| } |
| } |
| |
| /** |
| * ILayerCommandHandler for ColumnReorderCommands that ensures that no |
| * reordering can be performed programmatically or via dialog, that would |
| * reorder columns between the split viewports. |
| */ |
| class SplitViewportColumnReorderCommandHandler extends |
| AbstractLayerCommandHandler<ColumnReorderCommand> { |
| |
| private ViewportLayer viewportLeft; |
| |
| public SplitViewportColumnReorderCommandHandler(ViewportLayer viewportLeft) { |
| this.viewportLeft = viewportLeft; |
| } |
| |
| @Override |
| protected boolean doCommand(ColumnReorderCommand command) { |
| if ((command.getFromColumnPosition() < this.viewportLeft.getColumnCount()) != (command.getToColumnPosition() < this.viewportLeft.getColumnCount())) { |
| // Bail out if trying to reorder from region A to B or vice |
| // versa |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public Class<ColumnReorderCommand> getCommandClass() { |
| return ColumnReorderCommand.class; |
| } |
| |
| } |
| |
| /** |
| * Reorder drag mode that avoids reordering between split viewports via |
| * dragging. |
| */ |
| class SplitViewportColumnReorderDragMode extends ColumnReorderDragMode { |
| |
| @Override |
| protected boolean isValidTargetColumnPosition(ILayer natLayer, |
| int dragFromGridColumnPosition, int dragToGridColumnPosition) { |
| |
| if (((NatTable) natLayer).getCursor() == null) { |
| int fromColumnIndex = natLayer.getColumnIndexByPosition(dragFromGridColumnPosition); |
| int toColumnIndex = natLayer.getColumnIndexByPosition(dragToGridColumnPosition); |
| |
| // ensure that dragging over split viewport borders is not |
| // allowed |
| if ((fromColumnIndex < SPLIT_COLUMN_INDEX && toColumnIndex < SPLIT_COLUMN_INDEX) |
| || (fromColumnIndex >= (SPLIT_COLUMN_INDEX) && toColumnIndex >= (SPLIT_COLUMN_INDEX))) { |
| |
| return super.isValidTargetColumnPosition(natLayer, |
| dragFromGridColumnPosition, |
| dragToGridColumnPosition); |
| } |
| } |
| |
| return false; |
| } |
| } |
| } |