package org.reldb.dbrowser.ui.content.rel.var.grids; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.Vector; import org.eclipse.jface.action.ContributionItem; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.window.DefaultToolTip; import org.eclipse.jface.window.ToolTip; import org.eclipse.nebula.widgets.nattable.NatTable; import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration; 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.IDataProvider; import org.eclipse.nebula.widgets.nattable.data.convert.DefaultDisplayConverter; import org.eclipse.nebula.widgets.nattable.data.convert.DefaultDoubleDisplayConverter; import org.eclipse.nebula.widgets.nattable.data.convert.DefaultIntegerDisplayConverter; import org.eclipse.nebula.widgets.nattable.edit.CellEditorCreatedEvent; import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes; import org.eclipse.nebula.widgets.nattable.edit.editor.CheckBoxCellEditor; import org.eclipse.nebula.widgets.nattable.edit.editor.MultiLineTextCellEditor; import org.eclipse.nebula.widgets.nattable.edit.editor.TextCellEditor; import org.eclipse.nebula.widgets.nattable.edit.gui.ICellEditDialog; import org.eclipse.nebula.widgets.nattable.export.ExportConfigAttributes; import org.eclipse.nebula.widgets.nattable.export.command.ExportCommand; import org.eclipse.nebula.widgets.nattable.extension.poi.HSSFExcelExporter; import org.eclipse.nebula.widgets.nattable.grid.GridRegion; import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultGridLayer; 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.LayerUtil; import org.eclipse.nebula.widgets.nattable.layer.cell.IConfigLabelAccumulator; import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; import org.eclipse.nebula.widgets.nattable.layer.event.CellVisualChangeEvent; import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent; import org.eclipse.nebula.widgets.nattable.painter.cell.CheckBoxPainter; import org.eclipse.nebula.widgets.nattable.painter.cell.ImagePainter; import org.eclipse.nebula.widgets.nattable.painter.cell.TextPainter; import org.eclipse.nebula.widgets.nattable.painter.cell.decorator.BeveledBorderDecorator; import org.eclipse.nebula.widgets.nattable.painter.cell.decorator.CellPainterDecorator; import org.eclipse.nebula.widgets.nattable.painter.cell.decorator.LineBorderDecorator; import org.eclipse.nebula.widgets.nattable.resize.command.InitializeAutoResizeColumnsCommand; import org.eclipse.nebula.widgets.nattable.resize.command.InitializeAutoResizeRowsCommand; import org.eclipse.nebula.widgets.nattable.selection.ITraversalStrategy; import org.eclipse.nebula.widgets.nattable.selection.MoveCellSelectionCommandHandler; import org.eclipse.nebula.widgets.nattable.selection.command.ClearAllSelectionsCommand; 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.RowSelectionEvent; import org.eclipse.nebula.widgets.nattable.style.BorderStyle; import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes; import org.eclipse.nebula.widgets.nattable.style.DisplayMode; import org.eclipse.nebula.widgets.nattable.style.HorizontalAlignmentEnum; import org.eclipse.nebula.widgets.nattable.style.Style; import org.eclipse.nebula.widgets.nattable.tickupdate.TickUpdateConfigAttributes; import org.eclipse.nebula.widgets.nattable.tooltip.NatTableContentTooltip; import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder; import org.eclipse.nebula.widgets.nattable.ui.util.CellEdgeEnum; import org.eclipse.nebula.widgets.nattable.util.GCFactory; import org.eclipse.nebula.widgets.nattable.util.GUIHelper; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.reldb.dbrowser.ui.DbConnection; import org.reldb.dbrowser.ui.IconLoader; import org.reldb.rel.client.Attribute; import org.reldb.rel.client.Connection.ExecuteResult; import org.reldb.rel.client.Tuple; import org.reldb.rel.client.Tuples; import org.reldb.rel.utilities.StringUtils; public abstract class Editor extends Grid { private NatTable table; protected Attribute[] heading; protected Tuples tuples; protected DataProvider dataProvider; private HeadingProvider headingProvider; private DefaultGridLayer gridLayer; private boolean popupEdit = false; private int lastRowSelected = -1; enum RowAction {UPDATE, INSERT}; class HeadingProvider implements IDataProvider { @Override public Object getDataValue(int columnIndex, int rowIndex) { Attribute attribute = heading[columnIndex]; switch (rowIndex) { case 0: return attribute.getName(); case 1: return attribute.getType().toString(); default: return ""; } } @Override public void setDataValue(int columnIndex, int rowIndex, Object newValue) { throw new UnsupportedOperationException(); } @Override public int getColumnCount() { return heading.length; } @Override public int getRowCount() { return 2 + ((keys == null) ? 0 : (((keys.size() > 1) ? keys.size() - 1 : 0))); } }; class Row { private HashMap<Integer, Object> originalData; private HashMap<Integer, Object> newData; private String error; private RowAction action; Row(Tuple tuple) { originalData = new HashMap<Integer, Object>(); newData = new HashMap<Integer, Object>(); for (int column=0; column<tuple.getAttributeCount(); column++) originalData.put(column, tuple.get(column)); reset(); action = RowAction.UPDATE; } Row() { originalData = new HashMap<Integer, Object>(); newData = new HashMap<Integer, Object>(); reset(); action = RowAction.INSERT; } Object getOriginalColumnValue(int column) { return originalData.get(column); } Object getColumnValue(int column) { Object v = newData.get(column); if (v != null) return v; return getOriginalColumnValue(column); } void setColumnValue(int column, Object newValue) { newData.put(column, newValue); } boolean isChanged(int columnIndex) { return newData.containsKey(columnIndex); } private void reset() { newData.clear(); error = null; } // Copy new data to original data, and clear new data void committed() { for (Entry<Integer, Object> entry: newData.entrySet()) originalData.put(entry.getKey(), entry.getValue()); reset(); action = RowAction.UPDATE; } // Clear new data public void cancelled() { reset(); } String getError() { return error; } void setError(String error) { this.error = error; } RowAction getAction() { return action; } public boolean isFilled() { for (int column=0; column<heading.length; column++) { String type = heading[column].getType().toString(); HashMap<Integer, Object> data = isChanged(column) ? newData : originalData; if (!type.equals("CHARACTER") && (data.get(column) == null || data.get(column).toString().trim().length() == 0)) return false; } return true; } } class DataProvider implements IDataProvider { private HashSet<Integer> processRows = new HashSet<Integer>(); private Vector<Row> rows = new Vector<Row>(); private String headingString; public DataProvider() { reload(); headingString = getRelHeading(); } public void reload() { rows.clear(); Iterator<Tuple> iterator = tuples.iterator(); while (iterator.hasNext()) rows.add(new Row(iterator.next())); rows.add(new Row()); processRows.clear(); } public String getError(int row) { if (row >= rows.size()) return null; return rows.get(row).getError(); } public boolean isChanged(int columnIndex, int rowIndex) { return rows.get(rowIndex).isChanged(columnIndex); } @Override public Object getDataValue(int columnIndex, int rowIndex) { return rows.get(rowIndex).getColumnValue(columnIndex); } private int getCountOfInsertErrors() { int count = 0; for (int row: processRows) if (rows.get(row).getError() != null && rows.get(row).getAction() == RowAction.INSERT) count++; return count; } @Override public void setDataValue(int columnIndex, int rowIndex, Object newValue) { if (newValue != null && newValue.toString().length() == 0) newValue = null; if (getDataValue(columnIndex, rowIndex) == null && newValue == null) return; if (getDataValue(columnIndex, rowIndex) != null && newValue != null) if (newValue.toString().equals(getDataValue(columnIndex, rowIndex).toString())) return; if (newValue == null) rows.get(rowIndex).setColumnValue(columnIndex, newValue); else rows.get(rowIndex).setColumnValue(columnIndex, newValue.toString()); processRows.add(rowIndex); int lastRowIndex = rows.size() - 1; if (rowIndex == lastRowIndex && getCountOfInsertErrors() == 0) { rows.add(new Row()); table.redraw(); } } @Override public int getColumnCount() { if (heading == null) return 0; return heading.length; } @Override public int getRowCount() { return rows.size(); } private String getKeySelectionExpression(int rownum) { HashSet<String> key; if (keys.size() == 0) { key = new HashSet<String>(); for (int column = 0; column < heading.length; column++) key.add(heading[column].getName()); } else key = keys.get(0); Row originalValues = rows.get(rownum); String keyspec = ""; for (int column = 0; column < heading.length; column++) { String attributeName = heading[column].getName(); if (key.contains(attributeName)) { if (keyspec.length() > 0) keyspec += " AND "; String attributeType = heading[column].getType().toString(); Object attributeValueRaw = originalValues.getOriginalColumnValue(column); String attributeValue = ""; if (attributeValueRaw != null) attributeValue = attributeValueRaw.toString(); if (attributeType.equals("CHARACTER")) attributeValue = "'" + StringUtils.quote(attributeValue) + "'"; keyspec += attributeName + " = " + attributeValue; } } return keyspec; } private void refreshAfterUpdate() { table.getDisplay().asyncExec(new Runnable() { @Override public void run() { if (!table.isDisposed()) table.redraw(); } }); } private synchronized void updateRow(Row row, int rownum) { if (relvarName == null) { row.committed(); processRows.remove(rownum); } else { String keyspec = getKeySelectionExpression(rownum); String updateQuery = "UPDATE " + relvarName + ((keyspec.length() > 0) ? " WHERE " + keyspec : "") + ": {"; String updateAttributes = ""; for (int column = 0; column < heading.length; column++) { if (row.isChanged(column)) { if (updateAttributes.length() > 0) updateAttributes += ", "; String attributeType = heading[column].getType().toString(); String attributeValue = row.getColumnValue(column).toString(); if (attributeType.equals("CHARACTER")) attributeValue = "'" + StringUtils.quote(attributeValue) + "'"; String attributeName = heading[column].getName(); updateAttributes += attributeName + " := " + attributeValue; } } updateQuery += updateAttributes + "};"; System.out.println("RelvarEditor: query is " + updateQuery); ExecuteResult result = connection.execute(updateQuery); if (result.failed()) row.setError("Unable to update tuples.\n\nQuery: " + updateQuery + " failed:\n\n" + result.getErrorMessage()); else { row.committed(); processRows.remove(rownum); } } refreshAfterUpdate(); } private String getTupleDefinitionFor(Row row) { String insertAttributes = ""; for (int column = 0; column < heading.length; column++) { if (insertAttributes.length() > 0) insertAttributes += ", "; String attributeType = heading[column].getType().toString(); Object attributeValueRaw = row.getColumnValue(column); String attributeValue = ""; if (attributeValueRaw != null) attributeValue = attributeValueRaw.toString(); else if (attributeType.equals("BOOLEAN")) attributeValue = "False"; else if (attributeType.equals("RATIONAL")) attributeValue = "0.0"; else if (attributeType.equals("INTEGER")) attributeValue = "0"; row.setColumnValue(column, attributeValue); if (attributeType.equals("CHARACTER")) attributeValue = "'" + StringUtils.quote(attributeValue) + "'"; String attributeName = heading[column].getName(); insertAttributes += attributeName + " " + attributeValue; } return "TUPLE {" + insertAttributes + "}"; } private synchronized void insertRow(Row row, int rownum) { if (relvarName == null) { row.committed(); processRows.remove(rownum); } else { String insertQuery = "D_INSERT " + relvarName + " RELATION {" + getTupleDefinitionFor(row) + "};"; System.out.println("RelvarEditor: query is " + insertQuery); ExecuteResult result = connection.execute(insertQuery); if (result.failed()) row.setError("Unable to insert tuple.\n\nQuery: " + insertQuery + " failed:\n\n" + result.getErrorMessage()); else { row.committed(); processRows.remove(rownum); } } refreshAfterUpdate(); } public void deleteRows(Set<Range> selections) { if (relvarName == null) { // TODO - implement this System.out.println("Editor: deleteRows() for relvarName == null not implemented yet."); } else { String deleteQuery = "DELETE " + relvarName + " WHERE "; String allKeysSpec = ""; int tupleCount = 0; for (Range range: selections) for (int rownum = range.start; rownum < range.end; rownum++) { if (rows.get(rownum).getAction() != RowAction.INSERT) { String keyspec = getKeySelectionExpression(rownum); if (allKeysSpec.length() > 0) allKeysSpec += " OR "; allKeysSpec += "(" + keyspec + ")"; } tupleCount++; } deleteQuery += allKeysSpec + ";"; System.out.println("RelvarEditor: query is " + deleteQuery); ExecuteResult result = connection.execute(deleteQuery); if (result.failed()) MessageDialog.openError(table.getShell(), "DELETE Error", "Unable to delete tuple" + ((tupleCount>1) ? "s" : "") + ".\n\nQuery: " + deleteQuery + " failed:\n\n" + result.getErrorMessage()); else refresh(); } } public void processDirtyRows() { for (Integer rownum: processRows.toArray(new Integer[0])) if (rownum != lastRowSelected) { Row row = rows.get(rownum); if (row.isFilled()) switch (row.getAction()) { case INSERT: insertRow(row, rownum); break; case UPDATE: updateRow(row, rownum); break; } } } public int countDirtyRows() { return processRows.size(); } public boolean isRVA(int columnIndex) { String attributeType = heading[columnIndex].getType().toString(); return attributeType.startsWith("RELATION "); } private String getRelHeading() { return new TypeInfo(connection).getHeadingDefinition("TYPE_OF(" + getAttributeSource() + ")"); } public String getLiteral() { String body = ""; for (int rownum = 0; rownum < rows.size() - 1; rownum++) body += ((body.length() > 0) ? "," : "") + "\n\t" + getTupleDefinitionFor(rows.get(rownum)); return headingString + " {" + body + "}"; } }; class HeaderConfiguration extends AbstractRegistryConfiguration { public void configureRegistry(IConfigRegistry configRegistry) { ImagePainter keyPainter = new ImagePainter(IconLoader.loadIconNormal("bullet_key")); CellPainterDecorator decorator = new CellPainterDecorator( new TextPainter(), CellEdgeEnum.RIGHT, keyPainter); configRegistry.registerConfigAttribute( CellConfigAttributes.CELL_PAINTER, new BeveledBorderDecorator(decorator), DisplayMode.NORMAL, "keycolumnintegrated"); configRegistry.registerConfigAttribute( CellConfigAttributes.CELL_PAINTER, new BeveledBorderDecorator(keyPainter), DisplayMode.NORMAL, "keycolumnalone"); BorderStyle borderStyle = new BorderStyle(); borderStyle.setColor(GUIHelper.COLOR_GRAY); configRegistry.registerConfigAttribute( CellConfigAttributes.CELL_PAINTER, new LineBorderDecorator(new TextPainter(), borderStyle), DisplayMode.NORMAL, GridRegion.CORNER); } } class EditorConfiguration extends AbstractRegistryConfiguration { @Override public void configureRegistry(IConfigRegistry configRegistry) { // editable configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITABLE_RULE, IEditableRule.ALWAYS_EDITABLE); // style for "changed" cells Style changedStyle = new Style(); changedStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, GUIHelper.COLOR_YELLOW); changedStyle.setAttributeValue(CellStyleAttributes.FOREGROUND_COLOR, GUIHelper.COLOR_BLACK); configRegistry.registerConfigAttribute( CellConfigAttributes.CELL_STYLE, changedStyle, DisplayMode.NORMAL, "changed"); configRegistry.registerConfigAttribute( CellConfigAttributes.CELL_STYLE, changedStyle, DisplayMode.SELECT, "changed"); // style for "error" cells Style errorStyle = new Style(); errorStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, GUIHelper.COLOR_RED); errorStyle.setAttributeValue(CellStyleAttributes.FOREGROUND_COLOR, GUIHelper.COLOR_BLACK); configRegistry.registerConfigAttribute( CellConfigAttributes.CELL_STYLE, errorStyle, DisplayMode.NORMAL, "error"); configRegistry.registerConfigAttribute( CellConfigAttributes.CELL_STYLE, errorStyle, DisplayMode.SELECT, "error"); // options for Excel export configRegistry.registerConfigAttribute(ExportConfigAttributes.EXPORTER, new HSSFExcelExporter()); // style for selected cells Style selectStyle = new Style(); configRegistry.registerConfigAttribute( CellConfigAttributes.CELL_STYLE, selectStyle, DisplayMode.SELECT); // default text editor configRegistry.registerConfigAttribute( EditConfigAttributes.CELL_EDITOR, new TextCellEditor(true, true) { protected Control activateCell(Composite parent, Object originalCanonicalValue) { editorBeenOpened(getRowIndex(), getColumnIndex()); return super.activateCell(parent, originalCanonicalValue); } public void close() { editorBeenClosed(getRowIndex(), getColumnIndex()); super.close(); } }, DisplayMode.NORMAL); // open adjacent editor when we leave the current one during editing configRegistry.registerConfigAttribute( EditConfigAttributes.OPEN_ADJACENT_EDITOR, Boolean.TRUE, DisplayMode.EDIT); // for each column... if (heading != null) for (int column = 0; column < heading.length; column++) { Attribute attribute = heading[column]; String columnLabel = "column" + column; String type = attribute.getType().toString(); if (type.equalsIgnoreCase("INTEGER")) registerIntegerColumn(configRegistry, columnLabel); else if (type.equalsIgnoreCase("RATIONAL")) registerRationalColumn(configRegistry, columnLabel); else if (type.equalsIgnoreCase("CHARACTER")) registerMultiLineEditorColumn(configRegistry, columnLabel); else if (type.equalsIgnoreCase("BOOLEAN")) registerBooleanColumn(configRegistry, columnLabel); else if (type.startsWith("RELATION ")) { String defaultValue = type + " {}"; registerRvaColumn(configRegistry, columnLabel, defaultValue); } else registerDefaultColumn(configRegistry, columnLabel); } } private void registerDefaultColumn(IConfigRegistry configRegistry, String columnLabel) { Style cellStyle = new Style(); cellStyle.setAttributeValue( CellStyleAttributes.HORIZONTAL_ALIGNMENT, HorizontalAlignmentEnum.LEFT); configRegistry.registerConfigAttribute( CellConfigAttributes.CELL_STYLE, cellStyle, DisplayMode.NORMAL, columnLabel); configRegistry.registerConfigAttribute( CellConfigAttributes.CELL_STYLE, cellStyle, DisplayMode.EDIT, columnLabel); } private void registerBooleanColumn(IConfigRegistry configRegistry, String columnLabel) { // register a CheckBoxCellEditor configRegistry.registerConfigAttribute( EditConfigAttributes.CELL_EDITOR, new CheckBoxCellEditor() { protected Control activateCell(Composite parent, Object originalCanonicalValue) { editorBeenOpened(getRowIndex(), getColumnIndex()); return super.activateCell(parent, originalCanonicalValue); } public void close() { editorBeenClosed(getRowIndex(), getColumnIndex()); super.close(); } }, DisplayMode.EDIT, columnLabel); // if you want to use the CheckBoxCellEditor, you should also consider // using the corresponding CheckBoxPainter to show the content like a // checkbox in your NatTable configRegistry.registerConfigAttribute( CellConfigAttributes.CELL_PAINTER, new CheckBoxPainter(), DisplayMode.NORMAL, columnLabel); // using a CheckBoxCellEditor also needs a Boolean conversion to work // correctly configRegistry.registerConfigAttribute( CellConfigAttributes.DISPLAY_CONVERTER, new DefaultDisplayConverter() { @Override public Object canonicalToDisplayValue(Object canonicalValue) { if (canonicalValue == null) return null; boolean isTrue = canonicalValue.toString().equalsIgnoreCase("True"); return new Boolean(isTrue); } @Override public Object displayToCanonicalValue(Object destinationValue) { return ((Boolean)destinationValue).booleanValue() ? "True" : "False"; } }, DisplayMode.NORMAL, columnLabel); } private void registerRationalColumn(IConfigRegistry configRegistry, String columnLabel) { // configure the tick update dialog to use the adjust mode configRegistry.registerConfigAttribute( TickUpdateConfigAttributes.USE_ADJUST_BY, Boolean.TRUE, DisplayMode.EDIT, columnLabel); // Use Double converter configRegistry.registerConfigAttribute( CellConfigAttributes.DISPLAY_CONVERTER, new DefaultDoubleDisplayConverter(), DisplayMode.NORMAL, columnLabel); } private void registerIntegerColumn(IConfigRegistry configRegistry, String columnLabel) { Style cellStyle = new Style(); cellStyle.setAttributeValue( CellStyleAttributes.HORIZONTAL_ALIGNMENT, HorizontalAlignmentEnum.RIGHT); configRegistry.registerConfigAttribute( CellConfigAttributes.CELL_STYLE, cellStyle, DisplayMode.NORMAL, columnLabel); configRegistry.registerConfigAttribute( CellConfigAttributes.CELL_STYLE, cellStyle, DisplayMode.EDIT, columnLabel); // Use Integer converter configRegistry.registerConfigAttribute( CellConfigAttributes.DISPLAY_CONVERTER, new DefaultIntegerDisplayConverter(), DisplayMode.NORMAL, columnLabel); } private void registerMultiLineEditorColumn(IConfigRegistry configRegistry, String columnLabel) { // configure the multi line text editor configRegistry.registerConfigAttribute( EditConfigAttributes.CELL_EDITOR, new MultiLineTextCellEditor(false) { protected Control activateCell(Composite parent, Object originalCanonicalValue) { editorBeenOpened(getRowIndex(), getColumnIndex()); return super.activateCell(parent, originalCanonicalValue); } public void close() { editorBeenClosed(getRowIndex(), getColumnIndex()); super.close(); } }, DisplayMode.NORMAL, columnLabel); Style cellStyle = new Style(); cellStyle.setAttributeValue( CellStyleAttributes.HORIZONTAL_ALIGNMENT, HorizontalAlignmentEnum.LEFT); configRegistry.registerConfigAttribute( CellConfigAttributes.CELL_STYLE, cellStyle, DisplayMode.NORMAL, columnLabel); configRegistry.registerConfigAttribute( CellConfigAttributes.CELL_STYLE, cellStyle, DisplayMode.EDIT, columnLabel); // configure custom dialog settings Display display = Display.getCurrent(); Map<String, Object> editDialogSettings = new HashMap<String, Object>(); editDialogSettings.put(ICellEditDialog.DIALOG_SHELL_TITLE, "Edit"); editDialogSettings.put(ICellEditDialog.DIALOG_SHELL_ICON, display.getSystemImage(SWT.ICON_WARNING)); editDialogSettings.put(ICellEditDialog.DIALOG_SHELL_RESIZABLE, Boolean.TRUE); Point size = new Point(400, 300); editDialogSettings.put(ICellEditDialog.DIALOG_SHELL_SIZE, size); int screenWidth = display.getBounds().width; int screenHeight = display.getBounds().height; Point location = new Point( (screenWidth / (2 * display.getMonitors().length)) - (size.x / 2), (screenHeight / 2) - (size.y / 2)); editDialogSettings.put(ICellEditDialog.DIALOG_SHELL_LOCATION, location); configRegistry.registerConfigAttribute( EditConfigAttributes.EDIT_DIALOG_SETTINGS, editDialogSettings, DisplayMode.EDIT, columnLabel); } private void registerRvaColumn(IConfigRegistry configRegistry, String columnLabel, String defaultValue) { // edit or not configRegistry.registerConfigAttribute( EditConfigAttributes.CELL_EDITABLE_RULE, new IEditableRule() { @Override public boolean isEditable(ILayerCell cell, IConfigRegistry configRegistry) { return isEditable(cell.getColumnIndex(), cell.getRowIndex()); } @Override public boolean isEditable(int columnIndex, int rowIndex) { return dataProvider.isRVA(columnIndex); } }, DisplayMode.EDIT, columnLabel); // Button displayed if editable ImagePainter imagePainter = new ImagePainter(IconLoader.loadIcon("table")); configRegistry.registerConfigAttribute( CellConfigAttributes.CELL_PAINTER, imagePainter, DisplayMode.NORMAL, "RVAeditor"); // Custom dialog box configRegistry.registerConfigAttribute( EditConfigAttributes.CELL_EDITOR, new RvaCellEditor(Editor.this, defaultValue), DisplayMode.EDIT, columnLabel); } } class PopupEditorConfiguration extends AbstractRegistryConfiguration { @Override public void configureRegistry(IConfigRegistry configRegistry) { // always/never open in a subdialog depending on popupEdit value configRegistry.unregisterConfigAttribute(EditConfigAttributes.OPEN_IN_DIALOG); configRegistry.registerConfigAttribute( EditConfigAttributes.OPEN_IN_DIALOG, popupEdit, DisplayMode.EDIT); } } public void refresh() { if (table != null) { if (dataProvider != null) dataProvider.reload(); table.refresh(); } } public Editor(Composite parent, DbConnection connection, String relvarName) { super(parent, connection, relvarName); } private static class EmptyGridData implements IDataProvider { @Override public Object getDataValue(int columnIndex, int rowIndex) { return "Variable is not editable because it has no attributes. Select Design from the toolbar to add attributes."; } @Override public void setDataValue(int columnIndex, int rowIndex, Object newValue) { } @Override public int getColumnCount() { return 1; } @Override public int getRowCount() { return 1; } } private static class EmptyGridHeading implements IDataProvider { @Override public Object getDataValue(int columnIndex, int rowIndex) { return null; } @Override public void setDataValue(int columnIndex, int rowIndex, Object newValue) { } @Override public int getColumnCount() { return 1; } @Override public int getRowCount() { return 0; } } protected void init() { if (heading == null) { gridLayer = new DefaultGridLayer(new EmptyGridData(), new EmptyGridHeading()); table = new NatTable(parent, gridLayer, true); table.addListener(SWT.Paint, new Listener(){ @Override public void handleEvent(Event arg0) { for (int i=0; i < table.getColumnCount(); i++) { InitializeAutoResizeColumnsCommand columnCommand = new InitializeAutoResizeColumnsCommand(table, i, table.getConfigRegistry(), new GCFactory(table)); table.doCommand(columnCommand); } for (int i=0; i < table.getRowCount(); i++) { InitializeAutoResizeRowsCommand rowCommand = new InitializeAutoResizeRowsCommand(table, i, table.getConfigRegistry(), new GCFactory(table)); table.doCommand(rowCommand); } table.removeListener(SWT.Paint, this); } }); table.configure(); return; } dataProvider = new DataProvider(); headingProvider = new HeadingProvider(); gridLayer = new DefaultGridLayer( dataProvider, headingProvider ); // CellLabelAccumulator determines how cells will be displayed class CellLabelAccumulator implements IConfigLabelAccumulator { @Override public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) { configLabels.addLabel("column" + columnPosition); // error? if (dataProvider.getError(rowPosition) != null) configLabels.addLabel("error"); // changed? else if (dataProvider.isChanged(columnPosition, rowPosition)) configLabels.addLabel("changed"); else if (dataProvider.isRVA(columnPosition)) configLabels.addLabel("RVAeditor"); } } DataLayer bodyDataLayer = (DataLayer)gridLayer.getBodyDataLayer(); CellLabelAccumulator cellLabelAccumulator = new CellLabelAccumulator(); bodyDataLayer.setConfigLabelAccumulator(cellLabelAccumulator); class HeadingLabelAccumulator implements IConfigLabelAccumulator { @Override public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) { if (keys != null && keys.size() > 0) { if (rowPosition == 0 && keys.get(0).contains(heading[columnPosition].getName())) configLabels.addLabel("keycolumnintegrated"); else if (rowPosition >= 2 && keys.size() > 1 && keys.get(rowPosition - 1).contains(heading[columnPosition].getName())) configLabels.addLabel("keycolumnalone"); } } } DataLayer headingDataLayer = (DataLayer)gridLayer.getColumnHeaderDataLayer(); HeadingLabelAccumulator columnLabelAccumulator = new HeadingLabelAccumulator(); headingDataLayer.setConfigLabelAccumulator(columnLabelAccumulator); table = new NatTable(parent, gridLayer, false); DefaultNatTableStyleConfiguration defaultStyle = new DefaultNatTableStyleConfiguration(); table.addConfiguration(defaultStyle); table.addConfiguration(new EditorConfiguration()); table.addConfiguration(new HeaderConfiguration()); ContributionItem columnMenuItems = new ContributionItem() { @Override public void fill(Menu menu, int index) { MenuItem doesPopupEdit = new MenuItem(menu, SWT.CHECK); doesPopupEdit.setText("Pop-up Edit Box"); doesPopupEdit.setImage(IconLoader.loadIcon("popup")); doesPopupEdit.setSelection(popupEdit); doesPopupEdit.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent evt) { popupEdit = !popupEdit; table.addConfiguration(new PopupEditorConfiguration()); table.configure(); } }); MenuItem export = new MenuItem(menu, SWT.PUSH); export.setText("Export"); export.setImage(IconLoader.loadIcon("export")); export.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent evt) { export(); } }); } }; table.addConfiguration(new MenuConfiguration( GridRegion.COLUMN_HEADER, new PopupMenuBuilder(table).withContributionItem(columnMenuItems))); ContributionItem rowMenuItems = new ContributionItem() { @Override public void fill(Menu menu, int index) { MenuItem doesDelete = new MenuItem(menu, SWT.PUSH); doesDelete.setText("Delete"); doesDelete.setImage(IconLoader.loadIcon("table_row_delete")); doesDelete.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent evt) { askDeleteSelected(); } }); MenuItem export = new MenuItem(menu, SWT.PUSH); export.setText("Export"); export.setImage(IconLoader.loadIcon("export")); export.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent evt) { export(); } }); } }; table.addConfiguration(new MenuConfiguration( GridRegion.ROW_HEADER, new PopupMenuBuilder(table).withContributionItem(rowMenuItems))); // Report row selection events, to help control updating table.addLayerListener(new ILayerListener() { @Override public void handleLayerEvent(ILayerEvent event) { if (event instanceof RowSelectionEvent) { rowBeenSelected(-1); } else if (event instanceof CellSelectionEvent) { CellSelectionEvent csEvent = (CellSelectionEvent) event; int row = LayerUtil.convertRowPosition(csEvent.getLayer(), csEvent.getRowPosition(), gridLayer.getBodyDataLayer()); rowBeenSelected(row); } else if (event instanceof CellVisualChangeEvent) { CellVisualChangeEvent cvEvent = (CellVisualChangeEvent)event; int row = LayerUtil.convertRowPosition(cvEvent.getLayer(), cvEvent.getRowPosition(), gridLayer.getBodyDataLayer()); rowBeenSelected(row); } else if (event instanceof CellEditorCreatedEvent) { } else { rowBeenSelected(-1); } } }); // Tabbing wraps and moves up/down gridLayer.registerCommandHandler( new MoveCellSelectionCommandHandler(gridLayer.getBodyLayer().getSelectionLayer(), ITraversalStrategy.TABLE_CYCLE_TRAVERSAL_STRATEGY)); table.configure(); table.getDisplay().addFilter(SWT.FocusIn, new Listener() { @Override public void handleEvent(Event event) { if (table.isDisposed()) return; if (!hasFocus(table)) lostFocus(); } }); // Tooltip for row/column headings new NatTableContentTooltip(table, GridRegion.COLUMN_HEADER, GridRegion.ROW_HEADER) { protected String getText(Event event) { return "Right-click for options."; } }; // Tooltip shows dataProvider update errors new DefaultToolTip(table, ToolTip.NO_RECREATE, false) { @Override protected Object getToolTipArea(Event event) { int x = table.getColumnPositionByX(event.x); int y = table.getRowPositionByY(event.y); return new Point(x, y); } @Override protected String getText(Event event) { int x = table.getColumnPositionByX(event.x); int y = table.getRowPositionByY(event.y); ILayerCell cell = table.getCellByPosition(x, y); if (cell == null) return null; int row = cell.getRowIndex(); return dataProvider.getError(row); } @Override protected boolean shouldCreateToolTip(Event event) { if (getText(event) != null) return super.shouldCreateToolTip(event); return false; } }; } public void export() { ExportCommand cmd = new ExportCommand(table.getConfigRegistry(), table.getShell()); table.doCommand(cmd); } public void processDirtyRows() { dataProvider.processDirtyRows(); lastRowSelected = -1; } public int countDirtyRows() { if (dataProvider == null) return 0; return dataProvider.countDirtyRows(); } private void editorBeenOpened(int row, int column) { lastRowSelected = row; processDirtyRows(); } private void editorBeenClosed(int row, int column) { lastRowSelected = row; processDirtyRows(); } private void rowBeenSelected(int row) { lastRowSelected = row; processDirtyRows(); } private void lostFocus() { lastRowSelected = -1; processDirtyRows(); } public Control getControl() { return table; } // Recursively determine if control or one of its children have the keyboard focus. public static boolean hasFocus(Control control) { if (control.isFocusControl()) return true; if (control instanceof Composite) for (Control child: ((Composite)control).getChildren()) if (hasFocus(child)) return true; return false; } protected abstract String getAttributeSource(); protected Tuples obtainTuples() { return connection.getTuples(getAttributeSource()); } public void goToInsertRow() { if (table.commitAndCloseActiveCellEditor()) { table.doCommand(new ClearAllSelectionsCommand()); if (gridLayer != null && dataProvider != null) table.doCommand(new SelectCellCommand(gridLayer.getBodyLayer(), 0, dataProvider.getRowCount() - 1, true, true)); table.setFocus(); } } private void doDeleteSelected() { Set<Range> selections = gridLayer.getBodyLayer().getSelectionLayer().getSelectedRowPositions(); if (dataProvider != null) dataProvider.deleteRows(selections); } public void askDeleteSelected() { if (dataProvider == null) return; if (countDirtyRows() > 0 && !MessageDialog.openConfirm( table.getShell(), "Unsaved Changes", "There are unsaved changes. If you proceed with deletion, they will be cancelled.\n\nPress OK to cancel unsaved changes and proceed with deletion.")) return; if (askDeleteConfirm) { int selectedRowCount = gridLayer.getBodyLayer().getSelectionLayer().getSelectedRowCount(); DeleteConfirmDialog deleteConfirmDialog = new DeleteConfirmDialog(table.getShell(), selectedRowCount, "tuple"); if (deleteConfirmDialog.open() == DeleteConfirmDialog.OK) { askDeleteConfirm = deleteConfirmDialog.getAskDeleteConfirm(); doDeleteSelected(); } } else doDeleteSelected(); } }