blob: 4b277cd82271c72e29282e75337052935dca8fc6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016, 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.ILayerCommand;
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.data.ReflectiveColumnPropertyAccessor;
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.GridRegion;
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.DimensionallyDependentIndexLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
import org.eclipse.nebula.widgets.nattable.layer.AbstractIndexLayerTransform;
import org.eclipse.nebula.widgets.nattable.layer.AbstractLayer;
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.IUniqueIndexLayer;
import org.eclipse.nebula.widgets.nattable.layer.LabelStack;
import org.eclipse.nebula.widgets.nattable.painter.IOverlayPainter;
import org.eclipse.nebula.widgets.nattable.painter.layer.CellLayerPainter;
import org.eclipse.nebula.widgets.nattable.resize.action.VerticalResizeCursorAction;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayerPainter;
import org.eclipse.nebula.widgets.nattable.selection.command.ClearAllSelectionsCommand;
import org.eclipse.nebula.widgets.nattable.selection.command.SelectAllCommand;
import org.eclipse.nebula.widgets.nattable.selection.command.SelectCellCommand;
import org.eclipse.nebula.widgets.nattable.ui.action.IDragMode;
import org.eclipse.nebula.widgets.nattable.ui.matcher.MouseEventMatcher;
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.nebula.widgets.nattable.viewport.command.RecalculateScrollBarsCommand;
import org.eclipse.nebula.widgets.nattable.viewport.command.ViewportSelectColumnCommand;
import org.eclipse.nebula.widgets.nattable.viewport.command.ViewportSelectRowCommand;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
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;
/**
*
*/
public class _5046_MultiScrollExample extends AbstractNatExample {
public static void main(String[] args) throws Exception {
StandaloneNatExampleRunner.run(600, 400, new _5046_MultiScrollExample());
}
@Override
public String getDescription() {
return "This example shows multiple scrollable regions.";
}
@Override
public Control createExampleControl(Composite parent) {
List<PersonWithAddress> values = PersonService.getPersonsWithAddress(10);
ContentBodyLayerStack contentBodyLayer = new ContentBodyLayerStack(values);
StructureBodyLayerStack structureBodyLayer = new StructureBodyLayerStack(values, contentBodyLayer);
// build the column header layer
IDataProvider contentHeaderDataProvider =
new DefaultColumnHeaderDataProvider(contentBodyLayer.propertyNames, contentBodyLayer.propertyToLabelMap);
DataLayer contentHeaderDataLayer =
new DefaultColumnHeaderDataLayer(contentHeaderDataProvider);
AbstractLayer contentColumnHeaderLayer =
new ColumnHeaderLayer(contentHeaderDataLayer, contentBodyLayer, contentBodyLayer.getSelectionLayer());
IDataProvider structureHeaderDataProvider =
new DefaultColumnHeaderDataProvider(structureBodyLayer.propertyNames, structureBodyLayer.propertyToLabelMap);
DataLayer structureHeaderDataLayer =
new DefaultColumnHeaderDataLayer(structureHeaderDataProvider);
ColumnHeaderLayer structureColumnHeaderLayer =
new ColumnHeaderLayer(structureHeaderDataLayer, structureBodyLayer, structureBodyLayer.selectionLayer);
structureColumnHeaderLayer.setVerticalLayerDependency(contentColumnHeaderLayer);
DataLayer rowHeaderDataLayer =
new DefaultRowHeaderDataLayer(new DefaultRowHeaderDataProvider(contentBodyLayer.bodyDataProvider));
ILayer rowHeaderLayer =
new RowHeaderLayer(rowHeaderDataLayer, contentBodyLayer, (SelectionLayer) null);
// build the corner layer
IDataProvider cornerDataProvider =
new DefaultCornerDataProvider(contentHeaderDataProvider, rowHeaderDataLayer.getDataProvider());
DataLayer cornerDataLayer =
new DataLayer(cornerDataProvider);
ILayer cornerLayer =
new CornerLayer(cornerDataLayer, rowHeaderLayer, contentColumnHeaderLayer);
ExtendedGridLayer gridLayer = new ExtendedGridLayer(
contentBodyLayer, contentColumnHeaderLayer,
structureBodyLayer, structureColumnHeaderLayer,
rowHeaderLayer, cornerLayer);
// MULTI-VIEWPORT-CONFIGURATION
// 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
int leftWidth = 80;
ClientAreaAdapter leftClientAreaAdapter =
new ClientAreaAdapter(structureBodyLayer.getViewportLayer().getClientAreaProvider());
leftClientAreaAdapter.setWidth(leftWidth);
structureBodyLayer.getViewportLayer().setClientAreaProvider(leftClientAreaAdapter);
structureBodyLayer.getViewportLayer().setVerticalScrollbarEnabled(false);
// use a cell layer painter that is configured for left clipping
// this ensures that the rendering works correctly for split
// viewports
contentBodyLayer.getSelectionLayer().setLayerPainter(new SelectionLayerPainter(true, false));
contentColumnHeaderLayer.setLayerPainter(new CellLayerPainter(true, false));
ScrolledComposite sc = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.V_SCROLL);
// Wrap NatTable in composite so we can slap on the external horizontal
// sliders
Composite composite = new Composite(sc, 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);
GridData gridData = new GridData();
gridData.horizontalAlignment = GridData.FILL;
gridData.verticalAlignment = GridData.FILL;
gridData.grabExcessHorizontalSpace = true;
gridData.grabExcessVerticalSpace = true;
natTable.setLayoutData(gridData);
createSplitSliders(composite, gridLayer, rowHeaderLayer.getWidth());
sc.setContent(composite);
sc.setExpandHorizontal(true);
sc.setExpandVertical(true);
updateScrolledCompositeSize(sc, gridLayer);
// add an IOverlayPainter to render the split viewport border
natTable.addOverlayPainter(new IOverlayPainter() {
@Override
public void paintOverlay(GC gc, ILayer layer) {
Color beforeColor = gc.getForeground();
gc.setForeground(GUIHelper.COLOR_GRAY);
int viewportBorderX = rowHeaderLayer.getWidth() + gridLayer.getStructureBody().getWidth() - 1;
gc.drawLine(viewportBorderX, 0, viewportBorderX, layer.getHeight() - 1);
gc.setForeground(beforeColor);
}
});
// Mouse move - Show resize cursor
natTable.getUiBindingRegistry().registerFirstMouseMoveBinding(
new ClientAreaResizeMatcher(gridLayer),
new VerticalResizeCursorAction());
natTable.getUiBindingRegistry().registerFirstMouseDragMode(
new ClientAreaResizeMatcher(gridLayer),
new ClientAreaResizeDragMode(gridLayer, sc));
return natTable;
}
/**
*
* @param sc
* @param gridLayer
*/
private void updateScrolledCompositeSize(ScrolledComposite sc, ExtendedGridLayer gridLayer) {
sc.setMinSize(
gridLayer.getRowHeaderLayer().getWidth() + gridLayer.getStructureBody().getWidth() + 100,
gridLayer.getContentHeader().getHeight() + 20);
}
/**
*
* @param natTableParent
* @param gridLayer
* @param fixedHeaderWidth
*/
private void createSplitSliders(
Composite natTableParent, ExtendedGridLayer gridLayer, int fixedHeaderWidth) {
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) gridLayer.getStructureBody().getViewportLayer().getClientAreaProvider()).getWidth() + fixedHeaderWidth;
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);
gridLayer.getStructureBody().getViewportLayer().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 = false;
sliderRight.setLayoutData(gridData);
gridLayer.getContentBody().getViewportLayer().setHorizontalScroller(new SliderScroller(sliderRight));
}
/**
*
*/
class ExtendedGridLayer extends CompositeLayer {
public ExtendedGridLayer(
ContentBodyLayerStack contentBodyLayer, ILayer contentColumnHeaderLayer,
StructureBodyLayerStack structureBodyLayer, ILayer structureColumnHeaderLayer,
ILayer rowHeaderLayer, ILayer cornerLayer) {
super(3, 2);
setChildLayer(GridRegion.CORNER, cornerLayer, 0, 0);
setChildLayer(GridRegion.ROW_HEADER, rowHeaderLayer, 0, 1);
setChildLayer(GridRegion.COLUMN_HEADER, structureColumnHeaderLayer, 1, 0);
setChildLayer(GridRegion.BODY, structureBodyLayer, 1, 1);
setChildLayer(GridRegion.COLUMN_HEADER, contentColumnHeaderLayer, 2, 0);
setChildLayer(GridRegion.BODY, contentBodyLayer, 2, 1);
}
public StructureBodyLayerStack getStructureBody() {
return (StructureBodyLayerStack) getChildLayerByLayoutCoordinate(1, 1);
}
public ContentBodyLayerStack getContentBody() {
return (ContentBodyLayerStack) getChildLayerByLayoutCoordinate(2, 1);
}
public ILayer getStructureHeader() {
return getChildLayerByLayoutCoordinate(1, 0);
}
public ILayer getContentHeader() {
return getChildLayerByLayoutCoordinate(2, 0);
}
public ILayer getRowHeaderLayer() {
return getChildLayerByLayoutCoordinate(0, 1);
}
@Override
protected boolean doCommandOnChildLayers(ILayerCommand command) {
if (doCommandOnChildLayer(command, getContentBody())) {
return true;
} else if (doCommandOnChildLayer(command, getStructureBody())) {
return true;
} else if (doCommandOnChildLayer(command, getContentHeader())) {
return true;
} else if (doCommandOnChildLayer(command, getStructureHeader())) {
return true;
} else if (doCommandOnChildLayer(command, getRowHeaderLayer())) {
return true;
} else {
return doCommandOnChildLayer(command, getChildLayerByLayoutCoordinate(0, 0));
}
}
private boolean doCommandOnChildLayer(ILayerCommand command, ILayer childLayer) {
ILayerCommand childCommand = command.cloneCommand();
return childLayer.doCommand(childCommand);
}
@Override
public boolean doCommand(ILayerCommand command) {
if (command instanceof ViewportSelectRowCommand
|| command instanceof ClearAllSelectionsCommand
|| command instanceof SelectAllCommand) {
doCommandOnChildLayer(command, getContentBody());
doCommandOnChildLayer(command, getStructureBody());
return true;
} else if (command instanceof SelectCellCommand) {
int layout = getLayoutXByColumnPosition(((SelectCellCommand) command).getColumnPosition());
if (layout == 2) {
doCommandOnChildLayer(new ClearAllSelectionsCommand(), getStructureBody());
} else {
doCommandOnChildLayer(new ClearAllSelectionsCommand(), getContentBody());
}
} else if (command instanceof ViewportSelectColumnCommand) {
int layout = getLayoutXByColumnPosition(((ViewportSelectColumnCommand) command).getColumnPosition());
if (layout == 2) {
doCommandOnChildLayer(new ClearAllSelectionsCommand(), getStructureBody());
} else {
doCommandOnChildLayer(new ClearAllSelectionsCommand(), getContentBody());
}
}
return super.doCommand(command);
}
}
/**
*
*/
class ContentBodyLayerStack extends AbstractIndexLayerTransform {
// property names of the Person class
public final String[] propertyNames = { "firstName", "lastName", "gender", "married", "birthday" };
// mapping from property to label, needed for column header labels
public final Map<String, String> propertyToLabelMap = new HashMap<>();
private final IDataProvider bodyDataProvider;
private final DataLayer bodyDataLayer;
private final SelectionLayer selectionLayer;
private final ViewportLayer viewportLayer;
public ContentBodyLayerStack(List<PersonWithAddress> values) {
this.propertyToLabelMap.put("firstName", "Firstname");
this.propertyToLabelMap.put("lastName", "Lastname");
this.propertyToLabelMap.put("gender", "Gender");
this.propertyToLabelMap.put("married", "Married");
this.propertyToLabelMap.put("birthday", "Birthday");
IColumnPropertyAccessor<PersonWithAddress> columnPropertyAccessor =
new ReflectiveColumnPropertyAccessor<>(this.propertyNames);
this.bodyDataProvider = new ListDataProvider<>(values, columnPropertyAccessor);
this.bodyDataLayer = new DataLayer(this.bodyDataProvider);
this.selectionLayer = new SelectionLayer(this.bodyDataLayer);
this.viewportLayer = new ViewportLayer(this.selectionLayer);
setUnderlyingLayer(this.viewportLayer);
}
public ViewportLayer getViewportLayer() {
return this.viewportLayer;
}
public SelectionLayer getSelectionLayer() {
return this.selectionLayer;
}
}
/**
*
*/
class StructureBodyLayerStack extends AbstractIndexLayerTransform {
// property names of the Address class
public final String[] propertyNames = { "address.street", "address.housenumber", "address.postalCode", "address.city" };
// mapping from property to label, needed for column header labels
public final Map<String, String> propertyToLabelMap = new HashMap<>();
private final IDataProvider bodyDataProvider;
private final DataLayer bodyDataLayer;
private final SelectionLayer selectionLayer;
private final ViewportLayer viewportLayer;
public StructureBodyLayerStack(List<PersonWithAddress> values, IUniqueIndexLayer verticalDependency) {
this.propertyToLabelMap.put("address.street", "Street");
this.propertyToLabelMap.put("address.housenumber", "Housenumber");
this.propertyToLabelMap.put("address.postalCode", "Postal Code");
this.propertyToLabelMap.put("address.city", "City");
IColumnPropertyAccessor<PersonWithAddress> columnPropertyAccessor =
new ExtendedReflectiveColumnPropertyAccessor<>(this.propertyNames);
this.bodyDataProvider = new ListDataProvider<>(values, columnPropertyAccessor);
this.bodyDataLayer = new DataLayer(this.bodyDataProvider);
this.selectionLayer = new SelectionLayer(this.bodyDataLayer);
this.viewportLayer = new ViewportLayer(this.selectionLayer);
setUnderlyingLayer(new DimensionallyDependentIndexLayer(this.viewportLayer, this.viewportLayer, verticalDependency));
}
public ViewportLayer getViewportLayer() {
return this.viewportLayer;
}
}
/**
*
*/
class ClientAreaResizeMatcher extends MouseEventMatcher {
ExtendedGridLayer gridLayer;
public ClientAreaResizeMatcher(ExtendedGridLayer gridLayer) {
this.gridLayer = gridLayer;
}
@Override
public boolean matches(NatTable natTable, MouseEvent event, LabelStack regionLabels) {
int viewportBorderX = this.gridLayer.getRowHeaderLayer().getWidth() + this.gridLayer.getStructureBody().getWidth();
if (regionLabels != null && regionLabels.hasLabel(GridRegion.COLUMN_HEADER)
&& (event.x >= viewportBorderX && event.x <= viewportBorderX + 4)) {
return true;
}
return false;
}
}
/**
*
*/
class ClientAreaResizeDragMode implements IDragMode {
ExtendedGridLayer gridLayer;
ScrolledComposite sc;
public ClientAreaResizeDragMode(ExtendedGridLayer gridLayer, ScrolledComposite sc) {
this.gridLayer = gridLayer;
this.sc = sc;
}
@Override
public void mouseDown(NatTable natTable, MouseEvent event) {
}
@Override
public void mouseMove(NatTable natTable, MouseEvent event) {
// TODO overlay support
}
@Override
public void mouseUp(NatTable natTable, MouseEvent event) {
int baseWidth = this.gridLayer.getStructureBody().bodyDataLayer.getWidth();
int newWidth = event.x - this.gridLayer.getRowHeaderLayer().getWidth();
if (newWidth < 0) {
newWidth = 1;
} else if (newWidth > baseWidth) {
newWidth = baseWidth;
}
((ClientAreaAdapter) this.gridLayer.getStructureBody().getViewportLayer().getClientAreaProvider()).setWidth(newWidth);
this.gridLayer.getStructureBody().getViewportLayer().invalidateHorizontalStructure();
this.gridLayer.getStructureBody().getViewportLayer().doCommand(new RecalculateScrollBarsCommand());
this.gridLayer.getContentBody().getViewportLayer().invalidateHorizontalStructure();
this.gridLayer.getContentBody().getViewportLayer().doCommand(new RecalculateScrollBarsCommand());
natTable.redraw();
natTable.getParent().layout(true, true);
updateScrolledCompositeSize(this.sc, this.gridLayer);
}
}
}