Add SWT UI

In order to add a new UI the core functionality of the calculator is
separated from the UI code.

Change-Id: I855a7554471ac45f62a7ce98d7cc6d07652f1d80
Signed-off-by: Stefan Lay <stefan.lay@sap.com>
diff --git a/org.eclipse.example.calc/META-INF/MANIFEST.MF b/org.eclipse.example.calc/META-INF/MANIFEST.MF
index f371248..d89f64e 100644
--- a/org.eclipse.example.calc/META-INF/MANIFEST.MF
+++ b/org.eclipse.example.calc/META-INF/MANIFEST.MF
@@ -6,7 +6,9 @@
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: J2SE-1.6
 Require-Bundle: org.junit4;bundle-version="[4.3.0,5.0.0)",
- org.hamcrest;bundle-version="[1.1.0,2.0.0)"
+ org.hamcrest;bundle-version="[1.1.0,2.0.0)",
+ org.eclipse.swt;bundle-version="[3.5.0,4.0.0)"
 Export-Package: org.eclipse.example.calc;version="0.1.0",
  org.eclipse.example.calc.internal.operations;version="0.1.0";x-internal:=true,
- org.eclipse.example.calc.internal.ui;version="0.1.0";x-internal:=true
+ org.eclipse.example.calc.internal.ui.swing;version="0.1.0";x-internal:=true,
+ org.eclipse.example.calc.internal.ui.swt;version="0.1.0";x-internal:=true
diff --git a/org.eclipse.example.calc/Calculator.launch b/org.eclipse.example.calc/SWTCalculatorUI.launch
similarity index 78%
copy from org.eclipse.example.calc/Calculator.launch
copy to org.eclipse.example.calc/SWTCalculatorUI.launch
index 00bf1b2..66e7080 100644
--- a/org.eclipse.example.calc/Calculator.launch
+++ b/org.eclipse.example.calc/SWTCalculatorUI.launch
@@ -1,15 +1,15 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
-<listEntry value="/org.eclipse.example.calc/src/org/eclipse/example/calc/internal/ui/Calculator.java"/>
+<listEntry value="/org.eclipse.example.calc/src/org/eclipse/example/calc/internal/ui/swt/CalculatorUI.java"/>
 </listAttribute>
 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
 <listEntry value="1"/>
 </listAttribute>
 <mapAttribute key="org.eclipse.debug.core.preferred_launchers">
-<mapEntry key="[debug]" value="org.eclipse.jdt.launching.localJavaApplication"/>
 <mapEntry key="[run]" value="org.eclipse.jdt.launching.localJavaApplication"/>
 </mapAttribute>
-<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.eclipse.example.calc.internal.ui.Calculator"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.eclipse.example.calc.internal.ui.swt.CalculatorUI"/>
 <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="org.eclipse.example.calc"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-XstartOnFirstThread"/>
 </launchConfiguration>
diff --git a/org.eclipse.example.calc/Calculator.launch b/org.eclipse.example.calc/SwingCalculatorUI.launch
similarity index 87%
rename from org.eclipse.example.calc/Calculator.launch
rename to org.eclipse.example.calc/SwingCalculatorUI.launch
index 00bf1b2..76ff670 100644
--- a/org.eclipse.example.calc/Calculator.launch
+++ b/org.eclipse.example.calc/SwingCalculatorUI.launch
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
-<listEntry value="/org.eclipse.example.calc/src/org/eclipse/example/calc/internal/ui/Calculator.java"/>
+<listEntry value="/org.eclipse.example.calc/src/org/eclipse/example/calc/internal/ui/swing/CalculatorUI.java"/>
 </listAttribute>
 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
 <listEntry value="1"/>
@@ -10,6 +10,6 @@
 <mapEntry key="[debug]" value="org.eclipse.jdt.launching.localJavaApplication"/>
 <mapEntry key="[run]" value="org.eclipse.jdt.launching.localJavaApplication"/>
 </mapAttribute>
-<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.eclipse.example.calc.internal.ui.Calculator"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.eclipse.example.calc.internal.ui.swing.CalculatorUI"/>
 <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="org.eclipse.example.calc"/>
 </launchConfiguration>
