/**
* OrbisGIS is a java GIS application dedicated to research in GIScience.
* OrbisGIS is developed by the GIS group of the DECIDE team of the
* Lab-STICC CNRS laboratory, see <http://www.lab-sticc.fr/>.
*
* The GIS group of the DECIDE team is located at :
*
* Laboratoire Lab-STICC – CNRS UMR 6285
* Equipe DECIDE
* UNIVERSITÉ DE BRETAGNE-SUD
* Institut Universitaire de Technologie de Vannes
* 8, Rue Montaigne - BP 561 56017 Vannes Cedex
*
* OrbisGIS is distributed under GPL 3 license.
*
* Copyright (C) 2007-2014 CNRS (IRSTV FR CNRS 2488)
* Copyright (C) 2015-2017 CNRS (Lab-STICC UMR CNRS 6285)
*
* This file is part of OrbisGIS.
*
* OrbisGIS is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* OrbisGIS is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* OrbisGIS. If not, see <http://www.gnu.org/licenses/>.
*
* For more information, please consult: <http://www.orbisgis.org/>
* or contact directly:
* info_at_ orbisgis.org
*/
package org.orbisgis.toolboxeditor.editor.process;
import net.miginfocom.swing.MigLayout;
import net.opengis.wps._2_0.*;
import org.orbisgis.sif.components.actions.ActionCommands;
import org.orbisgis.sif.components.actions.ActionDockingListener;
import org.orbisgis.sif.components.actions.DefaultAction;
import org.orbisgis.sif.docking.DockingLocation;
import org.orbisgis.sif.docking.DockingPanelParameters;
import org.orbisgis.sif.edition.EditableElement;
import org.orbisgis.sif.edition.EditorDockable;
import org.orbiswps.client.api.utils.ProcessExecutionType;
import org.orbisgis.toolboxeditor.WpsClientImpl;
import org.orbisgis.toolboxeditor.dataui.DataUI;
import org.orbisgis.toolboxeditor.dataui.DataUIManager;
import org.orbisgis.toolboxeditor.utils.Job;
import org.orbisgis.toolboxeditor.utils.ToolBoxIcon;
import org.orbiswps.server.execution.ProcessExecutionListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xnap.commons.i18n.I18n;
import org.xnap.commons.i18n.I18nFactory;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.EventHandler;
import java.math.BigInteger;
import java.net.URI;
import java.util.*;
import java.util.List;
import static org.orbiswps.client.api.utils.ProcessExecutionType.BASH;
import static org.orbiswps.client.api.utils.ProcessExecutionType.STANDARD;
/**
* UI for the configuration and the run of a WPS process.
* It extends the EditorDockable interface to be able to be docked in OrbisGIS.
*
* @author Sylvain PALOMINOS
*/
public class ProcessEditor extends JPanel implements EditorDockable {
/** Uni increment for the scrollbar (speed of the toolbar). */
private static final int SCROLLBAR_UNIT_INCREMENT = 16;
/** Properties used to pass information thanks to the swing components. */
public static final String PROCESS_PROPERTY = "PROGRESS_PROPERTY";
public static final String PANEL_PROPERTY = "PANEL_PROPERTY";
public static final String SCROLLPANE_PROPERTY = "SCROLLPANE_PROPERTY";
public static final String ACTION_TOGGLE_MODE = "ACTION_TOGGLE_MODE";
/** Name of the EditorDockable. */
public static final String NAME = "PROCESS_EDITOR";
/** I18N object */
private static final I18n I18N = I18nFactory.getI18n(ProcessEditor.class);
/** Logger object */
private static final Logger LOGGER = LoggerFactory.getLogger(ProcessEditor.class);
/** Editable element of this editor. */
private ProcessEditableElement processEditableElement;
/** OrbisGIS Wps client. */
private WpsClientImpl wpsClient;
/** Docking parameters. */
private DockingPanelParameters dockingPanelParameters = new DockingPanelParameters();;
/** DataUIManager used to create the UI corresponding the the data */
private DataUIManager dataUIManager;
/** Error label displayed when the process inputs and output are all defined. */
private JLabel errorMessage;
/** Execution mode of the processes. It can be BASH or STANDARD. */
private ProcessExecutionType mode;
/** Action changing the process execution mode from BASH to STANDARD or STANDARD to BASH. */
private DefaultAction toggleModeAction;
/** Map of the data that will be used to execute the processes.
* In the STANDARD mode, the map contains entry with the input/output URI as key, and the configured value as value.
* In the BASH mode, the map contains a string value as key and a map as value. The map value contains the
* input/outputs URI and the values.
*/
private Map<URI, Object> dataMap;
/**
* Main constructor of the ProcessEditor.
* @param wpsClient OrbisGIS Wps client.
* @param processEditableElement Editable element of this editor.
*/
public ProcessEditor(WpsClientImpl wpsClient, ProcessEditableElement processEditableElement){
if(processEditableElement.getProcessOffering(wpsClient) == null){
LOGGER.error(I18N.tr("Unable to load the ProcessEditor {0}.", processEditableElement.getProcessURI()));
return;
}
this.setLayout(new BorderLayout());
//Sets the attributes
this.wpsClient = wpsClient;
this.processEditableElement = processEditableElement;
this.dataMap = new HashMap<>();
this.dataUIManager = wpsClient.getDataUIManager();
//Sets the docking panel parameters
dockingPanelParameters.setName(NAME+"_"+processEditableElement.getProcess().getTitle().get(0).getValue());
dockingPanelParameters.setTitleIcon(ToolBoxIcon.getIcon(ToolBoxIcon.PROCESS));
dockingPanelParameters.setDefaultDockingLocation(
new DockingLocation(DockingLocation.Location.STACKED_ON, WpsClientImpl.TOOLBOX_REFERENCE));
dockingPanelParameters.setTitle(processEditableElement.getProcess().getTitle().get(0).getValue());
//Sets the actions
ActionCommands dockingActions = new ActionCommands();
dockingPanelParameters.setDockActions(dockingActions.getActions());
dockingActions.addPropertyChangeListener(new ActionDockingListener(dockingPanelParameters));
DefaultAction runAction = new DefaultAction("ACTION_RUN",
I18N.tr("Run the script"),
I18N.tr("Run the script"),
ToolBoxIcon.getIcon(ToolBoxIcon.EXECUTE),
EventHandler.create(ActionListener.class, this, "runProcess"),
null);
dockingActions.addAction(runAction);
toggleModeAction = new DefaultAction(ACTION_TOGGLE_MODE,
I18N.tr("Toggle execution mode."),
I18N.tr("Uses the bash mode."),
ToolBoxIcon.getIcon(ToolBoxIcon.TOGGLE_MODE),
EventHandler.create(ActionListener.class, this, "toggleMode"),
null);
dockingActions.addAction(toggleModeAction);
//Sets the execution type of the process and build the UI
switch(processEditableElement.getProcessExecutionType()){
case STANDARD:
this.add(buildSimpleUI(), BorderLayout.CENTER);
break;
case BASH:
this.add(buildBashUI(), BorderLayout.CENTER);
}
mode = processEditableElement.getProcessExecutionType();
this.revalidate();
}
/**
* Changes the process execution mode from STANDARD to BASH or from BASH to STANDARD and rebuild the UI.
*/
public void toggleMode(){
switch(mode) {
case BASH:
toggleModeAction.setToolTipText(I18N.tr("Uses the simple mode."));
mode = STANDARD;
this.removeAll();
this.add(buildSimpleUI(), BorderLayout.CENTER);
processEditableElement.resetDataMap();
this.revalidate();
break;
case STANDARD:
toggleModeAction.setToolTipText(I18N.tr("Uses the bash mode."));
mode = BASH;
this.removeAll();
this.add(buildBashUI(), BorderLayout.CENTER);
processEditableElement.resetDataMap();
this.revalidate();
break;
}
}
@Override
public DockingPanelParameters getDockingParameters() {
return dockingPanelParameters;
}
@Override
public JComponent getComponent() {
return this;
}
@Override
public boolean match(EditableElement editableElement) {
//Return true if the editable is the one contained by the Process editor
return editableElement instanceof ProcessEditableElement && editableElement.getId().equals(processEditableElement.getId());
}
@Override
public EditableElement getEditableElement() {
return processEditableElement;
}
@Override
public void setEditableElement(EditableElement editableElement) {
this.processEditableElement = (ProcessEditableElement)editableElement;
dockingPanelParameters.setTitle(processEditableElement.getProcess().getTitle().get(0).getValue());
}
/**
* Run the process if all the mandatory process inputs are defined.
*/
public void runProcess(){
URI uri = URI.create(processEditableElement.getProcess().getIdentifier().getValue());
switch(mode) {
case STANDARD:
//First check if all the inputs are defined.
boolean allDefined = true;
for (InputDescriptionType input : processEditableElement.getProcess().getInput()) {
URI identifier = URI.create(input.getIdentifier().getValue());
if (!input.getMinOccurs().equals(new BigInteger("0")) && !dataMap.containsKey(identifier)) {
allDefined = false;
}
}
if (allDefined) {
//Launch the process execution and build the Job object with the status info object.
StatusInfo statusInfo = wpsClient.executeProcess(uri, dataMap);
Job job = new Job(UUID.fromString(statusInfo.getJobID()), processEditableElement.getProcess());
//Ask the client to validate the execution by closing the editor an saving the Job.
wpsClient.validateInstance(this, job);
//Sets the job. It should be don after the client validation to be sure that the job has
// the listener add.
job.setStartTime(System.currentTimeMillis());
job.setStatus(statusInfo);
job.setProcessState(ProcessExecutionListener.ProcessState.IDLE);
//Dirty way to close the ProcessEditor
try {
Robot robot = new Robot();
robot.keyPress(KeyEvent.VK_CONTROL);
robot.keyPress(KeyEvent.VK_F4);
robot.delay(100);
robot.keyRelease(KeyEvent.VK_CONTROL);
robot.keyRelease(KeyEvent.VK_F4);
} catch (AWTException e) {
e.printStackTrace();
}
} else {
errorMessage.setText("Please, configure all the inputs/outputs before executing.");
}
break;
case BASH:
//As the execution mode is BASH, the map contains all the maps for each process instance.
// So a process is executed for each map.
for(Map.Entry<URI, Object> entry : dataMap.entrySet()) {
Map<URI, Object> map = null;
if(entry.getValue() instanceof Map) {
map = (Map<URI, Object>) entry.getValue();
}
if(map != null) {
//First check if all the inputs are defined.
allDefined = true;
for (InputDescriptionType input : processEditableElement.getProcess().getInput()) {
URI identifier = URI.create(input.getIdentifier().getValue());
if (!input.getMinOccurs().equals(new BigInteger("0")) && !map.containsKey(identifier)) {
allDefined = false;
}
}
if (allDefined) {
//Launch the process execution and build the Job object with the status info object.
StatusInfo statusInfo = wpsClient.executeProcess(uri, map);
Job job = new Job(UUID.fromString(statusInfo.getJobID()), processEditableElement.getProcess());
//Ask the client to validate the execution by closing the editor an saving the Job.
wpsClient.validateInstance(this, job);
//Sets the job. It should be don after the client validation to be sure that the job has
// the listener add.
job.setStartTime(System.currentTimeMillis());
job.setStatus(statusInfo);
job.setProcessState(ProcessExecutionListener.ProcessState.IDLE);
//Dirty way to close the ProcessEditor
try {
Robot robot = new Robot();
robot.keyPress(KeyEvent.VK_CONTROL);
robot.keyPress(KeyEvent.VK_F4);
robot.delay(100);
robot.keyRelease(KeyEvent.VK_CONTROL);
robot.keyRelease(KeyEvent.VK_F4);
} catch (AWTException e) {
e.printStackTrace();
}
} else {
errorMessage.setText("Please, configure all the inputs/outputs before executing.");
}
}
}
break;
}
}
/**
* Build the UI of the given process according to the given data.
* The UI is composed of a top panel with the process information and a bottom one with the swing component for the
* data configuration. The bottom panel is created with the DataUI class declared in the DataUIManager.
* @return The UI for the configuration of the process.
*/
private JComponent buildSimpleUI(){
ProcessDescriptionType process = processEditableElement.getProcess();
//Build the DataMap that will contains the process data
dataMap = new HashMap<>();
dataMap.putAll(processEditableElement.getDataMap());
JPanel returnPanel = new JPanel(new MigLayout("fill"));
//Build the panel containing the process information
AbstractScrollPane processPanel = new AbstractScrollPane();
processPanel.setLayout(new MigLayout("fill, ins 0, gap 0"));
//The abstract label
JLabel label = new JLabel("<html>"+process.getAbstract().get(0).getValue()+"</html>");
label.setFont(label.getFont().deriveFont(Font.ITALIC));
processPanel.add(label, "growx, span");
//The process version
String versionStr = I18N.tr("Version : ");
if(processEditableElement.getProcessOffering(wpsClient).getProcessVersion().isEmpty()){
versionStr += I18N.tr("unknown");
}
else{
versionStr += processEditableElement.getProcessOffering(wpsClient).getProcessVersion();
}
JLabel version = new JLabel(versionStr);
version.setFont(version.getFont().deriveFont(Font.ITALIC));
processPanel.add(version, "growx, span");
//Build the border panel containing a scrollpane with the process information
JPanel borderProcessPanel = new JPanel(new MigLayout("fill, ins 0, gap 0"));
borderProcessPanel.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(Color.DARK_GRAY), I18N.tr("Description")));
borderProcessPanel.add(new JScrollPane(processPanel), "growx");
returnPanel.add(borderProcessPanel, "wrap, growx, height ::30%");
//Build the panel with all the swing components for the data configuration.
boolean noParameters = true;
JPanel parameterPanel = new JPanel(new MigLayout("fill"));
JScrollPane scrollPane = new JScrollPane(parameterPanel);
//For each input, generate the UI and add it
for(InputDescriptionType i : process.getInput()){
DataUI dataUI = dataUIManager.getDataUI(i.getDataDescription().getValue().getClass());
if(dataUI!=null) {
//Retrieve the component containing all the UI components. The data map is passed to the DataUI to
// allow it to put the data inside
JComponent uiComponent = dataUI.createUI(i, dataMap, DataUI.Orientation.VERTICAL);
if(uiComponent != null) {
noParameters = false;
//If the input is optional, hide it
if(i.getMinOccurs().equals(new BigInteger("0"))) {
uiComponent.setVisible(false);
//This panel is the one which contains the header with the title of the input and
// the hide/show button
JPanel contentPanel = new JPanel(new MigLayout("fill, ins 0, gap 0"));
JPanel hideShowPanel = new JPanel(new MigLayout("ins 0, gap 0"));
//Sets the button to make it shown as just an icon
JButton showButton = new JButton(ToolBoxIcon.getIcon(ToolBoxIcon.BTNRIGHT));
showButton.setBorderPainted(false);
showButton.setMargin(new Insets(0, 0, 0, 0));
showButton.setContentAreaFilled(false);
showButton.setOpaque(false);
showButton.setFocusable(false);
showButton.putClientProperty("upPanel", hideShowPanel);
showButton.addMouseListener(EventHandler.create(MouseListener.class,
this, "onClickButton", "source", "mouseClicked"));
hideShowPanel.add(showButton);
hideShowPanel.add(new JLabel(i.getTitle().get(0).getValue() + " (optional)"), "growx, span");
hideShowPanel.setToolTipText("Hide/Show option");
hideShowPanel.putClientProperty("body", uiComponent);
hideShowPanel.putClientProperty("parent", contentPanel);
hideShowPanel.putClientProperty("button", showButton);
hideShowPanel.putClientProperty("scrollPane", scrollPane);
hideShowPanel.addMouseListener(EventHandler.create(MouseListener.class,
this, "onClickHeader", "source", "mouseClicked"));
contentPanel.add(hideShowPanel, "growx, span");
parameterPanel.add(contentPanel, "growx, span");
}
else{
parameterPanel.add(new JLabel(i.getTitle().get(0).getValue()), "growx, span");
parameterPanel.add(uiComponent, "growx, span");
}
parameterPanel.add(new JSeparator(), "growx, span");
}
}
}
//For each input, generate the UI and add it
for(OutputDescriptionType o : process.getOutput()){
DataUI dataUI = dataUIManager.getDataUI(o.getDataDescription().getValue().getClass());
if(dataUI!=null) {
JComponent component = dataUI.createUI(o, dataMap, DataUI.Orientation.VERTICAL);
if(component != null) {
noParameters = false;
parameterPanel.add(new JLabel(o.getTitle().get(0).getValue()), "growx, span");
parameterPanel.add(component, "growx, span");
parameterPanel.add(new JSeparator(), "growx, span");
}
}
}
//If there is input or output to display, adds the inside a JScrollPane
if(!noParameters) {
errorMessage = new JLabel();
errorMessage.setForeground(Color.RED);
parameterPanel.add(errorMessage, "growx, wrap");
scrollPane.getVerticalScrollBar().setUnitIncrement(SCROLLBAR_UNIT_INCREMENT);
scrollPane.getHorizontalScrollBar().setUnitIncrement(SCROLLBAR_UNIT_INCREMENT);
JPanel borderParameterPanel = new JPanel(new MigLayout("fill, ins 0, gap 0"));
borderParameterPanel.setBorder(BorderFactory.createTitledBorder(I18N.tr("Parameter(s)")));
borderParameterPanel.add(scrollPane, "growx");
returnPanel.add(borderParameterPanel, "wrap, growx, height ::70%");
}
return returnPanel;
}
/**
* Build the UI of the given process according to the given data.
* As the UI is in BASH mode, it is displayed by row, one row corresponding to a process instance.
* The input/output title are displayed on the first row and then the swing component are displayed row by row.
* On the start of each row, a button is used to remove the row.
* At the end of the UI, an other button add a new row.
* @return The UI for the configuration of the process.
*/
private JComponent buildBashUI(){
//Gets process basic information : process, uri and data map
URI uri = URI.create(processEditableElement.getProcess().getIdentifier().getValue());
ProcessDescriptionType process = wpsClient.getProcessCopy(uri);
dataMap = new HashMap<>();
JPanel returnPanel = new JPanel(new MigLayout("fill"));
//Build the first part of the UI containing the process information
AbstractScrollPane processPanel = new AbstractScrollPane();
processPanel.setLayout(new MigLayout("fill, ins 0, gap 0"));
//The process title
JLabel label = new JLabel("<html>"+process.getAbstract().get(0).getValue()+"</html>");
label.setFont(label.getFont().deriveFont(Font.ITALIC));
processPanel.add(label, "growx, span");
//The process version
String versionStr = I18N.tr("Version : ");
if(processEditableElement.getProcessOffering(wpsClient).getProcessVersion().isEmpty()){
versionStr += I18N.tr("unknown");
}
else{
versionStr += processEditableElement.getProcessOffering(wpsClient).getProcessVersion();
}
JLabel version = new JLabel(versionStr);
version.setFont(version.getFont().deriveFont(Font.ITALIC));
processPanel.add(version, "growx, span");
//The container panel
JPanel borderProcessPanel = new JPanel(new MigLayout("fill, ins 0, gap 0"));
borderProcessPanel.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(Color.DARK_GRAY), I18N.tr("Description")));
borderProcessPanel.add(new JScrollPane(processPanel), "growx");
returnPanel.add(borderProcessPanel, "wrap, growx, height ::30%");
JPanel panel = new JPanel(new MigLayout("fill"));
JScrollPane scrollPane = new JScrollPane(panel);
//Creates the panel that will contains all the inputs.
JPanel borderParameterPanel = new JPanel(new MigLayout("fill, ins 0, gap 0"));
borderParameterPanel.setBorder(BorderFactory.createTitledBorder(I18N.tr("Parameter(s)")));
JPanel parameterPanel = new JPanel(new MigLayout("fill, ins 5"));
//Gets the list of input/output title if they have a displayed component
List<DescriptionType> descriptionTypeList = new ArrayList<>();
for(OutputDescriptionType o : process.getOutput()){
if(dataUIManager.getDataUI(o.getDataDescription().getValue().getClass()) != null) {
descriptionTypeList.add(o);
}
}
for(InputDescriptionType i : process.getInput()){
if(dataUIManager.getDataUI(i.getDataDescription().getValue().getClass()) != null) {
descriptionTypeList.add(i);
}
}
//Add an empty label because of the miglayout : the first component of a row will be the remove button
parameterPanel.add(new JLabel());
//Adds all the input/output titles in the first row
for(int i = 0; i < descriptionTypeList.size(); i++){
String migOption = "growx";
if(i == descriptionTypeList.size()-1){
if(!migOption.isEmpty()){
migOption += ", ";
}
migOption += "wrap";
}
DescriptionType descriptionType = descriptionTypeList.get(i);
DataUI dataUI = null;
if(descriptionType instanceof InputDescriptionType) {
dataUI = dataUIManager.getDataUI(
((InputDescriptionType)descriptionType).getDataDescription().getValue().getClass());
}
if(descriptionType instanceof OutputDescriptionType) {
dataUI = dataUIManager.getDataUI(
((OutputDescriptionType)descriptionType).getDataDescription().getValue().getClass());
}
if(dataUI!=null) {
//Retrieve the component containing all the UI components.
JComponent uiComponent = dataUI.createUI(descriptionType,new HashMap<URI, Object>(), DataUI.Orientation.HORIZONTAL);
if(uiComponent != null) {
parameterPanel.add(new JLabel(descriptionType.getTitle().get(0).getValue()), migOption);
}
}
}
parameterPanel.add(new JSeparator(), "growx, span");
boolean isRowAdd = false;
//Adds a first component row if the default data map is empty
if(processEditableElement.getDataMap() == null || processEditableElement.getDataMap().size() == 0) {
onAddBashRow(wpsClient.getProcessCopy(uri), parameterPanel, new HashMap<URI, Object>());
isRowAdd = true;
}
//If the default data map contains maps of data, adds a row for each map
else{
for(Map.Entry<URI, Object> entry : processEditableElement.getDataMap().entrySet()){
if(entry.getValue() instanceof Map) {
onAddBashRow(wpsClient.getProcessCopy(uri), parameterPanel, (Map<URI, Object>)entry.getValue());
isRowAdd = true;
}
}
}
if(!isRowAdd){
onAddBashRow(wpsClient.getProcessCopy(uri), parameterPanel, processEditableElement.getDataMap());
}
//Adds the add new row button at the very en of the UI
JButton addButton = new JButton(ToolBoxIcon.getIcon(ToolBoxIcon.ADD));
addButton.putClientProperty(PROCESS_PROPERTY, process);
addButton.putClientProperty(PANEL_PROPERTY, parameterPanel);
addButton.putClientProperty(SCROLLPANE_PROPERTY, scrollPane);
addButton.addActionListener(EventHandler.create(ActionListener.class, this, "onAddBashRow", "source"));
addButton.setBorderPainted(false);
addButton.setContentAreaFilled(false);
parameterPanel.add(addButton, "wrap");
//Add the panel in a scroll pane with the error message used when all the field are not configured on running
panel.add(parameterPanel, "growx, span");
errorMessage = new JLabel();
errorMessage.setForeground(Color.RED);
panel.add(errorMessage, "growx, wrap");
scrollPane.getVerticalScrollBar().setUnitIncrement(SCROLLBAR_UNIT_INCREMENT);
scrollPane.getHorizontalScrollBar().setUnitIncrement(SCROLLBAR_UNIT_INCREMENT);
borderParameterPanel.add(scrollPane, "span, growx");
returnPanel.add(borderParameterPanel, "wrap, growx, height ::70%");
return returnPanel;
}
/**
* Method called on clicking on the add new row button.
* Its adds a new row of swing components for running one more process instance.
*
* @param source Source of the click
*/
public void onAddBashRow(Object source){
if(source instanceof JButton){
//First test if the JButton contains all the properties needed for the UI generation
JButton addButton = (JButton) source;
ProcessDescriptionType process;
if(addButton.getClientProperty(PROCESS_PROPERTY) instanceof ProcessDescriptionType){
process = (ProcessDescriptionType) addButton.getClientProperty(PROCESS_PROPERTY);
}
else{
LOGGER.warn(I18N.tr("Unable to add a new wps bash row. The property process is invalid."));
return;
}
JPanel parameterPanel;
if(addButton.getClientProperty(PANEL_PROPERTY) instanceof JPanel){
parameterPanel = (JPanel) addButton.getClientProperty(PANEL_PROPERTY);
}
else{
LOGGER.warn(I18N.tr("Unable to add a new wps bash row. The property parameterPanel is invalid."));
return;
}
JScrollPane scrollPane;
if(addButton.getClientProperty(SCROLLPANE_PROPERTY) instanceof JScrollPane){
scrollPane = (JScrollPane) addButton.getClientProperty(SCROLLPANE_PROPERTY);
}
else{
LOGGER.warn(I18N.tr("Unable to add a new wps bash row. The property scrollPane is invalid."));
return;
}
parameterPanel.remove(addButton);
ProcessDescriptionType processCopy =
wpsClient.getProcessCopy(URI.create(process.getIdentifier().getValue()));
if(processCopy != null) {
onAddBashRow(processCopy, parameterPanel, null);
}
else{
LOGGER.error(I18N.tr("Unable to get a copy of the process {0}.", process.getTitle().get(0).getValue()));
}
parameterPanel.add(addButton, "wrap");
scrollPane.validate();
scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getMaximum());
}
}
/**
* Adds to the parameterPanel a new row of swing components for the configuration of the data for the process
* execution with the given default data map.
*
* @param process Process to execute, used to get the swing components with the DataUIManager class.
* @param parameterPanel Panel where the row of components should be add.
* @param defaultDataMap Map of the default data to use.
*/
private void onAddBashRow(ProcessDescriptionType process, JPanel parameterPanel, Map<URI, Object> defaultDataMap){
//Generate the data map to use and add it to the map containing the final data.
// The final map will be used on execution the processes.
HashMap<URI, Object> map = new HashMap<>();
map.putAll(processEditableElement.getDataMap());
if(defaultDataMap != null) {
map.putAll(defaultDataMap);
}
URI key = URI.create(UUID.randomUUID().toString());
dataMap.put(key, map);
//Gets the list of input/output to display
List<DescriptionType> descriptionTypeList = new ArrayList<>();
for(OutputDescriptionType o : process.getOutput()){
if(dataUIManager.getDataUI(o.getDataDescription().getValue().getClass()) != null) {
descriptionTypeList.add(o);
}
}
for(InputDescriptionType i : process.getInput()){
if(dataUIManager.getDataUI(i.getDataDescription().getValue().getClass()) != null) {
descriptionTypeList.add(i);
}
}
//Adds the remove button at the start of the row
JButton removeRow = new JButton(ToolBoxIcon.getIcon(ToolBoxIcon.DELETE));
removeRow.setContentAreaFilled(false);
removeRow.setBorderPainted(false);
removeRow.putClientProperty("dataMap", dataMap);
removeRow.putClientProperty("key", key);
removeRow.putClientProperty("parameterPanel", parameterPanel);
List<JComponent> componentList = new ArrayList<>();
componentList.add(removeRow);
removeRow.putClientProperty("componentList", componentList);
removeRow.addActionListener(EventHandler.create(ActionListener.class, this, "onRemoveRow", "source"));
parameterPanel.add(removeRow);
//Adds the swing components of the inputs/outputs. The components ar also stored to remove them later form the
// panel on clicking on the remove row button.
for(int i = 0; i < descriptionTypeList.size(); i++){
String migOption = "growx";
if(i == descriptionTypeList.size()-1){
if(!migOption.isEmpty()){
migOption += ", ";
}
migOption += "wrap";
}
DescriptionType descriptionType = descriptionTypeList.get(i);
DataUI dataUI = null;
if(descriptionType instanceof InputDescriptionType) {
dataUI = dataUIManager.getDataUI(
((InputDescriptionType)descriptionType).getDataDescription().getValue().getClass());
}
if(descriptionType instanceof OutputDescriptionType) {
dataUI = dataUIManager.getDataUI(
((OutputDescriptionType)descriptionType).getDataDescription().getValue().getClass());
}
if(dataUI!=null) {
//Retrieve the component containing all the UI components.
JComponent uiComponent = dataUI.createUI(descriptionType, map, DataUI.Orientation.HORIZONTAL);
if(uiComponent != null) {
parameterPanel.add(uiComponent, migOption);
componentList.add(uiComponent);
}
}
}
JSeparator separator = new JSeparator();
parameterPanel.add(separator, "growx, span");
componentList.add(separator);
}
/**
* Action done on clicking on the remove row button.
*
* @param source Source of the click
*/
public void onRemoveRow(Object source){
if(source instanceof JButton){
//Gets the basic object used to remove the row
JButton removeRow = (JButton)source;
Map dataMap = (Map) removeRow.getClientProperty("dataMap");
URI key = (URI) removeRow.getClientProperty("key");
dataMap.remove(key);
//Removes from the
JPanel parameterPanel = (JPanel) removeRow.getClientProperty("parameterPanel");
List<JComponent> componentList = (List<JComponent>) removeRow.getClientProperty("componentList");
for(JComponent component : componentList){
parameterPanel.remove(component);
}
parameterPanel.revalidate();
}
}
/**
* When the title arrow button is clicked, expand the input/output components.
* @param source Arrow button.
*/
public void onClickButton(Object source) {
JButton button = (JButton)source;
onClickHeader(button.getClientProperty("upPanel"));
}
/**
* When the title is clicked, expand the input/output components. It is used for the optional input/output in the
* STANDARD execution mode.
* @param source Title text.
*/
public void onClickHeader(Object source){
JPanel panel = (JPanel)source;
JButton showButton = (JButton)panel.getClientProperty("button");
final JComponent body = (JComponent)panel.getClientProperty("body");
JComponent parent = (JComponent)panel.getClientProperty("parent");
final JScrollPane scrollPane = (JScrollPane)panel.getClientProperty("scrollPane");
boolean isVisible = body.isVisible();
if(isVisible) {
body.setVisible(false);
parent.remove(body);
showButton.setIcon(ToolBoxIcon.getIcon(ToolBoxIcon.BTNRIGHT));
}
else{
body.setVisible(true);
parent.add(body, "growx, span");
showButton.setIcon(ToolBoxIcon.getIcon(ToolBoxIcon.BTNDOWN));
//Later scrollDown to the element
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
scrollPane.scrollRectToVisible(body.getBounds());
}
});
scrollPane.scrollRectToVisible(body.getBounds());
}
parent.revalidate();
}
/**
* This extension of JPanel implementing Scrollable is used to create a panel which is only able to have a vertical
* scrolling.
*/
private class AbstractScrollPane extends JPanel implements Scrollable{
@Override
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
@Override
public int getScrollableUnitIncrement(Rectangle rectangle, int i, int i1) {
return 5;
}
@Override
public int getScrollableBlockIncrement(Rectangle rectangle, int i, int i1) {
return 5;
}
@Override
public boolean getScrollableTracksViewportWidth() {
return true;
}
@Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
}
}