/******************************************************************************* * Copyright (c) 2014 Open Door Logistics (www.opendoorlogistics.com) * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser Public License v3 * which accompanies this distribution, and is available at http://www.gnu.org/licenses/lgpl.txt ******************************************************************************/ package com.opendoorlogistics.studio.scripts.editor.adapters; import java.awt.Color; import java.awt.Component; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.TreeSet; import javax.swing.AbstractCellEditor; import javax.swing.BorderFactory; import javax.swing.DefaultCellEditor; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JTable; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import com.opendoorlogistics.api.ODLApi; import com.opendoorlogistics.api.tables.ODLColumnType; import com.opendoorlogistics.api.tables.ODLDatastore; import com.opendoorlogistics.api.tables.ODLDatastoreAlterable; import com.opendoorlogistics.api.tables.ODLTable; import com.opendoorlogistics.api.tables.ODLTableAlterable; import com.opendoorlogistics.api.tables.ODLTableDefinition; import com.opendoorlogistics.api.tables.TableFlags; import com.opendoorlogistics.codefromweb.BoundsPopupMenuListener; import com.opendoorlogistics.core.scripts.ScriptConstants; import com.opendoorlogistics.core.scripts.TargetIODsInterpreter; import com.opendoorlogistics.core.scripts.elements.AdaptedTableConfig; import com.opendoorlogistics.core.scripts.elements.AdapterColumnConfig; import com.opendoorlogistics.core.scripts.elements.AdapterColumnConfig.SortField; import com.opendoorlogistics.core.scripts.elements.AdapterConfig; import com.opendoorlogistics.core.scripts.execution.adapters.AdapterBuilderUtils; import com.opendoorlogistics.core.scripts.formulae.FmAggregate.AggregateType; import com.opendoorlogistics.core.scripts.wizard.ScriptGenerator; import com.opendoorlogistics.core.tables.decorators.column.ColumnDecorator; import com.opendoorlogistics.core.tables.utils.ExampleData; import com.opendoorlogistics.core.tables.utils.ODLDatastoreDefinitionProvider; import com.opendoorlogistics.core.tables.utils.TableUtils; import com.opendoorlogistics.core.utils.strings.Strings; import com.opendoorlogistics.core.utils.ui.ShowPanel; import com.opendoorlogistics.studio.InitialiseStudio; import com.opendoorlogistics.studio.controls.DynamicComboBox; import com.opendoorlogistics.studio.tables.ODLTableControl; import com.opendoorlogistics.utils.ui.tables.AbstractTableDefinitionGrid; public class AdapterTableDefinitionGrid extends AbstractTableDefinitionGrid { private static final int NAME_COL=0; private static final int TYPE_COL=1; private static final int CALC_COL=2; private static final int SRC_COL=3; private static final int FORMULA_COL=4; private static final int SORT_COL=5; private static final int FIRST_FLAG_FIELD=6; // names.add("Name"); // names.add("Type"); // names.add("Calculated?"); // names.add("Source"); // names.add("Formula"); private AdaptedTableConfig dfn; private final long visibleFlags; private final QueryAvailableData queryAvailableFields; private final ODLApi api; private ODLDatastoreDefinitionProvider targetDsDefnProvider; private class MyAvailableOptions extends QueryAvailableDataDecorator { public MyAvailableOptions(QueryAvailableData decorated) { super(decorated); } @Override public String[] queryAvailableFields(String datastore, String tablename) { String[] arr = super.queryAvailableFields(datastore, tablename); return arr; } @Override public String[] queryAvailableFormula(ODLColumnType columnType) { String[] arr = super.queryAvailableFormula(columnType); return arr; } } public AdapterTableDefinitionGrid(ODLApi api,AdaptedTableConfig dfn, long visibleFlags, QueryAvailableData query) { this.visibleFlags = visibleFlags; this.api = api; if (query != null) { this.queryAvailableFields = new MyAvailableOptions(query); } else { this.queryAvailableFields = null; } setTable(dfn); // table.setMinimumSize(new Dimension(400, 0)); updateAppearance(); } @Override protected JTable createJTable() { return new ODLTableControl() { @Override public String getToolTipText(MouseEvent e) { int row = rowAtPoint(e.getPoint()); int col = columnAtPoint(e.getPoint()); return ((MyTableModel) getModel()).getTooltip(row, col); } }; } private abstract class TableCellDynamicComboBox extends DynamicComboBox { int row = -1; int col = -1; public TableCellDynamicComboBox(String initialValue, boolean addPopupListener) { super(initialValue, addPopupListener,true); } } // private class DynamicComboCellEditor extends DefaultCellEditor { // // public DynamicComboCellEditor(TableCellDynamicComboBox comboBox) { // super(comboBox); // } // // @Override // public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { // TableCellDynamicComboBox combo = (TableCellDynamicComboBox) super.getTableCellEditorComponent(table, value, isSelected, row, column); // combo.row = row; // combo.col = column; // if (value != null) { // combo.getEditor().setItem(value.toString()); // } else { // combo.getEditor().setItem(""); // } // combo.updateMenu(); // return combo; // } // } private class DynamicComboCellEditorV2 extends AbstractCellEditor implements TableCellEditor{ private final TableCellDynamicComboBox combo; public DynamicComboCellEditorV2(TableCellDynamicComboBox comboBox) { super(); this.combo = comboBox; } @Override public Object getCellEditorValue() { return combo.getEditor().getItem(); } @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { combo.row = row; combo.col = column; if (value != null) { combo.getEditor().setItem(value.toString()); } else { combo.getEditor().setItem(""); } combo.updateMenu(); return combo; } } // /** // * Get group-by formula definitions // * @param all // * @param columnIndex // * @return // */ // private String[] prefixGroupByFormulae(String[] all, int columnIndex) { // TreeSet<Integer> groupByCols = new TreeSet<>(); // AdapterBuilderUtils.splitGroupByCols(dfn, groupByCols, null); // if (groupByCols.size() == 0 || groupByCols.contains(columnIndex)) { // return all; // } // // ArrayList<String> ret = new ArrayList<>(); // // // get available fields // String[] fields = queryAvailableFields.queryAvailableFields(AdapterTableDefinitionGrid.this.dfn.getFromDatastore(), AdapterTableDefinitionGrid.this.dfn.getFromTable()); // // // loop over all group by formulae // for (AggregateType type : AggregateType.values()) { // if(type == AggregateType.GROUPGEOMUNION){ // // doesn't work for geom union... // continue; // } // if (type.getNbArgs() == 1) { // if (fields != null && fields.length > 0) { // for (String field : fields) { // ret.add(type.getFormula(field)); // } // } else { // ret.add(type.getFormula("fieldname")); // } // } else if (type.getNbArgs() == 0) { // ret.add(type.getFormula()); // } // } // // if (all != null) { // for (String s : all) { // ret.add(s); // } // } // // Collections.sort(ret); // // return ret.toArray(new String[ret.size()]); // } private static String[] filterFromFieldsForGroupBy(String[] all, AdaptedTableConfig config, int columnIndex) { TreeSet<Integer> groupByFields = new TreeSet<>(); AdapterBuilderUtils.splitGroupByCols(config, groupByFields, null); // return all options if we're either not grouping by or this is the group by column if (groupByFields.size() == 0 || groupByFields.contains(columnIndex)) { return all; } // no options return new String[0]; } private static class EnumComboBox<T extends Enum<?>> extends JComboBox<T>{ public EnumComboBox(T[] items, JTable table, int col) { super(items); addPopupMenuListener(new BoundsPopupMenuListener(true,false)); TableColumn column = table.getColumnModel().getColumn(col); column.setCellEditor(new DefaultCellEditor(this)); } } public void setTable(final AdaptedTableConfig dfn) { this.dfn = dfn; table.setModel(new MyTableModel()); // make formula column big table.getColumnModel().getColumn(FORMULA_COL).setPreferredWidth(250); // create jcombobox for type enum new EnumComboBox<>(ODLColumnType.values(), table , TYPE_COL); // and for sort enum new EnumComboBox<>(SortField.values(), table , SORT_COL); // add editable combo box for 'to' field TableCellDynamicComboBox toFieldCombo = new TableCellDynamicComboBox("", false) { @Override protected List<String> getAvailableItems() { if (targetDsDefnProvider != null) { ODLDatastore<? extends ODLTableDefinition> ds = targetDsDefnProvider.getDatastoreDefinition(); ODLTableDefinition table = TableUtils.findTable(ds, dfn.getName()); if (table != null) { return asList(TableUtils.getColumnNames(table)); } } return new ArrayList<>(); } }; toFieldCombo.setEditable(true); table.getColumnModel().getColumn(NAME_COL).setCellEditor(new DynamicComboCellEditorV2(toFieldCombo)); // add editable combo box for 'from' field TableCellDynamicComboBox fromFieldCombo = new TableCellDynamicComboBox("", false) { @Override protected List<String> getAvailableItems() { if (queryAvailableFields != null) { String[] ret = queryAvailableFields.queryAvailableFields(AdapterTableDefinitionGrid.this.dfn.getFromDatastore(), AdapterTableDefinitionGrid.this.dfn.getFromTable()); ret = filterFromFieldsForGroupBy(ret, dfn, row); return asList(ret); } return new ArrayList<>(); } }; fromFieldCombo.setEditable(true); table.getColumnModel().getColumn(SRC_COL).setCellEditor(new DynamicComboCellEditorV2(fromFieldCombo)); // and a dynamic combo for the formula TableCellDynamicComboBox formulaCombo = new TableCellDynamicComboBox("", false) { @Override protected List<String> getAvailableItems() { // Turn off formula suggestions for the moment... // // get selected cell // ODLColumnType type = ODLColumnType.STRING; // if (row != -1 && row < dfn.getColumnCount()) { // type = dfn.getColumnType(row); // } // if (queryAvailableFields != null) { // return asList(prefixGroupByFormulae(queryAvailableFields.queryAvailableFormula(type), row)); // } return new ArrayList<>(); } }; fromFieldCombo.setEditable(true); // set dynamic editor for formula column table.getColumnModel().getColumn(FORMULA_COL).setCellEditor(new DynamicComboCellEditorV2(formulaCombo)); // grey out read only string cells final TableCellRenderer defaultRenderer = table.getDefaultRenderer(String.class); TableCellRenderer myRenderer = new TableCellRenderer() { private final JLabel label = new JLabel(); @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (table.getModel().isCellEditable(row, column)) { Component ret = defaultRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); boolean showError = false; if (row < dfn.getColumnCount()) { AdapterConfig dummyAdapter = new AdapterConfig(); dummyAdapter.getTables().add(dfn); HashMap<Object, String> errorMap = new TargetIODsInterpreter(api).validateAdapter(dummyAdapter, targetDsDefnProvider!=null?targetDsDefnProvider.getDatastoreDefinition():null); showError = errorMap.containsKey(dfn.getColumn(row)); } // boolean nameUnknown = false; // long colFlags = 0; // SortField sort = SortField.NO; // if (row < dfn.getColumnCount()) { // colFlags = dfn.getColumnFlags(row); // sort = dfn.getColumn(row).getSortField(); // if (getTargetTable() != null && getTargetTable().getColumnCount() > 0) { // // // check if the target is known // if (column == NAME_COL) { // ColumnDecorator colDec = getTargetColumnDfn(row); // if (colDec == null) { // nameUnknown = true; // } // } // // } // } // // // if name is unknown and we're not using a batch key or sort field... // boolean showError = false; // if (nameUnknown && (colFlags & TableFlags.FLAG_IS_BATCH_KEY) != TableFlags.FLAG_IS_BATCH_KEY && sort == SortField.NO) { // showError = true; // } // check source column if (column == SRC_COL && queryAvailableFields != null) { String[] fields = queryAvailableFields.queryAvailableFields(dfn.getFromDatastore(), dfn.getFromTable()); if (fields != null && value != null && value.toString().length() > 0) { boolean found=false; for(String field:fields){ if(Strings.equalsStd(field, value.toString())){ found = true; break; } } if (!found) { showError = true; } } } if (JLabel.class.isInstance(ret)) { JLabel label = (JLabel) ret; if (showError) { label.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.RED, 1), BorderFactory.createEmptyBorder(0, 4, 0, 0))); } else { label.setBorder(BorderFactory.createEmptyBorder(1, 5, 1, 1)); } } return ret; } else { // return greyed out label.setBackground(new Color(200, 200, 200)); label.setOpaque(true); return label; } } }; table.setDefaultRenderer(String.class, myRenderer); } private ODLTableDefinition getTargetTable() { if (targetDsDefnProvider != null && targetDsDefnProvider.getDatastoreDefinition() != null && Strings.isEmpty(dfn.getName()) == false) { return TableUtils.findTable(targetDsDefnProvider.getDatastoreDefinition(), dfn.getName()); } return null; } private ColumnDecorator getTargetColumnDfn(int col) { ODLTableDefinition targetTable = getTargetTable(); if (targetTable != null && col < dfn.getColumnCount() && !Strings.isEmpty(dfn.getColumnName(col))) { String name = dfn.getColumnName(col); int found = TableUtils.findColumnIndx(targetTable, name); if (found != -1) { return new ColumnDecorator(targetTable, found); } } return null; } private class MyTableModel extends AbstractTableModel { private final String[] colNames; private class VisibleFlag { private String name; private long flag; private boolean isEditable; VisibleFlag(String name, long flag, boolean isEditable) { this.name = name; this.flag = flag; this.isEditable = isEditable; } boolean isOn(int column, long flags) { return (flags & flag) == flag; } } private class VisibleOptionalFlag extends VisibleFlag { VisibleOptionalFlag() { super("Optional", TableFlags.FLAG_IS_OPTIONAL, false); } @Override boolean isOn(int column, long flags) { if (targetDsDefnProvider == null) { // assume nothing optional (as nothing is known) return false; } if (targetDsDefnProvider.getDatastoreDefinition() == null || targetDsDefnProvider.getDatastoreDefinition().getTableCount() == 0) { // component must have null input ds; all optional return true; } ODLTableDefinition table = getTargetTable(); if (table == null) { // can't find table; assume all optional return true; } if (table.getColumnCount() == 0) { // all optional return true; } ColumnDecorator colDecorator = getTargetColumnDfn(column); if (colDecorator == null) { return true; } return (colDecorator.getFlags() & TableFlags.FLAG_IS_OPTIONAL) == TableFlags.FLAG_IS_OPTIONAL; } // boolean isOn(long flags){ // // } } private final ArrayList<VisibleFlag> flags = new ArrayList<>(); // private final int nbNonFlagFields = 5; public MyTableModel() { ArrayList<VisibleFlag> tmp = new ArrayList<>(); tmp.add(new VisibleFlag("Group by", TableFlags.FLAG_IS_GROUP_BY_FIELD, true)); tmp.add(new VisibleFlag("Batch key", TableFlags.FLAG_IS_BATCH_KEY, true)); tmp.add(new VisibleFlag("Report key", TableFlags.FLAG_IS_REPORT_KEYFIELD, true)); tmp.add(new VisibleOptionalFlag()); for (VisibleFlag flag : tmp) { if ((flag.flag & visibleFlags) == flag.flag) { flags.add(flag); } } // get column names ArrayList<String> names = new ArrayList<>(); for(int i =0 ; i<FIRST_FLAG_FIELD; i++){ names.add(null); } names.set(NAME_COL, "Name"); names.set(TYPE_COL, "Type"); names.set(CALC_COL, "Calculated?"); names.set(SRC_COL, "Source"); names.set(FORMULA_COL, "Formula"); names.set(SORT_COL, "Sort"); for (VisibleFlag flag : flags) { names.add(flag.name); } colNames = names.toArray(new String[names.size()]); // TableModelUtils.getMultiLineColumnNames(colNames); } @Override public String getColumnName(int column) { if (column < colNames.length) { return colNames[column]; } return ""; } public String getTooltip(int row, int col) { if (row >= 0 && row < dfn.getColumnCount()) { // AdapterColumnConfig field = dfn.getColumn(row); if (col == NAME_COL) { ColumnDecorator coldec = getTargetColumnDfn(row); if (coldec != null) { return coldec.getDescription(); } // return field.getDescription(); } } return null; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { AdapterColumnConfig field = dfn.getColumn(rowIndex); if(columnIndex == NAME_COL){ return field.getSortField() == SortField.NO; } if (columnIndex == SRC_COL) { return field.isUseFormula() == false; } if (columnIndex == FORMULA_COL) { return field.isUseFormula(); } if (columnIndex < FIRST_FLAG_FIELD) { return true; } else return flags.get(columnIndex - FIRST_FLAG_FIELD).isEditable; } @Override public Class<?> getColumnClass(int columnIndex) { switch (columnIndex) { case NAME_COL: return String.class; case TYPE_COL: return ODLColumnType.class; case CALC_COL: return Boolean.class; case SRC_COL: return String.class; case FORMULA_COL: return String.class; case SORT_COL: return SortField.class; } return Boolean.class; } @Override public int getRowCount() { return dfn.getColumnCount(); } @Override public int getColumnCount() { return FIRST_FLAG_FIELD + flags.size(); } @Override public Object getValueAt(int rowIndex, int columnIndex) { AdapterColumnConfig field = dfn.getColumn(rowIndex); switch (columnIndex) { case NAME_COL: return field.getName(); case TYPE_COL: return field.getType(); case CALC_COL: return field.isUseFormula(); case SRC_COL: return field.getFrom(); case FORMULA_COL: return field.getFormula(); case SORT_COL: return field.getSortField(); } long flags = dfn.getColumnFlags(rowIndex); return this.flags.get(columnIndex - FIRST_FLAG_FIELD).isOn(rowIndex, flags); } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { AdapterColumnConfig field = dfn.getColumn(rowIndex); switch (columnIndex) { case NAME_COL: field.setName((String) aValue); break; case TYPE_COL: field.setType((ODLColumnType) aValue); break; case CALC_COL: field.setUseFormula((Boolean) aValue); repaint(); break; case SRC_COL: field.setFrom((String) aValue); break; case FORMULA_COL: field.setFormula((String) aValue); break; case SORT_COL: field.setSortField((SortField)aValue); break; } if (columnIndex >= FIRST_FLAG_FIELD) { long flags = field.getFlags(); long flag = this.flags.get(columnIndex - FIRST_FLAG_FIELD).flag; Boolean b = (Boolean) aValue; if (b) { flags |= flag; } else { flags &= ~flag; } field.setFlags(flags); } updateAppearance(); } } @Override protected void createNewColumn() { String name = TableUtils.getUniqueNumberedColumnName("New column", dfn); dfn.addColumn(-1, name, ODLColumnType.STRING, 0); AdapterColumnConfig field = dfn.getColumn(dfn.getColumnCount() - 1); field.setFrom("From column"); setTable(dfn); } @Override protected void moveItemUp(int row) { dfn.moveColumnUp(row); setTable(dfn); } @Override protected void moveItemDown(int row) { dfn.moveColumnDown(row); setTable(dfn); } @Override protected void deleteItem(int row) { dfn.deleteColumn(row); setTable(dfn); updateAppearance(); } // public static void main(String[] args) { // InitialiseStudio.initialise(); // final ODLDatastoreAlterable<ODLTableAlterable> ds = ExampleData.createTerritoriesExample(3); // ODLTable table = ds.getTableAt(0); // AdaptedTableConfig tableConfig = WizardUtils.createAdaptedTableConfig(table, table.getName()); // tableConfig.setFilterFormula("true"); // QueryAvailableDataImpl options = new QueryAvailableDataImpl() { // // @Override // protected ODLDatastore<? extends ODLTableDefinition> getDs() { // return ds; // } // // @Override // protected String getDsName() { // return ScriptConstants.EXTERNAL_DS_NAME; // } // // }; // // ShowPanel.showPanel(new AdapterTableDefinitionGrid(tableConfig, TableFlags.FLAG_IS_OPTIONAL, options)); // } public void setTargetDatastoreDefinitionProvider(ODLDatastoreDefinitionProvider targetDatastoreDefinitionProvider) { this.targetDsDefnProvider = targetDatastoreDefinitionProvider; } @Override public void updateAppearance() { super.updateAppearance(); table.repaint(); } }