diff --git a/org.eclipse.example.calc/src/org/eclipse/example/calc/internal/Calculator.java b/org.eclipse.example.calc/src/org/eclipse/example/calc/internal/Calculator.java
new file mode 100644
index 0000000..8d1725b
--- /dev/null
+++ b/org.eclipse.example.calc/src/org/eclipse/example/calc/internal/Calculator.java
@@ -0,0 +1,113 @@
+/*******************************************************************************

+ * Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com>

+ * Copyright (C) 2010, Stefan Lay <stefan.lay@sap.com>

+ *

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * which accompanies this distribution, and is available at

+ * http://www.eclipse.org/legal/epl-v10.html

+ *******************************************************************************/

+package org.eclipse.example.calc.internal;

+

+import org.eclipse.example.calc.BinaryOperation;

+import org.eclipse.example.calc.Operation;

+import org.eclipse.example.calc.Operations;

+import org.eclipse.example.calc.UnaryOperation;

+import org.eclipse.example.calc.internal.operations.Equals;

+import org.eclipse.example.calc.internal.operations.Minus;

+import org.eclipse.example.calc.internal.operations.Plus;

+import org.eclipse.example.calc.internal.operations.Square;

+

+public class Calculator {

+

+	private TextProvider textProvider;

+

+	private String cmd;

+

+	private boolean clearText;

+

+	private float value;

+

+	public static String NAME = "Simple Calculator";

+

+	public Calculator(TextProvider textProvider) {

+		this.textProvider = textProvider;

+		setupDefaultOperations();

+	}

+

+	private void setupDefaultOperations() {

+		new Equals();

+		new Minus();

+		new Plus();

+		new Square();

+	}

+

+	private void calculate(String cmdName) {

+		float curValue;

+		float newValue = 0;

+

+		// get current value of display

+		curValue = Float.parseFloat(textProvider.getDisplayText());

+

+		Operation currentOp = Operations.INSTANCE.getOperation(cmdName);

+		if ((currentOp instanceof BinaryOperation) && (cmd == null)) {

+			// if last clicked operation was binary and there is no saved

+			// operation, store it

+			cmd = cmdName;

+			setClearText(true);

+		} else {

+			// if saved command is binary perform it

+			Operation savedOp = Operations.INSTANCE.getOperation(cmd);

+			if (savedOp instanceof BinaryOperation) {

+				BinaryOperation bop = (BinaryOperation) savedOp;

+				newValue = bop.perform(value, curValue);

+			} // if current operation is unary perform it

+			else if (currentOp instanceof UnaryOperation) {

+				UnaryOperation uop = (UnaryOperation) currentOp;

+				newValue = uop.perform(curValue);

+			}

+

+			// display the result and prepare clear on next button

+			textProvider.setDisplayText("" + newValue);

+			setClearText(true);

+			if (currentOp instanceof Equals) {

+				// do not save "=" command

+				cmd = null;

+			} else if (currentOp instanceof BinaryOperation) {

+				// save binary commands as they are executed on next operation

+				cmd = cmdName;

+			} else {

+				// clear saved command

+				cmd = null;

+			}

+		}

+

+	}

+

+	private boolean isCommand(String name) {

+		return (Operations.INSTANCE.getOperation(name) != null);

+	}

+

+	public void handleButtonClick(String str) {

+		if (isCommand(str)) {

+			calculate(str);

+		} else {

+			char digit = (str.toCharArray())[0];

+			if (Character.isDigit(digit) || digit == '.') {

+				if (clearText) {

+					// save current value and clear the display

+					value = Float.parseFloat(textProvider.getDisplayText());

+					textProvider.setDisplayText("");

+					setClearText(false);

+				}

+

+				// add new digit to display

+				textProvider.setDisplayText(textProvider.getDisplayText() + digit);

+			}

+		}

+	}

+

+	public void setClearText(boolean clearText) {

+		this.clearText = clearText;

+	}

+}

