initial import
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/.classpath b/bundles/org.eclipse.rap.rwt.custom.demo/.classpath
new file mode 100644
index 0000000..2fbb7a2
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.4"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/.project b/bundles/org.eclipse.rap.rwt.custom.demo/.project
new file mode 100644
index 0000000..9233b75
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.rap.rwt.custom.demo</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.rap.rwt.custom.demo/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..4cd99ad
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,8 @@
+#Wed Nov 04 14:37:25 CET 2009
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2
+org.eclipse.jdt.core.compiler.compliance=1.4
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=warning
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=warning
+org.eclipse.jdt.core.compiler.source=1.3
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/META-INF/MANIFEST.MF b/bundles/org.eclipse.rap.rwt.custom.demo/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..842b380
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/META-INF/MANIFEST.MF
@@ -0,0 +1,8 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Demo
+Bundle-SymbolicName: org.eclipse.rap.rwt.custom.demo;singleton:=true
+Bundle-Version: 1.0.0.qualifier
+Require-Bundle: org.eclipse.rap.ui;bundle-version="1.3.0",
+ org.eclipse.rap.rwt.custom;bundle-version="0.0.0"
+Bundle-RequiredExecutionEnvironment: J2SE-1.4
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/build.properties b/bundles/org.eclipse.rap.rwt.custom.demo/build.properties
new file mode 100644
index 0000000..e4addeb
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/build.properties
@@ -0,0 +1,6 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               plugin.xml,\
+               theme/
diff --git "a/bundles/org.eclipse.rap.rwt.custom.demo/launch/SpreadSheet Demo \050Profiled\051.launch" "b/bundles/org.eclipse.rap.rwt.custom.demo/launch/SpreadSheet Demo \050Profiled\051.launch"
new file mode 100644
index 0000000..41fbd1a
--- /dev/null
+++ "b/bundles/org.eclipse.rap.rwt.custom.demo/launch/SpreadSheet Demo \050Profiled\051.launch"
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.rap.ui.launch.RAPLauncher">
+<booleanAttribute key="append.args" value="true"/>
+<booleanAttribute key="automaticAdd" value="false"/>
+<booleanAttribute key="automaticValidate" value="true"/>
+<stringAttribute key="bootstrap" value=""/>
+<stringAttribute key="browserMode" value="INTERNAL"/>
+<stringAttribute key="checked" value="[NONE]"/>
+<booleanAttribute key="clearConfig" value="true"/>
+<stringAttribute key="configLocation" value="${workspace_loc}/.metadata/.plugins/org.eclipse.pde.core/SpreadSheet Demo (Profiled)"/>
+<booleanAttribute key="default_auto_start" value="true"/>
+<intAttribute key="default_start_level" value="4"/>
+<stringAttribute key="entryPoint" value="default"/>
+<booleanAttribute key="includeOptional" value="false"/>
+<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -console -consolelog"/>
+<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-agentlib:&quot;C:\\Program Files\\YourKit Java Profiler 8.0.5\\bin\\win32\\yjpagent&quot;&#13;&#10;-Declipse.ignoreApp=true&#13;&#10;-Dosgi.noShutdown=true&#13;&#10;-Xmx512m&#13;&#10;-Dorg.eclipse.equinox.http.jetty.context.sessioninactiveinterval=360"/>
+<stringAttribute key="org.eclipse.rap.launch.browserMode" value="INTERNAL"/>
+<stringAttribute key="org.eclipse.rap.launch.entryPoint" value="spreadsheet"/>
+<stringAttribute key="org.eclipse.rap.launch.libraryVariant" value="STANDARD"/>
+<stringAttribute key="org.eclipse.rap.launch.logLevel" value="OFF"/>
+<booleanAttribute key="org.eclipse.rap.launch.openBrowser" value="true"/>
+<intAttribute key="org.eclipse.rap.launch.port" value="2011"/>
+<stringAttribute key="org.eclipse.rap.launch.servletName" value="spreadsheet"/>
+<booleanAttribute key="org.eclipse.rap.launch.terminatePrevious" value="true"/>
+<booleanAttribute key="org.eclipse.rap.launch.useManualPort" value="true"/>
+<stringAttribute key="pde.version" value="3.3"/>
+<booleanAttribute key="show_selected_only" value="false"/>
+<stringAttribute key="target_bundles" value="org.mortbay.jetty.server@default:default,org.eclipse.core.commands@default:default,org.eclipse.equinox.app@default:default,org.eclipse.equinox.http.servlet@default:default,org.eclipse.equinox.http.jetty@default:default,org.eclipse.help.base@default:default,org.eclipse.core.databinding.observable@default:default,org.eclipse.core.databinding@default:default,org.eclipse.core.contenttype@default:default,org.eclipse.equinox.http.registry@default:default,org.eclipse.core.resources@default:default,org.eclipse.core.expressions@default:default,org.eclipse.equinox.jsp.jasper.registry@default:default,org.eclipse.osgi@-1:true,org.apache.lucene@default:default,org.apache.jasper@default:default,org.apache.commons.el@default:default,org.eclipse.core.filesystem@default:default,org.eclipse.core.jobs@default:default,javax.servlet.jsp@default:default,org.eclipse.help.webapp@default:default,org.eclipse.equinox.registry@default:default,org.eclipse.help.appserver@default:default,org.eclipse.equinox.common@2:true,org.mortbay.jetty.util@default:default,org.eclipse.core.runtime@default:true,org.eclipse.osgi.services@default:default,org.eclipse.equinox.jsp.jasper@default:default,com.ibm.icu@default:default,org.eclipse.equinox.preferences@default:default,javax.servlet@default:default,org.eclipse.core.variables@default:default,org.eclipse.help@default:default,org.eclipse.core.databinding.property@default:default,org.apache.lucene.analysis@default:default,org.apache.commons.logging@default:default,org.eclipse.ant.core@default:default"/>
+<booleanAttribute key="terminatePrevious" value="true"/>
+<booleanAttribute key="tracing" value="false"/>
+<booleanAttribute key="useDefaultConfigArea" value="true"/>
+<stringAttribute key="workspace_bundles" value="org.eclipse.rap.rwt@default:default,org.eclipse.rap.jface.databinding@default:default,org.eclipse.rap.rwt.custom.demo@default:default,org.eclipse.rap.ui.views@default:default,org.eclipse.rap.jface@default:default,org.eclipse.rap.ui.workbench@default:default,org.eclipse.rap.ui@default:default,org.eclipse.rap.rwt.q07@default:false,org.eclipse.rap.rwt.custom@default:default"/>
+</launchConfiguration>
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/launch/SpreadSheet Demo.launch b/bundles/org.eclipse.rap.rwt.custom.demo/launch/SpreadSheet Demo.launch
new file mode 100644
index 0000000..65636b3
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/launch/SpreadSheet Demo.launch
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.rap.ui.launch.RAPLauncher">
+<booleanAttribute key="append.args" value="true"/>
+<booleanAttribute key="automaticAdd" value="false"/>
+<booleanAttribute key="automaticValidate" value="true"/>
+<stringAttribute key="bootstrap" value=""/>
+<stringAttribute key="browserMode" value="INTERNAL"/>
+<stringAttribute key="checked" value="[NONE]"/>
+<booleanAttribute key="clearConfig" value="true"/>
+<stringAttribute key="configLocation" value="${workspace_loc}/.metadata/.plugins/org.eclipse.pde.core/SpreadSheet Demo"/>
+<booleanAttribute key="default_auto_start" value="true"/>
+<intAttribute key="default_start_level" value="4"/>
+<stringAttribute key="entryPoint" value="default"/>
+<booleanAttribute key="includeOptional" value="false"/>
+<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -console -consolelog"/>
+<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Declipse.ignoreApp=true&#13;&#10;-Dosgi.noShutdown=true&#13;&#10;-Xmx512m&#13;&#10;-Dorg.eclipse.equinox.http.jetty.context.sessioninactiveinterval=360"/>
+<stringAttribute key="org.eclipse.rap.launch.browserMode" value="INTERNAL"/>
+<stringAttribute key="org.eclipse.rap.launch.entryPoint" value="spreadsheet"/>
+<stringAttribute key="org.eclipse.rap.launch.libraryVariant" value="STANDARD"/>
+<stringAttribute key="org.eclipse.rap.launch.logLevel" value="OFF"/>
+<booleanAttribute key="org.eclipse.rap.launch.openBrowser" value="true"/>
+<intAttribute key="org.eclipse.rap.launch.port" value="2010"/>
+<stringAttribute key="org.eclipse.rap.launch.servletName" value="spreadsheet"/>
+<booleanAttribute key="org.eclipse.rap.launch.terminatePrevious" value="true"/>
+<booleanAttribute key="org.eclipse.rap.launch.useManualPort" value="true"/>
+<stringAttribute key="pde.version" value="3.3"/>
+<booleanAttribute key="show_selected_only" value="false"/>
+<stringAttribute key="target_bundles" value="org.mortbay.jetty.server@default:default,org.eclipse.core.commands@default:default,org.eclipse.equinox.app@default:default,org.eclipse.equinox.http.servlet@default:default,org.eclipse.equinox.http.jetty@default:default,org.eclipse.help.base@default:default,org.eclipse.core.databinding.observable@default:default,org.eclipse.core.databinding@default:default,org.eclipse.core.contenttype@default:default,org.eclipse.equinox.http.registry@default:default,org.eclipse.core.resources@default:default,org.eclipse.core.expressions@default:default,org.eclipse.equinox.jsp.jasper.registry@default:default,org.eclipse.osgi@-1:true,org.apache.lucene@default:default,org.apache.jasper@default:default,org.apache.commons.el@default:default,org.eclipse.core.filesystem@default:default,org.eclipse.core.jobs@default:default,javax.servlet.jsp@default:default,org.eclipse.help.webapp@default:default,org.eclipse.equinox.registry@default:default,org.eclipse.help.appserver@default:default,org.eclipse.equinox.common@2:true,org.mortbay.jetty.util@default:default,org.eclipse.core.runtime@default:true,org.eclipse.osgi.services@default:default,org.eclipse.equinox.jsp.jasper@default:default,com.ibm.icu@default:default,org.eclipse.equinox.preferences@default:default,javax.servlet@default:default,org.eclipse.core.variables@default:default,org.eclipse.help@default:default,org.eclipse.core.databinding.property@default:default,org.apache.lucene.analysis@default:default,org.apache.commons.logging@default:default,org.eclipse.ant.core@default:default"/>
+<booleanAttribute key="terminatePrevious" value="true"/>
+<booleanAttribute key="tracing" value="false"/>
+<booleanAttribute key="useDefaultConfigArea" value="true"/>
+<stringAttribute key="workspace_bundles" value="org.eclipse.rap.rwt@default:default,org.eclipse.rap.jface.databinding@default:default,org.eclipse.rap.rwt.custom.demo@default:default,org.eclipse.rap.ui.views@default:default,org.eclipse.rap.jface@default:default,org.eclipse.rap.ui.workbench@default:default,org.eclipse.rap.ui@default:default,org.eclipse.rap.rwt.q07@default:false,org.eclipse.rap.rwt.custom@default:default"/>
+</launchConfiguration>
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/plugin.xml b/bundles/org.eclipse.rap.rwt.custom.demo/plugin.xml
new file mode 100644
index 0000000..2d2cc69
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/plugin.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.4"?>
+<plugin>
+  <extension
+        point="org.eclipse.rap.ui.branding">
+     <branding
+           defaultEntrypointId="org.eclipse.rap.rwt.custom.demo.spreadsheet"
+           id="org.eclipse.rap.rwt.custom.demo.spreadsheet"
+           servletName="spreadsheet"
+           themeId="org.eclipse.rap.rwt.custom.demo.theme"
+           title="Spreadsheet Demo">
+     </branding>
+  </extension>
+  <extension
+        point="org.eclipse.rap.ui.entrypoint">
+     <entrypoint
+           class="org.eclipse.rap.rwt.custom.demo.spreadsheet.SpreadSheetEntryPoint"
+           id="org.eclipse.rap.rwt.custom.demo.spreadsheet"
+           parameter="spreadsheet">
+     </entrypoint>
+  </extension>
+  <extension
+        point="org.eclipse.rap.ui.themes">
+     <theme
+           file="theme/theme.css"
+           id="org.eclipse.rap.rwt.custom.demo.theme"
+           name="Custom Demo Theme">
+     </theme>
+  </extension>
+  <extension
+        point="org.eclipse.ui.perspectives">
+     <perspective
+           class="org.eclipse.rap.rwt.custom.demo.spreadsheet.SpreadSheetPerspective"
+           id="org.eclipse.rap.rwt.custom.demo.perspectives.spreadsheet"
+           name="Spreadsheet Perspective">
+     </perspective>
+  </extension>
+  <extension
+        point="org.eclipse.ui.views">
+     <view
+            class="org.eclipse.rap.rwt.custom.demo.spreadsheet.SpreadSheetView"
+            id="org.eclipse.rap.rwt.custom.demo.views.spreadsheet"
+            name="Spreadsheet"
+            restorable="true">
+     </view>
+  </extension>
+</plugin>
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/src/org/eclipse/rap/rwt/custom/demo/spreadsheet/SpreadSheetEntryPoint.java b/bundles/org.eclipse.rap.rwt.custom.demo/src/org/eclipse/rap/rwt/custom/demo/spreadsheet/SpreadSheetEntryPoint.java
new file mode 100644
index 0000000..d9485a0
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/src/org/eclipse/rap/rwt/custom/demo/spreadsheet/SpreadSheetEntryPoint.java
@@ -0,0 +1,15 @@
+package org.eclipse.rap.rwt.custom.demo.spreadsheet;
+
+import org.eclipse.rwt.lifecycle.IEntryPoint;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.PlatformUI;
+
+
+public class SpreadSheetEntryPoint implements IEntryPoint {
+
+  public int createUI() {
+    Display display = PlatformUI.createDisplay();
+    SpreadSheetWorkbenchAdvisor advisor = new SpreadSheetWorkbenchAdvisor();
+    return PlatformUI.createAndRunWorkbench( display, advisor );
+  }
+}
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/src/org/eclipse/rap/rwt/custom/demo/spreadsheet/SpreadSheetPerspective.java b/bundles/org.eclipse.rap.rwt.custom.demo/src/org/eclipse/rap/rwt/custom/demo/spreadsheet/SpreadSheetPerspective.java
new file mode 100644
index 0000000..071f706
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/src/org/eclipse/rap/rwt/custom/demo/spreadsheet/SpreadSheetPerspective.java
@@ -0,0 +1,18 @@
+package org.eclipse.rap.rwt.custom.demo.spreadsheet;
+
+import org.eclipse.ui.*;
+
+
+public class SpreadSheetPerspective implements IPerspectiveFactory {
+
+  public void createInitialLayout( final IPageLayout layout ) {
+    String editorArea = layout.getEditorArea();
+    layout.setEditorAreaVisible( false );
+    String viewId = "org.eclipse.rap.rwt.custom.demo.views.spreadsheet";
+    layout.addStandaloneView( viewId, 
+                              false,
+                              IPageLayout.LEFT,
+                              0.75f,
+                              editorArea );
+  }
+}
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/src/org/eclipse/rap/rwt/custom/demo/spreadsheet/SpreadSheetView.java b/bundles/org.eclipse.rap.rwt.custom.demo/src/org/eclipse/rap/rwt/custom/demo/spreadsheet/SpreadSheetView.java
new file mode 100644
index 0000000..05efd42
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/src/org/eclipse/rap/rwt/custom/demo/spreadsheet/SpreadSheetView.java
@@ -0,0 +1,25 @@
+package org.eclipse.rap.rwt.custom.demo.spreadsheet;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SpreadSheet;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.part.ViewPart;
+
+
+public class SpreadSheetView extends ViewPart {
+
+  private SpreadSheet spreadSheet;
+
+  public void createPartControl( final Composite parent ) {
+    spreadSheet = new SpreadSheet( parent, SWT.BORDER );
+//    for( int i = 0; i < 100; i++ ) {
+//      for( int j = 0; j < 100; j++ ) {
+//        spreadSheet.setText( i + "_" + j, i, j );
+//      }
+//    }
+  }
+
+  public void setFocus() {
+    spreadSheet.setFocus();
+  }
+}
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/src/org/eclipse/rap/rwt/custom/demo/spreadsheet/SpreadSheetWorkbenchAdvisor.java b/bundles/org.eclipse.rap.rwt.custom.demo/src/org/eclipse/rap/rwt/custom/demo/spreadsheet/SpreadSheetWorkbenchAdvisor.java
new file mode 100644
index 0000000..dc41035
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/src/org/eclipse/rap/rwt/custom/demo/spreadsheet/SpreadSheetWorkbenchAdvisor.java
@@ -0,0 +1,17 @@
+package org.eclipse.rap.rwt.custom.demo.spreadsheet;
+
+import org.eclipse.ui.application.*;
+
+
+public class SpreadSheetWorkbenchAdvisor extends WorkbenchAdvisor {
+
+  public String getInitialWindowPerspectiveId() {
+    return "org.eclipse.rap.rwt.custom.demo.perspectives.spreadsheet";
+  }
+  
+  public WorkbenchWindowAdvisor createWorkbenchWindowAdvisor(
+    final IWorkbenchWindowConfigurer configurer )
+  {
+    return new SpreadSheetWorkbenchWindowAdvisor( configurer );
+  }
+}
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/src/org/eclipse/rap/rwt/custom/demo/spreadsheet/SpreadSheetWorkbenchWindowAdvisor.java b/bundles/org.eclipse.rap.rwt.custom.demo/src/org/eclipse/rap/rwt/custom/demo/spreadsheet/SpreadSheetWorkbenchWindowAdvisor.java
new file mode 100644
index 0000000..f2cb7e8
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/src/org/eclipse/rap/rwt/custom/demo/spreadsheet/SpreadSheetWorkbenchWindowAdvisor.java
@@ -0,0 +1,32 @@
+package org.eclipse.rap.rwt.custom.demo.spreadsheet;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.application.IWorkbenchWindowConfigurer;
+import org.eclipse.ui.application.WorkbenchWindowAdvisor;
+
+
+public class SpreadSheetWorkbenchWindowAdvisor extends WorkbenchWindowAdvisor {
+
+  public SpreadSheetWorkbenchWindowAdvisor(
+    final IWorkbenchWindowConfigurer configurer )
+  {
+    super( configurer );
+  }
+  
+  
+  public void preWindowOpen() {
+    IWorkbenchWindowConfigurer configurer = getWindowConfigurer();
+    Rectangle bounds = Display.getCurrent().getBounds();
+    configurer.setInitialSize( new Point( bounds.width, bounds.height ) );
+    configurer.setShowCoolBar( false );
+    configurer.setShowPerspectiveBar( false );
+    configurer.setShowProgressIndicator( false );
+    configurer.setShowStatusLine( false );
+    configurer.setShowMenuBar( false );
+    configurer.setTitle( "Spreadsheet Demo" );
+    configurer.setShellStyle( SWT.TITLE | SWT.RESIZE );
+  }
+}
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/check-grayed-hover.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/check-grayed-hover.png
new file mode 100644
index 0000000..7297274
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/check-grayed-hover.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/check-grayed.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/check-grayed.png
new file mode 100644
index 0000000..08ed526
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/check-grayed.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/check-selected-hover.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/check-selected-hover.png
new file mode 100644
index 0000000..472a085
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/check-selected-hover.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/check-selected.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/check-selected.png
new file mode 100644
index 0000000..69d2b70
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/check-selected.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/check-unselected-hover.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/check-unselected-hover.png
new file mode 100644
index 0000000..2193e15
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/check-unselected-hover.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/check-unselected.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/check-unselected.png
new file mode 100644
index 0000000..eb270d2
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/check-unselected.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/chevron-down-small.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/chevron-down-small.png
new file mode 100644
index 0000000..0cc88fe
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/chevron-down-small.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/chevron-down.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/chevron-down.png
new file mode 100644
index 0000000..e3a1adc
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/chevron-down.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/chevron-up-small.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/chevron-up-small.png
new file mode 100644
index 0000000..c13542d
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/chevron-up-small.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/expanditem-collapse.gif b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/expanditem-collapse.gif
new file mode 100644
index 0000000..5ddb8b8
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/expanditem-collapse.gif
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/expanditem-expand-hover.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/expanditem-expand-hover.png
new file mode 100644
index 0000000..98336d2
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/expanditem-expand-hover.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/expanditem-expand.gif b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/expanditem-expand.gif
new file mode 100644
index 0000000..307395c
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/expanditem-expand.gif
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/radio-selected-hover.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/radio-selected-hover.png
new file mode 100644
index 0000000..6c7c702
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/radio-selected-hover.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/radio-selected.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/radio-selected.png
new file mode 100644
index 0000000..44a72e6
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/radio-selected.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/radio-unselected-hover.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/radio-unselected-hover.png
new file mode 100644
index 0000000..51e6e90
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/radio-unselected-hover.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/radio-unselected.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/radio-unselected.png
new file mode 100644
index 0000000..54f8268
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/radio-unselected.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-active-background.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-active-background.png
new file mode 100644
index 0000000..96798d4
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-active-background.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-close-hover.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-close-hover.png
new file mode 100644
index 0000000..2df60b3
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-close-hover.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-close.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-close.png
new file mode 100644
index 0000000..13de98d
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-close.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-inactive-background.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-inactive-background.png
new file mode 100644
index 0000000..a146a32
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-inactive-background.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-max-hover.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-max-hover.png
new file mode 100644
index 0000000..6958bf3
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-max-hover.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-max.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-max.png
new file mode 100644
index 0000000..24bdbc4
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-max.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-min-hover.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-min-hover.png
new file mode 100644
index 0000000..ccd4958
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-min-hover.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-min.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-min.png
new file mode 100644
index 0000000..7230432
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/shell-min.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_active_close_active.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_active_close_active.png
new file mode 100644
index 0000000..d0b4147
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_active_close_active.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_active_close_active_hover.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_active_close_active_hover.png
new file mode 100644
index 0000000..b4771f3
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_active_close_active_hover.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_inactive_close_active.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_inactive_close_active.png
new file mode 100644
index 0000000..ae5a824
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_inactive_close_active.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_inactive_close_active_hover.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_inactive_close_active_hover.png
new file mode 100644
index 0000000..5737864
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_inactive_close_active_hover.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_overflow_active.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_overflow_active.png
new file mode 100644
index 0000000..d270caa
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_overflow_active.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_overflow_active_hover.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_overflow_active_hover.png
new file mode 100644
index 0000000..1beb59a
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_overflow_active_hover.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_overflow_inactive.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_overflow_inactive.png
new file mode 100644
index 0000000..1ea81f9
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_overflow_inactive.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_overflow_inactive_hover.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_overflow_inactive_hover.png
new file mode 100644
index 0000000..0b9f3a5
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/stack_tab_overflow_inactive_hover.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/toolbarButtonBg.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/toolbarButtonBg.png
new file mode 100644
index 0000000..aed08df
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/toolbarButtonBg.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/toolbar_overflow.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/toolbar_overflow.png
new file mode 100644
index 0000000..1958365
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/toolbar_overflow.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/toolbar_overflow_active.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/toolbar_overflow_active.png
new file mode 100644
index 0000000..d856c24
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/toolbar_overflow_active.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/toolbar_overflow_hover.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/toolbar_overflow_hover.png
new file mode 100644
index 0000000..57ec1ef
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/toolbar_overflow_hover.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/toolbar_overflow_hover_active.png b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/toolbar_overflow_hover_active.png
new file mode 100644
index 0000000..75bcb42
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/icons/toolbar_overflow_hover_active.png
Binary files differ
diff --git a/bundles/org.eclipse.rap.rwt.custom.demo/theme/theme.css b/bundles/org.eclipse.rap.rwt.custom.demo/theme/theme.css
new file mode 100644
index 0000000..4e466f7
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom.demo/theme/theme.css
@@ -0,0 +1,501 @@
+
+* {
+  color: #4a4a4a;
+  background-color: white;
+  font: 12px "Trebuchet MS", Arial, Helvetica, sans-serif;
+}
+
+*:disabled {
+  color: #CFCFCF;
+}
+
+*[BORDER] {
+  border: 1px solid #C1C1C1;
+}
+
+
+/* Button */
+
+Button[PUSH], Button[TOGGLE], Button[BORDER] {
+  border: 1px solid #A4A4A4;
+  border-radius: 3px;
+  padding: 2px 5px;
+  /* background-color: #f5f6f6; */
+  background-image: gradient(
+    linear, left top, left bottom,
+    from( #ffffff ),
+    color-stop( 48%, #f0f0f0 ),
+    color-stop( 52%, #e0e0e0 ),
+    to( #cccccc )
+  );
+}
+
+Button[PUSH]:pressed, Button[TOGGLE]:pressed {
+  background-image: gradient(
+    linear, left top, left bottom,
+    from( #e0e0e0 ),
+    color-stop( 52%, #e0e0e0 ),
+    to( #b0b0b0 )
+  );
+  padding: 4px 5px 2px 7px;
+}
+
+Button[TOGGLE]:selected {
+  /* background-color: #d3d3d3; */
+  background-image: gradient(
+    linear, left top, left bottom,
+    from( #e0e0e0 ),
+    color-stop( 52%, #e0e0e0 ),
+    to( #b0b0b0 )
+  );
+}
+
+Button:hover {
+  background-color: white;
+}
+
+Button.clearButton {
+  background-color: transparent;
+  background-image: none;
+  border: none;
+  padding: 2px;
+}
+
+Button.viewClose {
+  background-color: transparent;
+  background-image: url( /theme/icons/stack_tab_active_close_active.png );
+  border: none;
+  padding: 2px;
+}
+
+Button.viewClose:hover {
+  background-image: url( /theme/icons/stack_tab_active_close_active_hover.png );
+}
+
+Button.viewCloseInactive {
+  background-color: transparent;
+  background-image: url( /theme/icons/stack_tab_inactive_close_active.png );
+  border: none;
+}
+
+Button.viewCloseInactive:hover {
+  background-image: url( /theme/icons/stack_tab_inactive_close_active_hover.png );
+}
+
+Button.toolbarOverflowInactive {
+  background-color: transparent;
+  background-image: url( /theme/icons/toolbar_overflow.png );
+  border: none;
+  padding: 2px;
+}
+
+Button.toolbarOverflowInactive:hover {
+  background-image: url( /theme/icons/toolbar_overflow_hover.png );
+}
+
+Button.toolbarOverflowActive {
+  background-color: transparent;
+  background-image: url( /theme/icons/toolbar_overflow_active.png );
+  border: none;
+  padding: 2px;
+}
+
+Button.toolbarOverflowActive:hover {
+  background-image: url( /theme/icons/toolbar_overflow_hover_active.png );
+}
+
+Button.menuBar {
+  background-color: transparent;
+  background-image: none;
+  border: none;
+  color: rgb( 0, 89, 165 );
+  font: 13px Arial, Helvetica, sans-serif;
+}
+
+Button.coolBar {
+  background-color: transparent;
+  background-image: none;
+  border: none;
+  spacing: 6px;
+  color: rgb( 255, 255, 255 );
+  font: 12px "Trebuchet MS", Arial, Helvetica, sans-serif;
+}
+
+Button.coolBar:hover {
+  background-image: url( /theme/icons/toolbarButtonBg.png );
+}
+
+Button.coolBarPulldown {
+  background-color: transparent;
+  background-image: none;
+  border: none;
+  color: rgb( 255, 255, 255 );
+  font: 12px "Trebuchet MS", Arial, Helvetica, sans-serif;
+}
+
+Button.coolBarPulldown:hover {
+  background-image: url( /theme/icons/toolbarButtonBg.png );
+}
+
+Button.partActive {
+  background-color: transparent;
+  background-image: none;
+  border: none;
+  color: rgb( 252, 252, 252 );
+  font: 11px "Trebuchet MS", Arial, Helvetica, sans-serif;
+}
+
+Button.partInactive {
+  background-color: transparent;
+  background-image: none;
+  border: none;
+  color: rgb( 102, 102, 102 );
+  font: 11px "Trebuchet MS", Arial, Helvetica, sans-serif;
+}
+
+Button.inactivePerspective {
+  background-color: transparent;
+  background-image: none;
+  border: none;
+  color: rgb( 0, 80, 145 );
+}
+
+Button.tabOverflowActive {
+  background-color: transparent;
+  border: none;
+  padding: 2px;
+  background-image: url( /theme/icons/stack_tab_overflow_active.png );
+}
+
+Button.tabOverflowActive:hover {
+  background-image: url( /theme/icons/stack_tab_overflow_active_hover.png );
+}
+
+Button.tabOverflowInactive {
+  background-color: transparent;
+  border: none;
+  padding: 2px;
+  background-image: url( /theme/icons/stack_tab_overflow_inactive.png );
+}
+
+Button.tabOverflowInactive:hover {
+  background-image: url( /theme/icons/stack_tab_overflow_inactive_hover.png );
+}
+
+
+/* Shell */
+
+Shell-Titlebar {
+  color: white;
+  /* background-image: url( /theme/icons/shell-active-background.png ); */
+  background-image: gradient( linear, left top, left bottom,
+                              from( #005fac ), to( #002092 ) );
+  padding: 2px 5px 2px;
+  margin: 0px;
+  height: 27px;
+  font: bold 14px Verdana, "Lucida Sans", Arial, Helvetica, sans-serif;
+  border: none;
+  border-radius: 5px 5px 0px 0px;
+}
+
+Shell-Titlebar:inactive {
+  color: #aaaaaa;
+  /* background-image: url( /theme/icons/shell-inactive-background.png ); */
+  background-image: gradient( linear, left top, left bottom,
+                              from( #595959 ), to( #4b4b4b ) );
+}
+
+Shell {
+  background-color: white;
+}
+
+Shell[BORDER], Shell[TITLE] {
+  border: 1px solid #005092;
+  border-radius: 6px;
+  padding: 5px;
+}
+
+Shell[BORDER]:inactive, Shell[TITLE]:inactive {
+  border: 1px solid #4b4b4b;
+}
+
+Shell-CloseButton {
+  background-image: url( "/theme/icons/shell-close.png" );
+  margin: 0px 5px 0px 0px;
+}
+
+Shell-MaxButton {
+  background-image: url( "/theme/icons/shell-max.png" );
+  margin: 0px 6px 0px 0px;
+}
+
+Shell-MinButton {
+  background-image: url( "/theme/icons/shell-min.png" );
+  margin: 0px 6px 0px 0px;
+}
+
+Shell-CloseButton:hover {
+  background-image: url( "/theme/icons/shell-close-hover.png" );
+}
+
+Shell-MaxButton:hover {
+  background-image: url( "/theme/icons/shell-max-hover.png" );
+}
+
+Shell-MinButton:hover {
+  background-image: url( "/theme/icons/shell-min-hover.png" );
+}
+
+Shell.shellGray {
+  background-color: rgb( 235, 235, 235 );
+}
+
+Shell.toolbarLayer {
+  background-color: rgb( 255, 255, 255 );
+  border: none;
+}
+
+
+/* Table */
+
+Table-Column.overflow {
+  background-color: transparent;
+}
+
+TableItem.overflow {
+  background-color: transparent;
+}
+
+TableItem.overflow:even {
+  background-color: transparent;
+}
+
+Table-Column {
+  background-color: #f3f3f4;
+}
+
+Table-Cell {
+  spacing: 3px;
+  padding: 5px;
+}
+
+List-Item, TableItem {
+  background-color: transparent;
+}
+
+/*
+ disable alt row colors for Lists as they look weird inside examples' ExpandBar:
+ List-Item:even,
+ */
+TableItem:even {
+  background-color: #f3f3f4;
+}
+
+
+/* Check Buttons */
+
+Button-CheckIcon {
+  background-image: url( /theme/icons/check-unselected.png );
+}
+
+Button-CheckIcon:selected {
+  background-image: url( /theme/icons/check-selected.png );
+}
+
+Button-CheckIcon:grayed {
+  background-image: url( /theme/icons/check-grayed.png );
+}
+
+Button-CheckIcon:hover {
+  background-image: url( /theme/icons/check-unselected-hover.png );
+}
+
+Button-CheckIcon:selected:hover {
+  background-image: url( /theme/icons/check-selected-hover.png );
+}
+
+Button-CheckIcon:grayed:hover {
+  background-image: url( /theme/icons/check-grayed-hover.png );
+}
+
+
+/* Radio Buttons */
+
+Button-RadioIcon {
+  background-image: url( /theme/icons/radio-unselected.png );
+}
+
+Button-RadioIcon:selected {
+  background-image: url( /theme/icons/radio-selected.png );
+}
+
+Button-RadioIcon:hover {
+  background-image: url( /theme/icons/radio-unselected-hover.png );
+}
+
+Button-RadioIcon:selected:hover {
+  background-image: url( /theme/icons/radio-selected-hover.png );
+}
+
+
+/* Menu */
+
+Menu {
+  color: rgb( 0, 89, 165 );;
+  background-color: white;
+  border: 1px rgb( 0, 89, 165 );
+}
+
+
+/* Selection color */
+
+List-Item:selected, TableItem:selected, TreeItem:selected {
+  background-color: #D2D2D2;
+  color: #4a4a4a;
+}
+
+.menuBarPopup {
+  border: none;
+}
+
+Label.menuBorder {
+  background-color: rgb( 221, 221, 221 );
+}
+
+Label.stackBorder {
+  background-color: rgb( 235, 235, 235 );
+}
+
+Label.standaloneView {
+  background-color: transparent;
+  border: none;
+  color: rgb( 102, 102, 102 );
+  font: 11px "Trebuchet MS", Arial, Helvetica, sans-serif;
+}
+
+Tree {
+  border: none;
+}
+
+.compGray {
+  background-color: rgb( 235, 235, 235 );
+}
+
+.compTrans {
+  background-color: transparent;
+}
+
+.partBorder {
+  border: rgb( 235, 0, 0 );
+}
+
+.compTransNoBorder {
+  border: none;
+  background-color: rgb( 252, 252, 252 );
+}
+
+List, Text, Combo, Tree, Table {
+  background-color: rgb( 252, 252, 252 );
+}
+
+.formKey {
+  font: 11px "Trebuchet MS", Arial, Helvetica, sans-serif;
+  color: gray;
+}
+
+.formValue {
+  font: bold 14px "Trebuchet MS", Arial, Helvetica, sans-serif;
+}
+
+
+/* Expand Bar */
+
+ExpandBar {
+  color: white;
+}
+
+ExpandItem-Header {
+  background-color: #005397;
+}
+
+ExpandItem-Button {
+  background-image: url( /theme/icons/expanditem-expand.gif );
+}
+
+ExpandItem-Button:hover {
+  background-image: url( /theme/icons/expanditem-expand-hover.png );
+}
+
+ExpandItem-Button:expanded {
+  background-image: url( /theme/icons/expanditem-collapse.gif );
+}
+
+
+/* Text, Combo, Spinner, DateTime */
+
+/* Combo always shows a border, the BORDER style flag is ignored by SWT */
+Text[BORDER], Combo, CCombo[BORDER], Spinner[BORDER], DateTime[BORDER] {
+  border: 1px solid #c1c1c1;
+  border-radius: 2px;
+}
+
+Combo-Button, CCombo-Button, Spinner-UpButton, Spinner-DownButton, DateTime-UpButton, DateTime-DownButton {
+  background-color: transparent;
+  border: none;
+}
+
+Spinner-UpButton, DateTime-UpButton {
+  background-image: url( /theme/icons/chevron-up-small.png );
+}
+
+Spinner-DownButton, DateTime-DownButton {
+  background-image: url( /theme/icons/chevron-down-small.png );
+  /* background-color: #c1c1c1; */
+}
+
+Combo-Button:hover, CCombo-Button:hover,
+Spinner-UpButton:hover, Spinner-DownButton:hover,
+DateTime-UpButton:hover, DateTime-DownButton:hover {
+  background-color: #e2e2e2;
+}
+
+Combo-Button, CCombo-Button {
+  background-image: url( /theme/icons/chevron-down.png );
+}
+
+DateTime-Field:selected, DateTime-Calendar-Day:selected {
+  background-color: #D2D2D2;
+  color: #4a4a4a;
+}
+
+DateTime-Calendar-Day:otherMonth {
+  background-color: transparent;
+  color: rgb( 128, 128, 128 );
+}
+
+
+/* Group */
+
+Group-Frame {
+  padding: 5px;
+  border-radius: 5px 5px 0px 0px;
+}
+
+
+/* Link */
+
+Link-Hyperlink {
+  color: #195d94;
+}
+
+
+/* Progress Bar */
+
+ProgressBar {
+  background-color: #ffffff;
+  border: 1px solid #747a81;
+}
+
+ProgressBar-Indicator {
+  background-color: #7bb0db;
+}
diff --git a/bundles/org.eclipse.rap.rwt.custom/.classpath b/bundles/org.eclipse.rap.rwt.custom/.classpath
new file mode 100644
index 0000000..2fbb7a2
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.4"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/bundles/org.eclipse.rap.rwt.custom/.project b/bundles/org.eclipse.rap.rwt.custom/.project
new file mode 100644
index 0000000..8bb70a0
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.rap.rwt.custom</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/bundles/org.eclipse.rap.rwt.custom/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.rap.rwt.custom/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..711c15d
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,8 @@
+#Fri Aug 07 21:31:03 CEST 2009
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2
+org.eclipse.jdt.core.compiler.compliance=1.4
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=warning
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=warning
+org.eclipse.jdt.core.compiler.source=1.3
diff --git a/bundles/org.eclipse.rap.rwt.custom/META-INF/MANIFEST.MF b/bundles/org.eclipse.rap.rwt.custom/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..7f29cd4
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom/META-INF/MANIFEST.MF
@@ -0,0 +1,9 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %Bundle-Name
+Bundle-SymbolicName: org.eclipse.rap.rwt.custom
+Bundle-Version: 0.0.0.qualifier
+Bundle-Vendor: %Bundle-Vendor
+Bundle-RequiredExecutionEnvironment: J2SE-1.4
+Export-Package: org.eclipse.swt.custom
+Require-Bundle: org.eclipse.rap.rwt;bundle-version="1.3.0"
diff --git a/bundles/org.eclipse.rap.rwt.custom/about.html b/bundles/org.eclipse.rap.rwt.custom/about.html
new file mode 100644
index 0000000..248f88c
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom/about.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
+<title>About</title>
+</head>
+<body lang="EN-US">
+<h2>About This Content</h2>
+ 
+<p>August 08, 2009</p>	
+<h3>License</h3>
+
+<p>The Eclipse Foundation makes available all content in this plug-in (&quot;Content&quot;).  Unless otherwise 
+indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 (&quot;EPL&quot;).  A copy of the EPL is available 
+at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>.
+For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
+
+<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is 
+being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may
+apply to your use of any object code in the Content.  Check the Redistributor's license that was 
+provided with the Content.  If no such license exists, contact the Redistributor.  Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content
+and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p>
+
+</body>
+</html>
diff --git a/bundles/org.eclipse.rap.rwt.custom/build.properties b/bundles/org.eclipse.rap.rwt.custom/build.properties
new file mode 100644
index 0000000..34d2e4d
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom/build.properties
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/bundles/org.eclipse.rap.rwt.custom/plugin.properties b/bundles/org.eclipse.rap.rwt.custom/plugin.properties
new file mode 100644
index 0000000..7ac0385
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom/plugin.properties
@@ -0,0 +1,3 @@
+
+Bundle-Name = RWT Custom
+Bundle-Vendor = Eclipse.org - RAP
diff --git a/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/CellController.java b/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/CellController.java
new file mode 100644
index 0000000..5845dcf
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/CellController.java
@@ -0,0 +1,360 @@
+// Created on 16.08.2009
+package org.eclipse.swt.custom;
+
+import java.util.*;
+
+import org.eclipse.rwt.graphics.Graphics;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SpreadSheetModel.Adapter;
+import org.eclipse.swt.custom.SpreadSheetModel.Event;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.*;
+
+
+class CellController {
+  static final int DEFAULT_GRID_LINE_BREADTH = 1;
+  private static final String CELL_CONTROL_MARKER
+    = SpreadSheetLayout.class.getName() + "#Cell";
+
+  private final Composite spreadSheet;
+  private final Composite cellContainer;
+  private final SpreadSheetModel model;
+  private Label[] gridLines;
+
+
+  private final class ModelAdapter extends Adapter {
+    public void textChanged( final Event evt ) {
+      int row = evt.rowIndex;
+      int column = evt.columnIndex;
+      CellPosition position
+        = new CellPosition( row, column );
+      setText( evt.text, position );
+    }
+  }
+
+
+  CellController( final Composite spreadSheet,
+                  final SpreadSheetModel model )
+  {
+    SpreadSheetUtils.checkNotNull( spreadSheet, "spreadSheet" );
+    SpreadSheetUtils.checkNotNull( model, "model" );
+
+    this.spreadSheet = spreadSheet;
+    cellContainer = new Composite( spreadSheet, SWT.NONE );
+    cellContainer.setBackground( spreadSheet.getBackground() );
+    this.model = model;
+    this.gridLines = new Label[ 0 ];
+    model.addListener( new ModelAdapter() );
+  }
+
+  void adjustCellControls( ) {
+    // TODO [fappel]: check the algorithm in relation to memory consumption
+    //                and performance
+    cellContainer.moveBelow( null );
+    computeCellContainerBounds();
+    adjustGrid();
+    adjustCellLayoutData();
+    Map buffer = bufferExistingCells();
+    createMissingCells( buffer );
+    disposeOfSpareCells( buffer );
+    Rectangle clientArea = cellContainer.getClientArea();
+    Control[] children = cellContainer.getChildren();
+    for( int i = 0; i < children.length; i++ ) {
+      SpreadSheetUtils.computeBounds( clientArea, children[ i ], model );
+    }
+    updateText();
+  }
+
+  private void adjustGrid() {
+    adjustGridControlNumber();
+
+    int columnOffset = model.getColumnOffset();
+    int rowOffset = model.getRowOffset();
+    int xBase = SpreadSheetUtils.calcXPosition( 0, 0, columnOffset, model );
+    int yBase = SpreadSheetUtils.calcYPosition( 0, 0, rowOffset, model );
+    int gridLineIndex = 0;
+    
+    for( int i = 1; i < model.getVisibleRows(); i++ ) {
+      int index = rowOffset + i;
+      int yPos = yBase;
+      yPos =   SpreadSheetUtils.calcYPosition( yPos, rowOffset, index, model )
+             - DEFAULT_GRID_LINE_BREADTH;
+      int width = cellContainer.getClientArea().width;
+      Label line = gridLines[ gridLineIndex ];
+      line.setBounds( xBase, yPos, width, DEFAULT_GRID_LINE_BREADTH );
+      gridLineIndex++;
+    }
+    
+    for( int i = 1; i < model.getVisibleColumns(); i++ ) {
+      int index = columnOffset + i;
+      int xPos = xBase;
+      xPos
+        =   SpreadSheetUtils.calcXPosition( xBase, columnOffset, index, model )
+          - DEFAULT_GRID_LINE_BREADTH;
+      int height = cellContainer.getClientArea().height;
+      Label line = gridLines[ gridLineIndex ];
+      line.setBounds( xPos, yBase, DEFAULT_GRID_LINE_BREADTH, height );
+      gridLineIndex++;
+    }
+  }
+
+  private void adjustGridControlNumber() {
+    int visibleRows = model.getVisibleRows();
+    int visibleColumns = model.getVisibleColumns();
+    int newGridLineCount = Math.max( 0, visibleColumns + visibleRows - 2 );
+    Label[] newGridLines = new Label[ newGridLineCount ];
+    int linesToAdjust = Math.max( newGridLineCount, gridLines.length );
+    for( int i = 0; i < linesToAdjust; i++ ) {
+      if( i < gridLines.length && i < newGridLineCount ) {
+        // copy existing lines to new array for reuse
+        newGridLines[ i ] = gridLines[ i ];
+      } else if( i >= gridLines.length ) {
+        // the client area has been enlarged, so more lines are needed
+        newGridLines[ i ] = new Label( cellContainer, SWT.NONE );
+        newGridLines[ i ].setBackground( Graphics.getColor( 0, 0, 255 ) );
+      } else if( i >= newGridLineCount ) {
+        // the client area has been shrunken, so dispose of the spare ones.
+        gridLines[ i ].dispose();
+      }
+    }
+    gridLines = newGridLines;
+  }
+
+  private void adjustCellLayoutData() {
+    Set bufferedPositions = new HashSet();
+    Control[] children = cellContainer.getChildren();
+    for( int i = 0; i < children.length; i++ ) {
+      SpreadSheetData data = ( SpreadSheetData )children[ i ].getLayoutData();
+      if( data != null ) {
+        CellPosition position = data.getPosition();
+        bufferedPositions.add( position );
+      }
+    }
+
+    for( int i = 0; i < children.length; i++ ) {
+      SpreadSheetData data = ( SpreadSheetData )children[ i ].getLayoutData();
+      if( data != null ) {
+        CellPosition position = data.getPosition();
+        int oldRowIndex = position.getRowIndex();
+        int oldColumnIndex = position.getColumnIndex();
+        if(    needsNewRowIndex( oldRowIndex )
+            || needsNewColumnIndex( oldColumnIndex ) )
+        {
+          int rowIndex = calculateRowIndex( oldRowIndex );
+          int columnIndex = calculateColumn( oldColumnIndex );
+          CellPosition newPosition = new CellPosition( rowIndex, columnIndex );
+          if( !bufferedPositions.contains( newPosition ) ) {
+            SpreadSheetData newData = new SpreadSheetData( newPosition );
+            children[ i ].setLayoutData( newData );
+          }
+        }
+      }
+    }
+  }
+
+  private int calculateRowIndex( final int oldRowIndex ) {
+    int result = oldRowIndex;
+    if( needsNewRowIndex( oldRowIndex )  ) {
+      if( isInUpperOffset( oldRowIndex ) ) {
+        result = oldRowIndex + model.getVisibleRows();
+      } else {
+        result = oldRowIndex - model.getVisibleRows();
+      }
+    }
+    return result;
+  }
+
+  private int calculateColumn( final int oldColumnIndex ) {
+    int result = oldColumnIndex;
+    if( needsNewColumnIndex( oldColumnIndex ) ) {
+      if( isInLeftOffset( oldColumnIndex ) ) {
+        result = oldColumnIndex + model.getVisibleColumns();
+      } else {
+        result = oldColumnIndex - model.getVisibleColumns();
+      }
+    }
+    return result;
+  }
+
+  private boolean needsNewRowIndex( final int oldRowIndex ) {
+    return    isInUpperOffset( oldRowIndex )
+           || isInLowerOffset( oldRowIndex );
+  }
+
+  private boolean isInLowerOffset( final int oldRowIndex ) {
+    return oldRowIndex >= model.getVisibleRows() + model.getRowOffset();
+  }
+
+  private boolean isInUpperOffset( final int oldRowIndex ) {
+    return oldRowIndex < model.getRowOffset();
+  }
+
+  private boolean needsNewColumnIndex( final int oldColumnIndex ) {
+    return    isInLeftOffset( oldColumnIndex )
+           || isInRightOffset( oldColumnIndex );
+  }
+
+  private boolean isInRightOffset( final int oldColumnIndex ) {
+    int visibleColumns = model.getVisibleColumns();
+    return oldColumnIndex >= visibleColumns + model.getColumnOffset();
+  }
+
+  private boolean isInLeftOffset( final int oldColumnIndex ) {
+    return oldColumnIndex < model.getColumnOffset();
+  }
+
+  private void computeCellContainerBounds() {
+    Rectangle clientArea = spreadSheet.getClientArea();
+    int rowOffset = model.getRowOffset();
+    int yOffset = 0;
+    for( int i = 0; i < rowOffset; i++ ) {
+      yOffset += model.getRowHeight( i );
+    }
+    int columnOffset = model.getColumnOffset();
+    int xOffset = 0;
+    for( int i = 0; i < columnOffset; i++ ) {
+      xOffset += model.getColumnWidth( i );
+    }
+    cellContainer.setBounds( clientArea.x - xOffset,
+                             clientArea.y - yOffset,
+                             clientArea.width + xOffset,
+                             clientArea.height + yOffset );
+  }
+
+  private void updateText() {
+    Control[] children = cellContainer.getChildren();
+    for( int i = 0; i < children.length; i++ ) {
+      if( children[ i ].getData( CELL_CONTROL_MARKER ) != null ) {
+        SpreadSheetData data = ( SpreadSheetData )children[ i ].getLayoutData();
+        CellPosition position = data.getPosition();
+        Label control = ( Label )children[ i ];
+        String text = model.getText( position );
+        control.setText( text );
+      }
+    }
+  }
+
+  Map bufferExistingCells() {
+    Control[] children = cellContainer.getChildren();
+    Map result = new HashMap();
+    for( int i = 0; i < children.length; i++ ) {
+      Control control = children[ i ];
+      if( isCellControl( control ) ) {
+        SpreadSheetData data = ( SpreadSheetData )control.getLayoutData();
+        result.put( data.getPosition(), control );
+      }
+    }
+    return result;
+  }
+
+  private void createMissingCells( final Map buffer )
+  {
+    int rowOffset = model.getRowOffset();
+    int rows = model.getVisibleRows() + rowOffset;
+    int columnOffset = model.getColumnOffset();
+    int columns = model.getVisibleColumns() + columnOffset;
+    for( int i = rowOffset; i < rows; i++ ) {
+      for( int j = columnOffset; j < columns; j++ ) {
+        CellPosition position = new CellPosition( i, j );
+        Control control = ( Control )buffer.get( position );
+        if( control == null ) {
+          if( model.getText( position ) != "" ) {
+            createCellControl( position );
+          }
+        }
+      }
+    }
+  }
+
+  private Control createCellControl( final CellPosition position )
+  {
+    Label result = new Label( cellContainer, SWT.NONE );
+    result.setLayoutData( new SpreadSheetData( position ) );
+    result.setData( CELL_CONTROL_MARKER, CELL_CONTROL_MARKER );
+    Display current = Display.getCurrent();
+    Color bgColor = current.getSystemColor( SWT.COLOR_WHITE );
+    result.setBackground( bgColor );
+    result.addMouseListener( new MouseAdapter()  {
+      public void mouseUp( final MouseEvent evt ) {
+        SpreadSheet sheet = ( SpreadSheet )spreadSheet;
+        sheet.getCellEditorController().handleMouseUp( position );
+      }
+      public void mouseDown( final MouseEvent evt ) {
+        SpreadSheet sheet = ( SpreadSheet )spreadSheet;
+        sheet.getCellEditorController().handleMouseDown( position );
+      }
+    } );
+    return result;
+  }
+
+  static boolean isCellControl( final Control control ) {
+    Object data = control.getData( CELL_CONTROL_MARKER );
+    return    control instanceof Label
+           && control.getLayoutData() != null
+           && CELL_CONTROL_MARKER.equals( data );
+  }
+
+  void setText( final String text,
+                final CellPosition position )
+  {
+    Label cell = findCellControl( position );
+    if( cell != null && "".equals( text ) ) {
+      cell.dispose();
+    } else if( cell != null && !"".equals( text ) ) {
+      cell.setText( text );
+    } else if( cell == null && !"".equals( text ) ) {
+      cell = ( Label )createCellControl( position );
+      Rectangle clientArea = cellContainer.getClientArea();
+      SpreadSheetUtils.computeBounds( clientArea, cell, model );
+      cell.setText( text );
+    }
+  }
+
+  private Label findCellControl( final CellPosition position )
+  {
+    Label result = null;
+    Control[] children = cellContainer.getChildren();
+    for( int i = 0; result == null && i < children.length; i++ ) {
+      SpreadSheetData data = ( SpreadSheetData )children[ i ].getLayoutData();
+      // TODO [fappel]: cellContainer should contain cell controls only
+      //                -> check for cell control can be removed
+      if( data != null
+          && position.equals( data.getPosition() )
+          && isCellControl( children[ i ] ) )
+      {
+        result = ( Label )children[ i ];
+      }
+    }
+    return result;
+  }
+
+  void disposeOfSpareCells( final Map buffer )
+  {
+    int rowOffset = model.getRowOffset();
+    int rows = model.getVisibleRows() + rowOffset;
+    int columnOffset = model.getColumnOffset();
+    int columns = model.getVisibleColumns() + columnOffset;
+    for( int i = rowOffset; i < rows; i++ ) {
+      for( int j = columnOffset; j < columns; j++ ) {
+        CellPosition position = new CellPosition( i, j );
+        buffer.remove( position );
+      }
+    }
+    Iterator iterator = buffer.values().iterator();
+    while( iterator.hasNext() ) {
+      Control control = ( Control )iterator.next();
+      control.dispose();
+    }
+  }
+
+  Composite getCellContainer() {
+    return cellContainer;
+  }
+
+  Label[] getGridLines() {
+    return gridLines;
+  }
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/CellEditorController.java b/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/CellEditorController.java
new file mode 100644
index 0000000..300cb14
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/CellEditorController.java
@@ -0,0 +1,117 @@
+// Created on 16.08.2009
+package org.eclipse.swt.custom;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SpreadSheetModel.Adapter;
+import org.eclipse.swt.custom.SpreadSheetModel.Event;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Text;
+
+
+class CellEditorController {
+  private final SpreadSheet spreadSheet;
+  private final Text control;
+  private final SpreadSheetModel model;
+  
+
+  private final class EditorKeyAdapter extends KeyAdapter {
+    public void keyPressed( final KeyEvent evt ) {
+      handleKeyPressed( evt );
+    }
+  }
+
+  private final class ModelAdapter extends Adapter {
+    public void textChanged( final Event evt ) {
+      int row = evt.rowIndex;
+      int column = evt.columnIndex;
+      CellPosition position
+        = new CellPosition( row, column );
+      setText( evt.text, position );
+    }
+  }
+
+  
+  CellEditorController( final SpreadSheet spreadSheet,
+                        final SpreadSheetModel model )
+  {
+    SpreadSheetUtils.checkNotNull( spreadSheet, "spreadSheet" );
+    SpreadSheetUtils.checkNotNull( model, "model" );
+    
+    this.spreadSheet = spreadSheet;
+    this.model = model;
+    model.addListener( new ModelAdapter() );
+    control = new Text( spreadSheet, SWT.BORDER );
+    control.setLayoutData( new SpreadSheetData( 0, 0 ) );
+    control.addKeyListener( new EditorKeyAdapter() );
+  }
+
+  void setText( final String text, final CellPosition position ) {
+    SpreadSheetData editorData = ( SpreadSheetData )control.getLayoutData();
+    if( position.equals( editorData.getPosition() ) ) {
+      setText( text );
+    }
+  }
+
+  void setText( final String text ) {
+    control.setText( text );
+  }
+  
+  String getText() {
+    return control.getText();
+  }
+
+  boolean setFocus() {
+    return control.setFocus();
+  }
+  
+  void handleKeyPressed( final KeyEvent evt ) {
+    switch( evt.keyCode ) {
+      case SWT.ARROW_DOWN:
+        moveCellEditor( 1, 0 );
+      break;
+      case SWT.ARROW_UP:
+        moveCellEditor( -1, 0 );
+      break;
+      case SWT.ARROW_LEFT:
+        moveCellEditor( 0, -1 );
+      break;
+      case SWT.ARROW_RIGHT:
+        moveCellEditor( 0, 1 );
+      break;
+    }
+  }
+
+  void moveCellEditor( final int rowChange, final int columnChange )
+  {
+    SpreadSheetData old = ( SpreadSheetData )control.getLayoutData();
+    int newRow = old.getRowIndex() + rowChange;
+    int newColumn = old.getColumnIndex() + columnChange;
+    if( newRow >= 0 && newColumn >= 0 ) {
+      moveCellEditor( new CellPosition( newRow, newColumn ) );
+    }
+  }
+
+  void moveCellEditor( final CellPosition position ) {
+    SpreadSheetData oldData = ( SpreadSheetData )control.getLayoutData();
+    spreadSheet.setText( control.getText(), oldData.getPosition() );
+    control.setText( spreadSheet.getText( position ) );
+    control.setLayoutData( new SpreadSheetData( position ) );
+    Rectangle clientArea = spreadSheet.getClientArea();
+    SpreadSheetUtils.computeBounds( clientArea, control, model );
+  }
+
+  Control getControl() {
+    return control;
+  }
+
+  void handleMouseUp( final CellPosition position ) {
+    moveCellEditor( position );
+    setFocus();
+  }
+
+  void handleMouseDown( final CellPosition cell ) {
+  }
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/CellPosition.java b/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/CellPosition.java
new file mode 100644
index 0000000..6f999ab
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/CellPosition.java
@@ -0,0 +1,64 @@
+// Created on 15.08.2009
+package org.eclipse.swt.custom;
+
+
+public class CellPosition {
+  private final int columnIndex;
+  private final int rowIndex;
+
+  public CellPosition( final int rowIndex, final int columnIndex ) {
+    SpreadSheetUtils.checkNotNegative( rowIndex, "rowIndex" );
+    SpreadSheetUtils.checkNotNegative( columnIndex, "columnIndex" );
+    
+    this.rowIndex = rowIndex;
+    this.columnIndex = columnIndex;
+  }
+
+  public int getColumnIndex() {
+    return columnIndex;
+  }
+
+  public int getRowIndex() {
+    return rowIndex;
+  }
+
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + columnIndex;
+    result = prime * result + rowIndex;
+    return result;
+  }
+
+
+  public boolean equals( final Object obj ) {
+    // genereated code
+    if( this == obj ) {
+      return true;
+    }
+    if( obj == null ) {
+      return false;
+    }
+    if( getClass() != obj.getClass() ) {
+      return false;
+    }
+    CellPosition other = ( CellPosition )obj;
+    if( columnIndex != other.columnIndex ) {
+      return false;
+    }
+    if( rowIndex != other.rowIndex ) {
+      return false;
+    }
+    return true;
+  }
+  
+  public String toString() {
+    return   "CellPosition [columnIndex="
+           + columnIndex
+           + ", rowIndex="
+           + rowIndex
+           + "]";
+  }
+}
+
+
diff --git a/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/HeaderController.java b/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/HeaderController.java
new file mode 100644
index 0000000..dfba086
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/HeaderController.java
@@ -0,0 +1,273 @@
+// Created on 22.08.2009
+package org.eclipse.swt.custom;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.*;
+
+
+class HeaderController {
+  static final int DEFAULT_ROW_HEADER_WIDTH = 25;
+  static final int DEFAULT_COLUMN_HEADER_HEIGHT
+    = SpreadSheetModel.DEFAULT_ROW_HEIGHT;
+  static final String ROW_HEADER_MARKER
+    = SpreadSheetLayout.class.getName() + "#RowHeader";
+  static final String COLUMN_HEADER_MARKER
+    = SpreadSheetLayout.class.getName() + "#ColumnHeader";
+  private static final String SASH
+    = SpreadSheetLayout.class.getName() + "#Sash";
+  private static final int SASH_BREADTH = 5;
+  private static final int SASH_OFFSET = 2;
+
+  private final Color headerColor;
+  private final Composite spreadSheet;
+  private final SpreadSheetModel model;
+  private Control[] rowHeaders;
+  private Control[] columnHeaders;
+  private Composite rowContainer;
+  private Composite columnContainer;
+  private int bufferedRowOffset;
+  private int bufferedColumnOffset;
+
+  
+  public HeaderController( final Composite spreadSheet,
+                           final SpreadSheetModel model )
+  {
+    SpreadSheetUtils.checkNotNull( spreadSheet, "spreadSheet" );
+    SpreadSheetUtils.checkNotNull( model, "model" );
+    
+    this.spreadSheet = spreadSheet;
+    this.model = model;
+    this.bufferedRowOffset = model.getRowOffset();
+    this.bufferedColumnOffset = model.getColumnOffset();
+    this.rowHeaders = new Control[ 0 ];
+    this.columnHeaders = new Control[ 0 ];
+    rowContainer = new Composite( spreadSheet, SWT.NONE );
+    columnContainer = new Composite( spreadSheet, SWT.NONE );
+    Display display = Display.getDefault();
+    this.headerColor  = display.getSystemColor( SWT.COLOR_WIDGET_LIGHT_SHADOW );
+    rowContainer.setBackground( display.getSystemColor( SWT.COLOR_RED ) );
+    columnContainer.setBackground( display.getSystemColor( SWT.COLOR_RED ) );
+  }
+
+  void adjustHeaderControls() {
+    adjustRowHeaderContainer();
+    adjustColumnHeaderContainer();
+    adjustRowHeaders();
+    adjustColumnHeaders();
+  }
+
+  private void adjustRowHeaderContainer() {
+    Rectangle clientArea = spreadSheet.getClientArea();
+    int xPos = clientArea.x - DEFAULT_ROW_HEADER_WIDTH;
+    int rowOffset = model.getRowOffset();
+    int yOffset = 0;
+    for( int i = 0; i < rowOffset; i++ ) {
+      yOffset += model.getRowHeight( i );
+    }
+    int yPos = clientArea.y - yOffset;
+    int width =   DEFAULT_ROW_HEADER_WIDTH
+                - CellController.DEFAULT_GRID_LINE_BREADTH;
+    int height = clientArea.height + yOffset;
+    rowContainer.setBounds( xPos, yPos, width, height );
+  }
+  
+  private void adjustColumnHeaderContainer() {
+    Rectangle clientArea = spreadSheet.getClientArea();
+    int columnOffset = model.getColumnOffset();
+    int xOffset = 0;
+    for( int i = 0; i < columnOffset; i++ ) {
+      xOffset += model.getColumnWidth( i );
+    }
+    int xPos = clientArea.x - xOffset;
+    int yPos = clientArea.y - DEFAULT_COLUMN_HEADER_HEIGHT;
+    int width = clientArea.width + xOffset;
+    int height =   DEFAULT_COLUMN_HEADER_HEIGHT
+                - CellController.DEFAULT_GRID_LINE_BREADTH;
+    columnContainer.setBounds( xPos, yPos, width, height );
+  }
+
+  private void adjustColumnHeaders() {
+    adjustColumnHeaderNumber();
+    adjustColumnHeaderBounds();
+  }
+
+  private void adjustRowHeaders() {
+    adjustRowHeaderNumber();
+    adjustRowHeaderBounds();
+  }
+
+  private void adjustRowHeaderBounds() {
+    int yBase = 0;
+    int lowerBound = 0;
+    for( int i = 0; i < rowHeaders.length; i++ ) {
+      int row = i + model.getRowOffset();
+      int xPos = 0;
+      int yPos
+        = SpreadSheetUtils.calcYPosition( yBase, lowerBound, row, model );
+      yBase = yPos;
+      lowerBound = row;
+      int width =   getRowHeaderWidth()
+                  - CellController.DEFAULT_GRID_LINE_BREADTH;
+      int height =   model.getRowHeight( row )
+                   - CellController.DEFAULT_GRID_LINE_BREADTH;
+      rowHeaders[ i ].setBounds( xPos, yPos, width, height );
+      Sash sash = ( Sash )rowHeaders[ i ].getData( SASH );
+      sash.setBounds( 0, yPos + height - SASH_OFFSET, width, SASH_BREADTH );
+    }
+  }
+
+  private void adjustRowHeaderNumber() {
+    final Rectangle clientArea = spreadSheet.getClientArea();
+    int rows = model.getVisibleRows();
+    Control[] newRowHeaders = new Control[ rows ];
+    int maxRow = Math.max( rows, rowHeaders.length );
+    for( int i = 0; i < maxRow; i++ ) {
+      if( i < rowHeaders.length && i < newRowHeaders.length ) {
+        newRowHeaders[ i ] = rowHeaders[ i ];
+      } else if( i < newRowHeaders.length ) {
+        Label rowHeader = new Label( rowContainer, SWT.NONE );
+        rowHeader.setBackground( headerColor );
+        rowHeader.setData( ROW_HEADER_MARKER, String.valueOf( i ) );
+        newRowHeaders[ i ] = rowHeader;
+        Sash sash = new Sash( rowContainer, SWT.HORIZONTAL );
+        rowHeader.setData( SASH, sash );
+        final int rowIndex = i;
+        sash.addSelectionListener( new SelectionAdapter() {
+          public void widgetSelected( final SelectionEvent evt ) {
+            int yOffset = clientArea.y - DEFAULT_COLUMN_HEADER_HEIGHT;
+            int yPos = SpreadSheetUtils.calcYPosition( yOffset,
+                                                            0,
+                                                            rowIndex, 
+                                                            model );
+            int newRowHeight = Math.max( 0, evt.y - yPos + SASH_OFFSET + 1 );
+            model.setRowHeight( rowIndex, newRowHeight );
+          };
+        } );
+      } else {
+        Sash sash = ( Sash )rowHeaders[ i ].getData( SASH );
+        sash.dispose();
+        rowHeaders[ i ].dispose();
+      }
+    }
+    if( bufferedRowOffset != model.getRowOffset() ) {
+      int offSetChange = bufferedRowOffset - model.getRowOffset();
+      newRowHeaders = reorderHeaders( offSetChange, newRowHeaders );
+      bufferedRowOffset = model.getRowOffset();
+    }
+    rowHeaders = newRowHeaders;
+  }
+
+  private Control[] reorderHeaders( int offSetChange, Control[] headerControls )
+  {
+    Control[] offSetHeaders = new Control[ headerControls.length ];
+    for( int i = 0; i < headerControls.length; i++ ) {
+      int position = i + offSetChange;
+      if( position < 0 ) {
+        offSetHeaders[ headerControls.length + position ] = headerControls[ i ];
+      } else if ( position >= headerControls.length ) {
+        offSetHeaders[ position - headerControls.length ] = headerControls[ i ];          
+      } else {
+        offSetHeaders[ position ] = headerControls[ i ];
+      }
+    }
+    return offSetHeaders;
+  }
+
+  private void adjustColumnHeaderBounds() {
+    int xBase = 0;
+    int lowerBound = 0;
+    for( int i = 0; i < columnHeaders.length; i++ ) {
+      int col = i + model.getColumnOffset();
+      int xPos 
+        = SpreadSheetUtils.calcXPosition( xBase, lowerBound, col, model );
+      xBase = xPos;
+      lowerBound = col;
+      int yPos = 0;
+      int width =   model.getColumnWidth( col )
+                  - CellController.DEFAULT_GRID_LINE_BREADTH;
+      int height =   getColumnHeaderHeight()
+                   - CellController.DEFAULT_GRID_LINE_BREADTH;
+      columnHeaders[ i ].setBounds( xPos, yPos, width, height );
+      Sash sash = ( Sash )columnHeaders[ i ].getData( SASH );
+      sash.setBounds( xPos + width - SASH_OFFSET, 0, SASH_BREADTH, height );
+    }
+  }
+  
+  private void adjustColumnHeaderNumber() {
+    final Rectangle clientArea = spreadSheet.getClientArea();
+    int columns = model.getVisibleColumns();
+    Control[] newColumnHeaders = new Control[ columns ];
+    int maxColumns = Math.max( columns, columnHeaders.length );
+    for( int i = 0; i < maxColumns; i++ ) {
+      if( i < columnHeaders.length && i < newColumnHeaders.length ) {
+        newColumnHeaders[ i ] = columnHeaders[ i ];
+      } else if( i < newColumnHeaders.length ) {
+        Label columnHeader = new Label( columnContainer, SWT.NONE );
+        columnHeader.setBackground( headerColor );
+        columnHeader.setData( COLUMN_HEADER_MARKER, String.valueOf( i ) );
+        newColumnHeaders[ i ] = columnHeader;
+        Sash sash = new Sash( columnContainer, SWT.VERTICAL );
+        columnHeader.setData( SASH, sash );
+        final int columnIndex = i;
+        sash.addSelectionListener( new SelectionAdapter() {
+          public void widgetSelected( final SelectionEvent evt ) {
+            int xOffset = clientArea.x - DEFAULT_ROW_HEADER_WIDTH;
+            int xPos = SpreadSheetUtils.calcXPosition( xOffset,
+                                                            0,
+                                                            columnIndex, 
+                                                            model );
+            int newColumnWidth = Math.max( 0, evt.x - xPos + SASH_OFFSET + 1 );
+            model.setColumnWidth( columnIndex, newColumnWidth );
+          };
+        } );
+
+      } else {
+        Sash sash = ( Sash )columnHeaders[ i ].getData( SASH );
+        sash.dispose();
+        columnHeaders[ i ].dispose();
+      }
+    }
+    if( bufferedColumnOffset != model.getColumnOffset() ) {
+      int offSetChange = bufferedColumnOffset - model.getColumnOffset();
+      newColumnHeaders = reorderHeaders( offSetChange, newColumnHeaders );
+      bufferedColumnOffset = model.getColumnOffset();
+    }
+    columnHeaders = newColumnHeaders;
+  }
+  
+  Composite getColumnContainer() {
+    return columnContainer;
+  }
+  
+  Control[] getColumnHeaderControls() {
+    return columnHeaders;
+  }
+  
+  Composite getRowContainer() {
+    return rowContainer;
+  }
+  
+  Control[] getRowHeaderControls() {
+    return rowHeaders;
+  }
+  
+  int getRowHeaderWidth() {
+    return DEFAULT_ROW_HEADER_WIDTH;
+  }
+  
+  int getColumnHeaderHeight() {
+    return DEFAULT_COLUMN_HEADER_HEIGHT;
+  }
+
+  static boolean isRowHeaderControl( final Control control ) {
+    return control.getData( ROW_HEADER_MARKER ) != null;
+  }
+  
+  static boolean isColumnHeaderControl( final Control control ) {
+    return control.getData( COLUMN_HEADER_MARKER ) != null;
+  }
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/SliderController.java b/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/SliderController.java
new file mode 100644
index 0000000..37440c1
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/SliderController.java
@@ -0,0 +1,81 @@
+// Created on 22.08.2009
+package org.eclipse.swt.custom;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Slider;
+
+
+public class SliderController {
+  static final int PAGE_INCREMENT_OFFSET = 2;
+  static final int SLIDER_BREADTH = 20;
+
+  private final Composite spreadSheet;
+  private final HeaderController headerController;
+  private final Slider horizontalSlider;
+  private final Slider verticalSlider;
+  private final SpreadSheetModel model;
+
+
+  protected SliderController( final Composite spreadSheet,
+                              final HeaderController headerController,
+                              final SpreadSheetModel model )
+  {
+    SpreadSheetUtils.checkNotNull( spreadSheet, "spreadSheet" );
+    SpreadSheetUtils.checkNotNull( headerController, "headerController" );
+
+    this.spreadSheet = spreadSheet;
+    this.headerController = headerController;
+    this.model = model;
+    horizontalSlider = new Slider( spreadSheet, SWT.H_SCROLL );
+    horizontalSlider.addSelectionListener( new SelectionAdapter() {
+      public void widgetSelected( final SelectionEvent evt ) {
+        int selection = SliderController.this.horizontalSlider.getSelection();
+        SliderController.this.model.setColumnOffset( selection );
+      }
+    } );
+
+    verticalSlider = new Slider( spreadSheet, SWT.V_SCROLL );
+    verticalSlider.addSelectionListener( new SelectionAdapter() {
+      public void widgetSelected( final SelectionEvent evt ) {
+        int selection = SliderController.this.verticalSlider.getSelection();
+        SliderController.this.model.setRowOffset( selection );
+      };
+    } );
+  }
+
+  int getBreadth() {
+    return SliderController.SLIDER_BREADTH;
+  }
+
+  void adjustSlider() {
+    Rectangle clientArea = spreadSheet.getClientArea();
+    adjustHorizontalSlider( clientArea );
+    adjustVerticalSlider( clientArea );
+  }
+
+  private void adjustVerticalSlider( final Rectangle clientArea ) {
+    int vX = clientArea.width + headerController.getRowHeaderWidth();
+    int vHeight = clientArea.height + getBreadth();
+    verticalSlider.setBounds( vX, 0, getBreadth(), vHeight );
+    int rows = model.getVisibleRows();
+    verticalSlider.setMinimum( 0 );
+    verticalSlider.setMaximum( rows + PAGE_INCREMENT_OFFSET );
+    verticalSlider.setThumb( rows );
+    verticalSlider.setSelection( model.getRowOffset() );
+  }
+
+  private void adjustHorizontalSlider( final Rectangle clientArea ) {
+    int hY = clientArea.height + getBreadth();
+    int hWidth = clientArea.width + headerController.getRowHeaderWidth();
+    horizontalSlider.setBounds( 0, hY, hWidth, getBreadth() );
+    int columns = model.getVisibleColumns();
+    horizontalSlider.setMinimum( 0 );
+    horizontalSlider.setMaximum( columns + PAGE_INCREMENT_OFFSET );
+    horizontalSlider.setThumb( columns );
+    horizontalSlider.setSelection( model.getColumnOffset() );
+  }
+}
diff --git a/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/SpreadSheet.java b/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/SpreadSheet.java
new file mode 100644
index 0000000..78ca715
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/SpreadSheet.java
@@ -0,0 +1,128 @@
+// Created on 08.08.2009
+package org.eclipse.swt.custom;
+
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SpreadSheetModel.Adapter;
+import org.eclipse.swt.custom.SpreadSheetModel.Event;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Layout;
+
+
+public class SpreadSheet extends Composite {
+  private final SpreadSheetLayout layout;
+  private final SpreadSheetModel model;
+  private final CellController cellController;
+  private final CellEditorController cellEditorController;
+  private final SliderController sliderController;
+  private final HeaderController headerController;
+
+  
+  private final class ModelAdapter extends Adapter {
+    public void columnWidthChanged( final Event evt ) {
+      triggerLayout();
+    }
+    public void rowHeightChanged( final Event evt ) {
+      triggerLayout();
+    }
+    public void columnOffsetChanged( final Event event ) {
+      triggerLayout();
+    }
+    public void rowOffsetChanged( final Event event ) {
+      triggerLayout();
+    }
+  }
+
+  
+  public SpreadSheet( final Composite parent, final int style ) {
+    // TODO [fappel]: checkStyle( style ) implementation
+    super( parent, style );
+    setBackground( getDisplay().getSystemColor( SWT.COLOR_GRAY ) );
+
+    model = new SpreadSheetModel();
+    cellEditorController = new CellEditorController( this, model );
+    cellController = new CellController( this, model );
+    headerController = new HeaderController( this, model );
+    sliderController = new SliderController( this, headerController, model );
+    model.addListener( new ModelAdapter() );
+    layout = new SpreadSheetLayout( model,
+                                    cellController,
+                                    sliderController,
+                                    headerController );
+    super.setLayout( layout );
+  }
+
+  private void triggerLayout() {
+    layout();
+    cellEditorController.setFocus();
+  }
+
+  public Rectangle getClientArea() {
+    checkWidget();
+    Rectangle clientArea = super.getClientArea();
+    int xPos = headerController.getRowHeaderWidth();
+    int yPos = headerController.getColumnHeaderHeight();
+    int width =   clientArea.width
+                - sliderController.getBreadth()
+                - headerController.getRowHeaderWidth();
+    int height =   clientArea.height
+                 - sliderController.getBreadth()
+                 - headerController.getColumnHeaderHeight();
+    return new Rectangle( xPos, yPos, width, height );
+  }
+
+  public String getText( final int rowIndex, final int columnIndex ) {
+    checkWidget();
+    return model.getText( rowIndex, columnIndex );
+  }
+
+  public String getText( final CellPosition position ) {
+    checkWidget();
+    return getText( position.getRowIndex(), position.getColumnIndex() );
+  }
+
+  public void setText( final String text,
+                       final int rowIndex,
+                       final int columnIndex )
+  {
+    checkWidget();
+    model.setText( text, rowIndex, columnIndex );
+  }
+
+  public void setText( final String text, final CellPosition position ) {
+    checkWidget();
+    setText( text, position.getRowIndex(), position.getColumnIndex() );
+  }
+
+  public void setColumnOffset( final int columnOffset ) {
+    checkWidget();
+    model.setColumnOffset( columnOffset );
+  }
+
+  public void setRowOffset( final int rowOffset ) {
+    checkWidget();
+    model.setRowOffset( rowOffset );
+  }
+
+  CellEditorController getCellEditorController() {
+    return cellEditorController;
+  }
+
+  /////////////////
+  // start override
+
+  public boolean setFocus() {
+    return cellEditorController.setFocus();
+  }
+
+  public void setLayout( final Layout layout ) {
+    checkWidget();
+
+    String msg = "Changing the layout algorithm is not supported.";
+    throw new UnsupportedOperationException( msg );
+  }
+
+  // end override
+  ///////////////
+}
diff --git a/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/SpreadSheetData.java b/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/SpreadSheetData.java
new file mode 100644
index 0000000..09c2ac5
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/SpreadSheetData.java
@@ -0,0 +1,29 @@
+// Created on 07.08.2009
+package org.eclipse.swt.custom;
+
+
+
+class SpreadSheetData {
+
+  private final CellPosition position;
+  
+  SpreadSheetData( final CellPosition position ) {
+    this.position = position;
+  }
+
+  SpreadSheetData( final int rowIndex, final int columnIndex ) {
+    this( new CellPosition( rowIndex, columnIndex ) );
+  }
+
+  int getColumnIndex() {
+    return position.getColumnIndex();
+  }
+
+  int getRowIndex() {
+    return position.getRowIndex();
+  }
+  
+  CellPosition getPosition() {
+    return position;
+  }
+}
diff --git a/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/SpreadSheetLayout.java b/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/SpreadSheetLayout.java
new file mode 100644
index 0000000..5c8e5ba
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/SpreadSheetLayout.java
@@ -0,0 +1,45 @@
+// Created on 07.08.2009
+package org.eclipse.swt.custom;
+
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.*;
+
+class SpreadSheetLayout extends Layout {
+  private final SpreadSheetModel model;
+  private final CellController cellController;
+  private final SliderController sliderController;
+  private final HeaderController headerController;
+
+  
+  SpreadSheetLayout( final SpreadSheetModel model,
+                     final CellController cellController,
+                     final SliderController sliderController,
+                     final HeaderController headerController )
+  {
+    this.cellController = cellController;
+    this.sliderController = sliderController;
+    this.headerController = headerController;
+    this.model = model;
+  }
+
+  protected Point computeSize( final Composite composite,
+                               final int wHint,
+                               final int hHint,
+                               final boolean flushCache )
+  {
+    return null;
+  }
+
+  protected void layout( final Composite composite, final boolean flushCache ) {
+    Rectangle clientArea = composite.getClientArea();
+    model.updateVisibleRowAndColumns( clientArea );
+    sliderController.adjustSlider();
+    headerController.adjustHeaderControls();
+    cellController.adjustCellControls();
+    Control[] children = composite.getChildren();
+    for( int i = 0; i < children.length; i++ ) {
+      SpreadSheetUtils.computeBounds( clientArea, children[ i ], model );
+    }
+  }
+}
diff --git a/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/SpreadSheetModel.java b/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/SpreadSheetModel.java
new file mode 100644
index 0000000..1257a82
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/SpreadSheetModel.java
@@ -0,0 +1,269 @@
+// Created on 22.08.2009
+package org.eclipse.swt.custom;
+
+import java.text.MessageFormat;
+import java.util.*;
+
+import org.eclipse.swt.graphics.Rectangle;
+
+
+class SpreadSheetModel {
+  static final int DEFAULT_ROW_HEIGHT = 20;
+  static final int DEFAULT_COLUMN_WIDTH = 100;
+
+  private final Map rows;
+  private final Map columns;
+  private final Map texts;
+  private final Set listeners;
+  private int rowOffset;
+  private int columnOffset;
+  private int visibleColumns;
+  private int visibleRows;
+
+
+  static class Event {
+    private final static int ROW_HEIGHT_CHANGED = 0;
+    private final static int COLUMN_WIDTH_CHANGED = 1;
+    private final static int TEXT_CHANGED = 2;
+    private final static int ROW_OFFSET_CHANGED = 3;
+    private final static int COLUMN_OFFSET_CHANGED = 4;
+
+    int type;
+    int rowIndex;
+    int columnIndex;
+    int rowHeight;
+    int columnWidth;
+    String text;
+    int rowOffset;
+    int columnOffset;
+
+    public void fire( final Listener listener ) {
+      switch( type ) {
+        case ROW_HEIGHT_CHANGED:
+          listener.rowHeightChanged( this );
+        break;
+
+        case COLUMN_WIDTH_CHANGED:
+          listener.columnWidthChanged( this );
+        break;
+
+        case TEXT_CHANGED:
+          listener.textChanged( this );
+        break;
+        
+        case ROW_OFFSET_CHANGED:
+          listener.rowOffsetChanged( this );
+        break;
+        
+        case COLUMN_OFFSET_CHANGED:
+          listener.columnOffsetChanged( this );
+        break;
+
+        default:
+          String txt = "Event Type ''{0}''not supported";
+          Object[] param = new Object[] { new Integer( type ) };
+          String msg = MessageFormat.format( txt, param );
+          throw new IllegalStateException( msg );
+      }
+    }
+  }
+
+  static interface Listener {
+    void rowHeightChanged( Event evt );
+    void columnOffsetChanged( Event evt );
+    void rowOffsetChanged( Event evt );
+    void columnWidthChanged( Event evt );
+    void textChanged( Event evt );
+  }
+
+  static abstract class Adapter implements Listener {
+    public void rowHeightChanged( final Event evt ) {};
+    public void columnWidthChanged( final Event evt ) {};
+    public void textChanged( final Event evt ) {};
+    public void columnOffsetChanged( Event evt ) {};
+    public void rowOffsetChanged( Event evt ) {};
+  }
+
+
+  public SpreadSheetModel() {
+    rows = new HashMap();
+    columns = new HashMap();
+    texts = new HashMap();
+    listeners = new HashSet();
+    visibleColumns = 0;
+    visibleRows = 0;
+  }
+
+  int getRowHeight( final int rowIndex ) {
+    int result = DEFAULT_ROW_HEIGHT;
+    Integer key = new Integer( rowIndex );
+    Integer height = ( Integer )rows.get( key );
+    if( height != null ) {
+      result = height.intValue();
+    }
+    return result;
+  }
+
+  void setRowHeight( final int rowIndex, final int rowHeight ) {
+    SpreadSheetUtils.checkNotNegative( rowIndex, "rowIndex" );
+    SpreadSheetUtils.checkNotNegative( rowHeight, "rowHeight" );
+
+    Integer key = new Integer( rowIndex );
+    Integer oldRowHeight = ( Integer )rows.get( key );
+    Integer newRowHeight = new Integer( rowHeight );
+
+    if( rowHeight == SpreadSheetModel.DEFAULT_ROW_HEIGHT ) {
+      rows.remove( key );
+    } else {
+      rows.put( key, newRowHeight );
+    }
+
+    if( !newRowHeight.equals( oldRowHeight ) ) {
+      Event event = new Event();
+      event.type = Event.ROW_HEIGHT_CHANGED;
+      event.rowIndex = rowIndex;
+      event.rowHeight = rowHeight;
+      fireEvent( event );
+    }
+  }
+
+  int getColumnWidth( final int columnIndex ) {
+    int result = DEFAULT_COLUMN_WIDTH;
+    Integer key = new Integer( columnIndex );
+    Integer width = ( Integer )columns.get( key );
+    if( width != null ) {
+      result = width.intValue();
+    }
+    return result;
+  }
+
+  void setColumnWidth( final int columnIndex, final int columnWidth ) {
+    SpreadSheetUtils.checkNotNegative( columnIndex, "columnIndex" );
+    SpreadSheetUtils.checkNotNegative( columnWidth, "columnWidth" );
+
+    Integer key = new Integer( columnIndex );
+    Integer newColumnWidth = new Integer( columnWidth );
+    Integer oldColumnWidth = ( Integer )columns.get( key );
+
+    if( columnWidth == DEFAULT_COLUMN_WIDTH ) {
+      columns.remove( key );
+    } else {
+      columns.put( key, newColumnWidth );
+    }
+
+    if( !newColumnWidth.equals( oldColumnWidth ) ) {
+      Event event = new Event();
+      event.type = Event.COLUMN_WIDTH_CHANGED;
+      event.columnIndex = columnIndex;
+      event.columnWidth = columnWidth;
+      fireEvent( event );
+    }
+  }
+
+  String getText( final CellPosition position ) {
+    return getText( position.getRowIndex(), position.getColumnIndex() );
+  }
+
+  String getText( final int rowIndex, final int columnIndex ) {
+    SpreadSheetUtils.checkNotNegative( rowIndex, "rowIndex" );
+    SpreadSheetUtils.checkNotNegative( columnIndex, "columnIndex" );
+
+    CellPosition position = new CellPosition( rowIndex, columnIndex );
+    String result = ( String )texts.get( position );
+    return result == null ? "" : result;
+  }
+
+  void setText( final String text, final CellPosition position ) {
+    setText( text, position.getRowIndex(), position.getColumnIndex() );
+  }
+
+  void setText( final String text,
+                final int rowIndex,
+                final int columnIndex )
+  {
+    SpreadSheetUtils.checkNotNull( text, "text" );
+    SpreadSheetUtils.checkNotNegative( rowIndex, "rowIndex" );
+    SpreadSheetUtils.checkNotNegative( columnIndex, "columnIndex" );
+
+    CellPosition position = new CellPosition( rowIndex, columnIndex );
+    Object oldText = texts.get( position );
+
+    if( "".equals( text ) ) {
+      texts.remove( position );
+    } else {
+      texts.put( position, text );
+    }
+
+    if( !text.equals( oldText ) ) {
+      Event event = new Event();
+      event.type = Event.TEXT_CHANGED;
+      event.rowIndex = rowIndex;
+      event.columnIndex = columnIndex;
+      event.text = text;
+      fireEvent( event );
+    }
+  }
+
+  void setColumnOffset( final int columnOffset ) {
+    SpreadSheetUtils.checkNotNegative( columnOffset, "columnOffset" );
+    int oldOffset = this.columnOffset;
+    this.columnOffset = columnOffset;
+    if( oldOffset != columnOffset ) {
+      Event event = new Event();
+      event.type = Event.COLUMN_OFFSET_CHANGED;
+      event.columnOffset = columnOffset;
+      fireEvent( event );
+    }
+  }
+  
+  int getColumnOffset() {
+    return columnOffset;
+  }
+
+  void setRowOffset( final int rowOffset ) {
+    SpreadSheetUtils.checkNotNegative( rowOffset, "rowOffset" );
+    int oldOffset = this.rowOffset;
+    this.rowOffset = rowOffset;
+    if( oldOffset != rowOffset ) {
+      Event event = new Event();
+      event.type = Event.ROW_OFFSET_CHANGED;
+      event.rowOffset = rowOffset;
+      fireEvent( event );
+    }
+
+  }
+  
+  int getRowOffset() {
+    return rowOffset;
+  }
+  
+  void updateVisibleRowAndColumns( final Rectangle clientArea ) {
+    visibleColumns
+      = SpreadSheetUtils.calcVisibleColumns( clientArea.width, this );
+    visibleRows = SpreadSheetUtils.calcVisibleRows( clientArea.height, this );
+  }
+  
+  int getVisibleRows() {
+    return visibleRows;
+  }
+  
+  int getVisibleColumns() {
+    return visibleColumns;
+  }
+
+  void addListener( final Listener listener ) {
+    listeners.add( listener );
+  }
+
+  void removeListener( final Listener listener ) {
+    listeners.remove( listener );
+  }
+
+  private void fireEvent( final Event event ) {
+    Listener[] lsnrs = new Listener[ listeners.size() ];
+    listeners.toArray( lsnrs );
+    for( int i = 0; i < lsnrs.length; i++ ) {
+      event.fire( lsnrs[ i ] );
+    }
+  }
+}
diff --git a/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/SpreadSheetUtils.java b/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/SpreadSheetUtils.java
new file mode 100644
index 0000000..2e2e79d
--- /dev/null
+++ b/bundles/org.eclipse.rap.rwt.custom/src/org/eclipse/swt/custom/SpreadSheetUtils.java
@@ -0,0 +1,107 @@
+// Created on 09.08.2009
+package org.eclipse.swt.custom;
+
+import java.text.MessageFormat;
+
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Control;
+
+
+class SpreadSheetUtils {
+  
+  static void checkNotNegative( final int parameter,
+                                final String parameterName )
+  {
+    if( parameter < 0 ) {
+      String txt = "A negative value for parameter ''{0}'' is not allowed.";
+      String msg = MessageFormat.format( txt, new Object[] { parameterName } );
+      throw new IllegalArgumentException( msg );
+    }
+  }
+
+  static void checkNotNull( Object parameter, String parameterName ) {
+    if( parameter == null ) {
+      String txt = "Parameter ''{0}'' must not be null.";
+      String msg = MessageFormat.format( txt, new Object[]{ parameterName } );
+      throw new IllegalArgumentException( msg );
+    }
+  }
+
+  static int calcVisibleRows( final int height, final SpreadSheetModel model ) {
+    int rowIndex = 0;
+    int rowOffset = model.getRowOffset();
+    int calculatedHeight = model.getRowHeight( rowIndex + rowOffset );
+    while( calculatedHeight < height ) {
+      rowIndex++;
+      calculatedHeight += model.getRowHeight( rowIndex + rowOffset );
+    }
+    // we asume that the visible area most probably will show at least 
+    // one row;
+    return rowIndex + 1;
+  }
+
+  static int calcVisibleColumns( final int width, 
+                                 final SpreadSheetModel model )
+  {
+    int columnIndex = 0;
+    int columnOffset = model.getColumnOffset();
+    int calculatedWidth = model.getColumnWidth( columnIndex + columnOffset );
+    while( calculatedWidth < width ) {
+      columnIndex++;
+      calculatedWidth += model.getColumnWidth( columnIndex + columnOffset );
+    }
+    // we asume that the visible area most probably will show at least 
+    // one column;
+    return columnIndex + 1;
+  }
+
+  static int calcYPosition( final int yOffset,
+                            final int startIndex,
+                            final int rowIndex,
+                            final SpreadSheetModel model )
+  {
+    int result = yOffset;
+    for( int i = startIndex; i < rowIndex; i++ ) {
+      result += model.getRowHeight( i );
+    }
+    return result;
+  }
+
+  static int calcXPosition( final int xOffset,
+                            final int startIndex,
+                            final int columnIndex,
+                            final SpreadSheetModel model )
+  {
+    int result = xOffset;
+    for( int i = startIndex; i < columnIndex; i++ ) {
+      result += model.getColumnWidth( i );
+    }
+    return result;
+  }
+
+  static void computeBounds( final Rectangle clientArea,
+                             final Control control,
+                             final SpreadSheetModel model )
+  {
+    SpreadSheetData data = ( SpreadSheetData )control.getLayoutData();
+    if( data != null ) {
+      int x = calcXPosition( clientArea.x,
+                             0,
+                             data.getColumnIndex(),
+                             model );
+      int y = calcYPosition( clientArea.y,
+                             0,
+                             data.getRowIndex(),
+                             model );
+      int width =   model.getColumnWidth( data.getColumnIndex() )
+                  - CellController.DEFAULT_GRID_LINE_BREADTH;
+      int height =   model.getRowHeight( data.getRowIndex() )
+                   - CellController.DEFAULT_GRID_LINE_BREADTH;
+      control.setBounds( x, y, width, height );
+    }
+  }
+  
+  private SpreadSheetUtils() {
+    // prevent instance creation
+  }
+}
\ No newline at end of file
diff --git a/tests/org.eclipse.rap.rwt.custom.test/.classpath b/tests/org.eclipse.rap.rwt.custom.test/.classpath
new file mode 100644
index 0000000..2fbb7a2
--- /dev/null
+++ b/tests/org.eclipse.rap.rwt.custom.test/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.4"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/tests/org.eclipse.rap.rwt.custom.test/.project b/tests/org.eclipse.rap.rwt.custom.test/.project
new file mode 100644
index 0000000..00b16f0
--- /dev/null
+++ b/tests/org.eclipse.rap.rwt.custom.test/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.rap.rwt.custom.test</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/tests/org.eclipse.rap.rwt.custom.test/.settings/org.eclipse.jdt.core.prefs b/tests/org.eclipse.rap.rwt.custom.test/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..996653a
--- /dev/null
+++ b/tests/org.eclipse.rap.rwt.custom.test/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+#Fri Aug 07 21:40:34 CEST 2009
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2
+org.eclipse.jdt.core.compiler.compliance=1.4
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=warning
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=warning
+org.eclipse.jdt.core.compiler.source=1.3
diff --git a/tests/org.eclipse.rap.rwt.custom.test/META-INF/MANIFEST.MF b/tests/org.eclipse.rap.rwt.custom.test/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..30f327c
--- /dev/null
+++ b/tests/org.eclipse.rap.rwt.custom.test/META-INF/MANIFEST.MF
@@ -0,0 +1,8 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Test
+Bundle-SymbolicName: org.eclipse.rap.rwt.custom.test
+Bundle-Version: 0.0.0.qualifier
+Fragment-Host: org.eclipse.rap.rwt.custom;bundle-version="0.0.0.qualifier"
+Bundle-RequiredExecutionEnvironment: J2SE-1.4
+Require-Bundle: org.junit;bundle-version="3.8.2"
diff --git a/tests/org.eclipse.rap.rwt.custom.test/about.html b/tests/org.eclipse.rap.rwt.custom.test/about.html
new file mode 100644
index 0000000..248f88c
--- /dev/null
+++ b/tests/org.eclipse.rap.rwt.custom.test/about.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
+<title>About</title>
+</head>
+<body lang="EN-US">
+<h2>About This Content</h2>
+ 
+<p>August 08, 2009</p>	
+<h3>License</h3>
+
+<p>The Eclipse Foundation makes available all content in this plug-in (&quot;Content&quot;).  Unless otherwise 
+indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 (&quot;EPL&quot;).  A copy of the EPL is available 
+at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>.
+For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
+
+<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is 
+being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may
+apply to your use of any object code in the Content.  Check the Redistributor's license that was 
+provided with the Content.  If no such license exists, contact the Redistributor.  Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content
+and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p>
+
+</body>
+</html>
diff --git a/tests/org.eclipse.rap.rwt.custom.test/build.properties b/tests/org.eclipse.rap.rwt.custom.test/build.properties
new file mode 100644
index 0000000..34d2e4d
--- /dev/null
+++ b/tests/org.eclipse.rap.rwt.custom.test/build.properties
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/tests/org.eclipse.rap.rwt.custom.test/src/org/eclipse/swt/custom/AllCustomRWTTests.java b/tests/org.eclipse.rap.rwt.custom.test/src/org/eclipse/swt/custom/AllCustomRWTTests.java
new file mode 100644
index 0000000..ded613f
--- /dev/null
+++ b/tests/org.eclipse.rap.rwt.custom.test/src/org/eclipse/swt/custom/AllCustomRWTTests.java
@@ -0,0 +1,18 @@
+// Created on 07.08.2009
+package org.eclipse.swt.custom;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+
+public class AllCustomRWTTests {
+
+  public static Test suite() {
+    TestSuite suite = new TestSuite( "Test for org.eclipse.swt.custom" );
+    //$JUnit-BEGIN$
+    suite.addTestSuite( SpreadSheetLayout_Test.class );
+    suite.addTestSuite( SpreadSheet_Test.class );
+    //$JUnit-END$
+    return suite;
+  }
+}
diff --git a/tests/org.eclipse.rap.rwt.custom.test/src/org/eclipse/swt/custom/SpreadSheetFixture.java b/tests/org.eclipse.rap.rwt.custom.test/src/org/eclipse/swt/custom/SpreadSheetFixture.java
new file mode 100644
index 0000000..7ec59c8
--- /dev/null
+++ b/tests/org.eclipse.rap.rwt.custom.test/src/org/eclipse/swt/custom/SpreadSheetFixture.java
@@ -0,0 +1,25 @@
+// Created on 07.11.2009
+package org.eclipse.swt.custom;
+
+
+public class SpreadSheetFixture {
+
+  private static final int COLUMN_COUNT = 25;
+  private static final int ROW_COUNT = 50;
+
+  static void populateModel( final SpreadSheetModel model ) {
+    for( int i = 0; i < ROW_COUNT; i++ ) {
+      for( int j = 0; j < 25; j++ ) {
+        model.setText( i + "_" + j, i, j );
+      }
+    }
+  }
+
+  static void populateModel( final SpreadSheet spreadSheet ) {
+    for( int i = 0; i < ROW_COUNT; i++ ) {
+      for( int j = 0; j < COLUMN_COUNT; j++ ) {
+        spreadSheet.setText( i + "_" + j, i, j );
+      }
+    }
+  }
+}
diff --git a/tests/org.eclipse.rap.rwt.custom.test/src/org/eclipse/swt/custom/SpreadSheetLayout_Test.java b/tests/org.eclipse.rap.rwt.custom.test/src/org/eclipse/swt/custom/SpreadSheetLayout_Test.java
new file mode 100644
index 0000000..e182db5
--- /dev/null
+++ b/tests/org.eclipse.rap.rwt.custom.test/src/org/eclipse/swt/custom/SpreadSheetLayout_Test.java
@@ -0,0 +1,451 @@
+// Created on 07.08.2009
+package org.eclipse.swt.custom;
+
+import java.math.BigDecimal;
+
+import junit.framework.TestCase;
+
+import org.eclipse.swt.RWTFixture;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.*;
+
+
+/*
+ * TODO [fappel]: implement flushCache functionality of SpreadSheetLayout#layout 
+ * TODO [fappel]: check that only correct layout data types are used
+ */
+public class SpreadSheetLayout_Test extends TestCase {
+  private static final int DEFAULT_SHELL_HEIGHT = 400;
+  private static final int DEFAULT_SHELL_WIDTH = 400;
+  private static final int DEFAULT_COLUMN_HEADER_HEIGHT
+    = HeaderController.DEFAULT_COLUMN_HEADER_HEIGHT;
+  private static final int DEFAULT_ROW_HEADER_WIDTH 
+    = HeaderController.DEFAULT_ROW_HEADER_WIDTH;
+  private static final int DEFAULT_GRID_LINE_BREADTH
+    = CellController.DEFAULT_GRID_LINE_BREADTH;
+  private static final int DEFAULT_ROW_HEIGHT
+   = SpreadSheetModel.DEFAULT_ROW_HEIGHT;
+  private static final int DEFAULT_COLUMN_WIDTH
+    = SpreadSheetModel.DEFAULT_COLUMN_WIDTH;
+  private static final int DEFAULT_CELL_HEIGHT
+    =   SpreadSheetModel.DEFAULT_ROW_HEIGHT 
+      - DEFAULT_GRID_LINE_BREADTH;
+  private static final int DEFAULT_CELL_WIDTH
+    =   SpreadSheetModel.DEFAULT_COLUMN_WIDTH
+      - DEFAULT_GRID_LINE_BREADTH;
+  private static final int SASH_FACTOR = 2;
+  private static final int EXPECTED_SLIDER_COUNT = 2;
+  private static final int EXPECTED_CONTAINER_COUNT = 1;
+  private static final int EXPECTED_HEADER_COUNT = 2;
+  private static final int ROW_TO_TEST = 4;
+  private static final int ROW_TO_TEST_HEIGHT = 40;
+  private static final int COLUMN_TO_TEST = 5;
+  private static final int COLUMN_TO_TEST_WIDTH = 150;
+  private Display display;
+  private Shell shell;
+  private SpreadSheetModel model;
+  private CellController cellController;
+  private HeaderController headerController;
+  private SliderController sliderController;
+  private SpreadSheetLayout layout;
+  
+  public void testEmptyGrid() {
+    layout.layout( shell, false );
+    Composite cellContainer = cellController.getCellContainer();
+    Rectangle clientArea = shell.getClientArea();
+    int expectedColCount = calculateColumnCount( clientArea );
+    int expectedRowCount = calculateRowCount( clientArea );
+    Control[] children = cellContainer.getChildren();
+    assertEquals( expectedColCount + expectedRowCount - 2, children.length );
+
+    Label[] gridLines = cellController.getGridLines();
+    Label line = gridLines[ 0 ];
+    assertEquals( clientArea.x, line.getBounds().x );
+    assertEquals( DEFAULT_CELL_HEIGHT, line.getBounds().y );
+    assertEquals( clientArea.width, line.getBounds().width );
+    assertEquals( DEFAULT_GRID_LINE_BREADTH, line.getBounds().height );
+    
+    // change row and column offset
+    model.setRowOffset( 1 );
+    model.setColumnOffset( 1 );
+    layout.layout( shell, false );
+
+    // number of children must not change
+    children = cellContainer.getChildren();
+    assertEquals( expectedColCount + expectedRowCount - 2, children.length );
+    
+    gridLines = cellController.getGridLines();
+    line = gridLines[ 0 ];
+    assertEquals( clientArea.x + DEFAULT_COLUMN_WIDTH, line.getBounds().x );
+    assertEquals( DEFAULT_CELL_HEIGHT * 2 + DEFAULT_GRID_LINE_BREADTH,
+                  line.getBounds().y );
+    assertEquals( cellContainer.getBounds().width,
+                  line.getBounds().width );
+    assertEquals( DEFAULT_GRID_LINE_BREADTH, line.getBounds().height );
+
+  }
+  
+  public void testLayoutCompleteClientArea() {
+    Composite cellContainer = cellController.getCellContainer();
+    Composite rowHeaderContainer = headerController.getRowContainer();
+    Composite columnHeaderContainer = headerController.getColumnContainer();
+
+    int expectedControlCount =   EXPECTED_CONTAINER_COUNT
+                               + EXPECTED_SLIDER_COUNT
+                               + EXPECTED_HEADER_COUNT;
+    int sheetChildCount = shell.getChildren().length;
+    assertEquals( "Sliders or container are missing", 
+                  expectedControlCount, 
+                  sheetChildCount );
+    
+    for( int repetitions = 0; repetitions < 4; repetitions++ ) {
+      switch( repetitions ) {
+        case 1:
+          shell.setSize( 800, 800 );
+        break;
+        case 2:
+          shell.setSize( DEFAULT_SHELL_WIDTH, DEFAULT_SHELL_HEIGHT );
+        break;
+        case 3:
+          SpreadSheetFixture.populateModel( model );
+        break;
+      }
+      layout.layout( shell, false );
+      
+      Rectangle clientArea = shell.getClientArea();
+      assertEquals( clientArea, cellContainer.getBounds() );
+      int expectedColCount = calculateColumnCount( clientArea );
+      int expectedRowCount = calculateRowCount( clientArea );
+      int expectedCellCount = 0;
+      if( repetitions == 3 ) {
+        expectedCellCount = expectedColCount * expectedRowCount;
+      }
+      int expectedChildCount =   EXPECTED_SLIDER_COUNT
+                               + EXPECTED_CONTAINER_COUNT
+                               + EXPECTED_HEADER_COUNT;
+      assertEquals( expectedChildCount, shell.getChildren().length );
+
+      Control[] children = cellContainer.getChildren();
+      int cellCount = 0;
+      int headerCount = 0;
+      for( int i = 0; i < children.length; i++ ) {
+        if( CellController.isCellControl( children[ i ] ) ) {
+          cellCount++;
+        }
+      }
+      Control[] rowHeaderControls = rowHeaderContainer.getChildren();
+      Control[] columnHeaderControls = columnHeaderContainer.getChildren();
+      headerCount = rowHeaderControls.length + columnHeaderControls.length;
+      assertEquals( expectedCellCount, cellCount );
+      
+      int expectedHeaderCount
+        = ( expectedRowCount + expectedColCount ) * SASH_FACTOR;
+      assertEquals( expectedHeaderCount, headerCount );
+    }
+  }
+
+  private int calculateRowCount( final Rectangle clientArea ) {
+    BigDecimal height = new BigDecimal( clientArea.height );
+    BigDecimal rowHeight = new BigDecimal( DEFAULT_ROW_HEIGHT );
+    return height.divide( rowHeight, BigDecimal.ROUND_UP ).intValue();
+  }
+
+  private int calculateColumnCount( final Rectangle clientArea ) {
+    BigDecimal width = new BigDecimal( clientArea.width );
+    BigDecimal columnWidth = new BigDecimal( DEFAULT_COLUMN_WIDTH );
+    return width.divide( columnWidth, BigDecimal.ROUND_UP ).intValue();
+  }
+  
+  public void testHeaderPositions() {
+    Composite rowHeaderContainer = headerController.getRowContainer();
+    Composite columnHeaderContainer = headerController.getColumnContainer();
+
+    layout.layout( shell, false );
+    Rectangle clientArea = shell.getClientArea();
+ 
+    // Note that the headers belong to the trimmings of the surrounding
+    // composite and therefore they are not rendered into the clientarea
+    // of the shell. This may not feels reasonable but the spreadsheet is
+    // not intended to be used in another composites besides the SpreadSheet.
+    // TODO [fappel]: This construct may needs a second thought though.
+    Rectangle rowBounds = rowHeaderContainer.getBounds();
+    assertEquals( clientArea.x - DEFAULT_ROW_HEADER_WIDTH, rowBounds.x );
+    assertEquals( clientArea.y, rowBounds.y );
+    assertEquals( DEFAULT_ROW_HEADER_WIDTH - DEFAULT_GRID_LINE_BREADTH,
+                  rowBounds.width );
+    assertEquals( clientArea.height, rowBounds.height );
+
+    Rectangle columnBounds = columnHeaderContainer.getBounds();
+    assertEquals( clientArea.x, columnBounds.x );
+    assertEquals( clientArea.y - DEFAULT_COLUMN_HEADER_HEIGHT, columnBounds.y );
+    assertEquals( clientArea.width, columnBounds.width );
+    assertEquals( DEFAULT_COLUMN_HEADER_HEIGHT - DEFAULT_GRID_LINE_BREADTH,
+                  columnBounds.height);
+    
+    // test initial position of the header controls
+    Control[] rowHeaders = headerController.getRowHeaderControls();
+    assertEquals( model.getVisibleRows(), rowHeaders.length );
+    assertEquals( 0, rowHeaders[ 0 ].getBounds().x );
+    assertEquals( 0, rowHeaders[ 0 ].getBounds().y );
+
+    Control[] columnHeaders = headerController.getColumnHeaderControls();
+    assertEquals( model.getVisibleColumns(), columnHeaders.length );
+    assertEquals( 0, columnHeaders[ 0 ].getBounds().x );
+    assertEquals( 0, columnHeaders[ 0 ].getBounds().y );
+    
+    // change row and column offset
+    model.setRowOffset( 1 );
+    model.setColumnOffset( 1 );
+    layout.layout( shell, false );
+    
+    // test header container bounds with row and column offset
+    rowBounds = rowHeaderContainer.getBounds();
+    assertEquals( clientArea.x - DEFAULT_ROW_HEADER_WIDTH, rowBounds.x );
+    assertEquals( clientArea.y - DEFAULT_ROW_HEIGHT, rowBounds.y );
+    assertEquals( DEFAULT_ROW_HEADER_WIDTH - DEFAULT_GRID_LINE_BREADTH,
+                  rowBounds.width );
+    assertEquals( clientArea.height + DEFAULT_ROW_HEIGHT, rowBounds.height );
+
+    columnBounds = columnHeaderContainer.getBounds();
+    assertEquals( clientArea.x - DEFAULT_COLUMN_WIDTH, columnBounds.x );
+    assertEquals( clientArea.y - DEFAULT_COLUMN_HEADER_HEIGHT, columnBounds.y );
+    assertEquals( clientArea.width + DEFAULT_COLUMN_WIDTH, columnBounds.width );
+    assertEquals( DEFAULT_COLUMN_HEADER_HEIGHT - DEFAULT_GRID_LINE_BREADTH,
+                  columnBounds.height);
+
+    // test header controls positions with row and column offset
+    rowHeaders = headerController.getRowHeaderControls();
+    assertEquals( model.getVisibleRows(), rowHeaders.length );
+    assertEquals( 0, rowHeaders[ 0 ].getBounds().x );
+    assertEquals( DEFAULT_ROW_HEIGHT, rowHeaders[ 0 ].getBounds().y );
+    assertEquals( DEFAULT_ROW_HEIGHT * 2, rowHeaders[ 1 ].getBounds().y );
+    
+    columnHeaders = headerController.getColumnHeaderControls();
+    assertEquals( model.getVisibleColumns(), columnHeaders.length );
+    assertEquals( DEFAULT_COLUMN_WIDTH, columnHeaders[ 0 ].getBounds().x );
+    assertEquals( DEFAULT_COLUMN_WIDTH * 2, columnHeaders[ 1 ].getBounds().x );
+    assertEquals( 0, columnHeaders[ 0 ].getBounds().y );
+  }
+    
+  public void testCellBoundsAndPositionCalculation() {
+    SpreadSheetFixture.populateModel( model );
+    layout.layout( shell, false );
+    Composite container = cellController.getCellContainer();
+    assertEquals( shell.getClientArea(), container.getBounds() );
+
+    Control[] children = container.getChildren();
+    Control cell = null;
+    Control cell_0_0 = null;
+    for( int i = 0; i < children.length; i++ ) {
+      if( CellController.isCellControl( children[ i ] ) ) {
+        SpreadSheetData data = ( SpreadSheetData )children[ i ].getLayoutData();
+        if( new CellPosition( 0, 0 ).equals( data.getPosition() ) ) {
+          cell_0_0 = children[ i ];
+        }
+        if( new CellPosition( 2, 1 ).equals( data.getPosition() ) ) {
+          cell = children[ i ];
+        }
+      }
+    }
+    
+    // test cell position
+    Rectangle cellBounds = cell.getBounds();
+    Rectangle clientArea = container.getClientArea();
+    assertEquals( clientArea.x + DEFAULT_COLUMN_WIDTH, cellBounds.x );
+    assertEquals( clientArea.y + DEFAULT_ROW_HEIGHT * 2, cellBounds.y );
+    assertEquals( DEFAULT_CELL_WIDTH, cellBounds.width );
+    assertEquals( DEFAULT_CELL_HEIGHT, cellBounds.height );
+    
+    // test cellcontainer bounds with row and column offset
+    model.setRowOffset( 1 );
+    model.setColumnOffset( 1 );
+    layout.layout( shell, false );
+    int columnWidth = model.getColumnWidth( 0 );
+    int rowHeight = model.getRowHeight( 0 );
+    Rectangle cellContainerBounds = container.getBounds();
+    Rectangle shellClientArea = shell.getClientArea();
+    assertEquals( shellClientArea.x - columnWidth, cellContainerBounds.x );
+    assertEquals( shellClientArea.y - rowHeight, cellContainerBounds.y );
+    assertEquals( shellClientArea.width + columnWidth,
+                  cellContainerBounds.width );
+    assertEquals( shellClientArea.height + rowHeight,
+                  cellContainerBounds.height );
+    
+    // test movement of upper left cell in case of row and column offset
+    SpreadSheetData data = ( SpreadSheetData )cell_0_0.getLayoutData();
+    int visibleRows = model.getVisibleRows();
+    int visibleColumns = model.getVisibleColumns();
+    CellPosition expectedPosition 
+      = new CellPosition( visibleRows, visibleColumns );
+    assertEquals( expectedPosition, data.getPosition() );
+    
+    // ensure position of upper left cell after offsets have been removed
+    model.setRowOffset( 0 );
+    model.setColumnOffset( 0 );
+    layout.layout( shell, false );
+    data = ( SpreadSheetData )cell_0_0.getLayoutData();
+    assertEquals( new CellPosition( 0, 0 ), data.getPosition() );
+    
+    // test cell bounds with offset and changed size
+    int height = 150;
+    model.setRowHeight( 2, height );
+    int width = 300;
+    model.setColumnWidth( 1, width );
+    layout.layout( shell, false );
+    cellBounds = cell.getBounds();
+    clientArea = container.getClientArea();
+    assertEquals( clientArea.x + DEFAULT_COLUMN_WIDTH, cellBounds.x );
+    assertEquals( clientArea.y + DEFAULT_ROW_HEIGHT * 2, cellBounds.y );
+    assertEquals( width - DEFAULT_GRID_LINE_BREADTH , cellBounds.width );
+    assertEquals( height - DEFAULT_GRID_LINE_BREADTH, cellBounds.height );
+    
+    model.setColumnOffset( 1 );
+    model.setRowOffset( 1 );
+    layout.layout( shell, false );
+    cellBounds = cell.getBounds();
+    clientArea = container.getClientArea();
+    assertEquals( clientArea.x + DEFAULT_COLUMN_WIDTH, cellBounds.x );
+    assertEquals( clientArea.y + DEFAULT_ROW_HEIGHT * 2, cellBounds.y );
+    assertEquals( width - DEFAULT_GRID_LINE_BREADTH , cellBounds.width );
+    assertEquals( height - DEFAULT_GRID_LINE_BREADTH, cellBounds.height );
+  }
+
+  public void testLayoutingOfAdditionalChild() {
+    Label label = new Label( shell, SWT.NONE );
+    
+    // root coordinates
+    label.setLayoutData( new SpreadSheetData( 0, 0 ) );
+    layout.layout( shell, false );
+    Rectangle clientArea = shell.getClientArea();
+    Rectangle bounds = label.getBounds();
+    assertEquals( clientArea.x, bounds.x );
+    assertEquals( clientArea.y, bounds.y );
+    assertEquals( DEFAULT_CELL_WIDTH, bounds.width );
+    assertEquals( DEFAULT_CELL_HEIGHT, bounds.height );
+
+    
+    // arbitrary coordinates
+    label.setLayoutData( new SpreadSheetData( 2, 1 ) );
+    layout.layout( shell, false );
+    bounds = label.getBounds();
+    assertEquals( clientArea.x + DEFAULT_COLUMN_WIDTH, bounds.x );
+    assertEquals( clientArea.y + DEFAULT_ROW_HEIGHT * 2, bounds.y );
+    assertEquals( DEFAULT_CELL_WIDTH, bounds.width );
+    assertEquals( DEFAULT_CELL_HEIGHT, bounds.height );
+    
+    
+    // change height of a certain row
+    assertEquals( DEFAULT_ROW_HEIGHT, model.getRowHeight( ROW_TO_TEST ) );
+    model.setRowHeight( ROW_TO_TEST, ROW_TO_TEST_HEIGHT );
+    label.setLayoutData( new SpreadSheetData( ROW_TO_TEST, 0 ) );
+    layout.layout( shell, false );
+    bounds = label.getBounds();
+    assertEquals( clientArea.x, bounds.x );
+    assertEquals( clientArea.y + DEFAULT_ROW_HEIGHT * ROW_TO_TEST, bounds.y );
+    assertEquals( DEFAULT_CELL_WIDTH, bounds.width );
+    assertEquals( ROW_TO_TEST_HEIGHT, model.getRowHeight( ROW_TO_TEST ) );
+    
+    label.setLayoutData( new SpreadSheetData( ROW_TO_TEST + 1, 0 ) );    
+    layout.layout( shell, false );
+    bounds = label.getBounds();
+    assertEquals( clientArea.x, bounds.x );
+    int yPos
+      = clientArea.y + DEFAULT_ROW_HEIGHT * ROW_TO_TEST + ROW_TO_TEST_HEIGHT;
+    assertEquals( yPos, bounds.y );
+    assertEquals( DEFAULT_CELL_WIDTH, bounds.width );
+    assertEquals( ROW_TO_TEST_HEIGHT, model.getRowHeight( ROW_TO_TEST ) );
+    
+    model.setRowHeight( ROW_TO_TEST, SpreadSheetModel.DEFAULT_ROW_HEIGHT );
+    assertEquals( DEFAULT_ROW_HEIGHT, model.getRowHeight( ROW_TO_TEST ) );
+    
+    
+    // change width of a certain column
+    assertEquals( DEFAULT_COLUMN_WIDTH,
+                  model.getColumnWidth( COLUMN_TO_TEST ) );
+    model.setColumnWidth( COLUMN_TO_TEST, COLUMN_TO_TEST_WIDTH );
+    label.setLayoutData( new SpreadSheetData( 0, COLUMN_TO_TEST ) );
+    layout.layout( shell, false );
+    bounds = label.getBounds();
+    int xPos = clientArea.x + DEFAULT_COLUMN_WIDTH * COLUMN_TO_TEST;
+    assertEquals( xPos, bounds.x );
+    assertEquals( clientArea.y, bounds.y );
+    assertEquals( COLUMN_TO_TEST_WIDTH,
+                  model.getColumnWidth( COLUMN_TO_TEST ) );
+    assertEquals( DEFAULT_CELL_HEIGHT, bounds.height );
+    
+    label.setLayoutData( new SpreadSheetData( 0, COLUMN_TO_TEST + 1 ) );
+    layout.layout( shell, false );
+    bounds = label.getBounds();
+    xPos =    clientArea.x
+            + DEFAULT_COLUMN_WIDTH * COLUMN_TO_TEST 
+            + COLUMN_TO_TEST_WIDTH;
+    assertEquals( xPos, bounds.x );
+    assertEquals( clientArea.y, bounds.y );
+    assertEquals( COLUMN_TO_TEST_WIDTH,
+                  model.getColumnWidth( COLUMN_TO_TEST ) );
+    assertEquals( DEFAULT_CELL_HEIGHT, bounds.height );
+    
+    model.setColumnWidth( COLUMN_TO_TEST, DEFAULT_COLUMN_WIDTH );
+    assertEquals( DEFAULT_COLUMN_WIDTH,
+                  model.getColumnWidth( COLUMN_TO_TEST ) );
+  }
+  
+  public void testParams() {
+    try {
+      model.setColumnWidth( -1 , 4 );
+      fail( "Negative column index isn't allowed." );
+    } catch( final IllegalArgumentException iae ) {
+      // expected
+    }
+    try {
+      model.setColumnWidth( 1 , -4 );
+      fail( "Negative column width isn't allowed." );
+    } catch( final IllegalArgumentException iae ) {
+      // expected
+    }
+    try {
+      model.setRowHeight( -1 , 4 );
+      fail( "Negative row index isn't allowed." );
+    } catch( final IllegalArgumentException iae ) {
+      // expected
+    }
+    try {
+      model.setRowHeight( 1 , -4 );
+      fail( "Negative row height isn't allowed." );
+    } catch( final IllegalArgumentException iae ) {
+      // expected
+    }
+  }
+  
+  protected void setUp() throws Exception {
+    RWTFixture.setUp();
+    display = new Display();
+    shell = new Shell( display, SWT.SHELL_TRIM );
+    shell.setSize( DEFAULT_SHELL_WIDTH, DEFAULT_SHELL_HEIGHT );
+
+    model = new SpreadSheetModel();
+    cellController = new CellController( shell, model );
+    headerController = new HeaderController( shell, model );
+    sliderController = new SliderController( shell, headerController, model );
+    layout = new SpreadSheetLayout( model,
+                                    cellController,
+                                    sliderController,
+                                    headerController );
+  }
+
+  protected void tearDown() throws Exception {
+    model = null;
+    cellController = null;
+    headerController = null;
+    sliderController = null;
+    layout = null;
+    
+    if( display != null && !display.isDisposed() ) {
+      display.dispose();
+      shell = null;
+      display = null;
+    }
+    RWTFixture.tearDown();
+  }
+}
diff --git a/tests/org.eclipse.rap.rwt.custom.test/src/org/eclipse/swt/custom/SpreadSheet_Test.java b/tests/org.eclipse.rap.rwt.custom.test/src/org/eclipse/swt/custom/SpreadSheet_Test.java
new file mode 100644
index 0000000..7100fbc
--- /dev/null
+++ b/tests/org.eclipse.rap.rwt.custom.test/src/org/eclipse/swt/custom/SpreadSheet_Test.java
@@ -0,0 +1,348 @@
+// Created on 08.08.2009
+package org.eclipse.swt.custom;
+
+
+import junit.framework.TestCase;
+
+import org.eclipse.rwt.lifecycle.PhaseId;
+import org.eclipse.swt.RWTFixture;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.*;
+
+/*
+ * TODO [fappel]: complete MouseDown + MouseUp handling
+ * TODO [fappel]: check Display#map
+ * TODO [fappel]: visibility of trimmings (header, slider)
+ */
+public class SpreadSheet_Test extends TestCase {
+  private static final int DEFAULT_COLUMN_HEADER_HEIGHT
+    = HeaderController.DEFAULT_COLUMN_HEADER_HEIGHT;
+  private static final int DEFAULT_ROW_HEADER_WIDTH 
+    = HeaderController.DEFAULT_ROW_HEADER_WIDTH;
+  private static final int SLIDER_BREADTH = SliderController.SLIDER_BREADTH;
+  private static final String TEST_TEXT = "TextToSet";
+  private static final int ROW_TO_SELECT = 2;
+  private static final int COLUMN_TO_SELECT = 4;
+
+  private Display display;
+  private Shell shell;
+
+  public void testTextSetterAndGetter() {
+    SpreadSheet sheet = new SpreadSheet( shell, SWT.NONE );
+
+    try {
+      sheet.setText( null, ROW_TO_SELECT, COLUMN_TO_SELECT );
+      fail( "Parameter text must not be null." );
+    } catch( final IllegalArgumentException iae ) {
+      // expected
+    }
+    try {
+      sheet.setText( TEST_TEXT, -1, COLUMN_TO_SELECT );
+      fail( "Parameter rowIndex must not be negative." );
+    } catch( final IllegalArgumentException iae ) {
+      // expected
+    }
+    try {
+      sheet.setText( TEST_TEXT, ROW_TO_SELECT, -1 );
+      fail( "Parameter columnIndex must not be negative." );
+    } catch( final IllegalArgumentException iae ) {
+      // expected
+    }
+    try {
+      sheet.getText( -1, COLUMN_TO_SELECT );
+      fail( "Parameter columnIndex must not be negative." );
+    } catch( final IllegalArgumentException iae ) {
+      // expected
+    }
+    try {
+      sheet.getText( ROW_TO_SELECT, -1 );
+      fail( "Parameter rowIndex must not be negative." );
+    } catch( final IllegalArgumentException iae ) {
+      // expected
+    }
+
+    assertEquals( "", sheet.getText( ROW_TO_SELECT, COLUMN_TO_SELECT ) );
+    sheet.setText( TEST_TEXT, ROW_TO_SELECT, COLUMN_TO_SELECT );
+    assertEquals( TEST_TEXT,
+                  sheet.getText( ROW_TO_SELECT, COLUMN_TO_SELECT ) );
+    assertEquals( "", sheet.getText( ROW_TO_SELECT, COLUMN_TO_SELECT + 1 ) );
+    sheet.setText( "", ROW_TO_SELECT, COLUMN_TO_SELECT );
+    assertEquals( "", sheet.getText( ROW_TO_SELECT, COLUMN_TO_SELECT ) );
+
+    sheet.setText( TEST_TEXT, ROW_TO_SELECT, COLUMN_TO_SELECT );
+    RWTFixture.fakePhase( PhaseId.PREPARE_UI_ROOT );
+    shell.layout( true, true );
+    CellPosition position
+      = new CellPosition( ROW_TO_SELECT, COLUMN_TO_SELECT );
+    Label control = findCellControl( sheet, position );
+    String text = control.getText();
+    assertEquals( TEST_TEXT, text );
+    
+  }
+  
+  public void testSlider() {
+    SpreadSheet spreadSheet = new SpreadSheet( shell, SWT.NONE );
+    RWTFixture.fakePhase( PhaseId.PREPARE_UI_ROOT );
+    shell.layout();
+    
+    Slider hScroll = null;
+    Slider vScroll = null;
+    Control[] children = spreadSheet.getChildren();
+    for( int i = 0; i < children.length; i++ ) {
+      if( children[ i ] instanceof Slider ) {
+        Slider slider = ( Slider )children[ i ];
+        if( ( slider.getStyle() & SWT.H_SCROLL )  > 0 ) {
+          assertNull( hScroll );
+          hScroll = slider;
+        } else {
+          assertNull( vScroll );
+          vScroll = slider;
+        }
+      }
+    }
+    assertNotNull( hScroll );
+    assertNotNull( vScroll );
+    Rectangle shellClientArea = shell.getClientArea();
+    
+    
+    // slider positions
+    assertEquals( 0, hScroll.getLocation().x );
+    int yPosHScroll = shellClientArea.height - SLIDER_BREADTH;
+    assertEquals( yPosHScroll, hScroll.getLocation().y );
+    int widthHScroll = shellClientArea.width - SLIDER_BREADTH;
+    assertEquals( widthHScroll, hScroll.getSize().x );
+    assertEquals( SLIDER_BREADTH, hScroll.getSize().y );
+
+    int xPosVScroll = shellClientArea.width - SLIDER_BREADTH;
+    assertEquals( xPosVScroll, vScroll.getLocation().x );
+    assertEquals( 0, vScroll.getLocation().y );
+    assertEquals( SLIDER_BREADTH, vScroll.getSize().x );
+    int heightVScroll = shellClientArea.height - SLIDER_BREADTH;
+    assertEquals( heightVScroll, vScroll.getSize().y );
+
+    
+    // initial thumb sizes
+    int hMax = hScroll.getThumb() + SliderController.PAGE_INCREMENT_OFFSET;
+    assertEquals( hMax, hScroll.getMaximum() );
+    assertEquals( 0, hScroll.getSelection() );
+    int vMax = vScroll.getThumb() + SliderController.PAGE_INCREMENT_OFFSET;
+    assertEquals( vMax, vScroll.getMaximum() );
+    assertEquals( 0, vScroll.getSelection() );
+    
+    CellPosition position
+      = new CellPosition( ROW_TO_SELECT, COLUMN_TO_SELECT );
+    spreadSheet.setText( TEST_TEXT, position );
+    Label cell = findCellControl( spreadSheet, position );
+    assertSame( TEST_TEXT, cell.getText() );
+    SpreadSheetData cellData = ( SpreadSheetData )cell.getLayoutData();
+    assertEquals( position, cellData.getPosition() );
+    
+    
+    // row- and column-offset
+    CellPosition position_0_0 = new CellPosition( 0, 0 );
+    Label cell_0_0 = findCellControl( spreadSheet, position_0_0 );
+    assertNull( cell_0_0 );
+    spreadSheet.setText( TEST_TEXT, position_0_0 );
+    cell_0_0 = findCellControl( spreadSheet, position_0_0 );
+    assertSame( TEST_TEXT, cell_0_0.getText() );
+    
+    spreadSheet.setColumnOffset( 1 );
+    assertSame( "", cell_0_0.getText() );
+    assertSame( TEST_TEXT, cell.getText() );
+    assertEquals( 1, hScroll.getSelection() );
+    
+    spreadSheet.setColumnOffset( 0 );
+    assertSame( TEST_TEXT, cell_0_0.getText() );
+    assertSame( TEST_TEXT, cell.getText() );
+    assertEquals( 0, hScroll.getSelection() );
+    
+    spreadSheet.setRowOffset( 1 );
+    assertSame( "", cell_0_0.getText() );
+    assertSame( TEST_TEXT, cell.getText() );
+    assertEquals( 1, vScroll.getSelection() );
+    
+    spreadSheet.setRowOffset( 0 );
+    assertSame( TEST_TEXT, cell_0_0.getText() );
+    assertSame( TEST_TEXT, cell.getText() );
+    assertEquals( 0, vScroll.getSelection() );
+  }
+
+  public void testClientArea() {
+    SpreadSheet spreadSheet = new SpreadSheet( shell, SWT.NONE );
+    RWTFixture.fakePhase( PhaseId.PREPARE_UI_ROOT );
+    shell.layout();
+
+    Rectangle shellClientArea = shell.getClientArea();
+    Rectangle clientArea = spreadSheet.getClientArea();
+    assertEquals( DEFAULT_ROW_HEADER_WIDTH, clientArea.x );
+    assertEquals( DEFAULT_COLUMN_HEADER_HEIGHT, clientArea.y );
+    int width =   shellClientArea.width 
+                - SLIDER_BREADTH
+                - DEFAULT_ROW_HEADER_WIDTH;
+    assertEquals( width, clientArea.width );
+    int height =    shellClientArea.height
+                  - SLIDER_BREADTH
+                  - DEFAULT_COLUMN_HEADER_HEIGHT;
+    assertEquals( height, clientArea.height );
+  }
+
+  public void testMouseHandler() {
+    SpreadSheet sheet = new SpreadSheet( shell, SWT.NONE );
+    CellPosition position
+      = new CellPosition( ROW_TO_SELECT, COLUMN_TO_SELECT );
+    sheet.getCellEditorController().handleMouseDown( position );
+    sheet.getCellEditorController().handleMouseUp( position );
+    CellEditorController controller = sheet.getCellEditorController();
+    Control control = controller.getControl();
+    SpreadSheetData data = ( SpreadSheetData )control.getLayoutData();
+    assertEquals( ROW_TO_SELECT, data.getRowIndex() );
+    assertEquals( COLUMN_TO_SELECT, data.getColumnIndex() );
+  }
+
+  public void testCellEditorTextInAndOutput() {
+    SpreadSheet sheet = new SpreadSheet( shell, SWT.NONE );
+    shell.layout( true, true );
+
+    CellEditorController controller = sheet.getCellEditorController();
+    assertEquals( "", controller.getText() );
+
+    sheet.setText( TEST_TEXT, ROW_TO_SELECT, COLUMN_TO_SELECT );
+    CellPosition position
+      = new CellPosition( ROW_TO_SELECT, COLUMN_TO_SELECT );
+    Label cellControl = findCellControl( sheet, position );
+    assertEquals( TEST_TEXT, cellControl.getText() );
+    assertEquals( "", controller.getText() );
+    sheet.getCellEditorController().handleMouseDown( position );
+    sheet.getCellEditorController().handleMouseUp( position );
+    assertEquals( TEST_TEXT, controller.getText() );
+
+    controller.setText( "" );
+    assertEquals( TEST_TEXT, sheet.getText( ROW_TO_SELECT, COLUMN_TO_SELECT ) );
+    assertEquals( TEST_TEXT, cellControl.getText() );
+    KeyEvent evt = new KeyEvent( controller.getControl(),
+                                 KeyEvent.KEY_PRESSED );
+    evt.keyCode = SWT.ARROW_DOWN;
+    controller.handleKeyPressed( evt );
+    assertEquals( "", sheet.getText( ROW_TO_SELECT, COLUMN_TO_SELECT ) );
+    assertTrue( "", cellControl.isDisposed() );
+    assertEquals( "", controller.getText() );
+
+    sheet.setText( TEST_TEXT, ROW_TO_SELECT + 1, COLUMN_TO_SELECT );
+    assertEquals( TEST_TEXT, controller.getText() );
+  }
+
+  private Label findCellControl( final SpreadSheet sheet,
+                                 final CellPosition position )
+  {
+    Label result = null;
+    Control[] children = sheet.getChildren();
+    for( int i = 0; result == null && i < children.length; i++ ) {
+      if( children[ i ] instanceof Composite ) {
+        Composite cellContainer = ( Composite )children[ i ];
+        Control[] cellControls = cellContainer.getChildren();
+        for( int j = 0; j < cellControls.length; j++ ) {
+          SpreadSheetData data
+          = ( SpreadSheetData )cellControls[ j ].getLayoutData();
+          Control cellEditorControl
+          = sheet.getCellEditorController().getControl();
+          if(    data != null
+              && position.equals( data.getPosition() )
+              && cellControls[ j ] != cellEditorControl )
+          {
+            result = ( Label )cellControls[ j ];
+          }
+        }
+      }
+    }
+    return result;
+  }
+
+  public void testKeyHandler() {
+    SpreadSheet sheet = new SpreadSheet( shell, SWT.NONE );
+    CellEditorController controller = sheet.getCellEditorController();
+    Control cellEditor = controller.getControl();
+    SpreadSheetData data = ( SpreadSheetData )cellEditor.getLayoutData();
+    assertEquals( 0, data.getRowIndex() );
+    assertEquals( 0, data.getColumnIndex() );
+
+    KeyEvent evt = new KeyEvent( cellEditor, KeyEvent.KEY_PRESSED );
+    evt.keyCode = SWT.ARROW_DOWN;
+    controller.handleKeyPressed( evt );
+    data = ( SpreadSheetData )cellEditor.getLayoutData();
+    assertEquals( 1, data.getRowIndex() );
+    assertEquals( 0, data.getColumnIndex() );
+
+    evt = new KeyEvent( cellEditor, KeyEvent.KEY_PRESSED );
+    evt.keyCode = SWT.ARROW_RIGHT;
+    controller.handleKeyPressed( evt );
+    data = ( SpreadSheetData )cellEditor.getLayoutData();
+    assertEquals( 1, data.getRowIndex() );
+    assertEquals( 1, data.getColumnIndex() );
+
+    evt = new KeyEvent( cellEditor, KeyEvent.KEY_PRESSED );
+    evt.keyCode = SWT.ARROW_UP;
+    controller.handleKeyPressed( evt );
+    data = ( SpreadSheetData )cellEditor.getLayoutData();
+    assertEquals( 0, data.getRowIndex() );
+    assertEquals( 1, data.getColumnIndex() );
+
+    evt = new KeyEvent( cellEditor, KeyEvent.KEY_PRESSED );
+    evt.keyCode = SWT.ARROW_LEFT;
+    controller.handleKeyPressed( evt );
+    data = ( SpreadSheetData )cellEditor.getLayoutData();
+    assertEquals( 0, data.getRowIndex() );
+    assertEquals( 0, data.getColumnIndex() );
+
+    evt = new KeyEvent( cellEditor, KeyEvent.KEY_PRESSED );
+    evt.keyCode = SWT.ARROW_LEFT;
+    controller.handleKeyPressed( evt );
+    data = ( SpreadSheetData )cellEditor.getLayoutData();
+    assertEquals( 0, data.getRowIndex() );
+    assertEquals( 0, data.getColumnIndex() );
+
+    evt = new KeyEvent( cellEditor, KeyEvent.KEY_PRESSED );
+    evt.keyCode = SWT.ARROW_UP;
+    controller.handleKeyPressed( evt );
+    data = ( SpreadSheetData )cellEditor.getLayoutData();
+    assertEquals( 0, data.getRowIndex() );
+    assertEquals( 0, data.getColumnIndex() );
+  }
+
+  public void testFocusHandling() {
+    shell.open();
+    SpreadSheet sheet = new SpreadSheet( shell, SWT.NONE );
+    CellEditorController controller = sheet.getCellEditorController();
+    Control cellEditor = controller.getControl();
+    assertFalse( cellEditor.isFocusControl() );
+    sheet.setFocus();
+    assertTrue( cellEditor.isFocusControl() );
+    Text text = new Text( shell, SWT.NONE );
+    text.setFocus();
+    assertFalse( cellEditor.isFocusControl() );
+    CellPosition position = new CellPosition( 1, 1 );
+    sheet.getCellEditorController().handleMouseDown( position );
+    sheet.getCellEditorController().handleMouseUp( position );
+    assertTrue( cellEditor.isFocusControl() );
+  }
+
+  protected void setUp() throws Exception {
+    RWTFixture.setUp();
+    display = new Display();
+    shell = new Shell( display, SWT.SHELL_TRIM );
+    shell.setSize( 600, 400 );
+    shell.setLayout( new FillLayout() );
+  }
+
+  protected void tearDown() throws Exception {
+    if( display != null && !display.isDisposed() ) {
+      display.dispose();
+      shell = null;
+      display = null;
+    }
+    RWTFixture.tearDown();
+  }
+
+}