package problem;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;
import org.eclipse.nebula.widgets.nattable.config.AbstractUiBindingConfiguration;
import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.config.IEditableRule;
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
import org.eclipse.nebula.widgets.nattable.data.convert.DefaultBooleanDisplayConverter;
import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;
import org.eclipse.nebula.widgets.nattable.edit.action.CellEditDragMode;
import org.eclipse.nebula.widgets.nattable.edit.action.MouseEditAction;
import org.eclipse.nebula.widgets.nattable.edit.editor.CheckBoxCellEditor;
import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultBodyDataProvider;
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider;
import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultColumnHeaderDataLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.DimensionallyDependentLayer;
import org.eclipse.nebula.widgets.nattable.grid.layer.config.DefaultGridLayerConfiguration;
import org.eclipse.nebula.widgets.nattable.hideshow.ColumnHideShowLayer;
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.LabelStack;
import org.eclipse.nebula.widgets.nattable.layer.cell.IConfigLabelAccumulator;
import org.eclipse.nebula.widgets.nattable.layer.event.VisualRefreshEvent;
import org.eclipse.nebula.widgets.nattable.painter.cell.CheckBoxPainter;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.nebula.widgets.nattable.ui.binding.UiBindingRegistry;
import org.eclipse.nebula.widgets.nattable.ui.matcher.CellEditorMouseEventMatcher;
import org.eclipse.nebula.widgets.nattable.ui.matcher.CellPainterMouseEventMatcher;
import org.eclipse.nebula.widgets.nattable.ui.matcher.MouseEventMatcher;
import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
/**
* Short description: The sample is meant to show two problems i faced with NatTable, when trying to add an {@link DimensionallyDependentLayer} above the header.
* The layer contains controls (checkboxes, in reality also comboboxes) which are meant to change data for the elements in the whole row.
*
* {@link DataBean} - simple class only provided to show some data in the body area.
* {@link SettingsDataProvider} - shows the problems I face
* {@link TrivialRowSettingsBean} - one per row. Used to maintain the data for {@link SettingsDataProvider}
*
* Problem Description
*
* - {@link SettingsDataProvider#getDataValue(int, int)} always is invoked with the correct column indices, independent whether the first column is visible or not.
* {@link SettingsDataProvider#setDataValue(int, int, Object)} is called with the wrong indices. (0 for the first visible column, so the indices change dependent on whether the
* first column is shown or not)
*
* - Quite minor but confused me: I need to manually force a redraw of the table in {@link SettingsDataProvider#setDataValue(int, int, Object)}, otherwise the checkboxes dont
* display the changed state. This works out of the box in case the editors/painters are used in the body area.
*
*
* Well ok - likely I make a mistake somewhere, but I simply dont understand where ;-).
* In case it is not my mistake the sample may be valueable for you as it might show an existing problem.
*
* ah and not to forget: setDataValue only gets invoked in case there is a state change. As the indices are mixed up when the first column is hidden
* column with index 1 and 2 need to have the same state in order to trigger the setter (when clicking on column 2) as it seems to compare with the state of column 1
* before tiriggering the setDataValue method.
*
* @author chris lewold
*/
public class IndexTransformInHeader {
// trivial bean to be applied per row
private static class TrivialRowSettingsBean {
public TrivialRowSettingsBean(boolean b) {
selected = b;
}
boolean selected;;
}
public static class DataBean {
public DataBean(String col1, String col2, String col3) {
valCol1 = col1;
valCol2 = col2;
valCol3 = col3;
}
public String getValCol1() {
return valCol1;
}
public void setValCol1(String valCol1) {
this.valCol1 = valCol1;
}
public String getValCol2() {
return valCol2;
}
public void setValCol2(String valCol2) {
this.valCol2 = valCol2;
}
public String getValCol3() {
return valCol3;
}
public void setValCol3(String valCol3) {
this.valCol3 = valCol3;
}
private String valCol1;
private String valCol2;
private String valCol3;
}
public static void main(String[] args) {
run(600, 400, new IndexTransformInHeader());
}
static List getTestFixture() {
final List rc = new ArrayList<>();
for (int i = 1; i < 5; i++) {
final String prefix = "Bean " + i;
rc.add(new DataBean(prefix + ", col 1", prefix + ", col 2", prefix + ", col 3"));
}
return rc;
}
private class SettingsDataProvider implements IDataProvider {
@Override
public Object getDataValue(int columnIndex, int rowIndex) {
// always correct column index.
// in case the first column is hidden only index 1 and 2 is invoked
System.out.println("getDataValue for col " + columnIndex);
return rowSettingsBeans[columnIndex].selected;
}
@Override
public void setDataValue(int columnIndex, int rowIndex, Object newValue) {
// WRONG columnIndex in case the first column is hidden
// so e.g. if I hide the first column (index 0) and click the first visible checkbox
// then the provided columnIndex is not index 1 as expected, but index 0.
System.out.println("-- setDataValue for col " + columnIndex);
rowSettingsBeans[columnIndex].selected = (boolean) newValue;
// I don't know why, but I need to force redrawing the cell ....
natTable.fireLayerEvent(new VisualRefreshEvent(natTable));
}
@Override
public int getColumnCount() {
return 3;
}
@Override
public int getRowCount() {
return 1;
}
}
private static final String SETTINGS_REGION = "SETTINGS_REGION";
private static final String SETTINGS_LABEL = "SETTINGS_LABEL";
private final TrivialRowSettingsBean[] rowSettingsBeans = new TrivialRowSettingsBean[3];
// needed for redraw in setDataValue.
private NatTable natTable;
public IndexTransformInHeader() {
rowSettingsBeans[0] = new TrivialRowSettingsBean(true);
rowSettingsBeans[1] = new TrivialRowSettingsBean(false);
rowSettingsBeans[2] = new TrivialRowSettingsBean(true);
}
public Control createExampleControl(Composite parent) {
parent.setLayout(new GridLayout());
final String[] propertyNames = { "valCol1", "valCol2", "valCol3" };
final DataLayer bodyDataLayer = new DataLayer(new DefaultBodyDataProvider<>(getTestFixture(), propertyNames));
final ColumnHideShowLayer columnHideShowLayer = new ColumnHideShowLayer(bodyDataLayer);
final SelectionLayer selectionLayer = new SelectionLayer(columnHideShowLayer);
final ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);
viewportLayer.setRegionName(GridRegion.BODY);
final ILayer columnHeaderLayer = new ColumnHeaderLayer(
new DefaultColumnHeaderDataLayer(new DefaultColumnHeaderDataProvider(propertyNames)), viewportLayer, selectionLayer);
// The settings area as composite above the column headers
SettingsDataProvider settingsDataProvider = new SettingsDataProvider();
DataLayer settingsDataLayer = new DataLayer(settingsDataProvider);
settingsDataLayer.addConfiguration(new AbstractRegistryConfiguration() {
@Override
public void configureRegistry(IConfigRegistry configRegistry) {
configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITABLE_RULE, IEditableRule.ALWAYS_EDITABLE,
DisplayMode.EDIT, SETTINGS_LABEL);
configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, new CheckBoxCellEditor(), DisplayMode.EDIT,
SETTINGS_LABEL);
configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_PAINTER, new CheckBoxPainter(), DisplayMode.NORMAL,
SETTINGS_LABEL);
configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, new DefaultBooleanDisplayConverter(),
DisplayMode.NORMAL, SETTINGS_LABEL);
}
});
settingsDataLayer.addConfiguration(new AbstractUiBindingConfiguration() {
@Override
public void configureUiBindings(UiBindingRegistry uiBindingRegistry) {
uiBindingRegistry.registerSingleClickBinding(new CellEditorMouseEventMatcher(SETTINGS_REGION), new MouseEditAction());
uiBindingRegistry.registerMouseDragMode(new CellEditorMouseEventMatcher(SETTINGS_REGION), new CellEditDragMode());
uiBindingRegistry.registerFirstSingleClickBinding(
new CellPainterMouseEventMatcher(SETTINGS_REGION, MouseEventMatcher.LEFT_BUTTON, CheckBoxPainter.class),
new MouseEditAction());
uiBindingRegistry.registerFirstMouseDragMode(
new CellPainterMouseEventMatcher(SETTINGS_REGION, MouseEventMatcher.LEFT_BUTTON, CheckBoxPainter.class),
new CellEditDragMode());
}
});
settingsDataLayer.setConfigLabelAccumulator(new IConfigLabelAccumulator() {
@Override
public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) {
configLabels.addLabel(SETTINGS_LABEL);
configLabels.addLabel(SETTINGS_REGION);
}
});
DimensionallyDependentLayer dimLayer = new DimensionallyDependentLayer(settingsDataLayer, viewportLayer,
new SelectionLayer(settingsDataLayer));
CompositeLayer compositeLayer = new CompositeLayer(1, 3);
compositeLayer.setChildLayer(SETTINGS_REGION, dimLayer, 0, 0);
compositeLayer.setChildLayer(GridRegion.COLUMN_HEADER, columnHeaderLayer, 0, 1);
compositeLayer.setChildLayer(GridRegion.BODY, viewportLayer, 0, 2);
compositeLayer.addConfiguration(new DefaultGridLayerConfiguration(compositeLayer));
final Button checkBoxHideFirstColumn = new Button(parent, SWT.CHECK);
checkBoxHideFirstColumn.setText("Hide first column");
checkBoxHideFirstColumn.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (checkBoxHideFirstColumn.getSelection()) {
columnHideShowLayer.hideColumnPositions(Arrays.asList(0));
} else {
columnHideShowLayer.showAllColumns();
}
}
});
natTable = new NatTable(parent, NatTable.DEFAULT_STYLE_OPTIONS | SWT.BORDER, compositeLayer, false);
natTable.addConfiguration(new DefaultNatTableStyleConfiguration());
natTable.configure();
GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable);
return natTable;
}
private static void run(int shellWidth, int shellHeight, IndexTransformInHeader example) {
final Display display = Display.getDefault();
final Shell shell = new Shell(display, SWT.SHELL_TRIM);
shell.setLayout(new FillLayout());
shell.setSize(shellWidth, shellHeight);
final Control exampleControl = example.createExampleControl(shell);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
exampleControl.dispose();
shell.dispose();
display.dispose();
}
}