diff --git a/org.eclipse.example.calc/src/org/eclipse/example/calc/internal/TextProvider.java b/org.eclipse.example.calc/src/org/eclipse/example/calc/internal/TextProvider.java
new file mode 100644
index 0000000..4a1993d
--- /dev/null
+++ b/org.eclipse.example.calc/src/org/eclipse/example/calc/internal/TextProvider.java
@@ -0,0 +1,26 @@
+/*******************************************************************************

+ * Copyright (C) 2010, Stefan Lay <stefan.lay@sap.com>

+ *

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * which accompanies this distribution, and is available at

+ * http://www.eclipse.org/legal/epl-v10.html

+ *******************************************************************************/

+package org.eclipse.example.calc.internal;

+

+/**

+ * Display abstraction for {@link Calculator}

+ */

+public interface TextProvider {

+

+	/**

+	 * @param text the text to display on the {@link Calculator}

+	 */

+	public void setDisplayText(String text);

+

+	/**

+	 * @return the text on the display of the  {@link Calculator}

+	 */

+	public String getDisplayText();

+

+}

diff --git a/org.eclipse.example.calc/src/org/eclipse/example/calc/internal/ui/Calculator.java b/org.eclipse.example.calc/src/org/eclipse/example/calc/internal/ui/Calculator.java
deleted file mode 100644
index 0e2a35d..0000000
--- a/org.eclipse.example.calc/src/org/eclipse/example/calc/internal/ui/Calculator.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*******************************************************************************
- * Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com>
- *
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *******************************************************************************/
-package org.eclipse.example.calc.internal.ui;
-
-import java.awt.BorderLayout;
-import java.awt.Container;
-import java.awt.GridLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-
-import javax.swing.BorderFactory;
-import javax.swing.JButton;
-import javax.swing.JFrame;
-import javax.swing.JPanel;
-import javax.swing.JTextField;
-import javax.swing.border.TitledBorder;
-
-import org.eclipse.example.calc.BinaryOperation;
-import org.eclipse.example.calc.Operation;
-import org.eclipse.example.calc.Operations;
-import org.eclipse.example.calc.UnaryOperation;
-import org.eclipse.example.calc.internal.operations.Equals;
-import org.eclipse.example.calc.internal.operations.Minus;
-import org.eclipse.example.calc.internal.operations.Plus;
-import org.eclipse.example.calc.internal.operations.Square;
-
-/*
- * A simple calculator featuring a Swing UI.
- */
-public class Calculator extends JFrame implements ActionListener {
-	private static final long serialVersionUID = 1L;
-
-	private String cmd;
-
-	private boolean clearDisplay;
-
-	private float value;
-
-	private JTextField display;
-
-	private JPanel buttonsPanel;
-
-	private JPanel numberButtonsPanel;
-
-	private JPanel cmdButtonsPanel;
-
-	private JButton numberButtons[];
-
-	private JButton cmdButtons[];
-
-	public static void main(String args[]) {
-		new Calculator().setVisible(true);
-	}
-
-	public Calculator() {
-		setupOperations();
-		setupGUI();
-	}
-
-	private void setupOperations() {
-		new Equals();
-		new Minus();
-		new Plus();
-		new Square();
-	}
-
-	private void setupGUI() {
-		setTitle("Simple Calculator");
-		Container c = getContentPane();
-		c.setLayout(new BorderLayout());
-		setLocationByPlatform(true);
-
-		setupDisplay(c);
-		setupButtonsPanel(c);
-		setupNumberButtons();
-		setupCommandButtons();
-
-		pack();
-	}
-
-	private void setupDisplay(Container c) {
-		display = new JTextField("0");
-		display.setHorizontalAlignment(JTextField.TRAILING);
-		c.add(display, BorderLayout.NORTH);
-		// initially clear the display
-		clearDisplay = true;
-	}
-
-	private void setupButtonsPanel(Container c) {
-		buttonsPanel = new JPanel();
-		buttonsPanel.setLayout(new GridLayout(2, 1));
-		c.add(buttonsPanel);
-	}
-
-	private void setupNumberButtons() {
-		numberButtonsPanel = new JPanel();
-		numberButtonsPanel.setLayout(new GridLayout(3, 4));
-		buttonsPanel.add(numberButtonsPanel, BorderLayout.CENTER);
-		numberButtons = new JButton[11];
-
-		for (int i = 0; i < numberButtons.length - 1; i++) {
-			addNumberButton(i, Integer.valueOf(i).toString());
-		}
-		addNumberButton(10, ".");
-	}
-
-	private void addNumberButton(int i, String name) {
-		numberButtons[i] = new JButton();
-		numberButtons[i].setText(name);
-		numberButtons[i].addActionListener(this);
-		numberButtonsPanel.add(numberButtons[i]);
-	}
-
-	private void setupCommandButtons() {
-		// command buttons
-		cmdButtonsPanel = new JPanel();
-		cmdButtonsPanel.setLayout(new GridLayout(1, 0));
-		buttonsPanel.add(cmdButtonsPanel, BorderLayout.CENTER);
-		TitledBorder title = BorderFactory.createTitledBorder("Operations");
-		cmdButtonsPanel.setBorder(title);
-		cmdButtons = new JButton[Operations.INSTANCE.size()];
-
-		// make the buttons, set ActionListener and add to panel
-		for (int i = 0; i < cmdButtons.length; i++) {
-			addCommandButton(i);
-		}
-	}
-
-	private void addCommandButton(int i) {
-		cmdButtons[i] = new JButton();
-		cmdButtons[i].setText(Operations.INSTANCE.getOperationName(i));
-		cmdButtons[i].addActionListener(this);
-		cmdButtonsPanel.add(cmdButtons[i]);
-	}
-
-	public void actionPerformed(ActionEvent e) {
-		String str = e.getActionCommand();
-		if (isCommand(str)) {
-			calculate(str);
-		} else {
-			char digit = (str.toCharArray())[0];
-			if (Character.isDigit(digit) || digit == '.') {
-				if (clearDisplay) {
-					// save current value and clear the display
-					value = Float.parseFloat(display.getText());
-					display.setText("");
-					clearDisplay = false;
-				}
-
-				// add new digit to display
-				display.setText(display.getText() + digit);
-			}
-		}
-	}
-
-	private boolean isCommand(String name) {
-		return (Operations.INSTANCE.getOperation(name) != null);
-	}
-
-	private void calculate(String cmdName) {
-		float curValue;
-		float newValue = 0;
-
-		// get current value of display
-		curValue = Float.parseFloat(display.getText());
-
-		Operation currentOp = Operations.INSTANCE.getOperation(cmdName);
-		if ((currentOp instanceof BinaryOperation) && (cmd == null)) {
-			// if last clicked operation was binary and there is no saved
-			// operation, store it
-			cmd = cmdName;
-			clearDisplay = true;
-		} else {
-			// if saved command is binary perform it
-			Operation savedOp = Operations.INSTANCE.getOperation(cmd);
-			if (savedOp instanceof BinaryOperation) {
-				BinaryOperation bop = (BinaryOperation) savedOp;
-				newValue = bop.perform(value, curValue);
-			} // if current operation is unary perform it
-			else if (currentOp instanceof UnaryOperation) {
-				UnaryOperation uop = (UnaryOperation) currentOp;
-				newValue = uop.perform(curValue);
-			}
-
-			// display the result and prepare clear on next button
-			display.setText("" + newValue);
-			clearDisplay = true;
-			if (currentOp instanceof Equals) {
-				// do not save "=" command
-				cmd = null;
-			} else if (currentOp instanceof BinaryOperation) {
-				// save binary commands as they are executed on next operation
-				cmd = cmdName;
-			} else {
-				// clear saved command
-				cmd = null;
-			}
-		}
-
-	}
-}
diff --git a/org.eclipse.example.calc/src/org/eclipse/example/calc/internal/ui/swing/CalculatorUI.java b/org.eclipse.example.calc/src/org/eclipse/example/calc/internal/ui/swing/CalculatorUI.java
new file mode 100644
index 0000000..128a27f
--- /dev/null
+++ b/org.eclipse.example.calc/src/org/eclipse/example/calc/internal/ui/swing/CalculatorUI.java
@@ -0,0 +1,144 @@
+/*******************************************************************************
+ * Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+package org.eclipse.example.calc.internal.ui.swing;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.WindowConstants;
+import javax.swing.border.TitledBorder;
+
+import org.eclipse.example.calc.Operations;
+import org.eclipse.example.calc.internal.Calculator;
+import org.eclipse.example.calc.internal.TextProvider;
+
+/*
+ * A simple calculator featuring a Swing UI.
+ */
+public class CalculatorUI extends JFrame implements TextProvider,
+		ActionListener {
+	private static final long serialVersionUID = 1L;
+
+	private Calculator calculator;
+
+	private JTextField display;
+
+	private JPanel buttonsPanel;
+
+	private JPanel numberButtonsPanel;
+
+	private JPanel cmdButtonsPanel;
+
+	private JButton numberButtons[];
+
+	private JButton cmdButtons[];
+
+	public static void main(String args[]) {
+		new CalculatorUI().setVisible(true);
+	}
+
+	public CalculatorUI() {
+		calculator = new Calculator(this);
+		setupGUI();
+		setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+	}
+
+	private void setupGUI() {
+		setTitle(Calculator.NAME);
+		Container c = getContentPane();
+		c.setLayout(new BorderLayout());
+		setLocationByPlatform(true);
+
+		setupDisplay(c);
+		setupButtonsPanel(c);
+		setupNumberButtons();
+		setupCommandButtons();
+
+		pack();
+	}
+
+	private void setupDisplay(Container c) {
+		display = new JTextField("0");
+		display.setHorizontalAlignment(JTextField.TRAILING);
+		c.add(display, BorderLayout.NORTH);
+		// initially clear the display
+		calculator.setClearText(true);
+	}
+
+	private void setupButtonsPanel(Container c) {
+		buttonsPanel = new JPanel();
+		buttonsPanel.setLayout(new GridLayout(2, 1));
+		c.add(buttonsPanel);
+	}
+
+	private void setupNumberButtons() {
+		numberButtonsPanel = new JPanel();
+		numberButtonsPanel.setLayout(new GridLayout(3, 4));
+		buttonsPanel.add(numberButtonsPanel, BorderLayout.CENTER);
+		numberButtons = new JButton[11];
+
+		for (int i = 0; i < numberButtons.length - 1; i++) {
+			addNumberButton(i, Integer.valueOf(i).toString());
+		}
+		addNumberButton(10, ".");
+	}
+
+	private void addNumberButton(int i, String name) {
+		numberButtons[i] = new JButton();
+		numberButtons[i].setText(name);
+		numberButtons[i].addActionListener(this);
+		numberButtonsPanel.add(numberButtons[i]);
+	}
+
+	private void setupCommandButtons() {
+		// command buttons
+		cmdButtonsPanel = new JPanel();
+		cmdButtonsPanel.setLayout(new GridLayout(1, 0));
+		buttonsPanel.add(cmdButtonsPanel, BorderLayout.CENTER);
+		TitledBorder title = BorderFactory.createTitledBorder("Operations");
+		cmdButtonsPanel.setBorder(title);
+		cmdButtons = new JButton[Operations.INSTANCE.size()];
+
+		// make the buttons, set ActionListener and add to panel
+		for (int i = 0; i < cmdButtons.length; i++) {
+			addCommandButton(i);
+		}
+	}
+
+	private void addCommandButton(int i) {
+		cmdButtons[i] = new JButton();
+		cmdButtons[i].setText(Operations.INSTANCE.getOperationName(i));
+		cmdButtons[i].addActionListener(this);
+		cmdButtonsPanel.add(cmdButtons[i]);
+	}
+
+	public void actionPerformed(ActionEvent e) {
+		String str = e.getActionCommand();
+		calculator.handleButtonClick(str);
+	}
+
+	@Override
+	public void setDisplayText(String text) {
+		display.setText(text);
+	}
+
+	@Override
+	public String getDisplayText() {
+		return display.getText();
+	}
+
+}
diff --git a/org.eclipse.example.calc/src/org/eclipse/example/calc/internal/ui/swt/CalculatorUI.java b/org.eclipse.example.calc/src/org/eclipse/example/calc/internal/ui/swt/CalculatorUI.java
new file mode 100644
index 0000000..2961499
--- /dev/null
+++ b/org.eclipse.example.calc/src/org/eclipse/example/calc/internal/ui/swt/CalculatorUI.java
@@ -0,0 +1,147 @@
+/*******************************************************************************

+ * Copyright (C) 2010, Stefan Lay <stefan.lay@sap.com>

+ *

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * which accompanies this distribution, and is available at

+ * http://www.eclipse.org/legal/epl-v10.html

+ *******************************************************************************/

