/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.components.cpacs.vampzeroinitializer.gui;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.xml.xpath.XPathExpressionException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.forms.widgets.TableWrapData;
import org.eclipse.ui.forms.widgets.TableWrapLayout;
import de.rcenvironment.components.cpacs.vampzeroinitializer.gui.model.Component;
import de.rcenvironment.components.cpacs.vampzeroinitializer.gui.model.Discipline;
import de.rcenvironment.components.cpacs.vampzeroinitializer.gui.model.Parameter;
import de.rcenvironment.core.gui.utils.common.components.PropertyTabGuiHelper;
import de.rcenvironment.core.utils.common.xml.XMLException;
/**
* GUI-Controller handling the main Vampzero application. This controller doesn't control resource handles, it's all delegated to the
* factory and the caller.
*
* @author Arne Bachmann
* @author Markus Kunde
*/
public class MainGuiController {
private static final int SIZE_200 = 200;
/**
* The logger.
*/
private static final Log LOGGER = LogFactory.getLog(MainGuiController.class);
/**
* Contains all parameters that were modified by the user.
*/
private List<Component> modifiedComponents = new ArrayList<Component>();
/**
* Gui creation factory.
*/
private FormToolkitSwtHelper factory;
/**
* The tree viewer.
*/
private TreeViewer viewer;
/**
* Listener to call when "store cpacs" is clicked.
*/
private InputTransferable listener;
/**
* Path to a gui.xml on the local file system.
*/
private String xmlPath = null;
/**
* All widgets to dispose after closing the app.
*/
private List<Control> disposables = new LinkedList<Control>();
/**
* All components in the GUI definition file.
*/
private List<Component> components;
private GuiInputParser guiInputParser = new GuiInputParser();
private ToolspecificOutputWriter toolspecificOutputWriter = new ToolspecificOutputWriter();
/**
* Constructor.
*
* @param factory The factory to use when creating this gui.
* @param performListener The listener to activate when hitting save
*/
public MainGuiController(final FormToolkitSwtHelper factory, final InputTransferable performListener) {
this.factory = factory;
this.listener = performListener;
}
/**
* Constructor.
*
* @param factory The factory to use when creating this gui.
* @param performListener The listener to activate when hitting save
* @param guiXmlpath Local file system path to load the GUI description from
*/
public MainGuiController(final FormToolkitSwtHelper factory, final InputTransferable performListener, final String guiXmlPath) {
this.factory = factory;
this.listener = performListener;
this.xmlPath = guiXmlPath;
}
/**
* Remove all unmanaged widgets, in Eclipse most are managed by the FormFactory.
*/
public void dispose() {
while (disposables.size() > 0) {
if (disposables instanceof LinkedList) {
((LinkedList<Control>) disposables).getLast().dispose();
((LinkedList<Control>) disposables).removeLast();
}
}
}
/**
* Gui creation helper. Use either the provided file path or load a standard GUI config from the bundle.
*
* @return The main controls
*/
public Composite createControls() {
List<Component> componentsTemp = null;
if ((xmlPath != null) && (new File(xmlPath).canRead())) {
FileInputStream fis;
boolean killSwitch = false;
try {
fis = new FileInputStream(xmlPath);
componentsTemp = guiInputParser.parse(fis);
fis.close();
} catch (XPathExpressionException | IOException | XMLException e) {
LOGGER.error(e);
killSwitch = true;
}
if (killSwitch) {
return null;
}
} else {
try {
componentsTemp = guiInputParser.parse(getClass().getClassLoader().getResourceAsStream("resources/gui.xml"));
} catch (final XPathExpressionException | XMLException e) {
LOGGER.error(e.getCause().getMessage());
componentsTemp = new ArrayList<Component>();
}
}
components = componentsTemp;
final Composite mainComposite = factory.createMainComposite();
disposables.add(mainComposite);
final TableWrapLayout layout = new TableWrapLayout();
layout.numColumns = 5;
layout.horizontalSpacing = 4;
layout.verticalSpacing = 4;
mainComposite.setLayout(layout);
// drop down boxes and main mask
final Composite comboComposite = factory.createComposite(mainComposite, 3, 3);
disposables.add(comboComposite);
final Combo componentCombo = factory.createCombo(comboComposite, null, null);
final Combo disciplineCombo = factory.createCombo(comboComposite, null, null);
final Button loadButton = factory.createButton(comboComposite, "Load configuration...", new Listener() {
@Override
public void handleEvent(final Event event) {
IFile file = PropertyTabGuiHelper.selectFileFromProjects(comboComposite.getShell(), "Foo", "FooBar");
if (file != null) {
StringWriter writer = new StringWriter();
try {
IOUtils.copy(file.getContents(), writer);
String theString = writer.toString();
setSelectedParameters(theString);
} catch (IOException e) {
LOGGER.error("Cannot read content from file.");
} catch (CoreException e) {
LOGGER.error("Cannot read content from file.");
}
}
}
});
disposables.add(componentCombo);
disposables.add(disciplineCombo);
disposables.add(loadButton);
final Label separator = factory.createSeparator(mainComposite, true);
((TableWrapData) separator.getLayoutData()).valign = TableWrapData.FILL;
((TableWrapData) separator.getLayoutData()).rowspan = 2;
disposables.add(separator);
final Composite rightComposite = factory.createComposite(mainComposite);
disposables.add(rightComposite);
final TableWrapData td = new TableWrapData();
td.align = TableWrapData.FILL;
td.valign = TableWrapData.FILL;
td.grabHorizontal = true;
td.grabVertical = true;
td.rowspan = 2;
rightComposite.setLayoutData(td);
rightComposite.setLayout(new GridLayout(1, false));
final Button createButton = new Button(rightComposite, SWT.PUSH | SWT.FLAT);
disposables.add(createButton);
createButton.setText("Create CPACS");
createButton.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent(final Event event) {
listener.transfer(toolspecificOutputWriter.createOutput(modifiedComponents));
}
});
createButton.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, false, false));
createButton.setImage(factory.saveImage);
viewer = new TreeViewer(rightComposite, SWT.SINGLE | SWT.READ_ONLY | SWT.BORDER);
disposables.add(viewer.getTree());
viewer.setContentProvider(new ParameterTreeContentProvider());
viewer.setLabelProvider(new ParameterTreeLabelProvider(viewer.getTree().getDisplay()));
ColumnViewerToolTipSupport.enableFor(viewer);
final Set<List<Component>> set = new HashSet<List<Component>>();
set.add(modifiedComponents); // can't use newSet(collection...) because then not the list
// but its contents are added,which are none
viewer.setInput(set); // create root "set" with the list of all components in it
final GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true);
gd.minimumHeight = SIZE_200;
gd.minimumWidth = SIZE_200;
final Composite contentComposite = factory.createComposite(mainComposite, 1, 1);
disposables.add(contentComposite);
contentComposite.setLayoutData(new TableWrapData(TableWrapData.FILL, TableWrapData.FILL, 1, 1));
contentComposite.setLayout(new GridLayout(1, false));
// initiall combo fillings
for (final Component component : components) {
componentCombo.add(component.getName());
}
for (final Discipline discipline : components.get(0).getDisciplines()) {
disciplineCombo.add(discipline.getName());
}
final ParameterCompositeWrapper parameterCompositeWrapper =
new ParameterCompositeWrapper(factory, contentComposite, modifiedComponents);
parameterCompositeWrapper.setDiscipline(components.get(0).getDisciplines().get(0));
parameterCompositeWrapper.setViewer(viewer);
viewer.getTree().setLayoutData(gd);
viewer.getTree().addListener(SWT.Selection,
getTreeViewListener(componentCombo, disciplineCombo, parameterCompositeWrapper, contentComposite));
viewer.getTree().addKeyListener(getKeyAdapter());
componentCombo.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent(final Event event) {
final String newSelection = componentCombo.getText();
final Component component = getComponentForName(components, newSelection);
fillDisciplines(disciplineCombo, component);
parameterCompositeWrapper.setDiscipline(component.getDisciplines().get(0));
parameterCompositeWrapper.redraw();
disciplineCombo.select(0);
}
});
disciplineCombo.addListener(SWT.Selection, getDisciplineComboListener(disciplineCombo, parameterCompositeWrapper, componentCombo));
componentCombo.select(0); // initial selection is not empty
disciplineCombo.select(0);
return mainComposite;
}
private Listener getDisciplineComboListener(final Combo disciplineCombo, final ParameterCompositeWrapper parameterCompositeWrapper,
final Combo componentCombo) {
return new Listener() {
@Override
public void handleEvent(final Event event) {
final String newSelection = disciplineCombo.getText();
final Component component = getComponentForName(components, componentCombo.getText());
parameterCompositeWrapper.setDiscipline(component.getDisciplineForName(newSelection));
parameterCompositeWrapper.redraw();
}
};
}
private Listener getTreeViewListener(final Combo componentCombo, final Combo disciplineCombo,
final ParameterCompositeWrapper parameterCompositeWrapper, final Composite contentComposite) {
return new Listener() {
@Override
public void handleEvent(final Event event) {
if (viewer.getTree().getSelectionCount() != 1) {
return; // to preven empty selection (if possible at all)
}
final TreeItem[] items = viewer.getTree().getSelection();
TreeItem item = items[0];
String path = item.getText();
while (item.getParentItem() != null) {
item = item.getParentItem();
path = item.getText() + "/\\/" + path;
}
final String[] parameterBreakDown = path.split("/\\\\/"); // component/discipline/parameter
if (parameterBreakDown.length < 2) { // no need to change comp/disc/param ( first is empty)
return;
}
if (!componentCombo.getText().equals(parameterBreakDown[1])) { // we need to switch the component
final Component component = getComponentForName(components, parameterBreakDown[1]);
componentCombo.select(componentCombo.indexOf(component.getName()));
fillDisciplines(disciplineCombo, component);
if (parameterBreakDown.length > 2) { // a discipline has been clicked
parameterCompositeWrapper.setDiscipline(component.getDisciplineForName(parameterBreakDown[2]));
parameterCompositeWrapper.redraw();
disciplineCombo.select(disciplineCombo.indexOf(parameterBreakDown[2]));
} else { // no discipline is set: take first one
parameterCompositeWrapper.setDiscipline(component.getDisciplines().get(0));
parameterCompositeWrapper.redraw();
disciplineCombo.select(0);
}
} else if (parameterBreakDown.length > 2) {
if (!disciplineCombo.getText().equals(parameterBreakDown[2])) {
parameterCompositeWrapper.setDiscipline(getComponentForName(components, parameterBreakDown[1])
.getDisciplineForName(parameterBreakDown[2]));
parameterCompositeWrapper.redraw();
disciplineCombo.select(disciplineCombo.indexOf(parameterBreakDown[2]));
}
}
if (parameterBreakDown.length > 3) { // a parameter name has been clicked
for (final Component component : modifiedComponents) {
if (component.getName().equals(parameterBreakDown[1])) {
final Parameter param =
component.getDisciplineForName(parameterBreakDown[2]).getParameterForName(parameterBreakDown[3]);
parameterCompositeWrapper.setName(param.getName());
parameterCompositeWrapper.setDescription(param.getDescription());
parameterCompositeWrapper.setValue(param.getValue());
parameterCompositeWrapper.setFactor(param.getFactor());
}
}
final org.eclipse.swt.widgets.List paramList =
(org.eclipse.swt.widgets.List) ((Composite) contentComposite.getChildren()[0]).getChildren()[0];
for (int i = 0, n = paramList.getItemCount(); i < n; i++) { // find the correct
// parameter in the
// list
if (paramList.getItem(i).equals(parameterBreakDown[3])) {
paramList.setSelection(i);
break;
}
}
}
}
};
}
private KeyListener getKeyAdapter() {
return new KeyAdapter() {
@Override
public void keyReleased(final KeyEvent event) {
if (event.character == SWT.DEL) {
if (viewer.getTree().getSelectionCount() != 1) {
return; // to preven empty selection (if possible at all)
}
final TreeItem[] items = viewer.getTree().getSelection();
TreeItem item = items[0];
String path = item.getText();
while (item.getParentItem() != null) {
item = item.getParentItem();
path = item.getText() + "/\\/" + path;
}
final String[] parameterBreakDown = path.split("/\\\\/"); // component/discipline/parameter
if (parameterBreakDown.length < 4) { // only remove params
return;
}
final Discipline discipline = getComponentForName(modifiedComponents, parameterBreakDown[1])
.getDisciplineForName(parameterBreakDown[2]);
discipline.getParameters().remove(discipline.getParameterForName(parameterBreakDown[3]));
if (discipline.getParameters().size() == 0) {
discipline.getComponent().getDisciplines().remove(discipline);
if (discipline.getComponent().getDisciplines().size() == 0) {
modifiedComponents.remove(discipline.getComponent());
}
}
viewer.refresh();
}
}
};
}
/**
* Helper to update the drop-down box.
*
* @param disciplineCombo The combo
* @param component The parent component
*/
private void fillDisciplines(final Combo disciplineCombo, final Component component) {
disciplineCombo.removeAll();
for (final Discipline discipline : component.getDisciplines()) {
disciplineCombo.add(discipline.getName());
}
}
/**
* Call this with an existing created configuration to set the GUI contents accordingly. The routine doesn't allow non-existing entries
* of the initially loaded GUI definition file.
*
* @param configuration The configuration to parse as one XML string.
*/
public void setSelectedParameters(final String configuration) {
try {
final List<Component> setComponents = guiInputParser.parse(configuration);
modifiedComponents.clear();
for (final Component setComponent : setComponents) {
final Component compDefinition = getComponentForName(components, setComponent.getName());
if (compDefinition == null) {
LOGGER.warn("Ignoring component setting " + setComponent.getName());
continue; // skip loaded setting, because it's not allowed
}
if (getComponentForName(modifiedComponents, setComponent.getName()) != null) {
LOGGER.warn("Ignoring duplicate component setting " + setComponent.getName());
continue;
}
final Component newComponent = new Component();
newComponent.setName(setComponent.getName());
modifiedComponents.add(newComponent);
for (final Discipline setDiscipline : setComponent.getDisciplines()) {
final Discipline discDefinition = compDefinition.getDisciplineForName(setDiscipline.getName());
if (discDefinition == null) {
LOGGER.warn("Ignoring discipline setting " + setDiscipline.getName());
continue;
}
if (newComponent.getDisciplineForName(setDiscipline.getName()) != null) {
LOGGER.warn("Ignoring duplicate discipline setting " + setDiscipline.getName());
continue;
}
final Discipline newDiscipline = new Discipline();
newDiscipline.setName(setDiscipline.getName());
newComponent.getDisciplines().add(newDiscipline);
for (final Parameter setParameter : setDiscipline.getParameters()) {
final Parameter paramDefinition = discDefinition.getParameterForName(setParameter.getName());
if (paramDefinition == null) {
LOGGER.warn("Ignoring parameter setting " + setParameter.getName());
continue;
}
if (newDiscipline.getParameterForName(setParameter.getName()) != null) {
LOGGER.warn("Ignoring duplicate parameter setting " + setParameter.getName());
continue;
}
final Parameter newParameter = new Parameter(setParameter);
newParameter.setName(setParameter.getName());
newDiscipline.getParameters().add(newParameter);
}
}
}
viewer.refresh();
viewer.expandAll();
} catch (final XPathExpressionException | XMLException e) {
LOGGER.error("Error parsing earlier vampzero configuration", e);
}
}
/**
* Helper symmetrical to the component and discipline method helpers.
*
* @param comp The list of all loaded components
* @param name The name to look for
* @return The component with the given name or null if not found
*/
private Component getComponentForName(final List<Component> comp, final String name) {
assert comp != null;
assert comp.size() > 0;
assert name != null;
for (final Component component : comp) {
if (component.getName().equals(name)) {
return component;
}
}
return null; // nothing found throws exception, shouldn't ever happen
}
}