// ============================================================================ // // Copyright (C) 2006-2016 Talend Inc. - www.talend.com // // This source code is available under agreement available at // %InstallDIR%\features\org.talend.rcp.branding.%PRODUCTNAME%\%PRODUCTNAME%license.txt // // You should have received a copy of the agreement // along with this program; if not, write to Talend SA // 9 rue Pages 92150 Suresnes, France // // ============================================================================ package org.talend.dataquality.record.linkage.ui.composite.table; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.nebula.widgets.nattable.NatTable; 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.coordinate.Range; import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor; import org.eclipse.nebula.widgets.nattable.data.IDataProvider; import org.eclipse.nebula.widgets.nattable.data.ReflectiveColumnPropertyAccessor; import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes; 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.GridLayer; import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer; import org.eclipse.nebula.widgets.nattable.grid.layer.event.ColumnHeaderSelectionEvent; import org.eclipse.nebula.widgets.nattable.hideshow.ColumnHideShowLayer; import org.eclipse.nebula.widgets.nattable.layer.AbstractLayerTransform; import org.eclipse.nebula.widgets.nattable.layer.DataLayer; import org.eclipse.nebula.widgets.nattable.layer.ILayerListener; import org.eclipse.nebula.widgets.nattable.layer.LabelStack; import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent; import org.eclipse.nebula.widgets.nattable.painter.cell.BackgroundPainter; import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter; import org.eclipse.nebula.widgets.nattable.painter.cell.TextPainter; import org.eclipse.nebula.widgets.nattable.reorder.ColumnReorderLayer; import org.eclipse.nebula.widgets.nattable.reorder.event.ColumnReorderEvent; import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; import org.eclipse.nebula.widgets.nattable.sort.SortDirectionEnum; import org.eclipse.nebula.widgets.nattable.sort.config.DefaultSortConfiguration; import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes; import org.eclipse.nebula.widgets.nattable.style.DisplayMode; import org.eclipse.nebula.widgets.nattable.style.IStyle; import org.eclipse.nebula.widgets.nattable.style.Style; import org.eclipse.nebula.widgets.nattable.util.GUIHelper; import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Rectangle; 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.Display; import org.talend.cwm.helper.ColumnHelper; import org.talend.dataprofiler.core.ui.grid.utils.Observerable; import org.talend.dataprofiler.core.ui.grid.utils.TDQObserver; import org.talend.dataquality.PluginConstant; import org.talend.dataquality.record.linkage.ui.composite.ListObjectDataProvider; import org.talend.dataquality.record.linkage.ui.composite.utils.ImageLib; import org.talend.dataquality.record.linkage.ui.composite.utils.MatchRuleAnlaysisUtils; import org.talend.dataquality.record.linkage.utils.MatchAnalysisConstant; import org.talend.utils.sugars.ReturnCode; import orgomg.cwm.objectmodel.core.ModelElement; /** * when the property(displayed column) name of the table is changed, need to call initTableProperty first. if only the * data of the table changed, need only to call createTableControl. TODO: refresh the table with new data(column not * changed), and no need to create the table for every refresh; or give all the data to the table at one time? */ public class DataSampleTable implements TDQObserver<ModelElement[]>, Observerable<Map<String, Integer>> { private BodyLayerStack bodyLayer; @SuppressWarnings("rawtypes") private ListObjectDataProvider bodyDataProvider; private String[] propertyNames; private List<String> lastTimePropertyNameOrder; private Map<String, String> propertyToLabels; private NatTable natTable; public NatTable getNatTable() { return this.natTable; } public static final String MATCH_EKY = "MATCH"; //$NON-NLS-1$ public static final String BLOCK_EKY = "BLOCK"; //$NON-NLS-1$ /** * the font used for nattable. for the font size, when the system font size is normal(normal and 125% all is 8), it * can show well;when the system font size is bigger(150% is 9), we set the size smaller to make it show well. */ public static final Font font = new Font(Display.getCurrent(), GUIHelper.DEFAULT_FONT.getFontData()[0].getName(), GUIHelper.DEFAULT_FONT.getFontData()[0].getHeight() > 8 ? (org.apache.commons.lang3.SystemUtils.IS_OS_MAC ? 12 : 8) : GUIHelper.DEFAULT_FONT.getFontData()[0].getHeight(), SWT.NONE); protected PropertyChangeSupport listeners = new PropertyChangeSupport(this); private String currentSelectedColumn = null; // record the columns which is used as block keys private List<String> markedAsBlockKey = null; // record the columns which is used as match keys private List<String> markedAsMatchKey = null; // TDQ-9297 msjian: set the default value the same as lessSpin default value. private int minGrpSize = PluginConstant.HIDDEN_GROUP_LESS_THAN_DEFAULT; private SortState sortState = new SortState(-1); // <groupid, related color> private Map<String, Integer> rowOfGIDWithColor = new HashMap<String, Integer>(); private int masterColumn; private boolean isContainGID = false; private final static Color[] COLOR_LIST = MatchRuleColorRegistry.getColorsForSwt(); private ColumnPosition additionalColumnPosition; protected Composite tablePanel = null; private Composite drawCanvas = null; private boolean needLoadData = false; private int limitNumber = 0; private Boolean isShowRandomData = Boolean.FALSE; private boolean isSameTable = true; private ReturnCode isDataAvailable = new ReturnCode(); public ReturnCode isDataAvailable() { return this.isDataAvailable; } private List<TDQObserver<Map<String, Integer>>> Observers = null; Map<String, Integer> ColumnIndexMap = null; private List<Object[]> existPreviewData; public DataSampleTable() { } /** * create the nattable every time when the user select some columns * * @param columns * @param listOfData */ public TControl createTable(Composite parentContainer, ModelElement[] columns, List<Object[]> listOfData) { reset(); initTableProperty(columns); // initial the data if it is empty existPreviewData = listOfData == null ? new ArrayList<Object[]>() : listOfData; List<Object[]> results = existPreviewData; results = handleEmptyRow(columns, results); TControl handleGID = handleGID(parentContainer, results); if (handleGID != null) { return handleGID; } return createTableControl(parentContainer, results); } /** * DOC talend Comment method "handleEmptyRow". * * @param columns * @param results */ protected List<Object[]> handleEmptyRow(ModelElement[] columns, List<Object[]> results) { if (results.size() < 1) { results.add(getEmptyRow(columns.length)); } return results; } /** * DOC talend Comment method "handleGID". * * @param parentContainer * @param results */ protected TControl handleGID(Composite parentContainer, List<Object[]> results) { // Two kinds of result for listOfData : 1) is coming from match: the result contains GID // 2) is coming from the query without match/or from block:the result did not contain GID isContainGID = isContainGID(results); if (isContainGID) { initGIDMap(results); if (minGrpSize > 1) { return hideGroup(parentContainer, results); } } return null; } /** * DOC talend Comment method "isContainGID". * * @param results * @return */ protected boolean isContainGID(List<Object[]> results) { return results.get(0).length > additionalColumnPosition.GIDindex; } /** * before create the table control, need to init the property name and name to label map * * @param proNames */ private void initTableProperty(ModelElement[] columns) { propertyNames = createColumnLabel(columns); Map<String, String> columnToLabelMap = new HashMap<String, String>(); for (String label : propertyNames) { columnToLabelMap.put(label, label); } this.propertyToLabels = columnToLabelMap; } private String[] createColumnLabel(ModelElement[] columns) { int columnCount = getFixedColumnCount(); if (columns != null) { columnCount = columns.length + columnCount; } List<String> columnsName = new ArrayList<String>(); if (columns != null) { for (ModelElement column : columns) { if (column == null) { continue; } columnsName.add(column.getName()); } } columnsName.addAll(createFixedColumns(columns == null ? 0 : columns.length)); return columnsName.toArray(new String[columnsName.size()]); } /** * DOC talend Comment method "createFixedColumns". * * @return */ protected Collection<? extends String> createFixedColumns(int columnSize) { List<String> columnNames = new ArrayList<String>(); columnNames.add(MatchAnalysisConstant.BLOCK_KEY); // remember the index of the GID; additionalColumnPosition = new ColumnPosition(columnSize + 1); columnNames.add(MatchAnalysisConstant.GID); // record the index of the GRP_SIZE sortState = new SortState(columnSize + 2); columnNames.add(MatchAnalysisConstant.GRP_SIZE); this.masterColumn = columnSize + 3; columnNames.add(MatchAnalysisConstant.MASTER); columnNames.add(MatchAnalysisConstant.SCORE); columnNames.add(MatchAnalysisConstant.GRP_QUALITY); columnNames.add(MatchAnalysisConstant.ATTRIBUTE_SCORES); return columnNames; } /** * DOC talend Comment method "getFixedColumnCount". */ protected int getFixedColumnCount() { return 7; } /** * store the GID's group size. * * @param listOfData */ private void initGIDMap(List<Object[]> listOfData) { for (Object[] row : listOfData) { int grpSize = getGroupSize(row); if (grpSize == 0) { continue; } String groupId = (String) row[additionalColumnPosition.GIDindex]; // String[] gids = StringUtils.splitByWholeSeparatorPreserveAllTokens(groupId, PluginConstant.COMMA_STRING); // for (String gid : gids) { // this.rowOfGIDWithColor.put(gid, grpSize); // } this.rowOfGIDWithColor.put(groupId, grpSize); } } private int getGroupSize(Object[] row) { try { return Integer.valueOf((String) row[sortState.getGrpSizeIndex()]); } catch (java.lang.NumberFormatException nfe) { // no need to handle--when no column given return 0; } } /** * DOC yyin Comment method "reset". */ private void reset() { // reset some properties: GID-Grpsize map, this.rowOfGIDWithColor.clear(); } private TControl hideGroup(Composite parentContainer, List<Object[]> listOfData) { List<Object[]> filteredList = new ArrayList<Object[]>(); boolean flag = false; for (Object[] row : listOfData) { int grpSize = getGroupSize(row); if (grpSize == 0) { if (flag) { filteredList.add(row); } } else if (grpSize >= minGrpSize) { flag = true; filteredList.add(row); } else { flag = false; } } return createTableControl(parentContainer, filteredList); } private void addCustomSelectionBehaviour() { natTable.addLayerListener(new ILayerListener() { @Override public void handleLayerEvent(ILayerEvent event) { if (event instanceof ColumnHeaderSelectionEvent) { ColumnHeaderSelectionEvent columnEvent = (ColumnHeaderSelectionEvent) event; Collection<Range> ranges = columnEvent.getColumnPositionRanges(); if (ranges.size() > 0) { Range range = ranges.iterator().next(); handleColumnSelectionChange(range.start); } } else if (event instanceof ColumnReorderEvent) { if (ColumnIndexMap == null) { ColumnIndexMap = new HashMap<String, Integer>(); } else { ColumnIndexMap.clear(); } // save propertyNames oder into lastTimePropertyNameOrder initPropertyNameOrder(); // Fill elements into ColumnIndexMap before the order change ones. fillPreElement(); // Fill the oder all of elements which display on the table into ColumnIndexMap fillElementWhichDisplayOnTable(); // Fill the oder all of elements which after the order change ones. fillEndElement(); // update newest order state on the lastTimePropertyNameOrder list savePropertyNameState(); notifyObservers(); } } /** * DOC talend Comment method "fillElementWhichDisplayOnTable". copy by #fillElementWhichDisplayOnTable */ private void fillElementWhichDisplayOnTable() { for (int index = 1; index < natTable.getColumnCount(); index++) { int columnIndexByPosition = natTable.getColumnIndexByPosition(index); ColumnIndexMap.put(propertyNames[columnIndexByPosition], ColumnIndexMap.size()); } } private void fillEndElement() { for (int index = ColumnIndexMap.size(); index < propertyNames.length; index++) { ColumnIndexMap.put(lastTimePropertyNameOrder.get(index), index); } } private void fillPreElement() { int disColumnCount = natTable.getColumnCount() - 1; // display all case so that no preElement one need to be filled if (disColumnCount >= lastTimePropertyNameOrder.size()) { return; } // not all display String endName = propertyNames[natTable.getColumnIndexByPosition(1)]; int firstOneLastPosition = lastTimePropertyNameOrder.indexOf(endName); endName = propertyNames[natTable.getColumnIndexByPosition(2)]; int secondOneLastPosition = lastTimePropertyNameOrder.indexOf(endName); if (secondOneLastPosition - firstOneLastPosition > 0) { // 1 is not be change order case endName = lastTimePropertyNameOrder.get(firstOneLastPosition); // For example 1->2+ if (secondOneLastPosition - firstOneLastPosition == 1) { String moveColName = findMoveColumnName(firstOneLastPosition - 1); if (moveColName != null) { endName = moveColName; } } // For example 1->2,2+->1 } else if (secondOneLastPosition - firstOneLastPosition < 0) { // in fact the value shoud be // firstOneLastPosition+secondOneLastPosition-firstOneLastPosition endName = lastTimePropertyNameOrder.get(secondOneLastPosition); } // ~ not all display int index = 0; for (String propertyName : lastTimePropertyNameOrder) { if (endName.equals(propertyName)) { break; } ColumnIndexMap.put(propertyName, index++); } } /** * * Find the column name which be moved on the ui * * @param spacing * @return */ private String findMoveColumnName(int spacing) { for (int index = 3; index < natTable.getColumnCount() - 1; index++) { String propertyName = propertyNames[natTable.getColumnIndexByPosition(index)]; int lastPosition = lastTimePropertyNameOrder.indexOf(propertyName); if (spacing > lastPosition - index) { return propertyName; } else if (spacing == lastPosition - index) { continue; } else { break; } } return null; } }); } private void savePropertyNameState() { lastTimePropertyNameOrder.clear(); String[] nameOrderArray = new String[ColumnIndexMap.size()]; for (String propertyName : ColumnIndexMap.keySet()) { nameOrderArray[ColumnIndexMap.get(propertyName)] = propertyName; } this.lastTimePropertyNameOrder.addAll(Arrays.asList(nameOrderArray)); } /** * DOC talend Comment method "initPropertyNameOrder". */ private void initPropertyNameOrder() { if (lastTimePropertyNameOrder == null) { lastTimePropertyNameOrder = new ArrayList<String>(); for (String name : propertyNames) { lastTimePropertyNameOrder.add(name); } } } private void handleColumnSelectionChange(int index) { sortState.setSelectedColumnIndex(index - 1); // need also to remember the selected column name, for fix the arrow on the column when scrolling sortState.setSelectedColumnName(this.propertyNames[index - 1]); currentSelectedColumn = this.getUserColumnNameByPosition(index); listeners.firePropertyChange(MatchAnalysisConstant.DATA_SAMPLE_TABLE_COLUMN_SELECTION, true, false); } public String getCurrentSelectedColumn() { return this.currentSelectedColumn; } public void addPropertyChangeListener(PropertyChangeListener listener) { listeners.addPropertyChangeListener(listener); } // sort by the current selected column public void sortByColumn(List<ModelElement> columns) { if (columns == null || columns.size() < 1) { return; } // if the next sort direction is back to original SortDirectionEnum nextSortDirection = sortState.getNextSortDirection(); List<Object[]> sortedData = bodyDataProvider.getList(); if (SortDirectionEnum.NONE.equals(nextSortDirection) && isContainGID) { // if the data has GID, back to order by GID sortedData = MatchRuleAnlaysisUtils.sortResultByGID(propertyNames, sortedData); } else { sortedData = MatchRuleAnlaysisUtils.sortDataByColumn(sortState, sortedData, columns); } // refresh the table by the sorted data Composite parent = this.natTable.getParent(); createTableControl(parent, sortedData); GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable); // add sort data function natTable.addConfiguration(new DefaultSortConfiguration()); natTable.configure(); this.natTable.redraw(); parent.getParent().layout(); parent.layout(); } /** * when the data is empty, the column can not response the click event, so we need to add an empty row to it. * * @param length * * @return */ private Object[] getEmptyRow(int length) { Object[] emptyRow = new Object[length]; for (int i = 0; i < length; i++) { emptyRow[i] = StringUtils.EMPTY; } return emptyRow; } public void changeColumnHeaderLabelColor(String columnName, Color color, String keyName) { updateMarkedKeys(columnName, color, keyName); Style cellStyle = new Style(); cellStyle.setAttributeValue(CellStyleAttributes.FOREGROUND_COLOR, color); cellStyle.setAttributeValue(CellStyleAttributes.FONT, font); natTable.getConfigRegistry().registerConfigAttribute(CellConfigAttributes.CELL_STYLE, cellStyle, DisplayMode.NORMAL, columnName); natTable.configure(); } /** * update Marked Keys * * @param columnName * @param color * @param keyName */ private void updateMarkedKeys(String columnName, Color color, String keyName) { if (this.markedAsBlockKey == null) { markedAsBlockKey = new ArrayList<String>(); } if (this.markedAsMatchKey == null) { markedAsMatchKey = new ArrayList<String>(); } if (GUIHelper.COLOR_BLACK.equals(color)) { removeMarkedKey(columnName, keyName); } else if (GUIHelper.COLOR_GREEN.equals(color)) { markedAsBlockKey.add(columnName); } else if (GUIHelper.COLOR_RED.equals(color)) { this.markedAsMatchKey.add(columnName); } } /** * remove Marked Key". * * @param columnName * @param keyName */ private void removeMarkedKey(String columnName, String keyName) { if (DataSampleTable.MATCH_EKY.equals(keyName)) { this.markedAsMatchKey.remove(columnName); } else if (DataSampleTable.BLOCK_EKY.endsWith(keyName)) { this.markedAsBlockKey.remove(keyName); } } /** * When the column is the user selected one, return its name; when the column is the default additional one, return * null * * @param position * @return */ public String getUserColumnNameByPosition(int position) { if (position > propertyNames.length - getFixedColumnCount()) { return null; } return propertyNames[position - 1]; } public void refresh() { if (natTable == null) { return; } natTable.refresh(); } /** * create the NatTable according to the property, and list of data to display * * @param parent * @param data * @return */ public TControl createTableControl(Composite parent, List<Object[]> data) { bodyDataProvider = setupBodyDataProvider(data); bodyLayer = new BodyLayerStack(bodyDataProvider); DefaultColumnHeaderDataProvider colHeaderDataProvider = new DefaultColumnHeaderDataProvider(propertyNames, propertyToLabels); ColumnHeaderLayerStack columnHeaderLayer = new ColumnHeaderLayerStack(colHeaderDataProvider); DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(bodyDataProvider); RowHeaderLayerStack rowHeaderLayer = new RowHeaderLayerStack(rowHeaderDataProvider); DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(colHeaderDataProvider, rowHeaderDataProvider); CornerLayer cornerLayer = new CornerLayer(new DataLayer(cornerDataProvider), rowHeaderLayer, columnHeaderLayer); GridLayer gridLayer = new GridLayer(bodyLayer, columnHeaderLayer, rowHeaderLayer, cornerLayer); if (natTable != null) { clearTable(); } natTable = new NatTable(parent, NatTable.DEFAULT_STYLE_OPTIONS | SWT.BORDER, gridLayer, false); natTable.addConfiguration(new DefaultNatTableStyleConfiguration() { {// use the own painter to paint the group color cellPainter = new RowBackgroundGroupPainter(new ForegroundTextPainter(false, false, false)); } }); natTable.configure(); setNatTableFont(natTable); natTable.getConfigRegistry().registerConfigAttribute(EditConfigAttributes.CELL_EDITABLE_RULE, IEditableRule.NEVER_EDITABLE, DisplayMode.EDIT, "ODD_BODY"); //$NON-NLS-1$ natTable.getConfigRegistry().registerConfigAttribute(EditConfigAttributes.CELL_EDITABLE_RULE, IEditableRule.NEVER_EDITABLE, DisplayMode.EDIT, "EVEN_BODY"); //$NON-NLS-1$ natTable.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); // add the listener for the column header selection addCustomSelectionBehaviour(); TControl retObj = new TControl(); retObj.setControl(natTable); // the width = (columnCount * DEFAULT_COLUMN_WIDTH) + 60, 60 is the width of first sequence number column retObj.setWidth(colHeaderDataProvider.getColumnCount() * DataLayer.DEFAULT_COLUMN_WIDTH + 60); return retObj; } private void clearTable() { natTable.dispose(); if (this.markedAsBlockKey != null) { this.markedAsBlockKey.clear(); } if (this.markedAsMatchKey != null) { this.markedAsMatchKey.clear(); } } @SuppressWarnings({ "unchecked", "rawtypes" }) private ListObjectDataProvider setupBodyDataProvider(List<Object[]> data) { IColumnPropertyAccessor columnPropertyAccessor = new ReflectiveColumnPropertyAccessor(propertyNames); return new ListObjectDataProvider(data, columnPropertyAccessor); } public List<Object[]> getExistPreviewData() { return this.existPreviewData; } private class ForegroundTextPainter extends TextPainter { private boolean changeForegroundColor = false; private boolean drawImage = false; private Image masterImage = ImageLib.getImage(ImageLib.MASTER_IMAGE); public ForegroundTextPainter(boolean wrapText, boolean paintBg, boolean calculate) { super(wrapText, paintBg, calculate); } @Override public void paintCell(ILayerCell cell, GC gc, Rectangle rectangle, IConfigRegistry configRegistry) { super.paintCell(cell, gc, rectangle, configRegistry); if (drawImage && !changeForegroundColor & cell.getColumnIndex() == 0) { gc.drawImage(masterImage, rectangle.x, rectangle.y); } } @Override public void setupGCFromConfig(GC gc, IStyle cellStyle) { super.setupGCFromConfig(gc, cellStyle); if (changeForegroundColor) { gc.setForeground(ImageLib.COLOR_GREY); } } public void setChangeForegroundColor(boolean isChange) { changeForegroundColor = isChange; } protected void setDrawImage(boolean isDraw) { drawImage = isDraw; } } // for different data group, use different background color private class RowBackgroundGroupPainter extends BackgroundPainter {// GradientBackgroundPainter { private String previousGID = null; public RowBackgroundGroupPainter(ICellPainter painter) { super(painter); } @Override public void paintCell(ILayerCell cell, GC gc, Rectangle bounds, IConfigRegistry configRegistry) { String currentGID = getGID(cell); if (currentGID == null) {// when only show the data without match, use the grey color for the text ((ForegroundTextPainter) getWrappedPainter()).setChangeForegroundColor(true); } // if the row is not a master row, make the text color grey. if (currentGID != null && !StringUtils.EMPTY.equals(currentGID)) { ((ForegroundTextPainter) getWrappedPainter()).setDrawImage(true); Object[] rowObject = (Object[]) bodyDataProvider.getRowObject(cell.getRowIndex()); // use master to judge instead of "0" Boolean isMaster = Boolean.parseBoolean(rowObject[masterColumn].toString()); if (isMaster) { ((ForegroundTextPainter) getWrappedPainter()).setChangeForegroundColor(false); } else { ((ForegroundTextPainter) getWrappedPainter()).setChangeForegroundColor(true); } } else { ((ForegroundTextPainter) getWrappedPainter()).setDrawImage(false); } super.paintCell(cell, gc, bounds, configRegistry); // when the GID changed, draw a line if (currentGID != null) { // only draw a line when the group with same size neighbour with each other and the record in current // line is master. Object[] rowObject = (Object[]) bodyDataProvider.getRowObject(cell.getRowIndex()); Boolean isMaster = Boolean.parseBoolean(rowObject[masterColumn].toString()); if (isMaster && !StringUtils.equals(previousGID, currentGID) && isEqualGroupSize(previousGID, currentGID)) { gc.setLineWidth(gc.getLineWidth() * 2); gc.setLineStyle(SWT.LINE_DOT); gc.setForeground(GUIHelper.COLOR_BLUE); gc.drawLine(bounds.x, bounds.y, bounds.x + bounds.width, bounds.y); } previousGID = currentGID; } } @Override protected Color getBackgroundColour(ILayerCell cell, IConfigRegistry configRegistry) { int grpSizeValue = getGrpSize(cell); if (grpSizeValue == 0) {// default color when no return GUIHelper.COLOR_LIST_BACKGROUND; } return COLOR_LIST[Math.abs((grpSizeValue - 1) % COLOR_LIST.length)]; } /** * first: if the row of the cell is a master row, return its group size second: if the row of the cell is not a * master one, return the previous one * * @param cell * @return */ private int getGrpSize(ILayerCell cell) { Object[] rowObject = (Object[]) bodyDataProvider.getRowObject(cell.getRowIndex()); // if the row record contains the group size info, continue if (rowObject != null && isContainGID) { // find the group size from the map first, GID index = grp_size_index-1 Integer groupSize = rowOfGIDWithColor.get(rowObject[additionalColumnPosition.GIDindex]); if (groupSize != null) { return groupSize; } else { // if the group id has no related group size, get it return getGroupSize(rowObject); } } return 0; } // get the group id of the cell if any private String getGID(ILayerCell cell) { Object[] rowObject = (Object[]) bodyDataProvider.getRowObject(cell.getRowIndex()); // if the row record contains the group size info, continue if (rowObject != null && isContainGID) { return (String) rowObject[additionalColumnPosition.GIDindex]; } else { return null; } } /** * check if the group size of the two group is equal or not. * * @param previousGID2 * @param currentGID * @return */ private boolean isEqualGroupSize(String groupId1, String groupId2) { Integer size1 = rowOfGIDWithColor.get(groupId1); Integer size2 = rowOfGIDWithColor.get(groupId2); if (size1 != null && size2 != null && size1 == size2) { return true; } return false; } } class BodyLayerStack extends AbstractLayerTransform { private SelectionLayer selectionLayer; public BodyLayerStack(IDataProvider dataProvider) { DataLayer bodyDataLayer = new DataLayer(dataProvider); ColumnReorderLayer columnReorderLayer = new ColumnReorderLayer(bodyDataLayer); ColumnHideShowLayer columnHideShowLayer = new ColumnHideShowLayer(columnReorderLayer); this.selectionLayer = new SelectionLayer(columnHideShowLayer); ViewportLayer viewportLayer = new ViewportLayer(this.selectionLayer); setUnderlyingLayer(viewportLayer); } public SelectionLayer getSelectionLayer() { return this.selectionLayer; } } class ColumnHeaderLayerStack extends AbstractLayerTransform { public ColumnHeaderLayerStack(IDataProvider dataProvider) { DataLayer dataLayer = new DataLayer(dataProvider); ColumnHeaderLayer colHeaderLayer = new ColumnHeaderLayer(dataLayer, bodyLayer, bodyLayer.getSelectionLayer()); setUnderlyingLayer(colHeaderLayer); dataLayer.setColumnPercentageSizing(true); } @Override public LabelStack getConfigLabelsByPosition(int columnPosition, int rowPosition) { // use columnPosition to get its property name, if the name is selected, return new LabelStack, else // return super String currentColumnName = getCurrentColumnName(columnPosition, rowPosition); // if the current column is used as key, return its labelstack.(the color of it will keep) if (isColumnMarkedAsKeys(currentColumnName)) { if (columnPosition < propertyNames.length + 1) { return addSortArrow(currentColumnName, new LabelStack(currentColumnName)); } } return addSortArrow(currentColumnName, super.getConfigLabelsByPosition(columnPosition, rowPosition)); } private LabelStack addSortArrow(String currentColumnName, LabelStack configLabels) { // if current is sorting, add sort icon, else no need to add if (sortState.isSortActive()) { if (sortState.getSelectedColumnIndex() != -1 && sortState.isSelectedColumn(currentColumnName)) { switch (sortState.getCurrentSortDirection()) { case ASC: configLabels.addLabelOnTop(DefaultSortConfiguration.SORT_UP_CONFIG_TYPE); break; case DESC: configLabels.addLabelOnTop(DefaultSortConfiguration.SORT_DOWN_CONFIG_TYPE); break; default: } } } return configLabels; } /** * get Current Column Name. * * @param columnPosition * @return */ private String getCurrentColumnName(int columnPosition, int rowPosition) { ILayerCell cell = this.getCellByPosition(columnPosition, rowPosition); Object dataValue = cell.getDataValue(); return String.valueOf(dataValue); } } class RowHeaderLayerStack extends AbstractLayerTransform { public RowHeaderLayerStack(IDataProvider dataProvider) { DataLayer dataLayer = new DataLayer(dataProvider, 50, 20); RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(dataLayer, bodyLayer, bodyLayer.getSelectionLayer()); setUnderlyingLayer(rowHeaderLayer); } } /** * check if the current column is marked as block/match keys. * * @param additionalColumnPosition * @return */ public boolean isColumnMarkedAsKeys(String column) { boolean isMarked = false; if (this.markedAsBlockKey != null && this.markedAsBlockKey.size() > 0) { if (this.markedAsBlockKey.contains(column)) { isMarked = true; } } if (this.markedAsMatchKey != null && this.markedAsMatchKey.size() > 0) { if (this.markedAsMatchKey.contains(column)) { isMarked = true; } } return isMarked; } /** * DOC sizhaoliu Comment method "setMinGroupSize". * * @param valueOf */ public void setMinGroupSize(int size) { this.minGrpSize = size; } public void resetSortSelection() { this.sortState.resetSelectedColumn(); } /** * A control and it's width. */ public class TControl { Control control; Integer width; /** * Getter for control. * * @return the control */ public Control getControl() { return this.control; } /** * Sets the control. * * @param control the control to set */ public void setControl(Control control) { this.control = control; } /** * Getter for width. * * @return the width */ public Integer getWidth() { return this.width; } /** * Sets the width. * * @param width the width to set */ public void setWidth(Integer width) { this.width = width; } } private class ColumnPosition { protected final int GIDindex; public ColumnPosition(int GIDIndex) { GIDindex = GIDIndex; } } /** * * Redraw the table by special columns and reload the data if needed * * @param columns New input columns * @param withData where need to reload data with same time */ public void reDrawTable(ModelElement[] columns, boolean withData) { if (tablePanel != null && !tablePanel.isDisposed()) { tablePanel.dispose(); } needLoadData = withData; List<Object[]> listOfData = null; try { listOfData = getPreviewData(columns, withData); isDataAvailable = new ReturnCode(); } catch (SQLException e) { isDataAvailable.setMessage(e.getMessage()); isDataAvailable.setOk(false); needLoadData = false; } createNatTable(listOfData, drawCanvas, columns); drawCanvas.setVisible(columns != null && columns.length > 0); drawCanvas.layout(); } /** * DOC talend Comment method "getPreviewData". * * @return * @throws SQLException */ private List<Object[]> getPreviewData(ModelElement[] columns, boolean withData) throws SQLException { if (withData && isSameTable) { return createPreviewData(columns); } return new ArrayList<Object[]>(); } /** * DOC talend Comment method "createPreviewData". * * @param columns * @throws SQLException */ protected List<Object[]> createPreviewData(ModelElement[] columns) throws SQLException { return new ArrayList<Object[]>(); } /** * DOC talend Comment method "createNatTable". * * @param listOfData * @param dataTableComp */ public void createNatTable(List<Object[]> listOfData, Composite dataTableComp, ModelElement[] columns) { isSameTable = ColumnHelper.checkSameTable(columns); drawCanvas = dataTableComp; tablePanel = new Composite(drawCanvas, SWT.NONE); GridDataFactory.fillDefaults().align(SWT.LEFT, SWT.TOP).applyTo(tablePanel); tablePanel.setLayout(new GridLayout(1, Boolean.FALSE)); GridData layoutDataFillBoth = new GridData(GridData.FILL_BOTH); Composite subPanel = new Composite(tablePanel, SWT.NONE); subPanel.setLayoutData(layoutDataFillBoth); subPanel.setLayout(new GridLayout(1, true)); DataSampleTable.TControl tControl = this.createTable(subPanel, columns, listOfData); GridDataFactory.fillDefaults().grab(true, true).applyTo(tControl.getControl()); // when refresh the data, the dataSampleSection's width is not 0 initTablePanelLayoutPanel(drawCanvas, layoutDataFillBoth, tControl); tablePanel.layout(); } /** * Getter for isSameTable. * * @return the isSameTable */ public boolean isSameTable() { return this.isSameTable; } /** * DOC talend Comment method "initTablePanelLayoutPanel". * * @param dataPreviewSection * @param layoutDataFillBoth * @param tControl */ protected void initTablePanelLayoutPanel(Composite dataTableComp, GridData layoutDataFillBoth, DataSampleTable.TControl tControl) { if (dataTableComp.getBounds().width > 0) { GridData gridData = new GridData(GridData.FILL_VERTICAL); // get the min value between the NatTable's width and dataSampleSection's width // if the NatTable's width larger than dataSampleSection's width, should minus 40 to let the vertical scroll // bar show int width = Math.min(tControl.getWidth(), dataTableComp.getBounds().width - 40); // the width must langer than 0 width = width > 0 ? width : dataTableComp.getBounds().width - 40; gridData.widthHint = width; tablePanel.setLayoutData(gridData); } else { // when open the editor, the dataSampleSection's width is 0, just set the layout fill both. tablePanel.setLayoutData(layoutDataFillBoth); } } /** * Sets the limitNumber. * * @param limitNumber the limitNumber to set */ public void setLimitNumber(int limitNumber) { this.limitNumber = limitNumber; } /** * Getter for limitNumber. * * @return the limitNumber */ public int getLimitNumber() { return this.limitNumber; } /* * (non-Javadoc) * * @see org.talend.dataprofiler.core.ui.grid.utils.TDQObserver#update(java.lang.Object) */ @Override public void update(ModelElement[] columns) { reDrawTable(columns, needLoadData); } /* * (non-Javadoc) * * @see * org.talend.dataprofiler.core.ui.grid.utils.Observerable#addObserver(org.talend.dataprofiler.core.ui.grid.utils * .TDQObserver) */ @Override public boolean addObserver(TDQObserver<Map<String, Integer>> observer) { initObserverable(); return Observers.add(observer); } /* * (non-Javadoc) * * @see * org.talend.dataprofiler.core.ui.grid.utils.Observerable#removeObserver(org.talend.dataprofiler.core.ui.grid.utils * .TDQObserver) */ @Override public boolean removeObserver(TDQObserver<Map<String, Integer>> observer) { if (Observers == null) { return false; } return Observers.remove(observer); } /* * (non-Javadoc) * * @see org.talend.dataprofiler.core.ui.grid.utils.Observerable#clearObserver() */ @Override public void clearObserver() { if (Observers == null) { return; } Observers.clear(); } /* * (non-Javadoc) * * @see org.talend.dataprofiler.core.ui.grid.utils.Observerable#notifyObservers() */ @Override public void notifyObservers() { if (Observers == null) { return; } for (TDQObserver<Map<String, Integer>> observer : Observers) { observer.update(ColumnIndexMap); } } private void initObserverable() { if (Observers == null) { Observers = new ArrayList<TDQObserver<Map<String, Integer>>>(); } } /** * Getter for isShowRandomData. * * @return the isShowRandomData */ public Boolean isShowRandomData() { return isShowRandomData; } /** * Sets the isShowRandomData. * * @param isShowRandomData the isShowRandomData to set */ public void setShowRandomData(Boolean isShowRandomData) { this.isShowRandomData = isShowRandomData; } /** * set the NatTable Font with a fixed size. */ public void setNatTableFont(NatTable natTable) { // TDQ-9725: we use the fixed font size to make it show well even the user changed the default font size IStyle rowHeaderNormalStyle = natTable.getConfigRegistry().getConfigAttribute(CellConfigAttributes.CELL_STYLE, DisplayMode.NORMAL, GridRegion.ROW_HEADER); if (rowHeaderNormalStyle != null) { rowHeaderNormalStyle.setAttributeValue(CellStyleAttributes.FONT, font); } IStyle columnHeaderNormalStyle = natTable.getConfigRegistry().getConfigAttribute(CellConfigAttributes.CELL_STYLE, DisplayMode.NORMAL, GridRegion.COLUMN_HEADER); if (columnHeaderNormalStyle != null) { columnHeaderNormalStyle.setAttributeValue(CellStyleAttributes.FONT, font); } IStyle dataNormalStyle = natTable.getConfigRegistry().getConfigAttribute(CellConfigAttributes.CELL_STYLE, DisplayMode.NORMAL, GridRegion.DATAGRID); if (dataNormalStyle != null) { dataNormalStyle.setAttributeValue(CellStyleAttributes.FONT, font); } IStyle selectionStyle = natTable.getConfigRegistry().getConfigAttribute(CellConfigAttributes.CELL_STYLE, DisplayMode.SELECT, GridRegion.ROW_HEADER); if (selectionStyle != null) { selectionStyle.setAttributeValue(CellStyleAttributes.FONT, font); } } /** * Getter for propertyNames. * * @return the propertyNames */ public String[] getPropertyNames() { return this.propertyNames; } }