+package org.eclipse.example.calc.internal.ui.swt;

+

+import org.eclipse.example.calc.Operations;

+import org.eclipse.example.calc.internal.Calculator;

+import org.eclipse.example.calc.internal.TextProvider;

+import org.eclipse.swt.SWT;

+import org.eclipse.swt.events.SelectionEvent;

+import org.eclipse.swt.events.SelectionListener;

+import org.eclipse.swt.layout.GridData;

+import org.eclipse.swt.layout.GridLayout;

+import org.eclipse.swt.widgets.Button;

+import org.eclipse.swt.widgets.Composite;

+import org.eclipse.swt.widgets.Display;

+import org.eclipse.swt.widgets.Group;

+import org.eclipse.swt.widgets.Shell;

+import org.eclipse.swt.widgets.Text;

+

+/*

+ * A simple calculator featuring a SWT

+ *  UI.

+ */

+public class CalculatorUI implements TextProvider, SelectionListener {

+

+	private static final long serialVersionUID = 1L;

+

+	private Calculator calculator;

+

+	private Shell shell;

+

+	private Text display;

+

+	private Button[] numberButtons;

+

+	private Button[] cmdButtons;

+

+	public static void main(String[] args) {

+		Display display = new Display();

+		Shell shell = new CalculatorUI().open(display);

+		while (!shell.isDisposed()) {

+			if (!display.readAndDispatch())

+				display.sleep();

+		}

+		display.dispose();

+	}

+

+	private Shell open(Display display) {

+		shell = new Shell(display);

+		setupGUI();

+

+		shell.pack();

+		shell.open();

+		return shell;

+	}

+

+	public CalculatorUI() {

+		calculator = new Calculator(this);

+	}

+

+	private void setupGUI() {

+		shell.setText(Calculator.NAME);

+		GridLayout gridLayout = new GridLayout();

+		gridLayout.numColumns = 1;

+		gridLayout.marginHeight = gridLayout.marginWidth = 0;

+		shell.setLayout(gridLayout);

+

+		setupDisplay();

+		setupNumberButtons();

+		setupCommandButtons();

+

+	}

+

+	private void setupDisplay() {

+		GridData gridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL);

+		display = new Text(shell, SWT.BORDER_SOLID | SWT.RIGHT);

+		display.setLayoutData(gridData);

+		display.setText("0");

+		calculator.setClearText(true);

+	}

