/* * ARX: Powerful Data Anonymization * Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors * * 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. */ package org.deidentifier.arx.gui.view.impl.common; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.deidentifier.arx.gui.view.def.IComponent; import org.deidentifier.arx.gui.view.impl.common.table.CTConfiguration; import org.deidentifier.arx.gui.view.impl.common.table.CTContext; import org.deidentifier.arx.gui.view.impl.common.table.CTDataProvider; import org.deidentifier.arx.gui.view.impl.common.table.DataProviderWrapped; import org.deidentifier.arx.gui.view.impl.common.table.FillLayerResetCommand; import org.deidentifier.arx.gui.view.impl.common.table.LayerBody; import org.deidentifier.arx.gui.view.impl.common.table.LayerColumnHeader; import org.deidentifier.arx.gui.view.impl.common.table.LayerRowHeader; import org.deidentifier.arx.gui.view.impl.common.table.StyleConfigurationTable; import org.eclipse.nebula.widgets.nattable.NatTable; import org.eclipse.nebula.widgets.nattable.config.AbstractUiBindingConfiguration; import org.eclipse.nebula.widgets.nattable.data.IColumnAccessor; import org.eclipse.nebula.widgets.nattable.data.IDataProvider; import org.eclipse.nebula.widgets.nattable.data.ListDataProvider; import org.eclipse.nebula.widgets.nattable.grid.GridRegion; import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider; import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer; import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer; import org.eclipse.nebula.widgets.nattable.layer.CompositeLayer; import org.eclipse.nebula.widgets.nattable.layer.DataLayer; import org.eclipse.nebula.widgets.nattable.layer.ILayerListener; import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent; import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; import org.eclipse.nebula.widgets.nattable.selection.action.SelectCellAction; import org.eclipse.nebula.widgets.nattable.selection.command.SelectCellCommand; import org.eclipse.nebula.widgets.nattable.selection.event.CellSelectionEvent; import org.eclipse.nebula.widgets.nattable.selection.event.ColumnSelectionEvent; import org.eclipse.nebula.widgets.nattable.selection.event.RowSelectionEvent; import org.eclipse.nebula.widgets.nattable.ui.binding.UiBindingRegistry; import org.eclipse.nebula.widgets.nattable.ui.matcher.MouseEventMatcher; import org.eclipse.nebula.widgets.nattable.viewport.action.ViewportSelectColumnAction; import org.eclipse.nebula.widgets.nattable.viewport.action.ViewportSelectRowAction; import org.eclipse.swt.SWT; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; /** * A virtual table implemented with NatTable. * * @author Fabian Prasser */ public class ComponentTable implements IComponent { /** * Checkstyle. * * @param style * @return */ private static int checkStyle(int style) { return style & (SWT.BORDER | SWT.NONE); } /** The parent. */ private final Composite root; /** The underlying nattable instance. */ private final NatTable table; /** The context. */ private final CTContext context; /** Data provider. */ private final CTDataProvider dataProviderRowHeader; /** Data provider. */ private final CTDataProvider dataProviderColumnHeader; /** Data provider. */ private final CTDataProvider dataProviderBody; /** State. */ private Integer selectedRow = null; /** State. */ private Integer selectedColumn = null; /** Body layer. */ private final LayerBody bodyLayer; /** Listeners. */ private List<SelectionListener> selectionListeners = new ArrayList<SelectionListener>(); /** * Creates a new instance. * * @param parent * @param style */ public ComponentTable(Composite parent, int style) { this(parent, style, new CTConfiguration(parent)); } /** * Creates a new instance. * * @param parent * @param style * @param config */ public ComponentTable(final Composite parent, final int style, final CTConfiguration config) { // Check and store if (config == null) { throw new IllegalArgumentException("Config must not be null"); //$NON-NLS-1$ } if (parent == null) { throw new IllegalArgumentException("Parent must not be null"); //$NON-NLS-1$ } this.root = new Composite(parent, checkStyle(style)); this.root.setLayout(new FillLayout()); // Create context this.context = new CTContext(){ public NatTable getTable() { return table; } }; // Create data providers this.dataProviderRowHeader = new DataProviderWrapped(); this.dataProviderColumnHeader = new DataProviderWrapped(); this.dataProviderBody = new DataProviderWrapped(); // Create grid if (config.getStyle() == CTConfiguration.STYLE_GRID) { // Create layers bodyLayer = new LayerBody(this.dataProviderBody, config, context); LayerColumnHeader layerColumnHeader = new LayerColumnHeader(root, dataProviderColumnHeader, bodyLayer, config, context); LayerRowHeader layerRowHeader = new LayerRowHeader(root, dataProviderRowHeader, bodyLayer, config, context); CornerLayer layerCorner = new CornerLayer(new DataLayer(new DefaultCornerDataProvider(dataProviderColumnHeader, dataProviderRowHeader)), layerRowHeader, layerColumnHeader); GridLayer gridLayer = new GridLayer(bodyLayer, layerColumnHeader, layerRowHeader, layerCorner); // Create table this.table = new NatTable(root, gridLayer, false); this.table.addConfiguration(new StyleConfigurationTable(config)); this.table.configure(); this.addSelectionListener(bodyLayer.getSelectionLayer()); // Create a table } else { // Create layers bodyLayer = new LayerBody(dataProviderBody, config, context); LayerColumnHeader layerColumnHeader = new LayerColumnHeader(root, dataProviderColumnHeader, bodyLayer, config, context); CompositeLayer layerComposite = new CompositeLayer(1, 2); layerComposite.setChildLayer(GridRegion.BODY, bodyLayer, 0, 1); layerComposite.setChildLayer(GridRegion.COLUMN_HEADER, layerColumnHeader, 0, 0); // Make corner resizable layerComposite.addConfiguration(new AbstractUiBindingConfiguration() { @Override public void configureUiBindings(UiBindingRegistry uiBindingRegistry) { uiBindingRegistry.registerMouseDownBinding(new MouseEventMatcher(SWT.NONE,GridRegion.BODY, MouseEventMatcher.RIGHT_BUTTON), new SelectCellAction(){ @Override public void run(NatTable natTable, MouseEvent event) { if (config == null || config.isCellSelectionEnabled()) super.run(natTable, event); } }); uiBindingRegistry.registerMouseDownBinding(new MouseEventMatcher(SWT.NONE,GridRegion.COLUMN_HEADER, MouseEventMatcher.RIGHT_BUTTON), new ViewportSelectColumnAction(true, true){ @Override public void run(NatTable natTable, MouseEvent event) { if (config == null || config.isColumnSelectionEnabled()) super.run(natTable, event); } }); uiBindingRegistry.registerMouseDownBinding(new MouseEventMatcher(SWT.NONE,GridRegion.ROW_HEADER, MouseEventMatcher.RIGHT_BUTTON), new ViewportSelectRowAction(true, true){ @Override public void run(NatTable natTable, MouseEvent event) { if (config == null || config.isRowSelectionEnabled()) super.run(natTable, event); } }); } }); // Create table this.table = new NatTable(root, layerComposite, false); this.table.addConfiguration(new StyleConfigurationTable(config)); this.table.configure(); this.addSelectionListener(bodyLayer.getSelectionLayer()); } } /** * Adds a listener. * * @param arg0 */ public void addMouseListener(MouseListener arg0) { table.addMouseListener(arg0); } /** * Adds a selection listener. * * @param e * @return */ public boolean addSelectionListener(SelectionListener e) { return selectionListeners.add(e); } /** * Clears the table. */ public void clear() { this.table.doCommand(new FillLayerResetCommand()); this.dataProviderBody.clear(); this.dataProviderColumnHeader.clear(); this.dataProviderRowHeader.clear(); this.table.refresh(); this.selectedRow = null; this.selectedColumn = null; } /** * Returns the backing widget. * * @return */ public Control getControl() { return this.root; } /** * * Returns the selected column, or null. * * @return */ public Integer getSelectedColumn() { return selectedColumn; } /** * Returns the selected row, or null. * * @return */ public Integer getSelectedRow() { return selectedRow; } /** * Redraws the table. */ public void refresh() { this.table.refresh(); if (this.selectedColumn == null || this.selectedColumn >= dataProviderBody.getColumnCount() || this.dataProviderBody.getColumnCount() == 0) { this.selectedColumn = null; } if (this.selectedRow == null || this.selectedRow >= dataProviderBody.getRowCount() || this.dataProviderBody.getRowCount() == 0) { this.selectedRow = null; } } /** * Removes a listener. * * @param arg0 */ public void removeMouseListener(MouseListener arg0) { table.removeMouseListener(arg0); } /** * Removes a selection listener. * * @param index * @return */ public SelectionListener removeSelectionListener(int index) { return selectionListeners.remove(index); } /** * Updates the underlying table. * * @param data */ public void setData(IDataProvider data) { this.setData(data, createRowHeaderDataProvider(data.getRowCount()), createColumnHeaderDataProvider(data.getColumnCount())); } /** * Updates the underlying table. * * @param data * @param columns */ public void setData(IDataProvider data, IDataProvider columns) { this.setData(data, createRowHeaderDataProvider(data.getRowCount()), columns); } /** * Updates the underlying table. * * @param data * @param rows * @param columns */ public void setData(IDataProvider data, IDataProvider rows, IDataProvider columns) { // Disable redrawing this.root.setRedraw(false); this.table.doCommand(new FillLayerResetCommand()); this.dataProviderBody.setData(data); this.dataProviderColumnHeader.setData(columns); this.dataProviderRowHeader.setData(rows); this.table.refresh(); // Redraw this.root.setRedraw(true); this.root.layout(true); // Reset state this.selectedRow = null; this.selectedColumn = null; } /** * Updates the underlying table. * * @param data * @param columns */ public void setData(IDataProvider data, String[] columns) { this.setData(data, createRowHeaderDataProvider(data.getRowCount()), createColumnHeaderDataProvider(columns)); } /** * Updates the underlying table. * * @param data * @param rows * @param columns */ public void setData(IDataProvider data, String[] rows, String[] columns) { this.setData(data, createRowHeaderDataProvider(rows), createColumnHeaderDataProvider(columns)); } /** * Updates the underlying table. * * @param data */ public void setData(String[][] data) { this.setData(createBodyDataProvider(data)); } /** * Updates the underlying table. * * @param data * @param columns */ public void setData(String[][] data, String[] columns) { IDataProvider body = createBodyDataProvider(data); setData(body, createRowHeaderDataProvider(body.getRowCount()), createColumnHeaderDataProvider(columns)); } /** * Updates the underlying table. * * @param data * @param rows * @param columns */ public void setData(String[][] data, String[] rows, String[] columns) { setData(createBodyDataProvider(data), rows, columns); } /** * Updates the selection. * * @param row * @param column */ public void setSelection(int row, int column) { this.table.doCommand(new SelectCellCommand(bodyLayer.getSelectionLayer(), column, row, false, false)); } /** * To display coordinates. * * @param x * @param y * @return */ public Point toDisplay(int x, int y) { return table.toDisplay(x, y); } /** * Action. * * @param arg0 * @return */ private boolean actionCellSelected(CellSelectionEvent arg0) { // Reset this.selectedColumn = null; this.selectedRow = null; // Set int column = arg0.getColumnPosition(); int row = arg0.getRowPosition(); if (column>=0 && row>=0 && row<dataProviderBody.getRowCount() && column<dataProviderBody.getColumnCount()){ this.selectedColumn = column; this.selectedRow = row; fireSelectionEvent(); return true; } else { return false; } } /** * Action. * * @param arg0 * @return */ private boolean actionColumnSelected(ColumnSelectionEvent arg0) { // Reset this.selectedColumn = null; this.selectedRow = null; // Set int column = arg0.getColumnPositionRanges().iterator().next().start; if (column>=0 && column<dataProviderBody.getColumnCount()){ this.selectedColumn = column; fireSelectionEvent(); return true; } else { return false; } } /** * Action. * * @param arg0 * @return */ private boolean actionRowSelected(RowSelectionEvent arg0) { // Reset this.selectedColumn = null; this.selectedRow = null; // Set int row = arg0.getRowPositionRanges().iterator().next().start; if (row>=0 && row<dataProviderBody.getRowCount()){ this.selectedRow = row; fireSelectionEvent(); return true; } else { return false; } } /** * Adds a selection listener. * * @param layer */ private void addSelectionListener(final SelectionLayer layer) { layer.addLayerListener(new ILayerListener(){ @Override public void handleLayerEvent(ILayerEvent arg0) { if (arg0 instanceof CellSelectionEvent) { if (!actionCellSelected((CellSelectionEvent)arg0)){ layer.clear(true); } } else if (arg0 instanceof ColumnSelectionEvent) { if (!actionColumnSelected((ColumnSelectionEvent)arg0)){ layer.clear(true); } } else if (arg0 instanceof RowSelectionEvent) { if (!actionRowSelected((RowSelectionEvent)arg0)) { layer.clear(true); } } } }); } /** * * * @param data * @return */ private IDataProvider createBodyDataProvider(final String[][] data) { return new ListDataProvider<String[]>(Arrays.asList(data), new IColumnAccessor<String[]>(){ @Override public int getColumnCount() { return data==null || data.length==0 || data[0]==null ? 0 : data[0].length; } @Override public Object getDataValue(String[] arg0, int arg1) { return arg0[arg1]; } @Override public void setDataValue(String[] arg0, int arg1, Object arg2) { arg0[arg1] = arg2.toString(); } }); } /** * * * @param length * @return */ private IDataProvider createColumnHeaderDataProvider(final int length) { return new IDataProvider(){ @Override public int getColumnCount() { return length; } @Override public Object getDataValue(int columnIndex, int rowIndex) { return columnIndex; } @Override public int getRowCount() { return 1; } @Override public void setDataValue(int columnIndex, int rowIndex, Object newValue) { // Ignore } }; } /** * * * @param data * @return */ private IDataProvider createColumnHeaderDataProvider(final String[] data) { return new IDataProvider(){ @Override public int getColumnCount() { return data.length; } @Override public Object getDataValue(int columnIndex, int rowIndex) { return data[columnIndex]; } @Override public int getRowCount() { return 1; } @Override public void setDataValue(int columnIndex, int rowIndex, Object newValue) { // Ignore } }; } /** * * * @param length * @return */ private IDataProvider createRowHeaderDataProvider(final int length) { return new IDataProvider(){ @Override public int getColumnCount() { return 1; } @Override public Object getDataValue(int columnIndex, int rowIndex) { return rowIndex; } @Override public int getRowCount() { return length; } @Override public void setDataValue(int columnIndex, int rowIndex, Object newValue) { // Ignore } }; } /** * * * @param data * @return */ private IDataProvider createRowHeaderDataProvider(final String[] data) { return new IDataProvider(){ @Override public int getColumnCount() { return 1; } @Override public Object getDataValue(int columnIndex, int rowIndex) { return data[rowIndex]; } @Override public int getRowCount() { return data.length; } @Override public void setDataValue(int columnIndex, int rowIndex, Object newValue) { // Ignore } }; } /** * Fires a new event. */ private void fireSelectionEvent(){ Event event = new Event(); event.display = table.getDisplay(); event.item = table; event.widget = table; SelectionEvent sEvent = new SelectionEvent(event); for (SelectionListener listener : selectionListeners) { listener.widgetSelected(sEvent); } } }