package org.reldb.dbrowser.ui.content.rel.var.grids;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import org.eclipse.jface.action.ContributionItem;
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.edit.EditConfigAttributes;
import org.eclipse.nebula.widgets.nattable.edit.editor.CheckBoxCellEditor;
import org.eclipse.nebula.widgets.nattable.edit.editor.ComboBoxCellEditor;
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.LabelStack;
import org.eclipse.nebula.widgets.nattable.layer.cell.IConfigLabelAccumulator;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
import org.eclipse.nebula.widgets.nattable.painter.cell.CheckBoxPainter;
import org.eclipse.nebula.widgets.nattable.painter.cell.ComboBoxPainter;
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.LineBorderDecorator;
import org.eclipse.nebula.widgets.nattable.selection.ITraversalStrategy;
import org.eclipse.nebula.widgets.nattable.selection.MoveCellSelectionCommandHandler;
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.tooltip.NatTableContentTooltip;
import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder;
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.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
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.Tuple;
import org.reldb.rel.client.Tuples;
public abstract class Designer extends Grid {
protected NatTable table;
protected DataProvider dataProvider;
private HeadingProvider headingProvider;
private DefaultGridLayer gridLayer;
private EditorConfiguration editorConfiguration;
class EditorConfiguration extends AbstractRegistryConfiguration {
private IConfigRegistry registry;
@Override
public void configureRegistry(IConfigRegistry configRegistry) {
registry = configRegistry;
// editable
configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITABLE_RULE, IEditableRule.ALWAYS_EDITABLE);
// style for selected cells
Style selectStyle = new Style();
configRegistry.registerConfigAttribute(
CellConfigAttributes.CELL_STYLE,
selectStyle,
DisplayMode.SELECT);
// open adjacent editor when we leave the current one during editing
configRegistry.registerConfigAttribute(
EditConfigAttributes.OPEN_ADJACENT_EDITOR,
Boolean.TRUE,
DisplayMode.EDIT);
// style for upper left corner
BorderStyle borderStyle = new BorderStyle();
borderStyle.setColor(GUIHelper.COLOR_GRAY);
configRegistry.registerConfigAttribute(
CellConfigAttributes.CELL_PAINTER,
new LineBorderDecorator(new TextPainter(), borderStyle),
DisplayMode.NORMAL,
GridRegion.CORNER);
// for each column...
for (int column = 0; column < headingProvider.getColumnCount(); column++)
addColumn(column);
}
public void addColumn(int column) {
String columnLabel = "column" + column;
switch (column) {
case Attr.NAME_COLUMN: registerAttributeNameColumn(registry, columnLabel); break;
case Attr.TYPE_COLUMN: registerTypeNameColumn(registry, columnLabel); break;
case Attr.HEADING_COLUMN: registerTypeDefinitionColumn(registry, columnLabel); break;
default: registerKeyColumn(registry, columnLabel);
}
}
private void registerAttributeNameColumn(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 registerTypeNameColumn(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);
// use a combobox
configRegistry.registerConfigAttribute(
EditConfigAttributes.CELL_EDITOR,
new ComboBoxCellEditor(getTypes()),
DisplayMode.EDIT,
columnLabel);
configRegistry.registerConfigAttribute(
CellConfigAttributes.CELL_PAINTER,
new ComboBoxPainter(),
DisplayMode.EDIT,
columnLabel);
}
private void registerTypeDefinitionColumn(IConfigRegistry configRegistry, String columnLabel) {
// 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.isEditableNonscalarDefinition(rowIndex);
}
},
DisplayMode.EDIT,
columnLabel);
// Button displayed if editable
ImagePainter imagePainter = new ImagePainter(IconLoader.loadIcon("table_design"));
configRegistry.registerConfigAttribute(
CellConfigAttributes.CELL_PAINTER,
imagePainter,
DisplayMode.NORMAL,
"nonscalareditor");
// Custom dialog box
configRegistry.registerConfigAttribute(
EditConfigAttributes.CELL_EDITOR,
new AttributeDesignerCellEditor(Designer.this),
DisplayMode.EDIT,
columnLabel);
}
private void registerKeyColumn(IConfigRegistry configRegistry, String columnLabel) {
configRegistry.registerConfigAttribute(
EditConfigAttributes.CELL_EDITOR,
new CheckBoxCellEditor(),
DisplayMode.EDIT,
columnLabel);
configRegistry.registerConfigAttribute(
CellConfigAttributes.CELL_PAINTER,
new CheckBoxPainter(),
DisplayMode.NORMAL,
columnLabel);
}
}
class HeadingProvider implements IDataProvider {
@Override
public Object getDataValue(int columnIndex, int rowIndex) {
switch (columnIndex) {
case Attr.NAME_COLUMN: return "Name";
case Attr.TYPE_COLUMN: return "Type";
case Attr.HEADING_COLUMN: return "Heading";
default: return (columnIndex == getColumnCount() - 1 && columnIndex > Attr.COLUMN_COUNT)
? "New Key"
: "Key " + (columnIndex - (Attr.COLUMN_COUNT - 1));
}
}
@Override
public void setDataValue(int columnIndex, int rowIndex, Object newValue) {
throw new UnsupportedOperationException();
}
@Override
public int getColumnCount() {
return dataProvider.getLastColumnIndex() + 1;
}
@Override
public int getRowCount() {
return 1;
}
};
/** Override to be notified when the relvar definition has (probably) changed. */
protected void changedDefinition() {}
class DataProvider implements IDataProvider {
private Vector<Attr> attributes;
private int lastColumnIndex = 0;
private String kind;
private TypeInfo typeInfo;
private int getLastColumnIndex() {
return Attr.COLUMN_COUNT + ((keys != null) ? keys.size() : 0) - 1;
}
public DataProvider() {
typeInfo = new TypeInfo(connection);
reload();
lastColumnIndex = getLastColumnIndex();
}
private String getKind() {
if (getAttributeSource().length() == 0)
return null;
return typeInfo.getKindFor(getAttributeSource());
}
// 1st column = attribute name; 2nd column = type name; 3rd column = TypeInfo
private Tuples getAttributes() {
if (getAttributeSource().length() == 0)
return null;
return typeInfo.getAttributesFor(getAttributeSource());
}
public void reload() {
kind = getKind();
attributes = new Vector<Attr>();
Tuples tuples = getAttributes();
if (tuples != null) {
Iterator<Tuple> iterator = tuples.iterator();
while (iterator.hasNext())
attributes.add(new Attr(iterator.next()));
}
attributes.add(new Attr());
}
@Override
public Object getDataValue(int columnIndex, int rowIndex) {
if (columnIndex < Attr.COLUMN_COUNT)
return attributes.get(rowIndex).getColumnValue(columnIndex);
else
return keys.get(columnIndex - Attr.COLUMN_COUNT).contains(attributes.get(rowIndex).getName());
}
@Override
public void setDataValue(int columnIndex, int rowIndex, Object newValue) {
Attr attribute = attributes.get(rowIndex);
if (columnIndex < Attr.COLUMN_COUNT) {
if (newValue == null || newValue.toString().length() == 0)
return;
if (columnIndex == 0) {
if (keys != null)
for (HashSet<String> key: keys) {
if (key.contains(attribute.getName())) {
key.remove(attribute.getName());
key.add(newValue.toString());
}
}
}
attribute.setColumnValue(columnIndex, newValue);
} else {
if (rowIndex == getRowCount() - 1 && !attribute.isFilled())
return;
if (newValue == null)
newValue = "false";
if (newValue.equals("false"))
keys.get(columnIndex - Attr.COLUMN_COUNT).remove(attribute.getName());
else
keys.get(columnIndex - Attr.COLUMN_COUNT).add(attribute.getName());
}
if (columnIndex >= lastColumnIndex && newValue != null && newValue.equals("true")) {
keys.add(new HashSet<String>());
lastColumnIndex++;
editorConfiguration.addColumn(columnIndex);
table.configure();
table.refresh();
} else if (columnIndex >= Attr.COLUMN_COUNT && getKeyAttributeCount(columnIndex - Attr.COLUMN_COUNT) == 0) {
keys.remove(columnIndex - Attr.COLUMN_COUNT);
lastColumnIndex--;
table.configure();
table.refresh();
}
int lastRowIndex = attributes.size() - 1;
if (rowIndex == lastRowIndex && attributes.get(lastRowIndex).isFilled()) {
attributes.add(new Attr());
table.redraw();
}
changedDefinition();
}
@Override
public int getColumnCount() {
return headingProvider.getColumnCount();
}
@Override
public int getRowCount() {
return attributes.size();
}
public boolean isEditableNonscalarDefinition(int rowIndex) {
return attributes.get(rowIndex).isEditableNonscalarDefinition();
}
public int getKeyAttributeCount(int keyColumn) {
return keys.get(keyColumn).size();
}
public void deleteRows(Set<Range> selections) {
HashSet<Integer> deleteMe = new HashSet<Integer>();
for (Range range: selections)
for (int n = range.start; n < range.end; n++)
deleteMe.add(n);
Vector<Attr> newCache = new Vector<Attr>();
int index = 0;
for (Attr attribute: attributes) {
if (deleteMe.contains(index)) {
if (keys != null)
for (HashSet<String> key: keys)
key.remove(attribute.getName());
} else
newCache.add(attribute);
index++;
}
if (keys != null) {
HashSet<HashSet<String>> newKeys = new HashSet<HashSet<String>>();
for (HashSet<String> key: keys)
if (key.size() > 0)
newKeys.add(key);
keys.clear();
keys.addAll(newKeys);
// Blank key definition allows user to add keys
keys.add(new HashSet<String>());
lastColumnIndex = getLastColumnIndex();
}
attributes = newCache;
table.refresh();
changedDefinition();
}
// Convert this into a TypeInfo literal
public String getTypeInfoLiteral() {
String body = "";
for (Attr attribute: attributes)
if (attribute.isFilled())
body += ((body.length() > 0) ? "," : "") + "\n\t" + attribute.getTypeInfoLiteral();
return "NonScalar(\"" + kind + "\", RELATION {AttrName CHARACTER, AttrType TypeInfo} {" + body + "})";
}
private String getRelKeysDefinition(Vector<HashSet<String>> keys) {
String keysDef = "";
if (keys.size() == 1)
keysDef = "KEY {}";
else
for (int keyIndex = 0; keyIndex < keys.size() - 1; keyIndex++) {
HashSet<String> key = keys.get(keyIndex);
String keyDef = "";
for (String keyAttribute: key) {
keyDef += ((keyDef.length() > 0) ? ", " : "") + keyAttribute;
}
keysDef += ((keysDef.length() > 0) ? " " : "") + "KEY {" + keyDef + "}";
}
return keysDef;
}
private String getRelKeysDefinition() {
return getRelKeysDefinition(keys);
}
private String getRelHeadingDefinition(Attr a) {
return typeInfo.getHeadingDefinition(a.getNewColumnValue(Attr.HEADING_COLUMN));
}
private String getRelAlterClause(Attr a) {
if (a.isNameChange() && a.isHeadingChange()) {
return "DROP " + a.getOriginalColumnValue(Attr.NAME_COLUMN) + "\n\t" +
"INSERT " + a.getName() + " " + getRelHeadingDefinition(a);
} else if (a.isNameChange() && a.isTypeNameChange() && !a.isHeadingChange()) {
return "DROP " + a.getOriginalColumnValue(Attr.NAME_COLUMN) + "\n\t" +
"INSERT " + a.getName() + " " + a.getNewColumnValue(Attr.TYPE_COLUMN);
} else if (a.isNameChange() && !a.isTypeNameChange() && !a.isHeadingChange()) {
return "RENAME " + a.getOriginalColumnValue(Attr.NAME_COLUMN) + " TO " + a.getName();
} else if (!a.isNameChange() && a.isHeadingChange()) {
return "TYPE_OF " + a.getOriginalColumnValue(Attr.NAME_COLUMN) + " TO " + getRelHeadingDefinition(a);
} else if (!a.isNameChange() && a.isTypeNameChange() && !a.isHeadingChange()) {
return "TYPE_OF " + a.getOriginalColumnValue(Attr.NAME_COLUMN) + " TO " + a.getNewColumnValue(Attr.TYPE_COLUMN);
} else
return null;
}
private String getRelAddClause(Attr a) {
return "INSERT " + a.getName() + " " +
(a.isEditableNonscalarDefinition()
? getRelHeadingDefinition(a)
: a.getColumnValue(Attr.TYPE_COLUMN));
}
public String getRelDefinition() {
// attributes
Tuples existingDefinitionTuples = getAttributes();
HashMap<String, Attr> existingDefinition = new HashMap<String, Attr>();
if (existingDefinitionTuples != null) {
Iterator<Tuple> iterator = existingDefinitionTuples.iterator();
while (iterator.hasNext()) {
Attr attribute = new Attr(iterator.next());
existingDefinition.put(attribute.getName(), attribute);
}
}
String body = "";
for (Attr attribute: attributes) {
if (!attribute.isFilled())
continue;
String originalAttributeName = attribute.getOriginalColumnValue(Attr.NAME_COLUMN);
if (originalAttributeName != null && existingDefinition.containsKey(originalAttributeName)) {
String alterClause = getRelAlterClause(attribute);
if (alterClause != null)
body += ((body.length() > 0) ? "\n" : "") + "\t" + alterClause;
existingDefinition.remove(originalAttributeName);
} else
body += ((body.length() > 0) ? "\n" : "") + "\t" + getRelAddClause(attribute);
}
for (String attributeName: existingDefinition.keySet())
body += ((body.length() > 0) ? "\n" : "") + "\t" + "DROP " + attributeName;
// keys
Vector<HashSet<String>> existingKeyDefinitions = getKeyDefinitions();
HashSet<HashSet<String>> existingKeys = new HashSet<HashSet<String>>();
if (existingKeyDefinitions != null) {
existingKeys.addAll(existingKeyDefinitions);
existingKeys.add(new HashSet<String>());
}
HashSet<HashSet<String>> newKeys = new HashSet<HashSet<String>>();
newKeys.addAll(keys);
if (!existingKeys.equals(newKeys))
body += ((body.length() > 0) ? "\n" : "") + "\t" + getRelKeysDefinition();
// produce output
if (body.length() == 0)
return "";
return "ALTER VAR " + relvarName + "\n" + body + ";";
}
};
public void refresh() {
table.refresh();
changedDefinition();
}
// Relvar designer
public Designer(Composite parent, DbConnection connection, String relvarName) {
super(parent, connection, relvarName);
}
protected void init() {
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);
if (dataProvider.isEditableNonscalarDefinition(rowPosition) && columnPosition == Attr.HEADING_COLUMN)
configLabels.addLabel("nonscalareditor");
}
}
DataLayer bodyDataLayer = (DataLayer)gridLayer.getBodyDataLayer();
CellLabelAccumulator cellLabelAccumulator = new CellLabelAccumulator();
bodyDataLayer.setConfigLabelAccumulator(cellLabelAccumulator);
table = new NatTable(parent, gridLayer, false);
editorConfiguration = new EditorConfiguration();
DefaultNatTableStyleConfiguration defaultStyle = new DefaultNatTableStyleConfiguration();
table.addConfiguration(defaultStyle);
table.addConfiguration(editorConfiguration);
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();
}
});
}
};
table.addConfiguration(new MenuConfiguration(
GridRegion.ROW_HEADER,
new PopupMenuBuilder(table).withContributionItem(rowMenuItems)));
// Tabbing wraps and moves up/down
gridLayer.registerCommandHandler(
new MoveCellSelectionCommandHandler(gridLayer.getBodyLayer().getSelectionLayer(),
ITraversalStrategy.TABLE_CYCLE_TRAVERSAL_STRATEGY));
table.configure();
// Tooltip for row/column headings
new NatTableContentTooltip(table, GridRegion.ROW_HEADER) {
protected String getText(Event event) {
return "Right-click for options.";
}
};
}
private void doDeleteSelected() {
Set<Range> selections = gridLayer.getBodyLayer().getSelectionLayer().getSelectedRowPositions();
dataProvider.deleteRows(selections);
}
public void askDeleteSelected() {
if (askDeleteConfirm) {
int selectedRowCount = gridLayer.getBodyLayer().getSelectionLayer().getSelectedRowCount();
DeleteConfirmDialog deleteConfirmDialog = new DeleteConfirmDialog(table.getShell(), selectedRowCount, "attribute");
if (deleteConfirmDialog.open() == DeleteConfirmDialog.OK) {
askDeleteConfirm = deleteConfirmDialog.getAskDeleteConfirm();
doDeleteSelected();
}
} else
doDeleteSelected();
}
public Control getControl() {
return table;
}
protected List<String> getTypes() {
Vector<String> types = new Vector<String>();
Tuples typeNames = connection.getTuples("sys.Types {Name}");
for (Tuple typeName: typeNames)
types.add(typeName.get("Name").toString());
types.add("RELATION");
types.add("TUPLE");
types.sort(null);
return types;
}
protected abstract String getAttributeSource();
public String getRelDefinition() {
return dataProvider.getRelDefinition();
}
}