+

+	private void setupNumberButtons() {

+		Composite numberButtonsPanel = new Composite(shell, SWT.NONE);

+		numberButtonsPanel.setLayout(new GridLayout(4, true));

+		numberButtons = new Button[11];

+

+		for (int i = 0; i < numberButtons.length - 1; i++) {

+			addNumberButton(numberButtonsPanel, i, Integer.valueOf(i)

+					.toString());

+		}

+		addNumberButton(numberButtonsPanel, 10, ".");

+	}

+

+	private void addNumberButton(Composite parent, int i, String name) {

+		numberButtons[i] = new Button(parent, SWT.PUSH);

+		numberButtons[i].setText(name);

+		numberButtons[i].addSelectionListener(this);

+	}

+

+	private void setupCommandButtons() {

+		// command buttons

+		Group cmdButtonsPanel = new Group(shell, SWT.NONE);

+		cmdButtonsPanel.setText("Operations");

+		cmdButtonsPanel.setLayout(new GridLayout(4, true));

+

+		cmdButtons = new Button[Operations.INSTANCE.size()];

+

+		// make the buttons, set ActionListener and add to panel

+		for (int i = 0; i < cmdButtons.length; i++) {

+			addCommandButton(cmdButtonsPanel, i);

+		}

+	}

+

