/*******************************************************************************
* 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.wizardgenerated;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.Icon;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import com.opendoorlogistics.api.ODLApi;
import com.opendoorlogistics.api.components.ComponentExecutionApi.ModalDialogResult;
import com.opendoorlogistics.api.components.ODLComponent;
import com.opendoorlogistics.api.scripts.ScriptAdapter.ScriptAdapterType;
import com.opendoorlogistics.api.scripts.ScriptOption;
import com.opendoorlogistics.api.scripts.ScriptOption.OutputType;
import com.opendoorlogistics.api.tables.ODLDatastore;
import com.opendoorlogistics.api.tables.ODLTableDefinition;
import com.opendoorlogistics.api.tables.TableFlags;
import com.opendoorlogistics.api.ui.UIFactory.TextChangedListener;
import com.opendoorlogistics.core.api.impl.scripts.ScriptOptionImpl;
import com.opendoorlogistics.core.components.ODLGlobalComponents;
import com.opendoorlogistics.core.scripts.elements.AdaptedTableConfig;
import com.opendoorlogistics.core.scripts.elements.AdapterConfig;
import com.opendoorlogistics.core.scripts.elements.ComponentConfig;
import com.opendoorlogistics.core.scripts.elements.InstructionConfig;
import com.opendoorlogistics.core.scripts.elements.Option;
import com.opendoorlogistics.core.scripts.elements.OutputConfig;
import com.opendoorlogistics.core.scripts.elements.Script;
import com.opendoorlogistics.core.scripts.elements.ScriptBaseElement;
import com.opendoorlogistics.core.scripts.io.ScriptIO;
import com.opendoorlogistics.core.scripts.parameters.ParametersImpl;
import com.opendoorlogistics.core.scripts.utils.AdapterExpectedStructureProvider;
import com.opendoorlogistics.core.scripts.utils.ScriptFieldsParser;
import com.opendoorlogistics.core.scripts.utils.ScriptFieldsParser.SourcedDatastore;
import com.opendoorlogistics.core.scripts.utils.ScriptUtils;
import com.opendoorlogistics.core.scripts.utils.ScriptUtils.OptionVisitor;
import com.opendoorlogistics.core.scripts.utils.ScriptUtils.OutputWindowSyncLevel;
import com.opendoorlogistics.core.scripts.utils.ScriptUtils.ScriptIds;
import com.opendoorlogistics.core.utils.strings.Strings;
import com.opendoorlogistics.core.utils.ui.LayoutUtils;
import com.opendoorlogistics.core.utils.ui.ModalDialog;
import com.opendoorlogistics.core.utils.ui.OkCancelDialog;
import com.opendoorlogistics.core.utils.ui.TextEntryPanel;
import com.opendoorlogistics.core.utils.ui.VerticalLayoutPanel;
import com.opendoorlogistics.studio.controls.ODLScrollableToolbar;
import com.opendoorlogistics.studio.scripts.componentwizard.SetupComponentWizard;
import com.opendoorlogistics.studio.scripts.editor.OutputPanel;
import com.opendoorlogistics.studio.scripts.editor.ScriptEditor;
import com.opendoorlogistics.studio.scripts.editor.ScriptEditorToolbar;
import com.opendoorlogistics.studio.scripts.editor.adapters.AdapterTablesTabControl;
import com.opendoorlogistics.studio.scripts.editor.adapters.UserFormulaEditor;
import com.opendoorlogistics.studio.scripts.execution.ScriptUIManager;
import com.opendoorlogistics.utils.ui.Icons;
import com.opendoorlogistics.utils.ui.ODLAction;
import com.opendoorlogistics.utils.ui.SimpleAction;
import com.opendoorlogistics.utils.ui.SimpleActionConfig;
final public class ScriptEditorWizardGenerated extends ScriptEditor {
private static final Map<DisplayNodeType, Icon> iconsByType;
private static final Icon openOptionIcon;
private static final Icon closedOptionIcon;
// private static final HashMap<Pair<String, Integer>, Icon> iconByComponent
// = new HashMap<>();
private static final String htmlHorizontalWhitespace;
private MyScrollPane currentPane;
private ArrayList<TreeAction> treeActions = new ArrayList<>();
private JTree tree;
private JSplitPane splitPane;
private static AdapterConfig dataAdapterClipboard;
private static Option optionClipboard;
private static abstract class TreeAction extends SimpleAction{
public TreeAction(SimpleActionConfig config) {
super(config);
}
public TreeAction(String name, String tooltip, String smallIconPng, String largeIconPng) {
super(name, tooltip, smallIconPng, largeIconPng);
}
public TreeAction(String name, String tooltip, String smallIconPng) {
super(name, tooltip, smallIconPng);
}
public boolean addToToolbar(){
return true;
}
}
static {
iconsByType = new HashMap<>();
iconsByType.put(DisplayNodeType.COMPONENT_CONFIGURATION, Icons.loadFromStandardPath("script-element-component-config.png"));
iconsByType.put(DisplayNodeType.DATA_ADAPTER, Icons.loadFromStandardPath("script-element-data-adapter.png"));
iconsByType.put(DisplayNodeType.PARAMETER, Icons.loadFromStandardPath("parameter.png"));
iconsByType.put(DisplayNodeType.INSTRUCTION, Icons.loadFromStandardPath("script-element-instruction.png"));
// iconsByType.put(DisplayNodeType.INSTRUCTION, new
// DefaultTreeCellRenderer().getLeafIcon());
iconsByType.put(DisplayNodeType.AVAILABLE_TABLES, Icons.loadFromStandardPath("available-tables.png"));
iconsByType.put(DisplayNodeType.COPY_TABLES, Icons.loadFromStandardPath("script-element-output.png"));
StringBuilder spacesBuilder = new StringBuilder();
for (int i = 0; i < 6; i++) {
spacesBuilder.append(" ");
}
htmlHorizontalWhitespace = spacesBuilder.toString();
openOptionIcon = Icons.loadFromStandardPath("script-option-open.png");
closedOptionIcon = Icons.loadFromStandardPath("script-option-closed.png");
}
enum DisplayNodeType {
OPTION, INSTRUCTION, DATA_ADAPTER, PARAMETER, COMPONENT_CONFIGURATION, COPY_TABLES, AVAILABLE_TABLES
}
class DisplayNode implements TreeNode {
DisplayNodeType type;
Option option;
AdapterConfig adapter;
ComponentConfig componentConfig;
InstructionConfig instruction;
String displayName;
boolean isRoot;
final List<OutputConfig> outputs = new ArrayList<>();
final List<DisplayNode> children = new ArrayList<>();
DisplayNode parent;
ScriptBaseElement getElement() {
switch (type) {
case OPTION:
return option;
case DATA_ADAPTER:
case PARAMETER:
return adapter;
case COMPONENT_CONFIGURATION:
return componentConfig;
case INSTRUCTION:
return instruction;
case COPY_TABLES:
return outputs.get(0);
}
throw new UnsupportedOperationException();
}
DisplayNode getRoot() {
DisplayNode ret = this;
while (ret.parent != null) {
ret = ret.parent;
}
return ret;
}
private void createInfoPanel(VerticalLayoutPanel panel) {
if (type == DisplayNodeType.OPTION) {
throw new RuntimeException();
}
String elementName = Strings.convertEnumToDisplayFriendly(type);
StringBuilder htmlBuilder = new StringBuilder();
htmlBuilder.append("<html><h2>" + elementName + ": " + displayName + "</h2>");
// add id
switch (type) {
case PARAMETER:
case DATA_ADAPTER:
htmlBuilder.append("<Strong>ID</Strong> : " + adapter.getId() + htmlHorizontalWhitespace);
break;
case COMPONENT_CONFIGURATION:
htmlBuilder.append("<Strong>ID</Strong> : " + componentConfig.getConfigId() + htmlHorizontalWhitespace);
break;
default:
break;
}
// add name
ScriptBaseElement element = getElement();
String name = element.getName();
htmlBuilder.append("<Strong>Name</Strong> : " + (Strings.isEmpty(name) ? "<none>" : name));
// add editable
htmlBuilder.append(htmlHorizontalWhitespace);
htmlBuilder.append("<Strong>Editable</Strong> : " + (element.isUserCanEdit() ? "yes" : "no"));
// work out what vertical spread we should use
boolean extraSpread = false;
switch (type) {
case COPY_TABLES:
extraSpread = true;
break;
case COMPONENT_CONFIGURATION:
extraSpread = true;
break;
case INSTRUCTION:
extraSpread = true;
break;
default:
break;
}
String vspace = "<br/>";
if (extraSpread) {
vspace += "<br/>";
}
// type specific
switch (type) {
case INSTRUCTION: {
String componentName = ScriptUtils.getComponentName(instruction);
// htmlBuilder.append(vspace + "<Strong>Input datastore</Strong>
// : " + instruction.getDatastore());
htmlBuilder.append(vspace + "<Strong>Calls component</Strong> : " + (componentName != null ? componentName : ""));
}
break;
case COMPONENT_CONFIGURATION: {
String componentName = ScriptUtils.getComponentName(componentConfig);
htmlBuilder.append(vspace + "<Strong>Configuration for component</Strong> : " + (componentName != null ? componentName : ""));
}
break;
default:
break;
}
// htmlBuilder.append("<br/><br/><hr/></html>");
htmlBuilder.append("</html>");
addLabel(panel, htmlBuilder.toString(), true);
// panel.addWhitespace(10);
}
/**
* Add the label with an editor. Editor only available in multipane
* view. Label will not be added in single pane view if its empty
*
* @param scriptElement
* @param panel
* @param isMultipane
* @return True if the label was added
*/
private boolean addLabelWithEditor(final ScriptBaseElement scriptElement, VerticalLayoutPanel panel, boolean isMultipane) {
// only allow editing if we're in multipane mode
if (!isMultipane) {
if (Strings.isEmpty(scriptElement.getEditorLabel()) == false) {
addLabel(panel, scriptElement.getEditorLabel(), true);
return true;
}
return false;
}
final String defaultText = "<html><i>...You can add your own notes here, just right click to edit them...</i></html>";
final String text;
if (Strings.isEmpty(scriptElement.getEditorLabel())) {
text = defaultText;
} else {
text = scriptElement.getEditorLabel();
}
JLabel label = new JLabel(text);
// label.setMaximumSize(new Dimension(400, 100));
// label.setPreferredSize(new Dimension(400, 60));
setDefaultBorder(label);
label.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEtchedBorder(), BorderFactory.createEmptyBorder(4, 4, 4, 4)));
label.addMouseListener(new MouseListener() {
@Override
public void mouseReleased(MouseEvent e) {
launchPopup(e);
}
@Override
public void mousePressed(MouseEvent e) {
launchPopup(e);
}
@Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
}
private void launchPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
JPopupMenu popup = new JPopupMenu();
popup.add(new AbstractAction("Clear note", Icons.loadFromStandardPath("script-note-clear.png")) {
@Override
public void actionPerformed(ActionEvent e) {
scriptElement.setEditorLabel("<html></html>");
rebuildActivePanel();
}
});
popup.add(new AbstractAction("Edit note", Icons.loadFromStandardPath("script-note-edit.png")) {
@Override
public void actionPerformed(ActionEvent e) {
// launch a text editor
String s = scriptElement.getEditorLabel();
if (Strings.isEmpty(s)) {
s = defaultText;
}
final JTextArea textArea = new JTextArea(s);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
textArea.setEditable(true);
textArea.setPreferredSize(new Dimension(600, 300));
OkCancelDialog dlg = new OkCancelDialog(SwingUtilities.getWindowAncestor(ScriptEditorWizardGenerated.this)) {
@Override
protected Component createMainComponent(boolean inWindowsBuilder) {
return new JScrollPane(textArea);
}
};
dlg.setTitle("Enter note text");
if (dlg.showModal() == OkCancelDialog.OK_OPTION) {
scriptElement.setEditorLabel(textArea.getText());
rebuildActivePanel();
}
}
});
popup.show(e.getComponent(), e.getX(), e.getY());
}
}
});
panel.add(label);
panel.addHalfWhitespace();
return true;
}
/**
* Create the pane to display this component
*
* @return
*/
MyScrollPane createPane(boolean isMultiPane) {
MyScrollPane ret = null;
if (isMultiPane) {
if (type == DisplayNodeType.OPTION) {
return createOptionPane();
}
if (type == DisplayNodeType.AVAILABLE_TABLES) {
return createAvailableTablesPane();
} else {
ret = new MyScrollPane(this);
createInfoPanel(ret.panel);
}
} else {
ret = new MyScrollPane(this);
}
// add adapter
if (adapter != null) {
// add adapter's label
addLabelWithEditor(adapter, ret.panel, isMultiPane);
if (adapter.isUserCanEdit()) {
// build the object which provides the adapter's destination
AdapterExpectedStructureProvider dfnProvider = ScriptUtils.createAdapterExpectedStructure(api, script, option, adapter.getId());
// show all flags for every adapter - even report key - even
// though they're not used on each one
long visibleColumnFlags = TableFlags.FLAG_IS_OPTIONAL | TableFlags.FLAG_IS_GROUP_BY_FIELD | TableFlags.FLAG_IS_BATCH_KEY | TableFlags.FLAG_IS_REPORT_KEYFIELD;
AdapterTablesTabControl tabControl = new AdapterTablesTabControl(api, adapter, visibleColumnFlags, createAvailableOptionsQuery(), dfnProvider,
ScriptEditorWizardGenerated.this.runner) {
protected List<ODLAction> createTabPageActions(final AdaptedTableConfig table) {
List<ODLAction> ret = super.createTabPageActions(table);
ret.add(new SimpleAction(new SimpleActionConfig("Show generated table", "Show the table generated by the selected adapted table.", "table-go.png", null, true)) {
@Override
public void actionPerformed(ActionEvent e) {
// view table
executeAdapterResultViewer(table, false);
}
});
ret.add(new SimpleAction(new SimpleActionConfig("Show generated map", "Show the map generated by the selected adapted table.", "world.png", null, true)) {
@Override
public void actionPerformed(ActionEvent e) {
// view table
executeAdapterResultViewer(table, true);
}
@Override
public void updateEnabledState() {
long flags = adapter.getFlags();
if (table != null) {
flags |= table.getFlags();
}
setEnabled((flags & TableFlags.FLAG_IS_DRAWABLES) == TableFlags.FLAG_IS_DRAWABLES);
}
});
return ret;
}
};
ret.adapterTabControls.add(tabControl);
ret.panel.addNoWrap(tabControl);
ret.panel.addHalfWhitespace();
}
}
// add stand-alone component config
if (componentConfig != null) {
// get component
ODLComponent component = ODLGlobalComponents.getProvider().getComponent(componentConfig.getComponent());
if (component == null) {
throw new RuntimeException("Unknown component: " + componentConfig.getComponent());
}
// create user panel
JPanel userPanel = null;
if (componentConfig.isUserCanEdit()) {
if (component.getConfigClass() != null) {
ScriptUtils.validateComponentConfigClass(component, componentConfig);
userPanel = component.createConfigEditorPanel(createComponentEditorAPI(component.getId(), option, instruction), -1, componentConfig.getComponentConfig(), true);
}
}
addLabelWithEditor(componentConfig, ret.panel, isMultiPane);
// add the user panel
if (userPanel != null) {
// userPanel.setBorder(BorderFactory.createEmptyBorder(5, 5,
// 5, 5));
// userPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
if (isMultiPane) {
setMultiPaneBorder(userPanel);
}
ret.panel.add(userPanel);
ret.panel.addHalfWhitespace();
}
}
// add instruction
if (instruction != null) {
// add instruction's label
boolean hasInstructionLabel = addLabelWithEditor(instruction, ret.panel, isMultiPane);
if (instruction.isUserCanEdit()) {
if (isMultiPane) {
// add text entry box for the component
TextEntryPanel dsid = new TextEntryPanel("Input datastore id: ", instruction.getDatastore(), new TextChangedListener() {
@Override
public void textChange(String newText) {
instruction.setDatastore(newText);
}
});
dsid.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
ret.panel.add(dsid);
ret.panel.addHalfWhitespace();
// and for reports top label
TextEntryPanel reportsTopLaber = new TextEntryPanel("Formula for label at the top of controls(s): ", instruction.getReportTopLabelFormula(), new TextChangedListener() {
@Override
public void textChange(String newText) {
instruction.setReportTopLabelFormula(newText);
}
});
reportsTopLaber.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
ret.panel.add(reportsTopLaber);
ret.panel.addHalfWhitespace();
}
ODLComponent component = ODLGlobalComponents.getProvider().getComponent(instruction.getComponent());
if (component == null) {
throw new RuntimeException("Unknown component: " + instruction.getComponent());
}
if (component.getConfigClass() != null) {
ScriptUtils.validateComponentConfigClass(component, instruction);
Serializable inplaceConfig = instruction.getComponentConfig();
if (inplaceConfig != null) {
JPanel userPanel = component.createConfigEditorPanel(createComponentEditorAPI(component.getId(), option, instruction), instruction.getExecutionMode(), inplaceConfig, true);
if (userPanel != null) {
if (isMultiPane) {
setMultiPaneBorder(userPanel);
} else {
if (adapter != null || outputs.size() > 0) {
// If we have other components on the
// frame, add a border with the text
// "Setting for ..."
if (hasInstructionLabel) {
userPanel.setBorder(LayoutUtils.createInsetTitledBorder(""));
} else {
String text = "Settings for " + displayName;
userPanel.setBorder(LayoutUtils.createInsetTitledBorder(text));
}
}
}
// else {
// // add a default label
// if (hasInstructionLabel == false) {
// String text = "<html><h3>Settings for <i>" +
// displayName + "</i>...</h3></html>";
// addLabel(ret.panel, text,true);
// }
// userPanel.setBorder(BorderFactory.createEmptyBorder(5,
// 5, 5, 5));
// }
ret.panel.add(userPanel);
ret.panel.addHalfWhitespace();
}
}
}
}
}
// add outputs
for (OutputConfig output : outputs) {
addLabelWithEditor(output, ret.panel, isMultiPane);
if (output.isUserCanEdit()) {
OutputPanel outputPanel = new OutputPanel(output, true, OutputType.values());
if (isMultiPane) {
setMultiPaneBorder(outputPanel);
} else {
String borderTitle;
if (Strings.isEmpty(output.getInputTable())) {
borderTitle = "Output new table(s)";
} else {
borderTitle = "Output table \"" + output.getInputTable() + "\"";
}
outputPanel.setBorder(LayoutUtils.createInsetTitledBorder(borderTitle));
}
ret.panel.add(outputPanel);
}
}
// if we're not multipane and we have children then add them as tabs
if (!isMultiPane && children.size() > 0) {
JTabbedPane tabbedPane = new JTabbedPane();
for (DisplayNode node : children) {
tabbedPane.addTab(node.displayName, node.createPane(false));
}
ret.panel.add(tabbedPane);
}
return ret;
}
private MyScrollPane createAvailableTablesPane() {
// create table listing control
TableListingsModel model = new TableListingsModel(api, this, runner != null ? runner.getDatastoreDefinition() : null);
final TableListings table = new TableListings();
table.setModel(model);
// create return scroll pane
MyScrollPane ret = new MyScrollPane(this) {
@Override
public void updateAppearance() {
super.updateAppearance();
// available tables may have changed if spreadsheet opened /
// closed
table.setModel(new TableListingsModel(api, DisplayNode.this, runner != null ? runner.getDatastoreDefinition() : null));
}
};
addLabel(ret.panel,
"<html><h2>Available tables</h2>The following tables are available to instructions within this option." + "<br/>Tables coloured in green are created within this option.</html>",
true);
// add table in its own scroll pane
JScrollPane scrollPane = new JScrollPane(table);
scrollPane.setPreferredSize(new Dimension(500, 160));
setDefaultBorder(scrollPane);
ret.panel.add(scrollPane);
addLabel(ret.panel, "<html>The selected table has the following columns:</html>", true);
// add table for fields in selected table
final FieldListings fieldListings = new FieldListings();
scrollPane = new JScrollPane(fieldListings);
scrollPane.setPreferredSize(new Dimension(500, 160));
setDefaultBorder(scrollPane);
ret.panel.add(scrollPane);
// add selection listener to update the fields table
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
int row = table.getSelectedRow();
ODLTableDefinition dfn = null;
if (row != -1) {
dfn = ((TableListingsModel) table.getModel()).getTableDefinition(row);
}
fieldListings.set(dfn);
}
});
if (table.getRowCount() > 0) {
table.getSelectionModel().setSelectionInterval(0, 0);
}
return ret;
}
/**
* @return
*/
private MyScrollPane createOptionPane() {
MyScrollPane ret = new MyScrollPane(this);
// add labels
String title;
if (isRoot) {
title = "Script";
} else if (ScriptUtils.isRunnableOption(this.option)) {
title = "Executable script option";
} else {
title = "Script option";
}
addLabel(ret.panel, "<html><h1>" + title + "</h1><h2><em>" + displayName + "</em></h2></html>", false);
// add label for the option
addLabelWithEditor(option, ret.panel, true);
// ret.panel.setAlignmentX(Component.LEFT_ALIGNMENT);
// add overrides for parameters
if (api.scripts().parameters().getControlFactory() != null) {
ret.panel.addWhitespace();
JCheckBox overrideParamsCB = new JCheckBox("Override visible parameters?", option.isOverrideVisibleParameters());
overrideParamsCB.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
JLabel label = new JLabel("<html>Define as a comma-separated line in the format:<br><i>[PROMPT_TYPE] parametername, e.g. ATTACH Potential, Sales, POPUP Workload, ...</i><html>");
label.setBorder(BorderFactory.createEmptyBorder(5, 5, 2, 5));
ret.panel.add(overrideParamsCB);
ret.panel.add(label);
JTextField editCtrl = new JTextField(option.getVisibleParametersOverride() != null ? option.getVisibleParametersOverride() : "");
overrideParamsCB.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
option.setOverrideVisibleParameters(overrideParamsCB.isSelected());
editCtrl.setEnabled(option.isOverrideVisibleParameters());
}
});
editCtrl.setMaximumSize(new Dimension(200, 26));
editCtrl.setEnabled(option.isOverrideVisibleParameters());
// hack - wrap edit control in additional to get formatting
// right
JPanel editPanel = new JPanel();
editPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
editPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
editPanel.setLayout(new BorderLayout());
editPanel.add(editCtrl, BorderLayout.CENTER);
ret.panel.add(editPanel);
editCtrl.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
readUI();
}
@Override
public void insertUpdate(DocumentEvent e) {
readUI();
}
@Override
public void changedUpdate(DocumentEvent e) {
readUI();
}
void readUI() {
option.setVisibleParametersOverride(editCtrl.getText());
}
});
// add checkbox for refresh always visible
ret.panel.add(Box.createVerticalGlue());
JCheckBox refreshAlwaysVisibleCB = new JCheckBox("Refresh button always enabled?", option.isRefreshButtonAlwaysEnabled());
refreshAlwaysVisibleCB.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
refreshAlwaysVisibleCB.addActionListener((e)->option.setRefreshButtonAlwaysEnabled(refreshAlwaysVisibleCB.isSelected()));
ret.panel.add(refreshAlwaysVisibleCB);
// and last refreshed label
ret.panel.add(Box.createVerticalGlue());
JCheckBox lastRefreshedLabel = new JCheckBox("Show last refreshed time?", option.isShowLastRefreshedTime());
lastRefreshedLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
lastRefreshedLabel.addActionListener((e)->option.setShowLastRefreshedTime(lastRefreshedLabel.isSelected()));
ret.panel.add(lastRefreshedLabel);
// create glue to swallow the spare space
ret.panel.add(Box.createVerticalGlue());
}
// add user formula editor panel
if(isRoot){
ret.panel.addWhitespace();
if(script.getUserFormulae()==null){
script.setUserFormulae(new ArrayList<>());
}
JPanel userformula =UserFormulaEditor.createUserFormulaListPanel(script.getUserFormulae());
userformula.setBorder(BorderFactory.createTitledBorder("Add global formulae here..."));
ret.panel.add(userformula);
}
return ret;
}
/**
* @param panel
*/
private void setMultiPaneBorder(JPanel panel) {
panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEtchedBorder(), BorderFactory.createEmptyBorder(5, 5, 5, 5)));
}
private void addLabel(VerticalLayoutPanel panel, String text, boolean halfspaceAfter) {
JLabel label = new JLabel(text);
setDefaultBorder(label);
panel.add(label);
if (halfspaceAfter) {
panel.addHalfWhitespace();
}
// editorPane.addHyperlinkListener(new HyperlinkListener() {
// /**
// * See
// http://stackoverflow.com/questions/3693543/hyperlink-in-jeditorpane
// */
// public void hyperlinkUpdate(HyperlinkEvent e) {
// if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
// if (Desktop.isDesktopSupported()) {
//
// try {
// URL url = e.getURL();
// URI uri = url.toURI();
// Desktop.getDesktop().browse(uri);
// } catch (Throwable e1) {
// // TODO Auto-generated catch block
// // e1.printStackTrace();
// }
//
// }
// }
// }
// });
}
/**
* @param component
*/
private void setDefaultBorder(JComponent component) {
component.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
}
int getNodeCount() {
int ret = 1;
for (DisplayNode child : children) {
ret += child.getNodeCount();
}
return ret;
}
@Override
public TreeNode getChildAt(int childIndex) {
return children.get(childIndex);
}
@Override
public int getChildCount() {
return children.size();
}
@Override
public TreeNode getParent() {
return parent;
}
@Override
public int getIndex(TreeNode node) {
for (int i = 0; i < children.size(); i++) {
if (children.get(i) == node) {
return i;
}
}
return -1;
}
@Override
public boolean getAllowsChildren() {
return true;
}
@Override
public boolean isLeaf() {
return children.size() == 0;
}
@Override
public Enumeration children() {
final Iterator<DisplayNode> it = children.iterator();
return new Enumeration<TreeNode>() {
@Override
public boolean hasMoreElements() {
return it.hasNext();
}
@Override
public TreeNode nextElement() {
return it.next();
}
};
}
@Override
public String toString() {
return displayName;
}
}
private static ComponentConfig findInstructionWithInputDS(String dsid, Iterable<InstructionConfig> instructions) {
for (InstructionConfig inst : instructions) {
if (Strings.equalsStd(dsid, inst.getDatastore())) {
return inst;
}
}
return null;
}
// private static ComponentConfig findInstructionWithOutputDS(String dsid,
// Iterable<InstructionConfig> instructions) {
// for (InstructionConfig inst : instructions) {
// if (Strings.equalsStd(dsid, inst.getOutputDatastore())) {
// return inst;
// }
// }
// return null;
// }
private class Splitter {
DisplayNode splitIntoDisplayNodes(Script script, boolean isSingleFrameView) {
// boolean isSingleFrameView = ScriptUtils.getOptionsCount(script)
// <= 1 && script.getAdapters().size()<=1;
DisplayNode ret = recurseSplitIntoDisplayNodes(script, isSingleFrameView);
fixParentReferences(null, ret);
ret.isRoot = true;
return ret;
}
private void fixParentReferences(DisplayNode parent, DisplayNode node) {
node.parent = parent;
for (DisplayNode child : node.children) {
fixParentReferences(node, child);
}
}
/**
* Split the script up into different display nodes to show in a tree
*
* @param script
* @return
*/
private DisplayNode recurseSplitIntoDisplayNodes(Option option, boolean isSingleFrameView) {
DisplayNode ret = new DisplayNode();
ret.displayName = Strings.isEmpty(option.getName()) ? option.getOptionId() : option.getName();
ret.type = DisplayNodeType.OPTION;
ret.option = option;
// unconnected adapters go on their own, at the start
ArrayList<DisplayNode> unconnectedAdapters = new ArrayList<>();
for (AdapterConfig adapter : option.getAdapters()) {
if (adapter.isUserCanEdit() && (isSingleFrameView == false || findInstructionWithInputDS(adapter.getId(), option.getInstructions()) == null)) {
unconnectedAdapters.add(createUnconnectedAdapterNode(option, adapter));
}
}
// then come standalone configs
ArrayList<DisplayNode> standaloneConfigs = new ArrayList<>();
for (ComponentConfig config : option.getComponentConfigs()) {
if (config.isUserCanEdit()) {
DisplayNode node = new DisplayNode();
node.option = option;
node.type = DisplayNodeType.COMPONENT_CONFIGURATION;
node.componentConfig = config;
node.displayName = Strings.isEmpty(config.getName()) ? config.getConfigId() : config.getName();
standaloneConfigs.add(node);
}
}
// each instruction has its own node, together with any connected
// adapters and outputs
ArrayList<DisplayNode> instructions = new ArrayList<>();
HashSet<OutputConfig> connectedOutputs = new HashSet<>();
for (InstructionConfig instruction : option.getInstructions()) {
DisplayNode node = new DisplayNode();
node.option = option;
node.type = DisplayNodeType.INSTRUCTION;
node.instruction = instruction;
node.displayName = Strings.isEmpty(instruction.getName()) ? ScriptUtils.getComponentName(instruction) : instruction.getName();
if (node.displayName == null) {
node.displayName = "Instruction";
}
for (AdapterConfig adapter : option.getAdapters()) {
if (adapter.isUserCanEdit() && isSingleFrameView && Strings.equalsStd(instruction.getDatastore(), adapter.getId())) {
node.adapter = adapter;
break;
}
}
for (OutputConfig output : option.getOutputs()) {
// link output if they output from the instruction or if the
// instruction owns the adapter and
// they output the adapter contents
if (output.isUserCanEdit() && isSingleFrameView && (Strings.equalsStd(instruction.getOutputDatastore(), output.getDatastore())
|| (node.adapter != null && Strings.equalsStd(node.adapter.getId(), output.getDatastore())))) {
node.outputs.add(output);
connectedOutputs.add(output);
}
}
// try measuring the instruction config height and unconnect the
// adapter if its too high
if (node.adapter != null) {
ODLComponent component = ScriptUtils.getComponent(instruction);
if (component != null) {
ScriptUtils.validateComponentConfigClass(component, instruction);
JPanel panel = component.createConfigEditorPanel(createComponentEditorAPI(component.getId(), option, instruction), instruction.getExecutionMode(),
instruction.getComponentConfig(), false);
if (panel != null) {
double prefHeight = panel.getPreferredSize().getHeight();
if (prefHeight > 300) {
unconnectedAdapters.add(createUnconnectedAdapterNode(option, node.adapter));
node.adapter = null;
}
}
}
}
instructions.add(node);
}
// unconnected outputs go on their own at the end
ArrayList<DisplayNode> unconnectedOutputs = new ArrayList<>();
for (OutputConfig output : option.getOutputs()) {
if (output.isUserCanEdit() && connectedOutputs.contains(output) == false) {
DisplayNode node = new DisplayNode();
node.option = option;
node.type = DisplayNodeType.COPY_TABLES;
node.outputs.add(output);
node.displayName = Strings.isEmpty(output.getName()) ? "Copy table(s)" : output.getName();
unconnectedOutputs.add(node);
}
}
// merge into one node if not too many items and merge is allowed
ArrayList<DisplayNode> allNodes = new ArrayList<>();
allNodes.addAll(unconnectedAdapters);
allNodes.addAll(standaloneConfigs);
allNodes.addAll(instructions);
allNodes.addAll(unconnectedOutputs);
if (allNodes.size() == 1 && isSingleFrameView) {
mergeChild(ret, allNodes.get(0));
} else {
// also create node for available tables
if (isSingleFrameView == false) {
DisplayNode availableTables = new DisplayNode();
availableTables.type = DisplayNodeType.AVAILABLE_TABLES;
availableTables.option = option;
availableTables.displayName = "Available tables";
ret.children.add(availableTables);
}
ret.children.addAll(allNodes);
}
// parse and add child options
for (Option child : option.getOptions()) {
ret.children.add(recurseSplitIntoDisplayNodes(child, isSingleFrameView));
}
return ret;
}
/**
* @param option
* @param adapter
* @return
*/
private DisplayNode createUnconnectedAdapterNode(Option option, AdapterConfig adapter) {
DisplayNode node = new DisplayNode();
node.option = option;
node.type = (adapter != null && adapter.getAdapterType() == ScriptAdapterType.PARAMETER) ? DisplayNodeType.PARAMETER : DisplayNodeType.DATA_ADAPTER;
node.adapter = adapter;
// just use the adapter id as we use this in formulae and allow the
// user to change it in the IU
node.displayName = adapter.getId();
return node;
}
private void mergeChild(DisplayNode parent, DisplayNode child) {
// add child's children
parent.children.clear();
parent.children.addAll(child.children);
// take child's name if we don't already have name
if (Strings.isEmpty(parent.displayName)) {
parent.displayName = child.displayName;
}
// take child's adapter, instruction and outputs BUT NOT THE TYPE
parent.adapter = child.adapter;
parent.instruction = child.instruction;
parent.componentConfig = child.componentConfig;
parent.outputs.addAll(child.outputs);
}
}
/**
* Wizard generated scripts need to support options... Options are
* hierarchical and only leaf options are runnable. We can assume that an
* adapter is owned by the instruction which references it if its part of
* the same option. Same for outputs.
*
* It is possible an adapter might go to several instructions (not in the
* same option).
*
* Options appear as hierarchical tabs where anything where the parent tab
* is the first and the later ones appear in the same tab sheet and are
* called 'option X', 'option Y' etc?
*
*/
public ScriptEditorWizardGenerated(ODLApi api, Script script, File file, String optionId, ScriptUIManager runner) {
super(api, script, file, runner);
// ensure script has valid synchronisation settings
if (ScriptUtils.validateSynchonisation(api, script) == false) {
throw new RuntimeException("Invalid script.");
}
initPanels(script, optionId, UseTree.UNDECIDED);
// ensure control isn't too high
setMaximumSize(new Dimension(Integer.MAX_VALUE, 800));
reinitialiseToolbar();
pack();
updateAppearance();
}
/**
* @param script
* @param optionId
*/
private void initPanels(Script script, String optionId, UseTree useTree) {
treeActions.clear();
// always use tree if we have more than one option...
if (script.getOptions() != null && script.getOptions().size() > 0) {
useTree = UseTree.YES;
} else {
// only one option...
if (useTree == UseTree.UNDECIDED) {
useTree = UseTree.NO;
}
}
DisplayNode rootNode = new Splitter().splitIntoDisplayNodes(script, useTree == UseTree.NO);
contentPane.removeAll();
if (useTree == UseTree.YES) {
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
splitPane.setResizeWeight(0.3);
createTree(optionId, rootNode);
initTreeActions();
JPanel treePanel = new JPanel();
treePanel.setLayout(new BorderLayout());
treePanel.add(new JScrollPane(tree), BorderLayout.CENTER);
treePanel.add(createTreeToolBar(), BorderLayout.SOUTH);
splitPane.setLeftComponent(treePanel);
contentPane.add(splitPane, BorderLayout.CENTER);
} else {
currentPane = rootNode.createPane(false);
contentPane.add(currentPane, BorderLayout.CENTER);
tree = null;
}
}
/**
* @param optionId
* @param rootNode
* @param splitter
* @return
*/
private JTree createTree(String optionId, DisplayNode rootNode) {
tree = new JTree(rootNode);
tree.setCellRenderer(new DefaultTreeCellRenderer() {
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
Component ret = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
DisplayNode node = (DisplayNode) value;
Icon icon = null;
switch (node.type) {
case OPTION:
// never show as leaf node
if (expanded) {
icon = openOptionIcon;// getOpenIcon();
} else {
icon = closedOptionIcon;// getClosedIcon();
}
setText("<html><strong>" + node.displayName + "</strong></html>");
break;
default:
// always show as a leaf node
icon = iconsByType.get(node.type);
if (node.type == DisplayNodeType.AVAILABLE_TABLES) {
setText("<html><em>" + node.displayName + "</em></html>");
} else {
setText("<html>" + node.displayName + "</html>");
}
break;
}
setIcon(icon);
return ret;
}
});
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
rebuildActivePanel();
}
});
tree.setRootVisible(true);
tree.setEditable(false);
tree.addMouseListener(new MouseListener() {
@Override
public void mouseReleased(MouseEvent e) {
ensureSelected(e);
launchPopup(e);
}
private void ensureSelected(MouseEvent e) {
// ensure correct one is selected
TreePath path = tree.getPathForLocation(e.getX(), e.getY());
if (path != null) {
tree.getSelectionModel().setSelectionPath(path);
}
}
private void launchPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
TreePath path = tree.getPathForLocation(e.getX(), e.getY());
if (path != null && path.getLastPathComponent() != null) {
JPopupMenu popup = new JPopupMenu();
for (Action action : treeActions) {
popup.add(action);
}
popup.show(e.getComponent(), e.getX(), e.getY());
}
}
}
@Override
public void mousePressed(MouseEvent e) {
ensureSelected(e);
launchPopup(e);
}
@Override
public void mouseExited(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseClicked(MouseEvent e) {
// System.out.println("mouse clicked " + e.getSource());
}
});
// expand the root only (looks too complicated otherwise)
tree.expandRow(0);
// set the starting path using the input id if we have one
selectOption(optionId, rootNode, tree);
return tree;
}
/**
* @param optionId
* @param rootNode
* @param tree
*/
private static void selectOption(String optionId, DisplayNode rootNode, JTree tree) {
TreePath path = null;
if (!Strings.isEmpty(optionId)) {
path = getTreePath(optionId, rootNode);
}
if (path == null) {
path = new TreePath(rootNode);
}
tree.setSelectionPath(path);
}
private JToolBar createTreeToolBar() {
JToolBar ret = new ODLScrollableToolbar().getToolBar();
for (TreeAction action : treeActions) {
if(action.addToToolbar()){
ret.add(action);
}
}
ret.setFloatable(false);
return ret;
}
public void setSelectedOption(String optionId) {
if (tree != null) {
reinitTree(optionId);
}
}
private static TreePath getTreePath(final String optionID, DisplayNode root) {
class Parser {
TreePath parse(DisplayNode current, ArrayList<TreeNode> path) {
// copy path
path = new ArrayList<>(path);
// add current
path.add(current);
// check for match
if (Strings.equals(current.option.getOptionId(), optionID)) {
return new TreePath(path.toArray());
}
// parse children
for (int i = 0; i < current.getChildCount(); i++) {
TreePath ret = parse((DisplayNode) current.getChildAt(i), path);
if (ret != null) {
return ret;
}
}
return null;
}
}
return new Parser().parse(root, new ArrayList<TreeNode>());
}
protected void messageBoxClose(String s) {
JOptionPane.showMessageDialog(this, "This script is corrupt and will be closed");
dispose();
}
private enum UseTree {
YES, NO, UNDECIDED
}
protected ScriptEditorToolbar createToolbar() {
ScriptEditorToolbar ret = new ScriptEditorToolbar(isRunScriptAllowed(),
currentPane != null ? currentPane.displayNode.option.isSynchronised() : false,
isRunScriptAllowed(),
currentPane != null ? currentPane.displayNode.option.isLaunchMultiple() : false) {
@Override
protected void syncBoxChanged(boolean isSelected) {
if (currentPane != null) {
currentPane.displayNode.option.setSynchronised(isSelected);
}
}
@Override
protected boolean isSyncBoxEnabled() {
if (isRunScriptAllowed()) {
return ScriptUtils.getOutputWindowSyncLevel(api, script, currentPane.displayNode.option.getOptionId()) == OutputWindowSyncLevel.MANUAL;
}
return false;
}
@Override
protected void toggleView() {
initPanels(script, currentPane != null && currentPane.displayNode != null && currentPane.displayNode.option != null ? currentPane.displayNode.option.getOptionId() : null,
tree == null ? UseTree.YES : UseTree.NO);
// Rectangle bounds = getBounds();
reinitialiseToolbar();
// pack();
repaint();
updateAppearance();
// setBounds(bounds);
}
@Override
protected boolean isToggleViewEnabled() {
return script.getOptions() == null || script.getOptions().size() == 0;
}
@Override
protected void launchMultipleChanged(boolean isLaunchMultiple) {
if (currentPane != null) {
currentPane.displayNode.option.setLaunchMultiple(isLaunchMultiple);
}
}
};
ret.addAction(createSaveScriptAction());
ret.addAction(createSaveScriptAsAction());
// disable copy for the moment
// ret.addAction(createCopyAction());
if (isRunScriptAllowed()) {
ret.addAction(createTestCompileScriptAction());
ret.addAction(createRunScriptAction());
}
return ret;
}
private class MyScrollPane extends JScrollPane {
final VerticalLayoutPanel panel;
final List<AdapterTablesTabControl> adapterTabControls = new ArrayList<>();
final DisplayNode displayNode;
public MyScrollPane(DisplayNode node) {
super(new VerticalLayoutPanel());
this.displayNode = node;
panel = (VerticalLayoutPanel) getViewport().getView();
}
public void updateAppearance() {
for (AdapterTablesTabControl tabs : adapterTabControls) {
tabs.updateAppearance(true);
}
}
}
@Override
public void updateAppearance() {
super.updateAppearance();
if (currentPane != null) {
currentPane.updateAppearance();
}
for (ODLAction action : treeActions) {
action.updateEnabledState();
}
}
@Override
protected boolean isRunScriptAllowed() {
return currentPane != null && currentPane.displayNode != null && currentPane.displayNode.option != null && ScriptUtils.isRunnableOption(currentPane.displayNode.option);
}
@Override
protected void executeScript() {
if (currentPane != null && runner != null) {
DisplayNode node = currentPane.displayNode;
runner.executeScript(script, node.isRoot ? null : new String[] { node.option.getOptionId() }, file != null ? file.getName() : null);
}
}
// public static void main(String[] args) throws Exception {
// InitialiseStudio.initialise();
// ODLDatastoreAlterable<? extends ODLTableAlterable> ds =
// ExampleData.createTerritoriesExample(3);
// ScriptBuilderImpl builder = new ScriptBuilderImpl(api,ds, ds);
// ScriptOptionBuilder option1 = builder.addOption("Option 1", "Option 1");
// ScriptOptionBuilder option2 = option1.addOption("Option 2", "Option 2");
// option2.addDataAdapter("Adapter 1");
// option2.addDataAdapter("Adapter 2");
// ScriptOptionBuilder option3 = option2.addOption("Option 3", "Option 3");
//
// ScriptOptionBuilder option4 = option1.addOption("Option 4", "Option 4");
// option4.addCopyTable("hkkhj", "hkhkjkj", OutputType.COPY_ALL_TABLES,
// "hhjgjhgjh");
// option4.addCopyTable("ffhgfhg", "fhgfhgfhg", OutputType.COPY_ALL_TABLES,
// "hhjgjhgjh");
// option4.addComponentConfig("bcconfig", new BarchartComponent().getId(),
// new BarchartComponent().getConfigClass().newInstance());
// option4.addInstruction("hkkjk", new BarchartComponent().getId(),
// ODLComponent.MODE_DEFAULT);
// Script script = builder.build();
// ScriptEditorWizardGenerated editor = new
// ScriptEditorWizardGenerated(script, null, null, null);
// ODLInternalFrame.showInDummyDesktopPane(editor);
// }
protected List<SourcedDatastore> getDatastores() {
ODLDatastore<? extends ODLTableDefinition> external = runner != null ? runner.getDatastoreDefinition() : null;
if (currentPane == null) {
return ScriptFieldsParser.getSingleLevelDatastores(api, null, null, external);
} else {
return ScriptFieldsParser.getMultiLevelDatastores(api, script, currentPane.displayNode.option.getOptionId(), external);
}
}
// protected List<SourcedColumn> getScriptInternalFields() {
// if (currentPane == null) {
// return new ArrayList<>();
// }
//
// // get the fields available to the current node
// return ScriptFieldsParser.getMultiLevelColumns(api, script,
// currentPane.displayNode.option.getOptionId(), null);
// }
private void reinitTree(String selectOptionId) {
DisplayNode rootNode = new Splitter().splitIntoDisplayNodes(script, false);
DefaultTreeModel model = new DefaultTreeModel(rootNode);
tree.setModel(model);
if (selectOptionId != null) {
selectOption(selectOptionId, rootNode, tree);
}
}
private void initTreeActions() {
abstract class EditOption extends TreeAction {
EditOption(String name, String tooltip, String smallIconPng) {
super(name, tooltip, smallIconPng);
}
boolean hasOption() {
return currentPane != null && currentPane.displayNode != null && currentPane.displayNode.type == DisplayNodeType.OPTION;
}
@Override
public void updateEnabledState() {
setEnabled(hasOption());
}
Option option() {
if (hasOption()) {
return currentPane.displayNode.option;
}
return null;
}
int optionIndex() {
if (hasOption()) {
DisplayNode node = currentPane.displayNode;
if (node.parent != null && node.parent.option != null) {
return node.parent.option.getOptions().indexOf(option());
} else {
return 0;
}
}
return -1;
}
}
for(boolean mergeWithCurrent : new boolean[]{false,true}){
treeActions.add(new EditOption(!mergeWithCurrent?"Add option using component":"Merge component into option",
!mergeWithCurrent?"Add a new option using a component to the script below the currently selected option.":
"Merge the option created by a component with the currently selected option",
!mergeWithCurrent?"add-script-option.png":"add-component-to-current-option.png") {
@Override
public void actionPerformed(ActionEvent e) {
SetupComponentWizard wizard = new SetupComponentWizard(SwingUtilities.getWindowAncestor(ScriptEditorWizardGenerated.this), api, createAvailableOptionsQuery());
wizard.setMergeWithInputOption(mergeWithCurrent);
Option newOption = wizard.showModal(script, currentPane.displayNode.option);
if (newOption != null) {
reinitTree(mergeWithCurrent? currentPane.displayNode.option.getOptionId():newOption.getOptionId());
}
}
});
}
treeActions.add(new EditOption("Add empty option", "Add an empty option", "add-empty-script-option.png") {
@Override
public void actionPerformed(ActionEvent e) {
if (hasOption()) {
Option parent = option();
ScriptOption parentBuilder = ScriptOptionImpl.createWrapperHierarchy(api, script, parent.getOptionId(), null);
ScriptOption newOption = parentBuilder.addOption("New option", "New option");
reinitTree(newOption.getOptionId());
}
}
});
for (ScriptAdapterType type : new ScriptAdapterType[] { ScriptAdapterType.NORMAL, ScriptAdapterType.PARAMETER }) {
String name;
String icon;
String shortName;
switch (type) {
case NORMAL:
shortName = "adapter";
name = "Add data adapter";
icon = "script-element-data-adapter.png";
break;
case PARAMETER:
shortName = "parameter";
name = "Add a parameter";
icon = "parameter.png";
break;
default:
throw new IllegalArgumentException();
}
treeActions.add(new EditOption(name, name, icon) {
@Override
public void actionPerformed(ActionEvent e) {
if (hasOption()) {
Option parent = option();
String name = JOptionPane.showInputDialog(ScriptEditorWizardGenerated.this, "Enter new " + shortName + " name", shortName);
if (name != null) {
name = ScriptUtils.createUniqueDatastoreId(script, name);
AdapterConfig newAdapter = null;
switch (type) {
case NORMAL:
newAdapter = new AdapterConfig(name);
break;
case PARAMETER:
newAdapter = new ParametersImpl(api).createParameterAdapter(name);
break;
default:
break;
}
parent.getAdapters().add(newAdapter);
reinitTree(parent.getOptionId());
}
}
}
@Override
public void updateEnabledState() {
boolean enabled = currentPane != null && currentPane.displayNode != null && (currentPane.displayNode.type == DisplayNodeType.OPTION);
setEnabled(enabled);
}
});
}
treeActions.add(new EditOption("Copy data adapter / parameter", "Copy the selected data adapter or parameter to the clipboard.", "copy-data-adapter.png") {
@Override
public void actionPerformed(ActionEvent e) {
if (currentPane != null && currentPane.displayNode != null && currentPane.displayNode.adapter != null) {
// take a deep copy of the script
Script scriptCopy = ScriptIO.instance().deepCopy(script);
// save the deep copied adapter to the global data adapter
dataAdapterClipboard = ScriptUtils.getAdapterById(scriptCopy, currentPane.displayNode.adapter.getId(), true);
}
}
@Override
public void updateEnabledState() {
boolean enabled = currentPane != null && currentPane.displayNode != null
&& (currentPane.displayNode.type == DisplayNodeType.DATA_ADAPTER || currentPane.displayNode.type == DisplayNodeType.PARAMETER);
setEnabled(enabled);
}
});
treeActions.add(new EditOption("Paste data adapter / parameter", "Paste the data adapter from the clipboard into the option.", "paste-data-adapter.png") {
@Override
public void actionPerformed(ActionEvent e) {
if (currentPane != null && currentPane.displayNode != null && currentPane.displayNode.type == DisplayNodeType.OPTION && dataAdapterClipboard != null) {
Option parent = currentPane.displayNode.option;
// create a dummy script with just the data adapter
Script tmp = new Script();
tmp.getAdapters().add(dataAdapterClipboard);
// deep copy it to get a fresh copy of the data adapter
tmp = ScriptIO.instance().deepCopy(tmp);
AdapterConfig conf = tmp.getAdapters().get(0);
// ensure id is unique
String id = ScriptUtils.createUniqueDatastoreId(script, conf.getId());
conf.setId(id);
// add it to the parent option and reinit the display tree
parent.getAdapters().add(conf);
reinitTree(parent.getOptionId());
}
}
@Override
public void updateEnabledState() {
boolean enabled = currentPane != null && currentPane.displayNode != null && (currentPane.displayNode.type == DisplayNodeType.OPTION && dataAdapterClipboard != null);
setEnabled(enabled);
}
});
treeActions.add(new EditOption("Copy option", "Copy the selected option to the clipboard.", "script-option-copy-paste.png") {
@Override
public void actionPerformed(ActionEvent e) {
if (currentPane != null && currentPane.displayNode != null && currentPane.displayNode.type == DisplayNodeType.OPTION) {
// take a deep copy of the script
Script scriptCopy = ScriptIO.instance().deepCopy(script);
// save the deep copied adapter to the global clipboard
if(currentPane.displayNode.isRoot){
optionClipboard =scriptCopy;
}else if(currentPane.displayNode.option!=null){
optionClipboard = ScriptUtils.getOption(scriptCopy, currentPane.displayNode.option.getOptionId());
}
}
}
@Override
public void updateEnabledState() {
boolean enabled = currentPane != null && currentPane.displayNode != null
&& (currentPane.displayNode.type == DisplayNodeType.OPTION);
setEnabled(enabled);
}
@Override
public boolean addToToolbar(){
return false;
}
});
treeActions.add(new EditOption("Paste option", "Paste the option from the option clipboard into the selected option.", "script-option-copy-paste.png") {
@Override
public void actionPerformed(ActionEvent e) {
if (currentPane != null && currentPane.displayNode != null && currentPane.displayNode.type == DisplayNodeType.OPTION && optionClipboard != null) {
// deep copy the option
Script tmp = new Script();
tmp.getOptions().add(optionClipboard);
tmp = ScriptIO.instance().deepCopy(tmp);
Option option = tmp.getOptions().get(0);
// check for used adapters etc
ScriptIds current = ScriptUtils.getIds(api, script);
ScriptIds pasting = ScriptUtils.getIds(api, option);
StringBuilder errors = new StringBuilder();
class Helper{
void checkOverlap(Set<String> A, Set<String> B, String message){
Set<String> overlap = ScriptIds.getCommonStrings(api, A, B);
if(overlap.size()>0){
StringBuilder builder = new StringBuilder();
int lineLen=0;
for(String s: overlap){
if(builder.length()>0){
builder.append(", ");
lineLen+=2;
}
if(lineLen>100){
builder.append("\n");
lineLen=0;
}
builder.append(s);
lineLen +=s.length();
}
errors.append(message + builder.toString());
}
}
}
Helper helper = new Helper();
helper.checkOverlap(current.datastoresAndAdapters, pasting.datastoresAndAdapters,
"Cannot paste option as the following output datastore or adapter ids would no longer be unique:\n");
if(errors.length()==0){
helper.checkOverlap(current.options, pasting.options,
"Cannot paste option as the following option ids would no longer be unique:\n");
}
if(errors.length()>0){
JOptionPane.showMessageDialog(ScriptEditorWizardGenerated.this, errors.toString());
return;
}
// add it to the parent option and reinit the display tree
Option parent = currentPane.displayNode.option;
parent.getOptions().add(option);
reinitTree(parent.getOptionId());
}
}
@Override
public void updateEnabledState() {
boolean enabled = currentPane != null && currentPane.displayNode != null && (currentPane.displayNode.type == DisplayNodeType.OPTION && optionClipboard != null);
setEnabled(enabled);
}
@Override
public boolean addToToolbar(){
return false;
}
});
treeActions.add(new EditOption("Rename", "Rename the selected item.", "script-rename.png") {
@Override
public void actionPerformed(ActionEvent e) {
Option option = currentPane != null && currentPane.displayNode != null ? currentPane.displayNode.option : null;
if (option != null) {
boolean modified = false;
DisplayNode node = currentPane.displayNode;
String current = null;
if (node.type == DisplayNodeType.OPTION) {
current = node.option.getName();
} else {
current = node.adapter.getId();
}
String newValue = JOptionPane.showInputDialog(ScriptEditorWizardGenerated.this, "Enter new name", current);
if (newValue != null) {
if (node.type == DisplayNodeType.OPTION) {
option.setName(newValue);
modified = true;
} else {
// if the standardised version of the value is
// changing, ensure its unique
if (Strings.equals(current, newValue)) {
newValue = ScriptUtils.createUniqueDatastoreId(script, newValue);
}
node.adapter.setId(newValue);
modified = true;
}
}
if (modified) {
reinitTree(option.getOptionId());
}
// String newValue =
// JOptionPane.showInputDialog(ScriptEditorWizardGenerated.this,
// "Enter new name name", option.getName());
// if(newValue!=null){
// option.setName(newValue);
// reinitTree(option.getOptionId());
// }
}
}
@Override
public void updateEnabledState() {
boolean enabled = currentPane != null && currentPane.displayNode != null && (currentPane.displayNode.type == DisplayNodeType.OPTION
|| currentPane.displayNode.type == DisplayNodeType.DATA_ADAPTER || currentPane.displayNode.type == DisplayNodeType.PARAMETER);
setEnabled(enabled);
}
});
treeActions.add(new EditOption("Move up", "Move the selected item up.", "script-option-up.png") {
@Override
public void actionPerformed(ActionEvent e) {
if (calcEnabled()) {
Option option = option();
if (currentPane.displayNode.type == DisplayNodeType.OPTION) {
int index = optionIndex();
if (option != null && index >= 1) {
moveUpList(currentPane.displayNode.parent.option.getOptions(), index);
}
}
else if(currentPane.displayNode.type == DisplayNodeType.INSTRUCTION){
option = currentPane.displayNode.option;
int index = option.getInstructions().indexOf(currentPane.displayNode.instruction);
moveUpList(option.getInstructions(), index);
}
else {
// must be adapter
option = currentPane.displayNode.option;
int index = option.getAdapters().indexOf(currentPane.displayNode.adapter);
moveUpList(option.getAdapters(), index);
}
reinitTree(option.getOptionId());
}
// Option option = option();
// int index = optionIndex();
// if(option!=null && index>=1){
// Option parent = currentPane.displayNode.parent.option;
// parent.getOptions().remove(index);
// parent.getOptions().add(index-1, option);
// }
}
private <T> void moveUpList(List<T> list, int index) {
if (index >= 1) {
T item = list.get(index);
list.remove(index);
list.add(index - 1, item);
}
}
@Override
public void updateEnabledState() {
setEnabled(calcEnabled());
}
private boolean calcEnabled() {
boolean enabled = currentPane != null && currentPane.displayNode != null;
if (enabled && currentPane.displayNode.type == DisplayNodeType.OPTION) {
enabled = optionIndex() >= 1;
} else if (enabled && (currentPane.displayNode.type == DisplayNodeType.DATA_ADAPTER || currentPane.displayNode.type == DisplayNodeType.PARAMETER)
&& currentPane.displayNode.adapter != null) {
enabled = currentPane.displayNode.option.getAdapters().indexOf(currentPane.displayNode.adapter) >= 1;
} else if (enabled && (currentPane.displayNode.type == DisplayNodeType.INSTRUCTION )
&& currentPane.displayNode.instruction != null) {
enabled = currentPane.displayNode.option.getInstructions().indexOf(currentPane.displayNode.instruction) >= 1;
}
else {
enabled = false;
}
return enabled;
}
});
treeActions.add(new EditOption("Move down", "Move the selected item down.", "script-option-down.png") {
@Override
public void actionPerformed(ActionEvent e) {
if (!isAllowed()) {
return;
}
Option option = option();
if (currentPane.displayNode.type == DisplayNodeType.OPTION) {
int index = optionIndex();
Option parent = currentPane.displayNode.parent.option;
parent.getOptions().remove(index);
parent.getOptions().add(index + 1, option);
}
else if(currentPane.displayNode.type ==DisplayNodeType.INSTRUCTION){
option = currentPane.displayNode.option;
int index = option.getInstructions().indexOf(currentPane.displayNode.instruction);
option.getInstructions().remove(index);
option.getInstructions().add(index + 1, currentPane.displayNode.instruction);
}
else {
// must be adapter
option = currentPane.displayNode.option;
List<AdapterConfig> adapters = option.getAdapters();
int index = adapters.indexOf(currentPane.displayNode.adapter);
adapters.remove(index);
adapters.add(index + 1, currentPane.displayNode.adapter);
}
reinitTree(option.getOptionId());
}
@Override
public void updateEnabledState() {
setEnabled(isAllowed());
}
/**
* @return
*/
protected boolean isAllowed() {
boolean enabled = currentPane != null && currentPane.displayNode != null;
DisplayNodeType type = enabled ? currentPane.displayNode.type : null;
if (enabled && type == DisplayNodeType.OPTION) {
int index = optionIndex();
if (index != -1 && currentPane.displayNode.parent != null) {
Option parent = currentPane.displayNode.parent.option;
enabled = parent != null && index < (parent.getOptions().size() - 1);
}
}
else if (enabled && (type == DisplayNodeType.INSTRUCTION ) && currentPane.displayNode.instruction != null) {
enabled = currentPane.displayNode.option.getInstructions().indexOf(currentPane.displayNode.instruction) < currentPane.displayNode.option.getInstructions().size() - 1;
}
else if (enabled && (type == DisplayNodeType.DATA_ADAPTER || type == DisplayNodeType.PARAMETER) && currentPane.displayNode.adapter != null) {
enabled = currentPane.displayNode.option.getAdapters().indexOf(currentPane.displayNode.adapter) < currentPane.displayNode.option.getAdapters().size() - 1;
} else {
enabled = false;
}
return enabled;
// boolean enabled=false;
// int index = optionIndex();
// if(index!=-1 && currentPane.displayNode.parent!=null){
// Option parent = currentPane.displayNode.parent.option;
// enabled = parent!=null && index <
// (parent.getOptions().size()-1);
// }
// return enabled;
}
});
treeActions.add(new EditOption("Move option to different parent", "Move option to different parent option.", "change-script-option-parent.png") {
@Override
public void actionPerformed(ActionEvent e) {
final Option movingOption = option();
if (movingOption == null) {
return;
}
class OptionContainer {
Option option;
int depth;
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < depth; i++) {
builder.append(" ");
}
if (depth > 0) {
builder.append("- ");
}
builder.append(option.getName());
return builder.toString();
}
}
final ArrayList<OptionContainer> containers = new ArrayList<>();
class MyVisitor implements OptionVisitor {
Option movingOptionParent;
@Override
public boolean visitOption(Option parent, Option currentOption, int depth) {
if (currentOption == movingOption) {
movingOptionParent = parent;
// do not add moving option or children of the
// moving option
return false;
}
OptionContainer container = new OptionContainer();
container.option = currentOption;
container.depth = depth;
containers.add(container);
return true;
}
}
MyVisitor visitor = new MyVisitor();
ScriptUtils.visitOptions(script, visitor);
// ensure we have a parent for the option
if (visitor.movingOptionParent == null) {
return;
}
// create a list of all valid options and select the current
// parent
JList<OptionContainer> list = new JList<>(containers.toArray(new OptionContainer[containers.size()]));
for (int i = 0; i < list.getModel().getSize(); i++) {
if (list.getModel().getElementAt(i).option == visitor.movingOptionParent) {
list.setSelectedIndex(i);
}
}
// show the dialog for selecting the new parent
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
panel.add(new JScrollPane(list));
ModalDialog dlg = new ModalDialog(SwingUtilities.getWindowAncestor(ScriptEditorWizardGenerated.this), panel, "Select new parent option", ModalDialogResult.OK,
ModalDialogResult.CANCEL);
dlg.setMinimumSize(new Dimension(300, 200));
if (dlg.showModal() == ModalDialogResult.OK && list.getSelectedValue() != null) {
// move it!
OptionContainer newParent = list.getSelectedValue();
visitor.movingOptionParent.getOptions().remove(movingOption);
newParent.option.getOptions().add(movingOption);
reinitTree(movingOption.getOptionId());
}
}
@Override
public void updateEnabledState() {
setEnabled(hasOption() && option() != script);
}
});
treeActions.add(new EditOption("Delete item", "Delete the selected item.", "delete-script-option.png") {
@Override
public void actionPerformed(ActionEvent e) {
if (JOptionPane.showConfirmDialog(ScriptEditorWizardGenerated.this, "Are you sure you want to delete this item? (This cannot be undone)", "Confirm",
JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
ScriptUtils.visitOptions(script, new OptionVisitor() {
@Override
public boolean visitOption(Option parent, Option option, int depth) {
DisplayNode node = currentPane.displayNode;
List<? extends Object> toDelete = null;
List<? extends Object> deleteFrom = null;
switch (node.type) {
case OPTION:
toDelete = Arrays.asList(node.option);
deleteFrom = option.getOptions();
break;
case INSTRUCTION:
toDelete = Arrays.asList(node.instruction);
deleteFrom = option.getInstructions();
break;
case COPY_TABLES:
toDelete = node.outputs;
deleteFrom = option.getOutputs();
break;
case DATA_ADAPTER:
case PARAMETER:
toDelete = Arrays.asList(node.adapter);
deleteFrom = option.getAdapters();
break;
case COMPONENT_CONFIGURATION:
toDelete = Arrays.asList(node.componentConfig);
deleteFrom = option.getComponentConfigs();
break;
default:
break;
}
if (deleteFrom != null && toDelete != null) {
for (Object o : toDelete) {
deleteFrom.remove(o);
}
}
return true;
}
});
reinitTree(null);
}
}
@Override
public void updateEnabledState() {
boolean enabled = currentPane != null && currentPane.displayNode != null && currentPane.displayNode.type != DisplayNodeType.AVAILABLE_TABLES && !currentPane.displayNode.isRoot;
setEnabled(enabled);
}
});
}
/**
* Completely rebuild the active panel
*/
private void rebuildActivePanel() {
TreePath path = tree.getSelectionPath();
if (path != null && path.getLastPathComponent() != null) {
// replace panel
DisplayNode node = (DisplayNode) path.getLastPathComponent();
currentPane = node.createPane(true);
splitPane.setRightComponent(currentPane);
Dimension dim = getSize();
int dividerLoc = splitPane.getDividerLocation();
pack();
if (dim != null) {
setSize(dim);
}
splitPane.setDividerLocation(dividerLoc);
} else {
currentPane = null;
splitPane.setRightComponent(new JPanel());
}
reinitialiseToolbar();
updateAppearance();
}
}