/******************************************************************************* * 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.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JTextField; import javax.swing.JToolBar; import javax.swing.border.EmptyBorder; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import com.opendoorlogistics.api.ODLApi; 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.api.ui.UIFactory.IntChangedListener; import com.opendoorlogistics.core.scripts.ScriptConstants; import com.opendoorlogistics.core.scripts.elements.AdaptedTableConfig; import com.opendoorlogistics.core.scripts.execution.adapters.AdapterBuilderUtils; import com.opendoorlogistics.core.scripts.formulae.FmIsSelectedInMap; import com.opendoorlogistics.core.scripts.wizard.ScriptGenerator; import com.opendoorlogistics.core.tables.utils.ExampleData; import com.opendoorlogistics.core.tables.utils.ODLDatastoreDefinitionProvider; import com.opendoorlogistics.core.utils.strings.StandardisedStringSet; import com.opendoorlogistics.core.utils.strings.Strings; import com.opendoorlogistics.core.utils.ui.IntegerEntryPanel; import com.opendoorlogistics.core.utils.ui.LayoutUtils; import com.opendoorlogistics.core.utils.ui.ShowPanel; import com.opendoorlogistics.core.utils.ui.VerticalLayoutPanel; import com.opendoorlogistics.studio.InitialiseStudio; import com.opendoorlogistics.studio.controls.DynamicComboBox; import com.opendoorlogistics.studio.controls.EditableComboBox.ValueChangedListener; import com.opendoorlogistics.utils.ui.ODLAction; public class AdaptedTableControl extends VerticalLayoutPanel { private final LabelledRadioButton noFilterCtrls; private final LabelledRadioButton selCtrls; private final CustomFormulaControls customFormulaControls; private final ArrayList<LabelledRadioButton> radioFilterCtrls = new ArrayList<>(); private final AdaptedTableConfig config; private final QueryAvailableData queryAvailableFields; private final FromDatastoreCombo fromDatastore; private final FromTableCombo fromTable; // private final JTextField outputTableName; private final JCheckBox joinCheckBox; private final JLabel joinLabel1; private final JLabel joinLabel2; private final FromDatastoreCombo joinDatastore; private final FromTableCombo joinTable; private final AdapterTableDefinitionGrid fieldGrid; private final ODLApi api; private final long visibleTableFlags; private FormChangedListener formChangedListener; public static final long DISABLE_SOURCE_FLAGS = 0x01; public static interface FormChangedListener { void formChanged(AdaptedTableControl form); } public void setFormChangedListener(FormChangedListener formChangedListener) { this.formChangedListener = formChangedListener; } private static abstract class AutocorrectCombo extends DynamicComboBox<String> { public AutocorrectCombo(String initialValue) { super(initialValue, true, true); // TODO Auto-generated constructor stub } protected void updateAppearance() { Component component = getEditor().getEditorComponent(); if (JComponent.class.isInstance(component)) { JComponent j = (JComponent) component; if (isUnknown() && !isImportLink()) { j.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.RED, 1), BorderFactory.createEmptyBorder(0, 4, 0, 0))); } else { j.setBorder(BorderFactory.createEmptyBorder(1, 5, 1, 1)); } } } protected abstract boolean isUnknown(); protected abstract boolean isImportLink(); } private static class FromDatastoreCombo extends AutocorrectCombo { private final QueryAvailableData queryAvailableFields; public FromDatastoreCombo(QueryAvailableData queryAvailableFields, String initialValue) { super(initialValue); this.queryAvailableFields = queryAvailableFields; } @Override protected List<String> getAvailableItems() { ArrayList<String> ret = new ArrayList<>(); if (queryAvailableFields != null) { String[] raw = queryAvailableFields.queryAvailableDatastores(); if (raw != null) { // to do .. filter these for (String ds : raw) { ret.add(ds); } } } return ret; } @Override protected boolean isUnknown() { if (queryAvailableFields != null) { return Strings.containsStandardised(getValue(), Arrays.asList(queryAvailableFields.queryAvailableDatastores())) == false; } return false; } @Override public boolean isImportLink() { return AdapterBuilderUtils.getFormulaFromText(getValue()) != null; // if(getValue()!=null){ // return getValue().contains(ScriptConstants.IMPORT_LINK_POSTFIX); // } // return false; } } private static class FromTableCombo extends AutocorrectCombo { private final FromDatastoreCombo datastoreCombo; private final boolean isJoin; private final QueryAvailableData queryAvailableFields; public FromTableCombo(QueryAvailableData queryAvailableFields, FromDatastoreCombo datastoreCombo, String initialValue, boolean isJoin) { super(initialValue); this.datastoreCombo = datastoreCombo; this.isJoin = isJoin; this.queryAvailableFields = queryAvailableFields; } @Override protected List<String> getAvailableItems() { if (queryAvailableFields != null) { return asList(queryAvailableFields.queryAvailableTables(datastoreCombo.getValue())); } return new ArrayList<>(); } @Override protected boolean isUnknown() { if (queryAvailableFields != null) { if (datastoreCombo.isUnknown() == false) { if (queryAvailableFields.getDatastoreDefinition(datastoreCombo.getValue()) == null) { // datastore may be known as its the external one (which // is assumed to always exist), // however tables are not known return false; } if (queryAvailableFields.getTableDefinition(datastoreCombo.getValue(), getValue()) == null) { return true; } } } return false; } @Override protected boolean isImportLink() { return false; } } private class MyQueryAvailableDataDecorator extends QueryAvailableDataDecorator { public MyQueryAvailableDataDecorator(QueryAvailableData decorated) { super(decorated); } @Override public String[] queryAvailableFields(String datastore, String tablename) { // add extra fields if we're joining and querying the source table if (isJoinTable(datastore, tablename)) { ODLTableDefinition dfn = getTableDefinition(datastore, tablename); ArrayList<String> newFields = new ArrayList<String>(); if (dfn != null) { int nc = dfn.getColumnCount(); for (int i = 0; i < nc; i++) { newFields.add(dfn.getColumnName(i)); } } return newFields.toArray(new String[newFields.size()]); } else { return super.queryAvailableFields(datastore, tablename); } } private boolean isJoinTable(String datastore, String tablename) { return config.isJoin() && Strings.equalsStd(datastore, config.getFromDatastore()) && Strings.equalsStd(tablename, config.getFromTable()); } @Override public ODLTableDefinition getTableDefinition(String datastore, String tablename) { if (isJoinTable(datastore, tablename)) { ODLTableDefinition inner = super.getTableDefinition(datastore, tablename); ODLTableDefinition outer = super.getTableDefinition(config.getJoinDatastore(), config.getJoinTable()); if (inner != null && outer != null) { return AdapterBuilderUtils.buildEmptyJoinTable(outer, inner).getTableAt(0); } } return super.getTableDefinition(datastore, tablename); } } private class CustomFormulaControls extends LabelledRadioButton { final JTextField text = new JTextField(); @Override JPanel createLine() { return LayoutUtils.createHorizontalBoxLayout(createIndent(), radio, createHSpace(), label, createHSpace(), text); } CustomFormulaControls(AdaptedTableConfig tableConfig) { super("Filter using formula", "Filter the rows using a custom formula", RadioOption.CUSTOM); text.setText(tableConfig.getFilterFormula()); // text.addKeyListener(createKeyListener()); text.getDocument().addDocumentListener(new DocumentListener() { @Override public void removeUpdate(DocumentEvent e) { readFromForm(); } @Override public void insertUpdate(DocumentEvent e) { readFromForm(); } @Override public void changedUpdate(DocumentEvent e) { readFromForm(); } }); setMaxHeight(text, 30); } @Override void setEnabled(boolean enabled) { label.setEnabled(enabled); text.setEnabled(enabled); } } private class LabelledRadioButton { final JRadioButton radio = new JRadioButton(); final JLabel label; final RadioOption type; LabelledRadioButton(String name, String tooltip, RadioOption type) { label = new JLabel(name); radio.setToolTipText(tooltip); label.setToolTipText(tooltip); this.type = type; } void updateEnabled() { setEnabled(radio.isSelected()); } void setEnabled(boolean enabled) { label.setEnabled(enabled); } JPanel createLine() { return LayoutUtils.createHorizontalBoxLayout(createIndent(), radio, createHSpace(), label); } } private void setMaxHeight(Component component, int height) { component.setMaximumSize(new Dimension(component.getMaximumSize().width, height)); } protected void readFromForm() { config.setFromDatastore(fromDatastore.getValue()); config.setFromTable(fromTable.getValue()); if (noFilterCtrls.radio.isSelected()) { config.setFilterFormula(""); } else if (selCtrls.radio.isSelected()) { config.setFilterFormula(new FmIsSelectedInMap().toString()); } else if (customFormulaControls.radio.isSelected()) { config.setFilterFormula(customFormulaControls.text.getText()); } config.setJoin(joinCheckBox.isSelected()); config.setJoinDatastore(joinDatastore.getValue()); config.setJoinTable(joinTable.getValue()); updateAppearance(); if (formChangedListener != null) { formChangedListener.formChanged(this); } } @SuppressWarnings("incomplete-switch") private void writeToForm() { // if(Strings.isEmpty(config.getFilterFormula())){ // noFilterCtrls.radio.setSelected(true); // }else { // customFormulaControls.radio.setSelected(true); // } // if (outputTableName != null) { // outputTableName.setText(config.getName()); // } String text = ""; switch (getOption()) { case SEL: text = new FmIsSelectedInMap().toString(); break; case CUSTOM: text = config.getFilterFormula(); break; } ; customFormulaControls.text.setText(text); } private Component createIndent() { return createHSpace(16); } private static Component createHSpace() { return createHSpace(5); } private static Component createHSpace(int width) { return Box.createRigidArea(new Dimension(width, 1)); } private RadioOption getOption() { String s = config.getFilterFormula(); if (Strings.isEmpty(config.getFilterFormula())) { return RadioOption.ALL; } else if (Strings.equalsStd(new FmIsSelectedInMap().toString(), s)) { return RadioOption.SEL; } else { return RadioOption.CUSTOM; } } @SuppressWarnings("serial") public AdaptedTableControl(ODLApi api, AdaptedTableConfig config, long visibleTableFlags, long visibleColumnFlags, QueryAvailableData availableOptionsQuery) { // decorate the options object so it knows about join fields availableOptionsQuery = new MyQueryAvailableDataDecorator(availableOptionsQuery); this.config = config; this.api = api; this.queryAvailableFields = availableOptionsQuery; this.visibleTableFlags = visibleTableFlags; class SetSize { void set(JComponent component) { Dimension size = new Dimension(160, 26); component.setPreferredSize(size); component.setMaximumSize(size); } void setBorder(Component line) { if (JComponent.class.isInstance(line)) { ((JComponent) line).setBorder(new EmptyBorder(0, 5, 0, 5)); } } } SetSize setSize = new SetSize(); ValueChangedListener<String> listener = new ValueChangedListener<String>() { @Override public void comboValueChanged(String newValue) { readFromForm(); } }; // join controls joinCheckBox = new JCheckBox("", config.isJoin()); joinCheckBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { readFromForm(); } }); joinDatastore = new FromDatastoreCombo(queryAvailableFields, config.getJoinDatastore()); joinDatastore.addValueChangedListener(listener); joinLabel1 = new JLabel("Join tables - run for each row in datastore "); joinLabel1.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { joinCheckBox.setSelected(!joinCheckBox.isSelected()); readFromForm(); } }); joinLabel2 = new JLabel(" table "); joinTable = new FromTableCombo(queryAvailableFields, joinDatastore, config.getJoinTable(), true); joinTable.addValueChangedListener(listener); setSize.set(joinDatastore); setSize.set(joinTable); setSize.setBorder(addLine(joinCheckBox, joinLabel1, joinDatastore, joinLabel2, joinTable)); updateJoinAppearance(); // from source datastore and table fromDatastore = new FromDatastoreCombo(queryAvailableFields, config.getFromDatastore()); fromTable = new FromTableCombo(queryAvailableFields, fromDatastore, config.getFromTable(), false); fromDatastore.addValueChangedListener(listener); fromTable.addValueChangedListener(listener); setSize.set(fromDatastore); setSize.set(fromTable); JLabel selectRowsLabel = new JLabel("Select rows from datastore "); JLabel tableLabel = new JLabel(" table "); setSize.setBorder(addLine(selectRowsLabel, fromDatastore, tableLabel, fromTable)); // create filters line noFilterCtrls = new LabelledRadioButton("All rows", "Select all rows from the table.", RadioOption.ALL); selCtrls = new LabelledRadioButton("Selected in map", "Select the rows from the table which are selected in the map.", RadioOption.SEL); // create custom filter customFormulaControls = new CustomFormulaControls(config); radioFilterCtrls.add(noFilterCtrls); radioFilterCtrls.add(selCtrls); radioFilterCtrls.add(customFormulaControls); addLine(noFilterCtrls.createLine(), selCtrls.createLine(), customFormulaControls.createLine()); initRadioGroupings(getOption()); // gap between source and fields addWhitespace(); // create fields header and grid this.fieldGrid = new AdapterTableDefinitionGrid(api, config, visibleColumnFlags, availableOptionsQuery) { IntegerEntryPanel limitNb; @Override protected List<ODLAction> createActions() { ArrayList<ODLAction> ret = new ArrayList<>(); ret.addAll(super.createActions()); ret.addAll(AdaptedTableControl.this.createActions()); return ret; } @Override protected void fillToolbar(JToolBar toolBar) { super.fillToolbar(toolBar); AdaptedTableControl.this.fillToolbar(toolBar); if ((visibleTableFlags & DISABLE_SOURCE_FLAGS) != DISABLE_SOURCE_FLAGS) { toolBar.addSeparator(); JCheckBox fetchSrc = new JCheckBox("Add src cols ", AdaptedTableControl.this.config.isFetchSourceFields()); fetchSrc.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { AdaptedTableControl.this.config.setFetchSourceFields(fetchSrc.isSelected()); } }); toolBar.add(fetchSrc); // add limit results box final JCheckBox limitBox = new JCheckBox("Limit results", AdaptedTableControl.this.config.isLimitResults()); limitBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { AdaptedTableControl.this.config.setLimitResults(limitBox.isSelected()); updateAppearance(); } }); limitNb = new IntegerEntryPanel("", AdaptedTableControl.this.config.getMaxNumberRows(), "Maximum number of rows", new IntChangedListener() { @Override public void intChange(int newInt) { AdaptedTableControl.this.config.setMaxNumberRows(newInt); } }); limitNb.setPreferredTextboxWidth(50); toolBar.add(limitBox); toolBar.add(limitNb); } } @Override public void updateAppearance() { super.updateAppearance(); if (limitNb != null) { limitNb.setEnabled(AdaptedTableControl.this.config.isLimitResults()); } } }; fieldGrid.setAlignmentX(Component.LEFT_ALIGNMENT); fieldGrid.setPreferredSize(new Dimension(650, 234)); Box box = new Box(BoxLayout.X_AXIS); // box gives correct alignment // (otherwise grid is // right-aligned) box.add(fieldGrid); addNoWrap(box); // disable source if set if ((visibleTableFlags & DISABLE_SOURCE_FLAGS) == DISABLE_SOURCE_FLAGS) { fromDatastore.setEnabled(false); fromTable.setEnabled(false); selectRowsLabel.setEnabled(false); tableLabel.setEnabled(false); for(LabelledRadioButton lrb : radioFilterCtrls){ lrb.label.setEnabled(false); lrb.radio.setEnabled(false); } joinCheckBox.setEnabled(false); } writeToForm(); } /** * Overridden in other classes... * * @param toolBar */ protected void fillToolbar(JToolBar toolBar) { // get checkboxes to add // ArrayList<JCheckBox> checkBoxes = new ArrayList<>(); // if(DefinedFlags.hasFlag(visibleTableFlags, // DefinedFlags.TABLE_IS_REPORT_HEADER_MAP)){ // final JCheckBox box = new JCheckBox("Report // header?",DefinedFlags.hasFlag(config.getFlags(), // DefinedFlags.TABLE_IS_REPORT_HEADER_MAP)); // box.addActionListener(new ActionListener() { // // @Override // public void actionPerformed(ActionEvent e) { // config.setFlags(DefinedFlags.setFlag(config.getFlags(), // DefinedFlags.TABLE_IS_REPORT_HEADER_MAP, box.isSelected())); // updateAppearance(); // } // }); // checkBoxes.add(box); // } // if(checkBoxes.size()>0){ // toolBar.addSeparator(); // for(JCheckBox box:checkBoxes){ // toolBar.add(box); // } // } } /** * Default does-nothing implementation; overridden in subclasses * * @return */ protected List<ODLAction> createActions() { ArrayList<ODLAction> ret = new ArrayList<>(); return ret; } private enum RadioOption { ALL, SEL, CUSTOM } private void initRadioGroupings(RadioOption option) { ButtonGroup group = new ButtonGroup(); group.add(noFilterCtrls.radio); group.add(selCtrls.radio); group.add(customFormulaControls.radio); ActionListener radioListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { for (LabelledRadioButton lrb : radioFilterCtrls) { lrb.updateEnabled(); } readFromForm(); writeToForm(); } }; for (LabelledRadioButton lrb : radioFilterCtrls) { lrb.radio.addActionListener(radioListener); lrb.radio.setSelected(option == lrb.type); } radioListener.actionPerformed(null); } // 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 AdaptedTableControl(tableConfig, true, true,0, // TableFlags.FLAG_IS_OPTIONAL, options)); // } public void updateAppearance() { if (fieldGrid != null) { fieldGrid.updateAppearance(); } if (fromDatastore != null) { fromDatastore.updateAppearance(); } if (fromTable != null) { fromTable.updateAppearance(); } updateJoinAppearance(); } private void updateJoinAppearance() { joinDatastore.setEnabled(config.isJoin()); joinLabel1.setEnabled(config.isJoin()); joinLabel2.setEnabled(config.isJoin()); joinTable.setEnabled(config.isJoin()); } public void setTargetDatastoreDefinitionProvider(ODLDatastoreDefinitionProvider targetDatastoreDefinitionProvider) { fieldGrid.setTargetDatastoreDefinitionProvider(targetDatastoreDefinitionProvider); } }