+	private void addCommandButton(Composite parent, int i) {

+		cmdButtons[i] = new Button(parent, SWT.NONE);

+		cmdButtons[i].setText(Operations.INSTANCE.getOperationName(i));

+		cmdButtons[i].addSelectionListener(this);

+	}

+

+	@Override

+	public void widgetDefaultSelected(SelectionEvent arg0) {

+		// empty

+	}

+

+	@Override

+	public void widgetSelected(SelectionEvent event) {

+		String str = ((Button) event.getSource()).getText();

+		calculator.handleButtonClick(str);

+	}

+

+	@Override

+	public void setDisplayText(String text) {

+		display.setText(text);

+	}

+

+	@Override

+	public String getDisplayText() {

+		return display.getText();

+	}

+

+}

diff --git a/org.eclipse.example.calc/tst/org/eclipse/example/calc/internal/CalculatorTest.java b/org.eclipse.example.calc/tst/org/eclipse/example/calc/internal/CalculatorTest.java
new file mode 100644
index 0000000..3741c1c
--- /dev/null
+++ b/org.eclipse.example.calc/tst/org/eclipse/example/calc/internal/CalculatorTest.java
@@ -0,0 +1,95 @@
+/*******************************************************************************

+ * Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com>

+ * Copyright (C) 2010, Stefan Lay <stefan.lay@sap.com>

+ *

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * which accompanies this distribution, and is available at

+ * http://www.eclipse.org/legal/epl-v10.html

+ *******************************************************************************/

