/**
* Copyright 2014 the staff of 52°North Initiative for Geospatial Open
* Source Software GmbH in their free time
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package spare.n52.yadarts.layout;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Spinner;
import org.eclipse.swt.widgets.Text;
import spare.n52.yadarts.i18n.I18N;
import spare.n52.yadarts.layout.GameParameter.Bounds;
/**
* A sophisticated dialog class that dynamically creates
* UI input fields based on the required parameters of the
* available games (see {@link GameView.AvailableGames})
*/
public class NewGameDialog extends Dialog {
private Shell shell;
private Combo comboDropDown;
private List<GameView> availableGames;
private Composite gameSpecificAreaStack;
private StackLayout gameSpecificAreaStackLayout = new StackLayout();
private Map<GameView, List<GameParameter<?>>> result;
private List<GameLayout> gameLayouts = new ArrayList<>();
protected GameLayout currentLayout;
public static NewGameDialog create(Shell shell) {
return new NewGameDialog(shell);
}
private NewGameDialog(Shell parent) {
super(parent);
availableGames = GameView.AvailableGames.get();
}
/**
* Creates a singleton map of {@link GameView} and
* compatible {@link GameParameter}s. The params
* are filled via the values of the dialogs UI.
* the resulting {@link GameView} shalled be
* {@link GameView#initialize(Composite, int, List)} with
* the provided list of {@link GameParameter}
*
* @return a singleton map of {@link GameView} and compatible {@link GameParameter}s.
*/
public Map<GameView, List<GameParameter<?>>> open() {
createContents();
shell.open();
shell.layout();
Display display = getParent().getDisplay();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
return result;
}
protected void createContents() {
shell = new Shell(getParent(), SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL);
shell.setText(I18N.getString("newGame"));
shell.setSize(600, 400);
GridLayout rl = new GridLayout(1, true);
shell.setLayout(rl);
/*
* which game?
*/
Composite comboDropDownRow = new Composite(shell, SWT.NONE);
comboDropDownRow.setLayout(new RowLayout(SWT.HORIZONTAL));
comboDropDownRow.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
new Label(comboDropDownRow, SWT.NONE).setText(I18N.getString("selectGame").concat(":"));
comboDropDown = new Combo(comboDropDownRow, SWT.READ_ONLY);
comboDropDown.setItems(createItems());
comboDropDown.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
super.widgetSelected(e);
currentLayout = gameLayouts.get(comboDropDown.getSelectionIndex());
gameSpecificAreaStackLayout.topControl = currentLayout;
gameSpecificAreaStack.layout();
}
});
/*
* stack layout for each game area
*/
gameSpecificAreaStack = new Composite(shell, SWT.BORDER);
gameSpecificAreaStack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
gameSpecificAreaStack.setLayout(gameSpecificAreaStackLayout);
Composite empty = new Composite(gameSpecificAreaStack, SWT.NONE);
empty.setLayout(new FillLayout());
/*
* create an area for each available game
*/
for (GameView gv : availableGames) {
gameLayouts.add(new GameLayout(gv, gameSpecificAreaStack));
}
gameSpecificAreaStackLayout.topControl = empty;
/*
* ok button
*/
Button okButton = new Button(shell, SWT.PUSH);
okButton.setText(I18N.getString("start"));
okButton.setLayoutData(new GridData(SWT.RIGHT, SWT.BOTTOM, false, false));
okButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
super.widgetSelected(e);
List<GameParameter<?>> params = currentLayout.computeFilledParameters();
result = Collections.singletonMap(currentLayout.game, params);
shell.dispose();
}
});
}
private String[] createItems() {
String[] array = new String[availableGames.size()];
int i = 0;
for (GameView gv : availableGames) {
array[i++] = gv.getGameName();
}
return array;
}
/**
* This class creates a UI for paramter input
* based on the defined {@link GameParameter}s of the
* {@link GameView}.
*/
private static class GameLayout extends Composite {
private GameView game;
private Map<GameParameter<?>, Stack<Text>> parameterInputs = new HashMap<>();
private ScrolledComposite scrollView;
public GameLayout(GameView gv, Composite parent) {
super(parent, SWT.NONE);
this.game = gv;
this.setLayout(new FillLayout());
/*
* this scrollView is updated when the UI changes
* dynamically
*/
scrollView = new ScrolledComposite(this, SWT.H_SCROLL
| SWT.V_SCROLL | SWT.BORDER);
scrollView.setExpandHorizontal(true);
scrollView.setExpandVertical(true);
/*
* the contentContainer is the root Composite of this game view.
* it is a child of scrollView to enable scrolling.
*/
final Composite contentContainer = new Composite(scrollView, SWT.NONE);
scrollView.setContent(contentContainer);
GridLayout layout = new GridLayout();
layout.numColumns = 1;
contentContainer.setLayout(layout);
/*
* define a nice headline
*/
new Label(contentContainer, SWT.NONE).setText(I18N.getString("newGame").concat(": ").concat(this.game.getGameName()));
for (final GameParameter<?> param : this.game.getInputParameters()) {
createParameterView(param, contentContainer);
}
/*
* initially compute the min size so the scroll bars are rendered
* when required
*/
scrollView.setMinSize(contentContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT));
}
/**
* creates an input area based on the provided {@link GameParameter} and
* its {@link Bounds}.
*
* @param param the input parameter
* @param contentContainer the parent Composite
*/
private void createParameterView(final GameParameter<?> param, final Composite contentContainer) {
if (param.getBounds().getMin() != param.getBounds().getMax()) {
createParameterViewWithSpinner(param, contentContainer);
}
else {
createStaticParameterView(param, contentContainer);
}
}
/**
* creates a parameter input view where the bounds are
* static. No dynamic including of additional fields
* is required here.
*
* @param param the game parameter
* @param contentContainer the parent Composite
*/
private void createStaticParameterView(final GameParameter<?> param,
final Composite contentContainer) {
new Label(contentContainer, SWT.NONE).setText(I18N.getString(param.getName()));
parameterInputs.put(param, new Stack<Text>());
final Composite parameterList = new Composite(contentContainer, SWT.NONE);
parameterList.setLayout(new GridLayout(1, true));
parameterList.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
for (int i = 0; i < param.getBounds().getMax(); i++) {
addParameterInput(parameterList, param);
}
}
/**
* creates a parameter input view where the bounds are
* dynamic. A spinner is used to define the amount
* of input fields.
*
* @param param the game parameter
* @param contentContainer the parent Composite
*/
private void createParameterViewWithSpinner(final GameParameter<?> param,
final Composite contentContainer) {
parameterInputs.put(param, new Stack<Text>());
Composite spinnerContainer = new Composite(contentContainer, SWT.NONE);
spinnerContainer.setLayout(new RowLayout());
new Label(spinnerContainer, SWT.NONE).setText(I18N.getString(param.getName()));
final Spinner spinner = new Spinner(spinnerContainer, SWT.READ_ONLY);
spinner.setMaximum(param.getBounds().getMax());
spinner.setMinimum(param.getBounds().getMin());
final Composite parameterList = new Composite(contentContainer, SWT.NONE);
parameterList.setLayout(new GridLayout(1, true));
parameterList.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
spinner.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
int count = Integer.parseInt(spinner.getText());
if (count > parameterInputs.get(param).size()) {
/*
* change the stack top child. call
* layout() on the stack's component
*/
addParameterInput(parameterList, param);
}
else {
removeLastParameterInput(parameterList, param);
}
/*
* updated the size of the scrollView as the contents
* have changed!
*/
scrollView.setMinSize(contentContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT));
contentContainer.layout();
}
});
for (int i = 0; i < param.getBounds().getMin(); i++) {
addParameterInput(parameterList, param);
}
}
/**
* add a single input field and store it in {@link #parameterInputs}
*
* @param parameterListContainer the parent
* @param param the defining game parameter
*/
protected void addParameterInput(Composite parameterListContainer, GameParameter<?> param) {
Text textInput = new Text(parameterListContainer, SWT.BORDER);
textInput.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
textInput.setSize(120, 32);
this.parameterInputs.get(param).push(textInput);
}
/**
* remove the last input field that has been added (both from the view
* and {@link #parameterInputs})
*
* @param parameterListContainer the parent
* @param param the defining game parameter
*/
protected void removeLastParameterInput(Composite parameterListContainer, GameParameter<?> param) {
Text toRemove = this.parameterInputs.get(param).pop();
toRemove.dispose();
}
/**
* @return the filled list of {@link GameParameter}s. the contents
* are read from the UI input fields
*/
public List<GameParameter<?>> computeFilledParameters() {
List<GameParameter<?>> list = new ArrayList<>();
for (GameParameter<?> gameParameter : this.parameterInputs.keySet()) {
list.add(computeParameterValues(gameParameter, this.parameterInputs.get(gameParameter)));
}
return list;
}
private GameParameter<?> computeParameterValues(
GameParameter<?> gameParameter, Stack<Text> inputFields) {
/*
* TODO implement other datatypes. currently only String
*/
if (gameParameter.getType().isAssignableFrom(String.class)) {
computeStringParameterValues(gameParameter, inputFields);
}
return gameParameter;
}
protected void computeStringParameterValues(
GameParameter<?> gameParameter, Stack<Text> inputFields) {
GameParameter<String> sp = (GameParameter<String>) gameParameter;
List<String> valueList = new ArrayList<>(inputFields.size());
for (Text text : inputFields) {
valueList.add(text.getText().trim());
}
sp.setValue(valueList);
}
}
}