+package org.eclipse.example.calc.internal;

+

+import static org.junit.Assert.assertEquals;

+

+import org.eclipse.example.calc.Operations;

+import org.junit.After;

+import org.junit.Test;

+

+public class CalculatorTest {

+

+	@After

+	public void tearDown() throws Exception {

+		Operations.INSTANCE.reset();

+	}

+

+	@Test

+	public void testPlus() {

+		TestTextProvider textProvider = new TestTextProvider();

+		Calculator calculator = new Calculator(textProvider);

+

+		calculator.handleButtonClick("1");

+		calculator.handleButtonClick("+");

+		calculator.handleButtonClick("2");

+		calculator.handleButtonClick("=");

+

+		assertEquals("3.0", textProvider.getDisplayText());

+	}

+

+	@Test

+	public void testTwoTimesPlus() {

+		TestTextProvider textProvider = new TestTextProvider();

+		Calculator calculator = new Calculator(textProvider);

+

+		calculator.handleButtonClick("1");

+		calculator.handleButtonClick("+");

+		calculator.handleButtonClick("2");

+		calculator.handleButtonClick("+");

+		calculator.handleButtonClick("3");

+		calculator.handleButtonClick("=");

+

+		assertEquals("6.0", textProvider.getDisplayText());

+	}

+

+	@Test

+	public void testPlusWithFraction() {

+		TestTextProvider textProvider = new TestTextProvider();

+		Calculator calculator = new Calculator(textProvider);

+

+		calculator.handleButtonClick("1");

+		calculator.handleButtonClick("+");

+		calculator.handleButtonClick("2");

+		calculator.handleButtonClick(".");

+		calculator.handleButtonClick("1");

+		calculator.handleButtonClick("=");

+

+		assertEquals("3.1", textProvider.getDisplayText());

+	}

+

+	@Test

+	public void testSquare() {

+		TestTextProvider textProvider = new TestTextProvider();

+		Calculator calculator = new Calculator(textProvider);

+

+		calculator.handleButtonClick("2");

+		calculator.handleButtonClick("x²");

+

+		assertEquals("4.0", textProvider.getDisplayText());

+	}

+

+	private static final class TestTextProvider implements TextProvider {

+

+		private String text = "0";

+

+		@Override

+		public void setDisplayText(String text) {

+			this.text = text;

+

+		}

+

+		@Override

+		public String getDisplayText() {

+			return text;

+		}

+

+	}

+}