]> source.dussan.org Git - vaadin-framework.git/commitdiff
Vaadin 7 compatible ColorPicker (#9201) 00/400/2
authorAnna Koskinen <anna@vaadin.com>
Tue, 27 Nov 2012 08:33:53 +0000 (10:33 +0200)
committerVaadin Code Review <review@vaadin.com>
Mon, 10 Dec 2012 13:11:13 +0000 (13:11 +0000)
Change-Id: I44962ceedd5ef607e2fbe2af0d96808e0aef9cc5

46 files changed:
WebContent/VAADIN/themes/base/base.scss
WebContent/VAADIN/themes/base/colorpicker/colorpicker.scss [new file with mode: 0644]
WebContent/VAADIN/themes/base/colorpicker/images/gradient.png [new file with mode: 0644]
WebContent/VAADIN/themes/base/colorpicker/images/gradient2.png [new file with mode: 0644]
WebContent/VAADIN/themes/base/colorpicker/images/resizebg-selected.png [new file with mode: 0644]
WebContent/VAADIN/themes/base/colorpicker/images/resizebg.png [new file with mode: 0644]
WebContent/VAADIN/themes/base/colorpicker/images/slider_hue_bg.png [new file with mode: 0644]
WebContent/VAADIN/themes/chameleon/components/colorpicker/colorpicker.scss [new file with mode: 0644]
WebContent/VAADIN/themes/chameleon/components/components.scss
WebContent/VAADIN/themes/liferay/colorpicker/colorpicker.scss [new file with mode: 0644]
WebContent/VAADIN/themes/liferay/liferay.scss
WebContent/VAADIN/themes/reindeer/colorpicker/colorpicker.scss [new file with mode: 0644]
WebContent/VAADIN/themes/reindeer/reindeer.scss
WebContent/VAADIN/themes/runo/colorpicker/colorpicker.scss [new file with mode: 0644]
WebContent/VAADIN/themes/runo/runo.scss
client/src/com/vaadin/client/ui/VColorPicker.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VColorPickerArea.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/colorpicker/AbstractColorPickerConnector.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/colorpicker/ColorPickerAreaConnector.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/colorpicker/ColorPickerConnector.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/colorpicker/ColorPickerGradientConnector.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/colorpicker/ColorPickerGridConnector.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/colorpicker/VColorPickerGradient.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/colorpicker/VColorPickerGrid.java [new file with mode: 0644]
server/src/com/vaadin/ui/AbstractColorPicker.java [new file with mode: 0644]
server/src/com/vaadin/ui/ColorPicker.java [new file with mode: 0644]
server/src/com/vaadin/ui/ColorPickerArea.java [new file with mode: 0644]
server/src/com/vaadin/ui/components/colorpicker/ColorChangeEvent.java [new file with mode: 0644]
server/src/com/vaadin/ui/components/colorpicker/ColorChangeListener.java [new file with mode: 0644]
server/src/com/vaadin/ui/components/colorpicker/ColorPickerGradient.java [new file with mode: 0644]
server/src/com/vaadin/ui/components/colorpicker/ColorPickerGrid.java [new file with mode: 0644]
server/src/com/vaadin/ui/components/colorpicker/ColorPickerHistory.java [new file with mode: 0644]
server/src/com/vaadin/ui/components/colorpicker/ColorPickerPopup.java [new file with mode: 0644]
server/src/com/vaadin/ui/components/colorpicker/ColorPickerPreview.java [new file with mode: 0644]
server/src/com/vaadin/ui/components/colorpicker/ColorPickerSelect.java [new file with mode: 0644]
server/src/com/vaadin/ui/components/colorpicker/ColorSelector.java [new file with mode: 0644]
server/src/com/vaadin/ui/components/colorpicker/HasColorChangeListener.java [new file with mode: 0644]
shared/src/com/vaadin/shared/ui/colorpicker/Color.java [new file with mode: 0644]
shared/src/com/vaadin/shared/ui/colorpicker/ColorPickerGradientServerRpc.java [new file with mode: 0644]
shared/src/com/vaadin/shared/ui/colorpicker/ColorPickerGradientState.java [new file with mode: 0644]
shared/src/com/vaadin/shared/ui/colorpicker/ColorPickerGridServerRpc.java [new file with mode: 0644]
shared/src/com/vaadin/shared/ui/colorpicker/ColorPickerGridState.java [new file with mode: 0644]
shared/src/com/vaadin/shared/ui/colorpicker/ColorPickerServerRpc.java [new file with mode: 0644]
shared/src/com/vaadin/shared/ui/colorpicker/ColorPickerState.java [new file with mode: 0644]
uitest/src/com/vaadin/tests/components/colorpicker/ColorPickerTest.html [new file with mode: 0644]
uitest/src/com/vaadin/tests/components/colorpicker/ColorPickerTest.java [new file with mode: 0644]

index a16b063ba81a3a1048eb4c58626846294fc84eab..640978d3168ae784303fe68bac5964843098b2bc 100644 (file)
@@ -5,6 +5,7 @@
 @import "button/checkbox.scss";
 @import "layout/layout.scss";
 @import "caption/caption.scss";
+@import "colorpicker/colorpicker.scss";
 @import "common/common.scss";
 @import "csslayout/csslayout.scss";
 @import "customcomponent/customcomponent.scss";
@@ -61,6 +62,7 @@
        @include base-nativebutton;
        @include base-checkbox;
        @include base-caption;
+       @include base-colorpicker;
        
        // here for now to preserve old semantics
        @include base-common;
diff --git a/WebContent/VAADIN/themes/base/colorpicker/colorpicker.scss b/WebContent/VAADIN/themes/base/colorpicker/colorpicker.scss
new file mode 100644 (file)
index 0000000..bd4420b
--- /dev/null
@@ -0,0 +1,209 @@
+@mixin base-colorpicker($name : v-colorpicker) {
+
+.#{$name} {
+       text-align: center;
+}
+
+.#{$name}-button-color {
+       border: 1px solid silver;
+       float: left;
+       width: 10px;
+       height: 10px;
+       margin-top: 2px;
+       margin-right: 5px;
+}
+
+.#{$name}-area {
+       border: 1px solid silver;
+       margin: 1px auto;
+}
+
+
+/***************** COLOR HISTORY COMPONENT *****************************/
+.#{$name}-history {
+       margin: 5px;
+}
+
+.#{$name}-history td {
+       border: 1px solid silver !important;
+}
+
+.#{$name}-history td {
+       line-height: 15px;
+}
+/***********************************************************************/
+
+
+/**************** COLOR POPUP COMPONENT ********************************/
+.#{$name}-popup {
+       width: 244px;
+}
+
+.#{$name}-popup .v-scrollable {
+       background-color: #dddddd;
+}
+
+.#{$name}-popup .v-tabsheet-content .v-scrollable {
+       background-color: #eaeaea;
+}
+
+.#{$name}-popup .v-tabsheet-content .v-tabsheet-tabsheetpanel {
+       background: transparent;
+}
+
+.#{$name}-popup .resize-button {
+       border: 0px solid silver;
+       background: url(images/resizebg.png);
+       margin-top: 5px;
+}
+
+.#{$name}-popup .resize-button-caption {
+       display: none;
+}
+
+.#{$name}-popup .resize-button:hover {
+       background: url(images/resizebg-selected.png);
+}
+
+.#{$name}-popup .resize-button:focus {
+       outline: none;
+}
+
+/***********************************************************************/
+
+
+/*********************** COLOR GRADIENT *******************************/
+.#{$name}-gradient {
+       width: 220px;
+       height: 220px;
+       padding-left: 10px;
+       margin-top: 0px;
+}
+
+.#{$name}-gradient .#{$name}-gradient-clicklayer {
+       background-color: white;
+       filter: alpha(opacity = 0);
+       opacity: 0;
+}
+
+.#{$name}-popup #rgb-gradient .#{$name}-gradient-background {
+       width: 220px;
+       height: 220px;
+       background: url(images/gradient2.png);
+}
+
+.#{$name}-popup #hsv-gradient .#{$name}-gradient-foreground {
+       background: url(images/gradient.png);
+}
+
+.#{$name}-popup .#{$name}-gradient-lowerbox {
+       border-right: 1px solid white;
+       border-top: 1px solid white;
+}
+
+.#{$name}-popup .#{$name}-gradient-higherbox {
+       border-left: 1px solid white;
+       border-bottom: 1px solid white;
+}
+/**********************************************************************/
+
+
+/************************ COLOR SLIDER ********************************/
+.#{$name}-popup .rgb-sliders {
+       width: 227px;
+       padding-left: 10px;
+       color: #444444;
+       text-shadow: 0 1px 0 #FFFFFF;
+}
+
+.#{$name}-popup .rgb-sliders .red .v-slider-base {
+       background-color: red;
+}
+
+.#{$name}-popup .rgb-sliders .green .v-slider-base {
+       background-color: green;
+}
+
+.#{$name}-popup .rgb-sliders .blue .v-slider-base {
+       background-color: blue;
+}
+
+.#{$name}-popup .hsv-sliders {
+       padding-left: 10px;
+       color: #444444;
+       text-shadow: 0 1px 0 #FFFFFF;
+}
+
+.#{$name}-popup .hue-slider {
+       height: 10px;
+       border: 0px solid silver;
+       background-image: url(images/slider_hue_bg.png);
+       background-color: transparent;
+       background-repeat: no-repeat;
+       background-position: 0 3px;
+       margin-top: 0px;
+}
+
+.#{$name}-popup .hue-slider .v-slider-handle {
+       margin-top: -2px;
+}
+
+.#{$name}-popup .hue-slider .v-slider-base {
+       border: 0px none;
+       height: 0px;
+       background-color: transparent;
+}
+/****************************************************************/
+
+
+/****************** COLOR PREVIEW *******************************/
+.#{$name}-popup .#{$name}-preview {
+       margin-top: 5px;
+       padding-left: 11px;
+       padding-right: 10px;
+       margin-bottom: 0px;
+}
+
+.#{$name}-popup .v-absolutelayout-wrapper {
+       width: 100%;
+       height: 100%;
+}
+
+.#{$name}-popup .#{$name}-preview-textfield {
+       background: none;
+       overflow: hidden;
+       overflow-y: hidden;
+       overflow-x: hidden;
+}
+
+.#{$name}-popup .v-textfield-dark {
+       color: #FFFFFF;
+}
+
+.#{$name}-popup .v-textfield-light {
+       color: #000000;
+}
+/****************************************************************/
+
+
+/*************** COLOR SELECT ***********************************/
+.#{$name}-popup .colorselect {
+       margin-top: 5px;
+       padding-left: 10px;
+       padding-right: 10px;
+}
+
+.#{$name}-popup .v-tabsheet .#{$name}-grid {
+       height: 319px;
+}
+
+.#{$name}-popup .colorselect td {
+       line-height: 15px;
+}
+
+.#{$name}-popup .v-filterselect {
+       padding-right: 16px;
+}
+/****************************************************************/
+
+}
\ No newline at end of file
diff --git a/WebContent/VAADIN/themes/base/colorpicker/images/gradient.png b/WebContent/VAADIN/themes/base/colorpicker/images/gradient.png
new file mode 100644 (file)
index 0000000..def063a
Binary files /dev/null and b/WebContent/VAADIN/themes/base/colorpicker/images/gradient.png differ
diff --git a/WebContent/VAADIN/themes/base/colorpicker/images/gradient2.png b/WebContent/VAADIN/themes/base/colorpicker/images/gradient2.png
new file mode 100644 (file)
index 0000000..f51ed75
Binary files /dev/null and b/WebContent/VAADIN/themes/base/colorpicker/images/gradient2.png differ
diff --git a/WebContent/VAADIN/themes/base/colorpicker/images/resizebg-selected.png b/WebContent/VAADIN/themes/base/colorpicker/images/resizebg-selected.png
new file mode 100644 (file)
index 0000000..6e56ec0
Binary files /dev/null and b/WebContent/VAADIN/themes/base/colorpicker/images/resizebg-selected.png differ
diff --git a/WebContent/VAADIN/themes/base/colorpicker/images/resizebg.png b/WebContent/VAADIN/themes/base/colorpicker/images/resizebg.png
new file mode 100644 (file)
index 0000000..b6e3532
Binary files /dev/null and b/WebContent/VAADIN/themes/base/colorpicker/images/resizebg.png differ
diff --git a/WebContent/VAADIN/themes/base/colorpicker/images/slider_hue_bg.png b/WebContent/VAADIN/themes/base/colorpicker/images/slider_hue_bg.png
new file mode 100644 (file)
index 0000000..bcef2c5
Binary files /dev/null and b/WebContent/VAADIN/themes/base/colorpicker/images/slider_hue_bg.png differ
diff --git a/WebContent/VAADIN/themes/chameleon/components/colorpicker/colorpicker.scss b/WebContent/VAADIN/themes/chameleon/components/colorpicker/colorpicker.scss
new file mode 100644 (file)
index 0000000..98f00f0
--- /dev/null
@@ -0,0 +1,15 @@
+@mixin chameleon-colorpicker($name : v-colorpicker) {
+
+.#{$name}-button-color {
+       border: 1px solid #8B8B8B;
+}
+
+.#{$name}-popup {
+       width: 248px;
+}
+
+.#{$name}-popup .v-tabsheet .#{$name}-grid {
+       height: 308px;
+}
+
+}
\ No newline at end of file
index 79b5a2d46885e8f59c7fd50467b20d93c414ca23..9f29827de034582e02737d66ab8cb18ffc83fa59 100644 (file)
@@ -1,5 +1,6 @@
 @import "accordion/accordion.scss";
 @import "button/button.scss";
+@import "colorpicker/colorpicker.scss";
 @import "label/label.scss";
 @import "menubar/menubar.scss";
 @import "notification/notification.scss";
@@ -22,6 +23,7 @@
 
        @include chameleon-accordion;
        @include chameleon-button;
+       @include chameleon-colorpicker;
        @include chameleon-label;
        @include chameleon-menubar;
        @include chameleon-notification;
diff --git a/WebContent/VAADIN/themes/liferay/colorpicker/colorpicker.scss b/WebContent/VAADIN/themes/liferay/colorpicker/colorpicker.scss
new file mode 100644 (file)
index 0000000..fc50297
--- /dev/null
@@ -0,0 +1,19 @@
+@mixin liferay-colorpicker($name : v-colorpicker) {
+
+.#{$name}-button-color {
+       border: 1px solid #999999;
+}
+
+.#{$name}-popup {
+       width: 252px;
+}
+
+.#{$name}-popup .v-tabsheet .#{$name}-grid {
+       height: 312px;
+}
+
+.#{$name}-popup .v-filterselect {
+       padding-right: 24px;
+}
+
+}
\ No newline at end of file
index 3128e48c76b10d78b635eba788eb67ce0298fa51..299542fea096863cef5c43d564f994db4f5ed9e1 100644 (file)
@@ -2,6 +2,7 @@
 
 @import "accordion/accordion.scss";
 @import "button/button.scss";
+@import "colorpicker/colorpicker.scss";
 
 @import "common/common.scss";
 
@@ -29,6 +30,7 @@
        // TODO @each
        @include liferay-accordion;
        @include liferay-button;
+       @include liferay-colorpicker;
        
        @include liferay-common;
        @include liferay-contextmenu;
diff --git a/WebContent/VAADIN/themes/reindeer/colorpicker/colorpicker.scss b/WebContent/VAADIN/themes/reindeer/colorpicker/colorpicker.scss
new file mode 100644 (file)
index 0000000..4224691
--- /dev/null
@@ -0,0 +1,15 @@
+@mixin reindeer-colorpicker($name : v-colorpicker) {
+
+.#{$name}-popup {
+       width: 246px;
+}
+
+.#{$name}-popup .v-tabsheet .#{$name}-grid {
+       height: 284px;
+}
+
+.#{$name}-popup .v-filterselect {
+       padding-right: 25px;
+}
+
+}
\ No newline at end of file
index ce6b54610ed1ffd0d04b67081d4ca9a2e0fdad4e..f02fcf18a89a1b94122cad35e7f1af3c2ddea75d 100644 (file)
@@ -5,6 +5,7 @@
 @import "a-sprite-definitions/a-sprite-definitions.scss";
 @import "button/button.scss";
 @import "button/nativebutton.scss";
+@import "colorpicker/colorpicker.scss";
 
 @import "common/common.scss";
 
@@ -42,6 +43,7 @@
        // TODO @include a-sprite-definitions;
        @include reindeer-button;
        @include reindeer-nativebutton;
+       @include reindeer-colorpicker;
        @include reindeer-common;
        @include reindeer-datefield;
        @include reindeer-inlinedatefield;
diff --git a/WebContent/VAADIN/themes/runo/colorpicker/colorpicker.scss b/WebContent/VAADIN/themes/runo/colorpicker/colorpicker.scss
new file mode 100644 (file)
index 0000000..df48c10
--- /dev/null
@@ -0,0 +1,20 @@
+@mixin runo-colorpicker($name : v-colorpicker) {
+
+.#{$name}-popup {
+       width: 248px;
+}
+
+.#{$name}-popup .v-tabsheet .#{$name}-grid {
+       height: 305px;
+}
+
+.#{$name}-popup .v-tabsheet-deco {
+       background: none;
+       height: 0;
+}
+
+.#{$name}-popup .v-filterselect {
+       padding-right: 25px;
+}
+
+}
\ No newline at end of file
index de4d2b3bbdd23e2705d87686f3af6af1d51ca1f8..395f4d0b1be5c955041cd3ff634f8cd2c2edbfcc 100644 (file)
@@ -4,6 +4,7 @@
 @import "accordion/accordion.scss";
 @import "button/button.scss";
 @import "caption/caption.scss";
+@import "colorpicker/colorpicker.scss";
 @import "common/common.scss";
 @import "datefield/datefield.scss";
 @import "inlinedatefield/inlinedatefield.scss";
@@ -39,6 +40,7 @@
        @include runo-accordion;
        @include runo-button;
        @include runo-caption;
+       @include runo-colorpicker;
        
        @include runo-common;
        
@@ -63,4 +65,4 @@
        @include runo-textfield;
        @include runo-tree;
        @include runo-window;
-}
\ No newline at end of file
+}
diff --git a/client/src/com/vaadin/client/ui/VColorPicker.java b/client/src/com/vaadin/client/ui/VColorPicker.java
new file mode 100644 (file)
index 0000000..7cd5fbf
--- /dev/null
@@ -0,0 +1,73 @@
+package com.vaadin.client.ui;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.HTML;
+
+/**
+ * Client side implementation for ColorPicker.
+ * 
+ * @since 7.0.0
+ */
+public class VColorPicker extends VButton implements ClickHandler {
+
+    private String color = null;
+
+    private boolean isOpen = false;
+
+    private HTML colorIcon;
+
+    @Override
+    public void onClick(ClickEvent event) {
+        super.onClick(event);
+
+        setOpen(!isOpen);
+    }
+
+    /**
+     * Set the color of the component, e.g. #ffffff
+     * 
+     * @param color
+     */
+    public void setColor(String color) {
+        this.color = color;
+    }
+
+    /**
+     * Mark the popup opened/closed.
+     * 
+     * @param open
+     */
+    public void setOpen(boolean open) {
+        isOpen = open;
+    }
+
+    /**
+     * Check the popup's marked state.
+     * 
+     * @return true if the popup has been marked being open, false otherwise.
+     */
+    public boolean isOpen() {
+        return isOpen;
+    }
+
+    /**
+     * Update color icon to show the currently selected color.
+     */
+    public void refreshColor() {
+        if (color != null) {
+
+            if (colorIcon == null) {
+                colorIcon = new HTML();
+                colorIcon.setStylePrimaryName("v-colorpicker-button-color");
+                wrapper.insertBefore(colorIcon.getElement(), captionElement);
+            }
+
+            // Set the color
+            DOM.setStyleAttribute(colorIcon.getElement(), "background", color);
+
+        }
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VColorPickerArea.java b/client/src/com/vaadin/client/ui/VColorPickerArea.java
new file mode 100644 (file)
index 0000000..2327b79
--- /dev/null
@@ -0,0 +1,181 @@
+package com.vaadin.client.ui;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HasHTML;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Client side implementation for ColorPickerArea.
+ * 
+ * @since 7.0.0
+ */
+public class VColorPickerArea extends Widget implements ClickHandler, HasHTML,
+        HasClickHandlers {
+
+    private String color = null;
+
+    private boolean isOpen;
+
+    private HTML caption;
+    private HTML area;
+
+    /**
+     * Initializes an area-style color picker widget.
+     */
+    public VColorPickerArea() {
+        super();
+        setElement(DOM.createDiv());
+
+        caption = new HTML();
+        caption.addStyleName("v-caption");
+        caption.setWidth("");
+
+        area = new HTML();
+        area.setStylePrimaryName(getStylePrimaryName() + "-area");
+
+        getElement().appendChild(caption.getElement());
+        getElement().appendChild(area.getElement());
+
+        addClickHandler(this);
+    }
+
+    /**
+     * Adds a click handler to the widget and sinks the click event.
+     * 
+     * @param handler
+     * @return HandlerRegistration used to remove the handler
+     */
+    public HandlerRegistration addClickHandler(ClickHandler handler) {
+        return addDomHandler(handler, ClickEvent.getType());
+    }
+
+    @Override
+    public void onClick(ClickEvent event) {
+        setOpen(!isOpen);
+    }
+
+    @Override
+    public void onBrowserEvent(Event event) {
+        int type = DOM.eventGetType(event);
+        switch (type) {
+        case Event.ONCLICK:
+            if (DOM.isOrHasChild(area.getElement(), DOM.eventGetTarget(event))) {
+                super.onBrowserEvent(event);
+            }
+            break;
+        default:
+            super.onBrowserEvent(event);
+        }
+    }
+
+    /**
+     * Mark the popup opened/closed.
+     * 
+     * @param open
+     */
+    public void setOpen(boolean open) {
+        isOpen = open;
+    }
+
+    /**
+     * Check the popup's marked state.
+     * 
+     * @return true if the popup has been marked being open, false otherwise.
+     */
+    public boolean isOpen() {
+        return isOpen;
+    }
+
+    /**
+     * Sets the caption's content to the given text.
+     * 
+     * @param text
+     * 
+     * @see Label#setText(String)
+     */
+    @Override
+    public void setText(String text) {
+        caption.setText(text);
+    }
+
+    /**
+     * Gets the caption's contents as text.
+     * 
+     * @return the caption's text
+     */
+    @Override
+    public String getText() {
+        return caption.getText();
+    }
+
+    /**
+     * Sets the caption's content to the given HTML.
+     * 
+     * @param html
+     */
+    @Override
+    public void setHTML(String html) {
+        caption.setHTML(html);
+    }
+
+    /**
+     * Gets the caption's contents as HTML.
+     * 
+     * @return the caption's HTML
+     */
+    @Override
+    public String getHTML() {
+        return caption.getHTML();
+    }
+
+    /**
+     * Sets the color for the area.
+     * 
+     * @param color
+     */
+    public void setColor(String color) {
+        this.color = color;
+    }
+
+    /**
+     * Update the color area with the currently set color.
+     */
+    public void refreshColor() {
+        if (color != null) {
+            // Set the color
+            DOM.setStyleAttribute(area.getElement(), "background", color);
+        }
+    }
+
+    @Override
+    public void setStylePrimaryName(String style) {
+        super.setStylePrimaryName(style);
+        area.setStylePrimaryName(getStylePrimaryName() + "-area");
+    }
+
+    /**
+     * Sets the color area's height. This height does not include caption or
+     * decorations such as border, margin, and padding.
+     */
+    @Override
+    public void setHeight(String height) {
+        area.setHeight(height);
+    }
+
+    /**
+     * Sets the color area's width. This width does not include caption or
+     * decorations such as border, margin, and padding.
+     */
+    @Override
+    public void setWidth(String width) {
+        area.setWidth(width);
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/colorpicker/AbstractColorPickerConnector.java b/client/src/com/vaadin/client/ui/colorpicker/AbstractColorPickerConnector.java
new file mode 100644 (file)
index 0000000..27261b3
--- /dev/null
@@ -0,0 +1,86 @@
+package com.vaadin.client.ui.colorpicker;
+
+import java.util.Set;
+
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.vaadin.client.communication.StateChangeEvent;
+import com.vaadin.client.ui.AbstractComponentConnector;
+import com.vaadin.shared.ui.colorpicker.ColorPickerState;
+
+/**
+ * An abstract class that defines default implementation for a color picker
+ * connector.
+ * 
+ * @since 7.0.0
+ */
+public abstract class AbstractColorPickerConnector extends
+        AbstractComponentConnector implements ClickHandler {
+
+    @Override
+    public ColorPickerState getState() {
+        return (ColorPickerState) super.getState();
+    }
+
+    @Override
+    public boolean delegateCaptionHandling() {
+        return false;
+    }
+
+    @Override
+    public void onStateChanged(StateChangeEvent stateChangeEvent) {
+        // NOTE: this method is called after @DelegateToWidget
+        super.onStateChanged(stateChangeEvent);
+        Set<String> changedProperties = stateChangeEvent.getChangedProperties();
+        if (changedProperties.contains("color")) {
+            refreshColor();
+
+            if (getState().showDefaultCaption
+                    && (getState().caption == null || ""
+                            .equals(getState().caption))) {
+
+                setCaption(getState().color);
+            }
+        }
+        if (changedProperties.contains("caption")
+                || changedProperties.contains("htmlContentAllowed")
+                || changedProperties.contains("showDefaultCaption")) {
+
+            setCaption(getCaption());
+        }
+    }
+
+    @Override
+    public void init() {
+        super.init();
+        if (getWidget() instanceof HasClickHandlers) {
+            ((HasClickHandlers) getWidget()).addClickHandler(this);
+        }
+    }
+
+    /**
+     * Get caption for the color picker widget.
+     * 
+     * @return
+     */
+    protected String getCaption() {
+        if (getState().showDefaultCaption
+                && (getState().caption == null || "".equals(getState().caption))) {
+            return getState().color;
+        }
+        return getState().caption;
+    }
+
+    /**
+     * Set caption of the color picker widget.
+     * 
+     * @param caption
+     */
+    protected abstract void setCaption(String caption);
+
+    /**
+     * Update the widget to show the currently selected color.
+     */
+    protected abstract void refreshColor();
+
+}
diff --git a/client/src/com/vaadin/client/ui/colorpicker/ColorPickerAreaConnector.java b/client/src/com/vaadin/client/ui/colorpicker/ColorPickerAreaConnector.java
new file mode 100644 (file)
index 0000000..12bc239
--- /dev/null
@@ -0,0 +1,53 @@
+package com.vaadin.client.ui.colorpicker;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.communication.RpcProxy;
+import com.vaadin.client.ui.VColorPickerArea;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.colorpicker.ColorPickerServerRpc;
+
+/**
+ * A class that defines an implementation for a color picker connector. Connects
+ * the server side {@link com.vaadin.ui.ColorPickerArea} with the client side
+ * counterpart {@link VColorPickerArea}
+ * 
+ * @since 7.0.0
+ */
+@Connect(com.vaadin.ui.ColorPickerArea.class)
+public class ColorPickerAreaConnector extends AbstractColorPickerConnector {
+
+    private ColorPickerServerRpc rpc = RpcProxy.create(
+            ColorPickerServerRpc.class, this);
+
+    @Override
+    protected Widget createWidget() {
+        return GWT.create(VColorPickerArea.class);
+    }
+
+    @Override
+    public VColorPickerArea getWidget() {
+        return (VColorPickerArea) super.getWidget();
+    }
+
+    @Override
+    public void onClick(ClickEvent event) {
+        rpc.openPopup(getWidget().isOpen());
+    }
+
+    @Override
+    protected void setCaption(String caption) {
+        if (getState().htmlContentAllowed) {
+            getWidget().setHTML(caption);
+        } else {
+            getWidget().setText(caption);
+        }
+    }
+
+    @Override
+    protected void refreshColor() {
+        getWidget().refreshColor();
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/colorpicker/ColorPickerConnector.java b/client/src/com/vaadin/client/ui/colorpicker/ColorPickerConnector.java
new file mode 100644 (file)
index 0000000..7329bff
--- /dev/null
@@ -0,0 +1,52 @@
+package com.vaadin.client.ui.colorpicker;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.communication.RpcProxy;
+import com.vaadin.client.ui.VColorPicker;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.colorpicker.ColorPickerServerRpc;
+
+/**
+ * A class that defines default implementation for a color picker connector.
+ * Connects the server side {@link com.vaadin.ui.ColorPicker} with the client
+ * side counterpart {@link VColorPicker}
+ * 
+ * @since 7.0.0
+ */
+@Connect(com.vaadin.ui.ColorPicker.class)
+public class ColorPickerConnector extends AbstractColorPickerConnector {
+
+    private ColorPickerServerRpc rpc = RpcProxy.create(
+            ColorPickerServerRpc.class, this);
+
+    @Override
+    protected Widget createWidget() {
+        return GWT.create(VColorPicker.class);
+    }
+
+    @Override
+    public VColorPicker getWidget() {
+        return (VColorPicker) super.getWidget();
+    }
+
+    @Override
+    public void onClick(ClickEvent event) {
+        rpc.openPopup(getWidget().isOpen());
+    }
+
+    @Override
+    protected void setCaption(String caption) {
+        if (getState().htmlContentAllowed) {
+            getWidget().setHtml(caption);
+        } else {
+            getWidget().setText(caption);
+        }
+    }
+
+    @Override
+    protected void refreshColor() {
+        getWidget().refreshColor();
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/colorpicker/ColorPickerGradientConnector.java b/client/src/com/vaadin/client/ui/colorpicker/ColorPickerGradientConnector.java
new file mode 100644 (file)
index 0000000..9a9dd09
--- /dev/null
@@ -0,0 +1,71 @@
+package com.vaadin.client.ui.colorpicker;
+
+import java.util.Set;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.MouseUpEvent;
+import com.google.gwt.event.dom.client.MouseUpHandler;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.communication.RpcProxy;
+import com.vaadin.client.communication.StateChangeEvent;
+import com.vaadin.client.ui.AbstractComponentConnector;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.colorpicker.ColorPickerGradientServerRpc;
+import com.vaadin.shared.ui.colorpicker.ColorPickerGradientState;
+
+/**
+ * A class that defines the default implementation for a color picker gradient
+ * connector. Connects the server side
+ * {@link com.vaadin.ui.components.colorpicker.ColorPickerGradient} with the
+ * client side counterpart {@link VColorPickerGradient}
+ * 
+ * @since 7.0.0
+ */
+@Connect(com.vaadin.ui.components.colorpicker.ColorPickerGradient.class)
+public class ColorPickerGradientConnector extends AbstractComponentConnector
+        implements MouseUpHandler {
+
+    private ColorPickerGradientServerRpc rpc = RpcProxy.create(
+            ColorPickerGradientServerRpc.class, this);
+
+    @Override
+    protected Widget createWidget() {
+        return GWT.create(VColorPickerGradient.class);
+    }
+
+    @Override
+    public VColorPickerGradient getWidget() {
+        return (VColorPickerGradient) super.getWidget();
+    }
+
+    @Override
+    public ColorPickerGradientState getState() {
+        return (ColorPickerGradientState) super.getState();
+    }
+
+    @Override
+    public void onMouseUp(MouseUpEvent event) {
+        rpc.select(getWidget().getCursorX(), getWidget().getCursorY());
+    }
+
+    @Override
+    public void onStateChanged(StateChangeEvent stateChangeEvent) {
+        super.onStateChanged(stateChangeEvent);
+        Set<String> changedProperties = stateChangeEvent.getChangedProperties();
+        if (changedProperties.contains("cursorX")
+                || changedProperties.contains("cursorY")) {
+
+            getWidget().setCursor(getState().cursorX, getState().cursorY);
+        }
+        if (changedProperties.contains("bgColor")) {
+            getWidget().setBGColor(getState().bgColor);
+        }
+    }
+
+    @Override
+    protected void init() {
+        super.init();
+        getWidget().addMouseUpHandler(this);
+    }
+
+}
\ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/colorpicker/ColorPickerGridConnector.java b/client/src/com/vaadin/client/ui/colorpicker/ColorPickerGridConnector.java
new file mode 100644 (file)
index 0000000..dbce0dd
--- /dev/null
@@ -0,0 +1,80 @@
+package com.vaadin.client.ui.colorpicker;
+
+import java.util.Set;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.communication.RpcProxy;
+import com.vaadin.client.communication.StateChangeEvent;
+import com.vaadin.client.ui.AbstractComponentConnector;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.colorpicker.ColorPickerGridServerRpc;
+import com.vaadin.shared.ui.colorpicker.ColorPickerGridState;
+
+/**
+ * A class that defines the default implementation for a color picker grid
+ * connector. Connects the server side
+ * {@link com.vaadin.ui.components.colorpicker.ColorPickerGrid} with the client
+ * side counterpart {@link VColorPickerGrid}
+ * 
+ * @since 7.0.0
+ */
+@Connect(com.vaadin.ui.components.colorpicker.ColorPickerGrid.class)
+public class ColorPickerGridConnector extends AbstractComponentConnector
+        implements ClickHandler {
+
+    private ColorPickerGridServerRpc rpc = RpcProxy.create(
+            ColorPickerGridServerRpc.class, this);
+
+    @Override
+    protected Widget createWidget() {
+        return GWT.create(VColorPickerGrid.class);
+    }
+
+    @Override
+    public VColorPickerGrid getWidget() {
+        return (VColorPickerGrid) super.getWidget();
+    }
+
+    @Override
+    public ColorPickerGridState getState() {
+        return (ColorPickerGridState) super.getState();
+    }
+
+    @Override
+    public void onClick(ClickEvent event) {
+        rpc.select(getWidget().getSelectedX(), getWidget().getSelectedY());
+    }
+
+    @Override
+    public void onStateChanged(StateChangeEvent stateChangeEvent) {
+        super.onStateChanged(stateChangeEvent);
+        Set<String> changedProperties = stateChangeEvent.getChangedProperties();
+        if (changedProperties.contains("rowCount")
+                || changedProperties.contains("columnCount")
+                || changedProperties.contains("updateGrid")) {
+
+            getWidget().updateGrid(getState().rowCount, getState().columnCount);
+        }
+        if (changedProperties.contains("changedX")
+                || changedProperties.contains("changedY")
+                || changedProperties.contains("changedColor")
+                || changedProperties.contains("updateColor")) {
+
+            getWidget().updateColor(getState().changedColor,
+                    getState().changedX, getState().changedY);
+
+            if (!getWidget().isGridLoaded()) {
+                rpc.refresh();
+            }
+        }
+    }
+
+    @Override
+    protected void init() {
+        super.init();
+        getWidget().addClickHandler(this);
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/colorpicker/VColorPickerGradient.java b/client/src/com/vaadin/client/ui/colorpicker/VColorPickerGradient.java
new file mode 100644 (file)
index 0000000..6defc57
--- /dev/null
@@ -0,0 +1,176 @@
+package com.vaadin.client.ui.colorpicker;
+
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.dom.client.MouseMoveEvent;
+import com.google.gwt.event.dom.client.MouseMoveHandler;
+import com.google.gwt.event.dom.client.MouseUpEvent;
+import com.google.gwt.event.dom.client.MouseUpHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.AbsolutePanel;
+import com.google.gwt.user.client.ui.FocusPanel;
+import com.google.gwt.user.client.ui.HTML;
+
+/**
+ * Client side implementation for ColorPickerGradient.
+ * 
+ * @since 7.0.0
+ * 
+ */
+public class VColorPickerGradient extends FocusPanel implements
+        MouseDownHandler, MouseUpHandler, MouseMoveHandler {
+
+    /** Set the CSS class name to allow styling. */
+    public static final String CLASSNAME = "v-colorpicker-gradient";
+    public static final String CLASSNAME_BACKGROUND = CLASSNAME + "-background";
+    public static final String CLASSNAME_FOREGROUND = CLASSNAME + "-foreground";
+    public static final String CLASSNAME_LOWERBOX = CLASSNAME + "-lowerbox";
+    public static final String CLASSNAME_HIGHERBOX = CLASSNAME + "-higherbox";
+    public static final String CLASSNAME_CONTAINER = CLASSNAME + "-container";
+    public static final String CLASSNAME_CLICKLAYER = CLASSNAME + "-clicklayer";
+
+    private final HTML background;
+    private final HTML foreground;
+    private final HTML lowercross;
+    private final HTML highercross;
+    private final HTML clicklayer;
+    private final AbsolutePanel container;
+
+    private boolean mouseIsDown = false;
+
+    private int cursorX;
+    private int cursorY;
+
+    /**
+     * Instantiates the client side component for a color picker gradient.
+     */
+    public VColorPickerGradient() {
+        super();
+
+        setStyleName(CLASSNAME);
+
+        int width = 220;
+        int height = 220;
+
+        background = new HTML();
+        background.setStyleName(CLASSNAME_BACKGROUND);
+        background.setPixelSize(width, height);
+
+        foreground = new HTML();
+        foreground.setStyleName(CLASSNAME_FOREGROUND);
+        foreground.setPixelSize(width, height);
+
+        clicklayer = new HTML();
+        clicklayer.setStyleName(CLASSNAME_CLICKLAYER);
+        clicklayer.setPixelSize(width, height);
+        clicklayer.addMouseDownHandler(this);
+        clicklayer.addMouseUpHandler(this);
+        clicklayer.addMouseMoveHandler(this);
+
+        lowercross = new HTML();
+        lowercross.setPixelSize(width / 2, height / 2);
+        lowercross.setStyleName(CLASSNAME_LOWERBOX);
+
+        highercross = new HTML();
+        highercross.setPixelSize(width / 2, height / 2);
+        highercross.setStyleName(CLASSNAME_HIGHERBOX);
+
+        container = new AbsolutePanel();
+        container.setStyleName(CLASSNAME_CONTAINER);
+        container.setPixelSize(width, height);
+        container.add(background, 0, 0);
+        container.add(foreground, 0, 0);
+        container.add(lowercross, 0, height / 2);
+        container.add(highercross, width / 2, 0);
+        container.add(clicklayer, 0, 0);
+
+        add(container);
+    }
+
+    /**
+     * Returns the latest x-coordinate for pressed-down mouse cursor.
+     */
+    protected int getCursorX() {
+        return cursorX;
+    }
+
+    /**
+     * Returns the latest y-coordinate for pressed-down mouse cursor.
+     */
+    protected int getCursorY() {
+        return cursorY;
+    }
+
+    /**
+     * Sets the given css color as the background.
+     * 
+     * @param bgColor
+     */
+    protected void setBGColor(String bgColor) {
+        background.getElement().getStyle().setProperty("background", bgColor);
+    }
+
+    @Override
+    public void onMouseDown(MouseDownEvent event) {
+        event.preventDefault();
+
+        mouseIsDown = true;
+        setCursor(event.getX(), event.getY());
+    }
+
+    @Override
+    public void onMouseUp(MouseUpEvent event) {
+        event.preventDefault();
+        mouseIsDown = false;
+        setCursor(event.getX(), event.getY());
+
+        cursorX = event.getX();
+        cursorY = event.getY();
+    }
+
+    @Override
+    public void onMouseMove(MouseMoveEvent event) {
+        event.preventDefault();
+
+        if (mouseIsDown) {
+            setCursor(event.getX(), event.getY());
+        }
+    }
+
+    /**
+     * Sets the latest coordinates for pressed-down mouse cursor and updates the
+     * cross elements.
+     * 
+     * @param x
+     * @param y
+     */
+    public void setCursor(int x, int y) {
+        cursorX = x;
+        cursorY = y;
+        if (x >= 0) {
+            DOM.setStyleAttribute(lowercross.getElement(), "width",
+                    String.valueOf(x) + "px");
+        }
+        if (y >= 0) {
+            DOM.setStyleAttribute(lowercross.getElement(), "top",
+                    String.valueOf(y) + "px");
+        }
+        if (y >= 0) {
+            DOM.setStyleAttribute(lowercross.getElement(), "height",
+                    String.valueOf((background.getOffsetHeight() - y)) + "px");
+        }
+
+        if (x >= 0) {
+            DOM.setStyleAttribute(highercross.getElement(), "width",
+                    String.valueOf((background.getOffsetWidth() - x)) + "px");
+        }
+        if (x >= 0) {
+            DOM.setStyleAttribute(highercross.getElement(), "left",
+                    String.valueOf(x) + "px");
+        }
+        if (y >= 0) {
+            DOM.setStyleAttribute(highercross.getElement(), "height",
+                    String.valueOf((y)) + "px");
+        }
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/colorpicker/VColorPickerGrid.java b/client/src/com/vaadin/client/ui/colorpicker/VColorPickerGrid.java
new file mode 100644 (file)
index 0000000..1af6573
--- /dev/null
@@ -0,0 +1,132 @@
+package com.vaadin.client.ui.colorpicker;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.AbsolutePanel;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.HTMLTable.Cell;
+
+/**
+ * Client side implementation for ColorPickerGrid.
+ * 
+ * @since 7.0.0
+ * 
+ */
+public class VColorPickerGrid extends AbsolutePanel implements ClickHandler,
+        HasClickHandlers {
+
+    private int rows = 1;
+    private int columns = 1;
+
+    private Grid grid;
+
+    private boolean gridLoaded = false;
+
+    private int selectedX;
+    private int selectedY;
+
+    /**
+     * Instantiates the client side component for a color picker grid.
+     */
+    public VColorPickerGrid() {
+        super();
+
+        this.add(createGrid(), 0, 0);
+    }
+
+    /**
+     * Creates a grid according to the current row and column count information.
+     * 
+     * @return grid
+     */
+    private Grid createGrid() {
+        grid = new Grid(rows, columns);
+        grid.setWidth("100%");
+        grid.setHeight("100%");
+        grid.addClickHandler(this);
+        return grid;
+    }
+
+    /**
+     * Updates the row and column count and creates a new grid based on them.
+     * The new grid replaces the old grid if one existed.
+     * 
+     * @param rowCount
+     * @param columnCount
+     */
+    protected void updateGrid(int rowCount, int columnCount) {
+        rows = rowCount;
+        columns = columnCount;
+        this.remove(grid);
+        this.add(createGrid(), 0, 0);
+    }
+
+    /**
+     * Updates the changed colors within the grid based on the given x- and
+     * y-coordinates. Nothing happens if any of the parameters is null or the
+     * parameter lengths don't match.
+     * 
+     * @param changedColor
+     * @param changedX
+     * @param changedY
+     */
+    protected void updateColor(String[] changedColor, String[] changedX,
+            String[] changedY) {
+        if (changedColor != null && changedX != null && changedY != null) {
+            if (changedColor.length == changedX.length
+                    && changedX.length == changedY.length) {
+                for (int c = 0; c < changedColor.length; c++) {
+                    Element element = grid.getCellFormatter().getElement(
+                            Integer.parseInt(changedX[c]),
+                            Integer.parseInt(changedY[c]));
+                    element.getStyle().setProperty("background",
+                            changedColor[c]);
+                }
+            }
+
+            gridLoaded = true;
+        }
+    }
+
+    /**
+     * Returns currently selected x-coordinate of the grid.
+     */
+    protected int getSelectedX() {
+        return selectedX;
+    }
+
+    /**
+     * Returns currently selected y-coordinate of the grid.
+     */
+    protected int getSelectedY() {
+        return selectedY;
+    }
+
+    /**
+     * Returns true if the colors have been successfully updated at least once,
+     * false otherwise.
+     */
+    protected boolean isGridLoaded() {
+        return gridLoaded;
+    }
+
+    @Override
+    public void onClick(ClickEvent event) {
+        Cell cell = grid.getCellForEvent(event);
+        if (cell == null) {
+            return;
+        }
+
+        selectedY = cell.getRowIndex();
+        selectedX = cell.getCellIndex();
+    }
+
+    @Override
+    public HandlerRegistration addClickHandler(ClickHandler handler) {
+        return addDomHandler(handler, ClickEvent.getType());
+    }
+
+}
diff --git a/server/src/com/vaadin/ui/AbstractColorPicker.java b/server/src/com/vaadin/ui/AbstractColorPicker.java
new file mode 100644 (file)
index 0000000..38cbedd
--- /dev/null
@@ -0,0 +1,450 @@
+package com.vaadin.ui;
+
+import java.lang.reflect.Method;
+
+import com.vaadin.shared.ui.colorpicker.Color;
+import com.vaadin.shared.ui.colorpicker.ColorPickerServerRpc;
+import com.vaadin.shared.ui.colorpicker.ColorPickerState;
+import com.vaadin.ui.Window.CloseEvent;
+import com.vaadin.ui.Window.CloseListener;
+import com.vaadin.ui.components.colorpicker.ColorChangeEvent;
+import com.vaadin.ui.components.colorpicker.ColorChangeListener;
+import com.vaadin.ui.components.colorpicker.ColorPickerPopup;
+import com.vaadin.ui.components.colorpicker.ColorSelector;
+
+/**
+ * An abstract class that defines default implementation for a color picker
+ * component.
+ * 
+ * @since 7.0.0
+ */
+public abstract class AbstractColorPicker extends AbstractComponent implements
+        CloseListener, ColorSelector {
+    private static final Method COLOR_CHANGE_METHOD;
+    static {
+        try {
+            COLOR_CHANGE_METHOD = ColorChangeListener.class.getDeclaredMethod(
+                    "colorChanged", new Class[] { ColorChangeEvent.class });
+        } catch (final java.lang.NoSuchMethodException e) {
+            // This should never happen
+            throw new java.lang.RuntimeException(
+                    "Internal error finding methods in ColorPicker");
+        }
+    }
+
+    /**
+     * Interface for converting 2d-coordinates to a Color
+     */
+    public interface Coordinates2Color {
+
+        /**
+         * Calculate color from coordinates
+         * 
+         * @param x
+         *            the x-coordinate
+         * @param y
+         *            the y-coordinate
+         * 
+         * @return the color
+         */
+        public Color calculate(int x, int y);
+
+        /**
+         * Calculate coordinates from color
+         * 
+         * @param c
+         *            the c
+         * 
+         * @return the integer array with the coordinates
+         */
+        public int[] calculate(Color c);
+    }
+
+    public enum PopupStyle {
+        POPUP_NORMAL("normal"), POPUP_SIMPLE("simple");
+
+        private String style;
+
+        PopupStyle(String styleName) {
+            style = styleName;
+        }
+
+        @Override
+        public String toString() {
+            return style;
+        }
+    }
+
+    private ColorPickerServerRpc rpc = new ColorPickerServerRpc() {
+
+        @Override
+        public void openPopup(boolean open) {
+            showPopup(open);
+        }
+    };
+
+    protected static final String STYLENAME_DEFAULT = "v-colorpicker";
+    protected static final String STYLENAME_BUTTON = "v-button";
+    protected static final String STYLENAME_AREA = "v-colorpicker-area";
+
+    protected PopupStyle popupStyle = PopupStyle.POPUP_NORMAL;
+
+    /** The popup window. */
+    private ColorPickerPopup window;
+
+    /** The color. */
+    protected Color color;
+
+    /** The UI. */
+    private UI parent;
+
+    protected String popupCaption = null;
+    private int positionX = 0;
+    private int positionY = 0;
+
+    protected boolean rgbVisible = true;
+    protected boolean hsvVisible = true;
+    protected boolean swatchesVisible = true;
+    protected boolean historyVisible = true;
+    protected boolean textfieldVisible = true;
+
+    /**
+     * Instantiates a new color picker.
+     */
+    public AbstractColorPicker() {
+        this("Colors", Color.WHITE);
+    }
+
+    /**
+     * Instantiates a new color picker.
+     * 
+     * @param popupCaption
+     *            the caption of the popup window
+     */
+    public AbstractColorPicker(String popupCaption) {
+        this(popupCaption, Color.WHITE);
+    }
+
+    /**
+     * Instantiates a new color picker.
+     * 
+     * @param popupCaption
+     *            the caption of the popup window
+     * @param initialColor
+     *            the initial color
+     */
+    public AbstractColorPicker(String popupCaption, Color initialColor) {
+        super();
+        registerRpc(rpc);
+        setColor(initialColor);
+        this.popupCaption = popupCaption;
+        setDefaultStyles();
+        setCaption("");
+    }
+
+    @Override
+    public void setColor(Color color) {
+        this.color = color;
+
+        if (window != null) {
+            window.setColor(color);
+        }
+        getState().color = color.getCSS();
+    }
+
+    @Override
+    public Color getColor() {
+        return color;
+    }
+
+    /**
+     * Set true if the component should show a default caption (css-code for the
+     * currently selected color, e.g. #ffffff) when no other caption is
+     * available.
+     * 
+     * @param enabled
+     */
+    public void setDefaultCaptionEnabled(boolean enabled) {
+        getState().showDefaultCaption = enabled;
+    }
+
+    /**
+     * Returns true if the component shows the default caption (css-code for the
+     * currently selected color, e.g. #ffffff) if no other caption is available.
+     */
+    public boolean isDefaultCaptionEnabled() {
+        return getState().showDefaultCaption;
+    }
+
+    /**
+     * Sets the position of the popup window
+     * 
+     * @param x
+     *            the x-coordinate
+     * @param y
+     *            the y-coordinate
+     */
+    public void setPosition(int x, int y) {
+        positionX = x;
+        positionY = y;
+
+        if (window != null) {
+            window.setPositionX(x);
+            window.setPositionY(y);
+        }
+    }
+
+    @Override
+    public void addColorChangeListener(ColorChangeListener listener) {
+        addListener(ColorChangeEvent.class, listener, COLOR_CHANGE_METHOD);
+    }
+
+    @Override
+    public void removeColorChangeListener(ColorChangeListener listener) {
+        removeListener(ColorChangeEvent.class, listener);
+    }
+
+    @Override
+    public void windowClose(CloseEvent e) {
+        if (e.getWindow() == window) {
+            getState().popupVisible = false;
+        }
+    }
+
+    /**
+     * Fired when a color change event occurs
+     * 
+     * @param event
+     *            The color change event
+     */
+    protected void colorChanged(ColorChangeEvent event) {
+        setColor(event.getColor());
+        fireColorChanged();
+    }
+
+    /**
+     * Notifies the listeners that the selected color has changed
+     */
+    public void fireColorChanged() {
+        fireEvent(new ColorChangeEvent(this, color));
+    }
+
+    /**
+     * The style for the popup window
+     * 
+     * @param style
+     *            The style
+     */
+    public void setPopupStyle(PopupStyle style) {
+        popupStyle = style;
+
+        switch (style) {
+        case POPUP_NORMAL: {
+            setRGBVisibility(true);
+            setHSVVisibility(true);
+            setSwatchesVisibility(true);
+            setHistoryVisibility(true);
+            setTextfieldVisibility(true);
+            break;
+        }
+
+        case POPUP_SIMPLE: {
+            setRGBVisibility(false);
+            setHSVVisibility(false);
+            setSwatchesVisibility(true);
+            setHistoryVisibility(false);
+            setTextfieldVisibility(false);
+            break;
+        }
+        }
+    }
+
+    /**
+     * Set the visibility of the RGB Tab
+     * 
+     * @param visible
+     *            The visibility
+     */
+    public void setRGBVisibility(boolean visible) {
+
+        if (!visible && !hsvVisible && !swatchesVisible) {
+            throw new IllegalArgumentException("Cannot hide all tabs.");
+        }
+
+        rgbVisible = visible;
+        if (window != null) {
+            window.setRGBTabVisible(visible);
+        }
+    }
+
+    /**
+     * Set the visibility of the HSV Tab
+     * 
+     * @param visible
+     *            The visibility
+     */
+    public void setHSVVisibility(boolean visible) {
+        if (!visible && !rgbVisible && !swatchesVisible) {
+            throw new IllegalArgumentException("Cannot hide all tabs.");
+        }
+
+        hsvVisible = visible;
+        if (window != null) {
+            window.setHSVTabVisible(visible);
+        }
+    }
+
+    /**
+     * Set the visibility of the Swatches Tab
+     * 
+     * @param visible
+     *            The visibility
+     */
+    public void setSwatchesVisibility(boolean visible) {
+        if (!visible && !hsvVisible && !rgbVisible) {
+            throw new IllegalArgumentException("Cannot hide all tabs.");
+        }
+
+        swatchesVisible = visible;
+        if (window != null) {
+            window.setSwatchesTabVisible(visible);
+        }
+    }
+
+    /**
+     * Sets the visibility of the Color History
+     * 
+     * @param visible
+     *            The visibility
+     */
+    public void setHistoryVisibility(boolean visible) {
+        historyVisible = visible;
+        if (window != null) {
+            window.setHistoryVisible(visible);
+        }
+    }
+
+    /**
+     * Sets the visibility of the CSS color code text field
+     * 
+     * @param visible
+     *            The visibility
+     */
+    public void setTextfieldVisibility(boolean visible) {
+        textfieldVisible = visible;
+        if (window != null) {
+            window.setPreviewVisible(visible);
+        }
+    }
+
+    @Override
+    protected ColorPickerState getState() {
+        return (ColorPickerState) super.getState();
+    }
+
+    /**
+     * Sets the default styles of the component
+     * 
+     */
+    abstract protected void setDefaultStyles();
+
+    /**
+     * Shows a popup-window for color selection.
+     */
+    public void showPopup() {
+        showPopup(true);
+    }
+
+    /**
+     * Hides a popup-window for color selection.
+     */
+    public void hidePopup() {
+        showPopup(false);
+    }
+
+    /**
+     * Shows or hides popup-window depending on the given parameter. If there is
+     * no such window yet, one is created.
+     * 
+     * @param open
+     */
+    protected void showPopup(boolean open) {
+        if (open && !isReadOnly()) {
+            if (parent == null) {
+                parent = getUI();
+            }
+
+            if (window == null) {
+
+                // Create the popup
+                window = new ColorPickerPopup(color);
+                window.setCaption(popupCaption);
+
+                window.setRGBTabVisible(rgbVisible);
+                window.setHSVTabVisible(hsvVisible);
+                window.setSwatchesTabVisible(swatchesVisible);
+                window.setHistoryVisible(historyVisible);
+                window.setPreviewVisible(textfieldVisible);
+
+                window.setImmediate(true);
+                window.addCloseListener(this);
+                window.addColorChangeListener(new ColorChangeListener() {
+                    public void colorChanged(ColorChangeEvent event) {
+                        AbstractColorPicker.this.colorChanged(event);
+                    }
+                });
+
+                window.getHistory().setColor(color);
+                parent.addWindow(window);
+                window.setVisible(true);
+                window.setPositionX(positionX);
+                window.setPositionY(positionY);
+
+            } else if (!parent.equals(window.getParent())) {
+
+                window.setRGBTabVisible(rgbVisible);
+                window.setHSVTabVisible(hsvVisible);
+                window.setSwatchesTabVisible(swatchesVisible);
+                window.setHistoryVisible(historyVisible);
+                window.setPreviewVisible(textfieldVisible);
+
+                window.setColor(color);
+                window.getHistory().setColor(color);
+                window.setVisible(true);
+                parent.addWindow(window);
+            }
+
+        } else if (window != null) {
+            window.setVisible(false);
+            parent.removeWindow(window);
+        }
+        getState().popupVisible = open;
+    }
+
+    /**
+     * Set whether the caption text is rendered as HTML or not. You might need
+     * to re-theme component to allow higher content than the original text
+     * style.
+     * 
+     * If set to true, the captions are passed to the browser as html and the
+     * developer is responsible for ensuring no harmful html is used. If set to
+     * false, the content is passed to the browser as plain text.
+     * 
+     * @param htmlContentAllowed
+     *            <code>true</code> if caption is rendered as HTML,
+     *            <code>false</code> otherwise
+     */
+    public void setHtmlContentAllowed(boolean htmlContentAllowed) {
+        getState().htmlContentAllowed = htmlContentAllowed;
+    }
+
+    /**
+     * Return HTML rendering setting
+     * 
+     * @return <code>true</code> if the caption text is to be rendered as HTML,
+     *         <code>false</code> otherwise
+     */
+    public boolean isHtmlContentAllowed() {
+        return getState().htmlContentAllowed;
+    }
+}
\ No newline at end of file
diff --git a/server/src/com/vaadin/ui/ColorPicker.java b/server/src/com/vaadin/ui/ColorPicker.java
new file mode 100644 (file)
index 0000000..1e729ba
--- /dev/null
@@ -0,0 +1,64 @@
+package com.vaadin.ui;
+
+import com.vaadin.shared.ui.colorpicker.Color;
+
+/**
+ * A class that defines default (button-like) implementation for a color picker
+ * component.
+ * 
+ * @since 7.0.0
+ * 
+ * @see ColorPickerArea
+ * 
+ */
+public class ColorPicker extends AbstractColorPicker {
+
+    /**
+     * Instantiates a new color picker.
+     */
+    public ColorPicker() {
+        super();
+    }
+
+    /**
+     * Instantiates a new color picker.
+     * 
+     * @param popupCaption
+     *            caption of the color select popup
+     */
+    public ColorPicker(String popupCaption) {
+        super(popupCaption);
+    }
+
+    /**
+     * Instantiates a new color picker.
+     * 
+     * @param popupCaption
+     *            caption of the color select popup
+     * @param initialColor
+     *            the initial color
+     */
+    public ColorPicker(String popupCaption, Color initialColor) {
+        super(popupCaption, initialColor);
+        setDefaultCaptionEnabled(true);
+    }
+
+    @Override
+    protected void setDefaultStyles() {
+        setPrimaryStyleName(STYLENAME_BUTTON);
+        addStyleName(STYLENAME_DEFAULT);
+    }
+
+    @Override
+    public void beforeClientResponse(boolean initial) {
+        super.beforeClientResponse(initial);
+
+        if (isDefaultCaptionEnabled()
+                && ((getState().caption == null || ""
+                        .equals(getState().caption)))
+                && "".equals(getState().width)) {
+            getState().width = "100px";
+        }
+    }
+
+}
diff --git a/server/src/com/vaadin/ui/ColorPickerArea.java b/server/src/com/vaadin/ui/ColorPickerArea.java
new file mode 100644 (file)
index 0000000..31deed4
--- /dev/null
@@ -0,0 +1,62 @@
+package com.vaadin.ui;
+
+import com.vaadin.shared.ui.colorpicker.Color;
+
+/**
+ * A class that defines area-like implementation for a color picker component.
+ * 
+ * @since 7.0.0
+ * 
+ * @see ColorPicker
+ * 
+ */
+public class ColorPickerArea extends AbstractColorPicker {
+
+    /**
+     * Instantiates a new color picker.
+     */
+    public ColorPickerArea() {
+        super();
+    }
+
+    /**
+     * Instantiates a new color picker.
+     * 
+     * @param popupCaption
+     *            caption of the color select popup
+     */
+    public ColorPickerArea(String popupCaption) {
+        super(popupCaption);
+    }
+
+    /**
+     * Instantiates a new color picker.
+     * 
+     * @param popupCaption
+     *            caption of the color select popup
+     * @param initialColor
+     *            the initial color
+     */
+    public ColorPickerArea(String popupCaption, Color initialColor) {
+        super(popupCaption, initialColor);
+        setDefaultCaptionEnabled(false);
+    }
+
+    @Override
+    protected void setDefaultStyles() {
+        // state already has correct default
+    }
+
+    @Override
+    public void beforeClientResponse(boolean initial) {
+        super.beforeClientResponse(initial);
+
+        if ("".equals(getState().height)) {
+            getState().height = "30px";
+        }
+        if ("".equals(getState().width)) {
+            getState().width = "30px";
+        }
+    }
+
+}
diff --git a/server/src/com/vaadin/ui/components/colorpicker/ColorChangeEvent.java b/server/src/com/vaadin/ui/components/colorpicker/ColorChangeEvent.java
new file mode 100644 (file)
index 0000000..8ad935f
--- /dev/null
@@ -0,0 +1,28 @@
+package com.vaadin.ui.components.colorpicker;
+
+import com.vaadin.shared.ui.colorpicker.Color;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.Component.Event;
+
+/**
+ * The color changed event which is passed to the listeners when a color change
+ * occurs.
+ * 
+ * @since 7.0.0
+ */
+public class ColorChangeEvent extends Event {
+    private final Color color;
+
+    public ColorChangeEvent(Component source, Color color) {
+        super(source);
+
+        this.color = color;
+    }
+
+    /**
+     * Returns the new color.
+     */
+    public Color getColor() {
+        return color;
+    }
+}
\ No newline at end of file
diff --git a/server/src/com/vaadin/ui/components/colorpicker/ColorChangeListener.java b/server/src/com/vaadin/ui/components/colorpicker/ColorChangeListener.java
new file mode 100644 (file)
index 0000000..9f8ea0f
--- /dev/null
@@ -0,0 +1,27 @@
+package com.vaadin.ui.components.colorpicker;
+
+import java.io.Serializable;
+
+/**
+ * The listener interface for receiving colorChange events. The class that is
+ * interested in processing a {@link ColorChangeEvent} implements this
+ * interface, and the object created with that class is registered with a
+ * component using the component's <code>addColorChangeListener</code> method.
+ * When the colorChange event occurs, that object's appropriate method is
+ * invoked.
+ * 
+ * @since 7.0.0
+ * 
+ * @see ColorChangeEvent
+ */
+public interface ColorChangeListener extends Serializable {
+
+    /**
+     * Called when a new color has been selected.
+     * 
+     * @param event
+     *            An event containing information about the color change.
+     */
+    void colorChanged(ColorChangeEvent event);
+
+}
\ No newline at end of file
diff --git a/server/src/com/vaadin/ui/components/colorpicker/ColorPickerGradient.java b/server/src/com/vaadin/ui/components/colorpicker/ColorPickerGradient.java
new file mode 100644 (file)
index 0000000..ddd61f3
--- /dev/null
@@ -0,0 +1,129 @@
+package com.vaadin.ui.components.colorpicker;
+
+import java.lang.reflect.Method;
+
+import com.vaadin.shared.ui.colorpicker.Color;
+import com.vaadin.shared.ui.colorpicker.ColorPickerGradientServerRpc;
+import com.vaadin.shared.ui.colorpicker.ColorPickerGradientState;
+import com.vaadin.ui.AbstractColorPicker.Coordinates2Color;
+import com.vaadin.ui.AbstractComponent;
+
+/**
+ * A component that represents a color gradient within a color picker.
+ * 
+ * @since 7.0.0
+ */
+public class ColorPickerGradient extends AbstractComponent implements
+        ColorSelector {
+
+    private static final Method COLOR_CHANGE_METHOD;
+    static {
+        try {
+            COLOR_CHANGE_METHOD = ColorChangeListener.class.getDeclaredMethod(
+                    "colorChanged", new Class[] { ColorChangeEvent.class });
+        } catch (final java.lang.NoSuchMethodException e) {
+            // This should never happen
+            throw new java.lang.RuntimeException(
+                    "Internal error finding methods in ColorPicker");
+        }
+    }
+
+    private ColorPickerGradientServerRpc rpc = new ColorPickerGradientServerRpc() {
+
+        @Override
+        public void select(int cursorX, int cursorY) {
+            x = cursorX;
+            y = cursorY;
+            color = converter.calculate(x, y);
+
+            fireColorChanged(color);
+        }
+    };
+
+    /** The converter. */
+    private final Coordinates2Color converter;
+
+    /** The foreground color. */
+    private Color color;
+
+    /** The x-coordinate. */
+    private int x = 0;
+
+    /** The y-coordinate. */
+    private int y = 0;
+
+    /** The background color. */
+    private Color backgroundColor;
+
+    /**
+     * Instantiates a new color picker gradient.
+     * 
+     * @param id
+     *            the id
+     * @param converter
+     *            the converter
+     */
+    public ColorPickerGradient(String id, Coordinates2Color converter) {
+        registerRpc(rpc);
+        getState().id = id;
+        // width and height must be set here instead of in theme, otherwise
+        // coordinate calculations fail
+        getState().width = "220px";
+        getState().height = "220px";
+        this.converter = converter;
+    }
+
+    @Override
+    public void setColor(Color c) {
+        color = c;
+
+        int[] coords = converter.calculate(c);
+        x = coords[0];
+        y = coords[1];
+
+        getState().cursorX = x;
+        getState().cursorY = y;
+
+    }
+
+    @Override
+    public void addColorChangeListener(ColorChangeListener listener) {
+        addListener(ColorChangeEvent.class, listener, COLOR_CHANGE_METHOD);
+    }
+
+    @Override
+    public void removeColorChangeListener(ColorChangeListener listener) {
+        removeListener(ColorChangeEvent.class, listener);
+    }
+
+    /**
+     * Sets the background color.
+     * 
+     * @param color
+     *            the new background color
+     */
+    public void setBackgroundColor(Color color) {
+        backgroundColor = color;
+        getState().bgColor = color.getCSS();
+    }
+
+    @Override
+    public Color getColor() {
+        return color;
+    }
+
+    /**
+     * Notifies the listeners that the color has changed
+     * 
+     * @param color
+     *            The color which it changed to
+     */
+    public void fireColorChanged(Color color) {
+        fireEvent(new ColorChangeEvent(this, color));
+    }
+
+    @Override
+    protected ColorPickerGradientState getState() {
+        return (ColorPickerGradientState) super.getState();
+    }
+}
\ No newline at end of file
diff --git a/server/src/com/vaadin/ui/components/colorpicker/ColorPickerGrid.java b/server/src/com/vaadin/ui/components/colorpicker/ColorPickerGrid.java
new file mode 100644 (file)
index 0000000..37e70c3
--- /dev/null
@@ -0,0 +1,240 @@
+package com.vaadin.ui.components.colorpicker;
+
+import java.awt.Point;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.vaadin.shared.ui.colorpicker.Color;
+import com.vaadin.shared.ui.colorpicker.ColorPickerGridServerRpc;
+import com.vaadin.shared.ui.colorpicker.ColorPickerGridState;
+import com.vaadin.ui.AbstractComponent;
+
+/**
+ * A component that represents a color selection grid within a color picker.
+ * 
+ * @since 7.0.0
+ */
+public class ColorPickerGrid extends AbstractComponent implements ColorSelector {
+
+    private static final String STYLENAME = "v-colorpicker-grid";
+
+    private static final Method COLOR_CHANGE_METHOD;
+    static {
+        try {
+            COLOR_CHANGE_METHOD = ColorChangeListener.class.getDeclaredMethod(
+                    "colorChanged", new Class[] { ColorChangeEvent.class });
+        } catch (final java.lang.NoSuchMethodException e) {
+            // This should never happen
+            throw new java.lang.RuntimeException(
+                    "Internal error finding methods in ColorPicker");
+        }
+    }
+
+    private ColorPickerGridServerRpc rpc = new ColorPickerGridServerRpc() {
+
+        @Override
+        public void select(int x, int y) {
+            ColorPickerGrid.this.x = x;
+            ColorPickerGrid.this.y = y;
+
+            fireColorChanged(colorGrid[y][x]);
+        }
+
+        @Override
+        public void refresh() {
+            for (int row = 0; row < rows; row++) {
+                for (int col = 0; col < columns; col++) {
+                    changedColors.put(new Point(row, col), colorGrid[row][col]);
+                }
+            }
+            sendChangedColors();
+            markAsDirty();
+        }
+    };
+
+    /** The x-coordinate. */
+    private int x = 0;
+
+    /** The y-coordinate. */
+    private int y = 0;
+
+    /** The rows. */
+    private int rows;
+
+    /** The columns. */
+    private int columns;
+
+    /** The color grid. */
+    private Color[][] colorGrid = new Color[1][1];
+
+    /** The changed colors. */
+    private final Map<Point, Color> changedColors = new HashMap<Point, Color>();
+
+    /**
+     * Instantiates a new color picker grid.
+     */
+    public ColorPickerGrid() {
+        registerRpc(rpc);
+        setPrimaryStyleName(STYLENAME);
+        setColorGrid(new Color[1][1]);
+        setColor(Color.WHITE);
+    }
+
+    /**
+     * Instantiates a new color picker grid.
+     * 
+     * @param rows
+     *            the rows
+     * @param columns
+     *            the columns
+     */
+    public ColorPickerGrid(int rows, int columns) {
+        registerRpc(rpc);
+        setPrimaryStyleName(STYLENAME);
+        setColorGrid(new Color[rows][columns]);
+        setColor(Color.WHITE);
+    }
+
+    /**
+     * Instantiates a new color picker grid.
+     * 
+     * @param colors
+     *            the colors
+     */
+    public ColorPickerGrid(Color[][] colors) {
+        registerRpc(rpc);
+        setPrimaryStyleName(STYLENAME);
+        setColorGrid(colors);
+    }
+
+    private void setColumnCount(int columns) {
+        this.columns = columns;
+        getState().columnCount = columns;
+    }
+
+    private void setRowCount(int rows) {
+        this.rows = rows;
+        getState().rowCount = rows;
+    }
+
+    private void sendChangedColors() {
+        if (!changedColors.isEmpty()) {
+            String[] colors = new String[changedColors.size()];
+            String[] XCoords = new String[changedColors.size()];
+            String[] YCoords = new String[changedColors.size()];
+            int counter = 0;
+            for (Point p : changedColors.keySet()) {
+                Color c = changedColors.get(p);
+                if (c == null) {
+                    continue;
+                }
+
+                String color = c.getCSS();
+
+                colors[counter] = color;
+                XCoords[counter] = String.valueOf((int) p.getX());
+                YCoords[counter] = String.valueOf((int) p.getY());
+                counter++;
+            }
+            getState().changedColor = colors;
+            getState().changedX = XCoords;
+            getState().changedY = YCoords;
+
+            changedColors.clear();
+        }
+    }
+
+    /**
+     * Sets the color grid.
+     * 
+     * @param colors
+     *            the new color grid
+     */
+    public void setColorGrid(Color[][] colors) {
+        setRowCount(colors.length);
+        setColumnCount(colors[0].length);
+        colorGrid = colors;
+
+        for (int row = 0; row < rows; row++) {
+            for (int col = 0; col < columns; col++) {
+                changedColors.put(new Point(row, col), colorGrid[row][col]);
+            }
+        }
+        sendChangedColors();
+
+        markAsDirty();
+    }
+
+    /**
+     * Adds a color change listener
+     * 
+     * @param listener
+     *            The color change listener
+     */
+    public void addColorChangeListener(ColorChangeListener listener) {
+        addListener(ColorChangeEvent.class, listener, COLOR_CHANGE_METHOD);
+    }
+
+    @Override
+    public Color getColor() {
+        return colorGrid[x][y];
+    }
+
+    /**
+     * Removes a color change listener
+     * 
+     * @param listener
+     *            The listener
+     */
+    public void removeColorChangeListener(ColorChangeListener listener) {
+        removeListener(ColorChangeEvent.class, listener);
+    }
+
+    @Override
+    public void setColor(Color color) {
+        colorGrid[x][y] = color;
+        changedColors.put(new Point(x, y), color);
+        sendChangedColors();
+        markAsDirty();
+    }
+
+    /**
+     * Sets the position.
+     * 
+     * @param x
+     *            the x
+     * @param y
+     *            the y
+     */
+    public void setPosition(int x, int y) {
+        if (x >= 0 && x < columns && y >= 0 && y < rows) {
+            this.x = x;
+            this.y = y;
+        }
+    }
+
+    /**
+     * Gets the position.
+     * 
+     * @return the position
+     */
+    public int[] getPosition() {
+        return new int[] { x, y };
+    }
+
+    /**
+     * Notifies the listeners that a color change has occurred
+     * 
+     * @param color
+     *            The color which it changed to
+     */
+    public void fireColorChanged(Color color) {
+        fireEvent(new ColorChangeEvent(this, color));
+    }
+
+    @Override
+    protected ColorPickerGridState getState() {
+        return (ColorPickerGridState) super.getState();
+    }
+}
\ No newline at end of file
diff --git a/server/src/com/vaadin/ui/components/colorpicker/ColorPickerHistory.java b/server/src/com/vaadin/ui/components/colorpicker/ColorPickerHistory.java
new file mode 100644 (file)
index 0000000..81d5212
--- /dev/null
@@ -0,0 +1,200 @@
+package com.vaadin.ui.components.colorpicker;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+
+import com.vaadin.shared.ui.colorpicker.Color;
+import com.vaadin.ui.CustomComponent;
+
+/**
+ * A component that represents color selection history within a color picker.
+ * 
+ * @since 7.0.0
+ */
+public class ColorPickerHistory extends CustomComponent implements
+        ColorSelector, ColorChangeListener {
+
+    private static final String STYLENAME = "v-colorpicker-history";
+
+    private static final Method COLOR_CHANGE_METHOD;
+    static {
+        try {
+            COLOR_CHANGE_METHOD = ColorChangeListener.class.getDeclaredMethod(
+                    "colorChanged", new Class[] { ColorChangeEvent.class });
+        } catch (final java.lang.NoSuchMethodException e) {
+            // This should never happen
+            throw new java.lang.RuntimeException(
+                    "Internal error finding methods in ColorPicker");
+        }
+    }
+
+    /** The rows. */
+    private static final int rows = 4;
+
+    /** The columns. */
+    private static final int columns = 15;
+
+    /** Temporary color history for when the component is detached. */
+    private ArrayBlockingQueue<Color> tempHistory = new ArrayBlockingQueue<Color>(
+            rows * columns);
+
+    /** The grid. */
+    private final ColorPickerGrid grid;
+
+    /**
+     * Instantiates a new color picker history.
+     */
+    public ColorPickerHistory() {
+        setPrimaryStyleName(STYLENAME);
+
+        grid = new ColorPickerGrid(rows, columns);
+        grid.setWidth("100%");
+        grid.setPosition(0, 0);
+        grid.addColorChangeListener(this);
+
+        setCompositionRoot(grid);
+    }
+
+    @Override
+    public void attach() {
+        super.attach();
+        createColorHistoryIfNecessary();
+    }
+
+    private void createColorHistoryIfNecessary() {
+        List<Color> tempColors = new ArrayList<Color>(tempHistory);
+        if (getSession().getAttribute("colorPickerHistory") == null) {
+            getSession().setAttribute("colorPickerHistory",
+                    new ArrayBlockingQueue<Color>(rows * columns));
+        }
+        for (Color color : tempColors) {
+            setColor(color);
+        }
+        tempHistory.clear();
+    }
+
+    @SuppressWarnings("unchecked")
+    private ArrayBlockingQueue<Color> getColorHistory() {
+        if (getSession() != null) {
+            Object colorHistory = getSession().getAttribute(
+                    "colorPickerHistory");
+            if (colorHistory instanceof ArrayBlockingQueue<?>) {
+                return (ArrayBlockingQueue<Color>) colorHistory;
+            }
+        }
+        return tempHistory;
+    }
+
+    @Override
+    public void setHeight(String height) {
+        super.setHeight(height);
+        grid.setHeight(height);
+    }
+
+    @Override
+    public void setColor(Color color) {
+
+        ArrayBlockingQueue<Color> colorHistory = getColorHistory();
+
+        // Check that the color does not already exist
+        boolean exists = false;
+        Iterator<Color> iter = colorHistory.iterator();
+        while (iter.hasNext()) {
+            if (color.equals(iter.next())) {
+                exists = true;
+                break;
+            }
+        }
+
+        // If the color does not exist then add it
+        if (!exists) {
+            if (!colorHistory.offer(color)) {
+                colorHistory.poll();
+                colorHistory.offer(color);
+            }
+        }
+
+        List<Color> colorList = new ArrayList<Color>(colorHistory);
+
+        // Invert order of colors
+        Collections.reverse(colorList);
+
+        // Move the selected color to the front of the list
+        Collections.swap(colorList, colorList.indexOf(color), 0);
+
+        // Create 2d color map
+        Color[][] colors = new Color[rows][columns];
+        iter = colorList.iterator();
+
+        for (int row = 0; row < rows; row++) {
+            for (int col = 0; col < columns; col++) {
+                if (iter.hasNext()) {
+                    colors[row][col] = iter.next();
+                } else {
+                    colors[row][col] = Color.WHITE;
+                }
+            }
+        }
+
+        grid.setColorGrid(colors);
+        grid.markAsDirty();
+    }
+
+    @Override
+    public Color getColor() {
+        return getColorHistory().peek();
+    }
+
+    /**
+     * Gets the history.
+     * 
+     * @return the history
+     */
+    public List<Color> getHistory() {
+        ArrayBlockingQueue<Color> colorHistory = getColorHistory();
+        Color[] array = colorHistory.toArray(new Color[colorHistory.size()]);
+        return Collections.unmodifiableList(Arrays.asList(array));
+    }
+
+    /**
+     * Checks if the history contains given color.
+     * 
+     * @param c
+     *            the color
+     * 
+     * @return true, if successful
+     */
+    public boolean hasColor(Color c) {
+        return getColorHistory().contains(c);
+    }
+
+    /**
+     * Adds a color change listener
+     * 
+     * @param listener
+     *            The listener
+     */
+    public void addColorChangeListener(ColorChangeListener listener) {
+        addListener(ColorChangeEvent.class, listener, COLOR_CHANGE_METHOD);
+    }
+
+    /**
+     * Removes a color change listener
+     * 
+     * @param listener
+     *            The listener
+     */
+    public void removeColorChangeListener(ColorChangeListener listener) {
+        removeListener(ColorChangeEvent.class, listener);
+    }
+
+    @Override
+    public void colorChanged(ColorChangeEvent event) {
+        fireEvent(new ColorChangeEvent(this, event.getColor()));
+    }
+}
\ No newline at end of file
diff --git a/server/src/com/vaadin/ui/components/colorpicker/ColorPickerPopup.java b/server/src/com/vaadin/ui/components/colorpicker/ColorPickerPopup.java
new file mode 100644 (file)
index 0000000..ddfbf73
--- /dev/null
@@ -0,0 +1,766 @@
+package com.vaadin.ui.components.colorpicker;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import com.vaadin.data.Property.ValueChangeEvent;
+import com.vaadin.data.Property.ValueChangeListener;
+import com.vaadin.shared.ui.MarginInfo;
+import com.vaadin.shared.ui.colorpicker.Color;
+import com.vaadin.ui.AbstractColorPicker.Coordinates2Color;
+import com.vaadin.ui.Alignment;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.ui.Button.ClickListener;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.Layout;
+import com.vaadin.ui.Slider;
+import com.vaadin.ui.Slider.ValueOutOfBoundsException;
+import com.vaadin.ui.TabSheet;
+import com.vaadin.ui.VerticalLayout;
+import com.vaadin.ui.Window;
+
+/**
+ * A component that represents color selection popup within a color picker.
+ * 
+ * @since 7.0.0
+ */
+public class ColorPickerPopup extends Window implements ClickListener,
+        ColorChangeListener, ColorSelector {
+
+    private static final String STYLENAME = "v-colorpicker-popup";
+
+    private static final Method COLOR_CHANGE_METHOD;
+    static {
+        try {
+            COLOR_CHANGE_METHOD = ColorChangeListener.class.getDeclaredMethod(
+                    "colorChanged", new Class[] { ColorChangeEvent.class });
+        } catch (final java.lang.NoSuchMethodException e) {
+            // This should never happen
+            throw new java.lang.RuntimeException(
+                    "Internal error finding methods in ColorPicker");
+        }
+    }
+
+    /** The tabs. */
+    private final TabSheet tabs = new TabSheet();
+
+    private Component rgbTab;
+
+    private Component hsvTab;
+
+    private Component swatchesTab;
+
+    /** The layout. */
+    private final VerticalLayout layout;
+
+    /** The ok button. */
+    private final Button ok = new Button("OK");
+
+    /** The cancel button. */
+    private final Button cancel = new Button("Cancel");
+
+    /** The resize button. */
+    private final Button resize = new Button("show/hide history");
+
+    /** The selected color. */
+    private Color selectedColor = Color.WHITE;
+
+    /** The history. */
+    private ColorPickerHistory history;
+
+    /** The history container. */
+    private Layout historyContainer;
+
+    /** The rgb gradient. */
+    private ColorPickerGradient rgbGradient;
+
+    /** The hsv gradient. */
+    private ColorPickerGradient hsvGradient;
+
+    /** The red slider. */
+    private Slider redSlider;
+
+    /** The green slider. */
+    private Slider greenSlider;
+
+    /** The blue slider. */
+    private Slider blueSlider;
+
+    /** The hue slider. */
+    private Slider hueSlider;
+
+    /** The saturation slider. */
+    private Slider saturationSlider;
+
+    /** The value slider. */
+    private Slider valueSlider;
+
+    /** The preview on the rgb tab. */
+    private ColorPickerPreview rgbPreview;
+
+    /** The preview on the hsv tab. */
+    private ColorPickerPreview hsvPreview;
+
+    /** The preview on the swatches tab. */
+    private ColorPickerPreview selPreview;
+
+    /** The color select. */
+    private ColorPickerSelect colorSelect;
+
+    /** The selectors. */
+    private final Set<ColorSelector> selectors = new HashSet<ColorSelector>();
+
+    /**
+     * Set true while the slider values are updated after colorChange. When
+     * true, valueChange reactions from the sliders are disabled, because
+     * otherwise the set color may become corrupted as it is repeatedly re-set
+     * in valueChangeListeners using values from sliders that may not have been
+     * updated yet.
+     */
+    private boolean updatingColors = false;
+
+    /**
+     * Instantiates a new color picker popup.
+     */
+    public ColorPickerPopup(Color initialColor) {
+        super();
+
+        selectedColor = initialColor;
+
+        // Set the layout
+        layout = new VerticalLayout();
+        layout.setSpacing(false);
+        layout.setMargin(false);
+        layout.setWidth("100%");
+        layout.setHeight(null);
+
+        setContent(layout);
+        setStyleName(STYLENAME);
+        setResizable(false);
+        setImmediate(true);
+
+        initContents();
+    }
+
+    private void initContents() {
+        // Create the history
+        history = new ColorPickerHistory();
+        history.addColorChangeListener(this);
+
+        // Create the preview on the rgb tab
+        rgbPreview = new ColorPickerPreview(selectedColor);
+        rgbPreview.setWidth("240px");
+        rgbPreview.setHeight("20px");
+        rgbPreview.addColorChangeListener(this);
+        selectors.add(rgbPreview);
+
+        // Create the preview on the hsv tab
+        hsvPreview = new ColorPickerPreview(selectedColor);
+        hsvPreview.setWidth("240px");
+        hsvPreview.setHeight("20px");
+        hsvPreview.addColorChangeListener(this);
+        selectors.add(hsvPreview);
+
+        // Create the preview on the swatches tab
+        selPreview = new ColorPickerPreview(selectedColor);
+        selPreview.setWidth("100%");
+        selPreview.setHeight("20px");
+        selPreview.addColorChangeListener(this);
+        selectors.add(selPreview);
+
+        // Create the tabs
+        rgbTab = createRGBTab(selectedColor);
+        tabs.addTab(rgbTab, "RGB", null);
+
+        hsvTab = createHSVTab(selectedColor);
+        tabs.addTab(hsvTab, "HSV", null);
+
+        swatchesTab = createSelectTab();
+        tabs.addTab(swatchesTab, "Swatches", null);
+
+        // Add the tabs
+        tabs.setWidth("100%");
+
+        layout.addComponent(tabs);
+
+        // Add the history
+        history.setWidth("97%");
+        history.setHeight("22px");
+
+        // Create the default colors
+        List<Color> defaultColors = new ArrayList<Color>();
+        defaultColors.add(Color.BLACK);
+        defaultColors.add(Color.WHITE);
+
+        // Create the history
+        VerticalLayout innerContainer = new VerticalLayout();
+        innerContainer.setWidth("100%");
+        innerContainer.setHeight(null);
+        innerContainer.addComponent(history);
+
+        VerticalLayout outerContainer = new VerticalLayout();
+        outerContainer.setWidth("99%");
+        outerContainer.setHeight("27px");
+        outerContainer.addComponent(innerContainer);
+        historyContainer = outerContainer;
+
+        layout.addComponent(historyContainer);
+
+        // Add the resize button for the history
+        resize.addClickListener(this);
+        resize.setData(new Boolean(false));
+        resize.setWidth("100%");
+        resize.setHeight("10px");
+        resize.setPrimaryStyleName("resize-button");
+        layout.addComponent(resize);
+
+        // Add the buttons
+        ok.setWidth("70px");
+        ok.addClickListener(this);
+
+        cancel.setWidth("70px");
+        cancel.addClickListener(this);
+
+        HorizontalLayout buttons = new HorizontalLayout();
+        buttons.addComponent(ok);
+        buttons.addComponent(cancel);
+        buttons.setWidth("100%");
+        buttons.setHeight("30px");
+        buttons.setComponentAlignment(ok, Alignment.MIDDLE_CENTER);
+        buttons.setComponentAlignment(cancel, Alignment.MIDDLE_CENTER);
+        layout.addComponent(buttons);
+    }
+
+    /**
+     * Creates the RGB tab.
+     * 
+     * @return the component
+     */
+    private Component createRGBTab(Color color) {
+        VerticalLayout rgbLayout = new VerticalLayout();
+        rgbLayout.setMargin(new MarginInfo(false, false, true, false));
+        rgbLayout.addComponent(rgbPreview);
+        rgbLayout.setStyleName("rgbtab");
+
+        // Add the RGB color gradient
+        rgbGradient = new ColorPickerGradient("rgb-gradient", RGBConverter);
+        rgbGradient.setColor(color);
+        rgbGradient.addColorChangeListener(this);
+        rgbLayout.addComponent(rgbGradient);
+        selectors.add(rgbGradient);
+
+        // Add the RGB sliders
+        VerticalLayout sliders = new VerticalLayout();
+        sliders.setStyleName("rgb-sliders");
+
+        redSlider = createRGBSlider("Red", "red");
+
+        try {
+            redSlider.setValue(((Integer) color.getRed()).doubleValue());
+        } catch (ValueOutOfBoundsException e) {
+        }
+
+        redSlider.addValueChangeListener(new ValueChangeListener() {
+            public void valueChange(ValueChangeEvent event) {
+                double red = (Double) event.getProperty().getValue();
+                if (!updatingColors) {
+                    Color newColor = new Color((int) red, selectedColor
+                            .getGreen(), selectedColor.getBlue());
+                    setColor(newColor);
+                }
+            }
+        });
+
+        sliders.addComponent(redSlider);
+
+        greenSlider = createRGBSlider("Green", "green");
+
+        try {
+            greenSlider.setValue(((Integer) color.getGreen()).doubleValue());
+        } catch (ValueOutOfBoundsException e) {
+        }
+
+        greenSlider.addValueChangeListener(new ValueChangeListener() {
+            public void valueChange(ValueChangeEvent event) {
+                double green = (Double) event.getProperty().getValue();
+                if (!updatingColors) {
+                    Color newColor = new Color(selectedColor.getRed(),
+                            (int) green, selectedColor.getBlue());
+                    setColor(newColor);
+                }
+            }
+        });
+        sliders.addComponent(greenSlider);
+
+        blueSlider = createRGBSlider("Blue", "blue");
+
+        try {
+            blueSlider.setValue(((Integer) color.getBlue()).doubleValue());
+        } catch (ValueOutOfBoundsException e) {
+        }
+
+        blueSlider.addValueChangeListener(new ValueChangeListener() {
+            public void valueChange(ValueChangeEvent event) {
+                double blue = (Double) event.getProperty().getValue();
+                if (!updatingColors) {
+                    Color newColor = new Color(selectedColor.getRed(),
+                            selectedColor.getGreen(), (int) blue);
+                    setColor(newColor);
+                }
+            }
+        });
+        sliders.addComponent(blueSlider);
+
+        rgbLayout.addComponent(sliders);
+
+        return rgbLayout;
+    }
+
+    private Slider createRGBSlider(String caption, String styleName) {
+        Slider redSlider = new Slider(caption, 0, 255);
+        redSlider.setImmediate(true);
+        redSlider.setStyleName("rgb-slider");
+        redSlider.setWidth("220px");
+        redSlider.addStyleName(styleName);
+        return redSlider;
+    }
+
+    /**
+     * Creates the hsv tab.
+     * 
+     * @return the component
+     */
+    private Component createHSVTab(Color color) {
+        VerticalLayout hsvLayout = new VerticalLayout();
+        hsvLayout.setMargin(new MarginInfo(false, false, true, false));
+        hsvLayout.addComponent(hsvPreview);
+        hsvLayout.setStyleName("hsvtab");
+
+        // Add the hsv gradient
+        hsvGradient = new ColorPickerGradient("hsv-gradient", HSVConverter);
+        hsvGradient.setColor(color);
+        hsvGradient.addColorChangeListener(this);
+        hsvLayout.addComponent(hsvGradient);
+        selectors.add(hsvGradient);
+
+        float[] hsv = color.getHSV();
+        VerticalLayout sliders = new VerticalLayout();
+        sliders.setStyleName("hsv-sliders");
+
+        hueSlider = new Slider("Hue", 0, 360);
+        try {
+            hueSlider.setValue(((Float) hsv[0]).doubleValue());
+        } catch (ValueOutOfBoundsException e1) {
+        }
+
+        hueSlider.setStyleName("hsv-slider");
+        hueSlider.addStyleName("hue-slider");
+        hueSlider.setWidth("220px");
+        hueSlider.setImmediate(true);
+        hueSlider.addValueChangeListener(new ValueChangeListener() {
+            public void valueChange(ValueChangeEvent event) {
+                if (!updatingColors) {
+                    float hue = (Float.parseFloat(event.getProperty()
+                            .getValue().toString())) / 360f;
+                    float saturation = (Float.parseFloat(saturationSlider
+                            .getValue().toString())) / 100f;
+                    float value = (Float.parseFloat(valueSlider.getValue()
+                            .toString())) / 100f;
+
+                    // Set the color
+                    Color color = new Color(Color.HSVtoRGB(hue, saturation,
+                            value));
+                    setColor(color);
+
+                    /*
+                     * Set the background color of the hue gradient. This has to
+                     * be done here since in the conversion the base color
+                     * information is lost when color is black/white
+                     */
+                    Color bgColor = new Color(Color.HSVtoRGB(hue, 1f, 1f));
+                    hsvGradient.setBackgroundColor(bgColor);
+                }
+            }
+        });
+        sliders.addComponent(hueSlider);
+
+        saturationSlider = new Slider("Saturation", 0, 100);
+
+        try {
+            saturationSlider.setValue(((Float) hsv[1]).doubleValue());
+        } catch (ValueOutOfBoundsException e1) {
+        }
+
+        saturationSlider.setStyleName("hsv-slider");
+        saturationSlider.setWidth("220px");
+        saturationSlider.setImmediate(true);
+        saturationSlider.addValueChangeListener(new ValueChangeListener() {
+            public void valueChange(ValueChangeEvent event) {
+                if (!updatingColors) {
+                    float hue = (Float.parseFloat(hueSlider.getValue()
+                            .toString())) / 360f;
+                    float saturation = (Float.parseFloat(event.getProperty()
+                            .getValue().toString())) / 100f;
+                    float value = (Float.parseFloat(valueSlider.getValue()
+                            .toString())) / 100f;
+                    Color color = new Color(Color.HSVtoRGB(hue, saturation,
+                            value));
+                    setColor(color);
+                }
+            }
+        });
+        sliders.addComponent(saturationSlider);
+
+        valueSlider = new Slider("Value", 0, 100);
+
+        try {
+            valueSlider.setValue(((Float) hsv[2]).doubleValue());
+        } catch (ValueOutOfBoundsException e1) {
+        }
+
+        valueSlider.setStyleName("hsv-slider");
+        valueSlider.setWidth("220px");
+        valueSlider.setImmediate(true);
+        valueSlider.addValueChangeListener(new ValueChangeListener() {
+            public void valueChange(ValueChangeEvent event) {
+                if (!updatingColors) {
+                    float hue = (Float.parseFloat(hueSlider.getValue()
+                            .toString())) / 360f;
+                    float saturation = (Float.parseFloat(saturationSlider
+                            .getValue().toString())) / 100f;
+                    float value = (Float.parseFloat(event.getProperty()
+                            .getValue().toString())) / 100f;
+
+                    Color color = new Color(Color.HSVtoRGB(hue, saturation,
+                            value));
+                    setColor(color);
+                }
+            }
+        });
+
+        sliders.addComponent(valueSlider);
+        hsvLayout.addComponent(sliders);
+
+        return hsvLayout;
+    }
+
+    /**
+     * Creates the select tab.
+     * 
+     * @return the component
+     */
+    private Component createSelectTab() {
+        VerticalLayout selLayout = new VerticalLayout();
+        selLayout.setMargin(new MarginInfo(false, false, true, false));
+        selLayout.addComponent(selPreview);
+        selLayout.addStyleName("seltab");
+
+        colorSelect = new ColorPickerSelect();
+        colorSelect.addColorChangeListener(this);
+        selLayout.addComponent(colorSelect);
+
+        return selLayout;
+    }
+
+    @Override
+    public void buttonClick(ClickEvent event) {
+        // History resize was clicked
+        if (event.getButton() == resize) {
+            boolean state = (Boolean) resize.getData();
+
+            // minimize
+            if (state) {
+                historyContainer.setHeight("27px");
+                history.setHeight("22px");
+
+                // maximize
+            } else {
+                historyContainer.setHeight("90px");
+                history.setHeight("85px");
+            }
+
+            resize.setData(new Boolean(!state));
+        }
+
+        // Ok button was clicked
+        else if (event.getButton() == ok) {
+            history.setColor(getColor());
+            fireColorChanged();
+            close();
+        }
+
+        // Cancel button was clicked
+        else if (event.getButton() == cancel) {
+            close();
+        }
+
+    }
+
+    /**
+     * Notifies the listeners that the color changed
+     */
+    public void fireColorChanged() {
+        fireEvent(new ColorChangeEvent(this, getColor()));
+    }
+
+    /**
+     * Gets the history.
+     * 
+     * @return the history
+     */
+    public ColorPickerHistory getHistory() {
+        return history;
+    }
+
+    @Override
+    public void setColor(Color color) {
+        if (color == null) {
+            return;
+        }
+
+        selectedColor = color;
+
+        hsvGradient.setColor(selectedColor);
+        hsvPreview.setColor(selectedColor);
+
+        rgbGradient.setColor(selectedColor);
+        rgbPreview.setColor(selectedColor);
+
+        selPreview.setColor(selectedColor);
+    }
+
+    @Override
+    public Color getColor() {
+        return selectedColor;
+    }
+
+    /**
+     * Gets the color history.
+     * 
+     * @return the color history
+     */
+    public List<Color> getColorHistory() {
+        return Collections.unmodifiableList(history.getHistory());
+    }
+
+    @Override
+    public void colorChanged(ColorChangeEvent event) {
+        setColor(event.getColor());
+
+        updatingColors = true;
+        try {
+            redSlider
+                    .setValue(((Integer) selectedColor.getRed()).doubleValue());
+            blueSlider.setValue(((Integer) selectedColor.getBlue())
+                    .doubleValue());
+            greenSlider.setValue(((Integer) selectedColor.getGreen())
+                    .doubleValue());
+
+            float[] hsv = selectedColor.getHSV();
+
+            hueSlider.setValue(((Float) (hsv[0] * 360f)).doubleValue());
+            saturationSlider.setValue(((Float) (hsv[1] * 100f)).doubleValue());
+            valueSlider.setValue(((Float) (hsv[2] * 100f)).doubleValue());
+
+        } catch (ValueOutOfBoundsException e) {
+            e.printStackTrace();
+        }
+        updatingColors = false;
+
+        for (ColorSelector s : selectors) {
+            if (event.getSource() != s && s != this
+                    && s.getColor() != selectedColor) {
+                s.setColor(selectedColor);
+            }
+        }
+    }
+
+    @Override
+    public void addColorChangeListener(ColorChangeListener listener) {
+        addListener(ColorChangeEvent.class, listener, COLOR_CHANGE_METHOD);
+    }
+
+    @Override
+    public void removeColorChangeListener(ColorChangeListener listener) {
+        removeListener(ColorChangeEvent.class, listener);
+    }
+
+    /**
+     * Checks the visibility of the given tab
+     * 
+     * @param tab
+     *            The tab to check
+     * @return true if tab is visible, false otherwise
+     */
+    private boolean tabIsVisible(Component tab) {
+        Iterator<Component> tabIterator = tabs.getComponentIterator();
+        while (tabIterator.hasNext()) {
+            if (tabIterator.next() == tab) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * How many tabs are visible
+     * 
+     * @return The number of tabs visible
+     */
+    private int tabsNumVisible() {
+        Iterator<Component> tabIterator = tabs.getComponentIterator();
+        int tabCounter = 0;
+        while (tabIterator.hasNext()) {
+            tabIterator.next();
+            tabCounter++;
+        }
+        return tabCounter;
+    }
+
+    /**
+     * Checks if tabs are needed and hides them if not
+     */
+    private void checkIfTabsNeeded() {
+        tabs.hideTabs(tabsNumVisible() == 1);
+    }
+
+    /**
+     * Set RGB tab visibility
+     * 
+     * @param visible
+     *            The visibility of the RGB tab
+     */
+    public void setRGBTabVisible(boolean visible) {
+        if (visible && !tabIsVisible(rgbTab)) {
+            tabs.addTab(rgbTab, "RGB", null);
+            checkIfTabsNeeded();
+        } else if (!visible && tabIsVisible(rgbTab)) {
+            tabs.removeComponent(rgbTab);
+            checkIfTabsNeeded();
+        }
+    }
+
+    /**
+     * Set HSV tab visibility
+     * 
+     * @param visible
+     *            The visibility of the HSV tab
+     */
+    public void setHSVTabVisible(boolean visible) {
+        if (visible && !tabIsVisible(hsvTab)) {
+            tabs.addTab(hsvTab, "HSV", null);
+            checkIfTabsNeeded();
+        } else if (!visible && tabIsVisible(hsvTab)) {
+            tabs.removeComponent(hsvTab);
+            checkIfTabsNeeded();
+        }
+    }
+
+    /**
+     * Set Swatches tab visibility
+     * 
+     * @param visible
+     *            The visibility of the Swatches tab
+     */
+    public void setSwatchesTabVisible(boolean visible) {
+        if (visible && !tabIsVisible(swatchesTab)) {
+            tabs.addTab(swatchesTab, "Swatches", null);
+            checkIfTabsNeeded();
+        } else if (!visible && tabIsVisible(swatchesTab)) {
+            tabs.removeComponent(swatchesTab);
+            checkIfTabsNeeded();
+        }
+    }
+
+    /**
+     * Set the History visibility
+     * 
+     * @param visible
+     */
+    public void setHistoryVisible(boolean visible) {
+        historyContainer.setVisible(visible);
+        resize.setVisible(visible);
+    }
+
+    /**
+     * Set the preview visibility
+     * 
+     * @param visible
+     */
+    public void setPreviewVisible(boolean visible) {
+        hsvPreview.setVisible(visible);
+        rgbPreview.setVisible(visible);
+        selPreview.setVisible(visible);
+    }
+
+    /** RGB color converter */
+    private Coordinates2Color RGBConverter = new Coordinates2Color() {
+
+        @Override
+        public Color calculate(int x, int y) {
+            float h = (x / 220f);
+            float s = 1f;
+            float v = 1f;
+
+            if (y < 110) {
+                s = y / 110f;
+            } else if (y > 110) {
+                v = 1f - (y - 110f) / 110f;
+            }
+
+            return new Color(Color.HSVtoRGB(h, s, v));
+        }
+
+        @Override
+        public int[] calculate(Color color) {
+
+            float[] hsv = color.getHSV();
+
+            int x = Math.round(hsv[0] * 220f);
+            int y = 0;
+
+            // lower half
+            if (hsv[1] == 1f) {
+                y = Math.round(110f - (hsv[1] + hsv[2]) * 110f);
+            } else {
+                y = Math.round(hsv[1] * 110f);
+            }
+
+            return new int[] { x, y };
+        }
+    };
+
+    /** HSV color converter */
+    Coordinates2Color HSVConverter = new Coordinates2Color() {
+        public int[] calculate(Color color) {
+
+            float[] hsv = color.getHSV();
+
+            // Calculate coordinates
+            int x = Math.round(hsv[2] * 220.0f);
+            int y = Math.round(220 - hsv[1] * 220.0f);
+
+            // Create background color of clean color
+            Color bgColor = new Color(Color.HSVtoRGB(hsv[0], 1f, 1f));
+            hsvGradient.setBackgroundColor(bgColor);
+
+            return new int[] { x, y };
+        }
+
+        public Color calculate(int x, int y) {
+            float saturation = 1f - (y / 220.0f);
+            float value = (x / 220.0f);
+            float hue = Float.parseFloat(hueSlider.getValue().toString()) / 360f;
+
+            Color color = new Color(Color.HSVtoRGB(hue, saturation, value));
+            return color;
+        }
+    };
+}
\ No newline at end of file
diff --git a/server/src/com/vaadin/ui/components/colorpicker/ColorPickerPreview.java b/server/src/com/vaadin/ui/components/colorpicker/ColorPickerPreview.java
new file mode 100644 (file)
index 0000000..cb6dba3
--- /dev/null
@@ -0,0 +1,143 @@
+package com.vaadin.ui.components.colorpicker;
+
+import java.lang.reflect.Method;
+
+import com.vaadin.data.Property.ValueChangeEvent;
+import com.vaadin.data.Property.ValueChangeListener;
+import com.vaadin.data.validator.RegexpValidator;
+import com.vaadin.shared.ui.colorpicker.Color;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.CssLayout;
+import com.vaadin.ui.TextField;
+
+/**
+ * A component that represents color selection preview within a color picker.
+ * 
+ * @since 7.0.0
+ */
+public class ColorPickerPreview extends CssLayout implements ColorSelector,
+        ValueChangeListener {
+
+    private static final String STYLE_DARK_COLOR = "v-textfield-dark";
+    private static final String STYLE_LIGHT_COLOR = "v-textfield-light";
+
+    private static final Method COLOR_CHANGE_METHOD;
+    static {
+        try {
+            COLOR_CHANGE_METHOD = ColorChangeListener.class.getDeclaredMethod(
+                    "colorChanged", new Class[] { ColorChangeEvent.class });
+        } catch (final java.lang.NoSuchMethodException e) {
+            // This should never happen
+            throw new java.lang.RuntimeException(
+                    "Internal error finding methods in ColorPicker");
+        }
+    }
+
+    /** The color. */
+    private Color color;
+
+    /** The field. */
+    private final TextField field;
+
+    /** The old value. */
+    private String oldValue;
+
+    /**
+     * Instantiates a new color picker preview.
+     */
+    public ColorPickerPreview(Color color) {
+        setStyleName("v-colorpicker-preview");
+        setImmediate(true);
+
+        this.color = color;
+
+        field = new TextField();
+        field.setReadOnly(true);
+        field.setImmediate(true);
+        field.setSizeFull();
+        field.setStyleName("v-colorpicker-preview-textfield");
+        field.setData(this);
+        field.addValueChangeListener(this);
+        field.addValidator(new RegexpValidator("#[0-9a-fA-F]{6}", true, ""));
+        addComponent(field);
+
+        setColor(color);
+    }
+
+    @Override
+    public void setColor(Color color) {
+        this.color = color;
+
+        // Unregister listener
+        field.removeValueChangeListener(this);
+        field.setReadOnly(false);
+
+        String colorCSS = color.getCSS();
+        field.setValue(colorCSS);
+
+        if (field.isValid()) {
+            oldValue = colorCSS;
+        } else {
+            field.setValue(oldValue);
+        }
+
+        // Re-register listener
+        field.setReadOnly(true);
+        field.addValueChangeListener(this);
+
+        // Set the text color
+        field.removeStyleName(STYLE_DARK_COLOR);
+        field.removeStyleName(STYLE_LIGHT_COLOR);
+        if (this.color.getRed() + this.color.getGreen() + this.color.getBlue() < 3 * 128) {
+            field.addStyleName(STYLE_DARK_COLOR);
+        } else {
+            field.addStyleName(STYLE_LIGHT_COLOR);
+        }
+
+        markAsDirty();
+    }
+
+    @Override
+    public Color getColor() {
+        return color;
+    }
+
+    @Override
+    public void addColorChangeListener(ColorChangeListener listener) {
+        addListener(ColorChangeEvent.class, listener, COLOR_CHANGE_METHOD);
+    }
+
+    @Override
+    public void removeColorChangeListener(ColorChangeListener listener) {
+        removeListener(ColorChangeEvent.class, listener);
+    }
+
+    @Override
+    public void valueChange(ValueChangeEvent event) {
+        String value = (String) event.getProperty().getValue();
+
+        if (!field.isValid()) {
+            field.setValue(oldValue);
+            return;
+        } else {
+            oldValue = value;
+        }
+
+        if (value != null && value.length() == 7) {
+            int red = Integer.parseInt(value.substring(1, 3), 16);
+            int green = Integer.parseInt(value.substring(3, 5), 16);
+            int blue = Integer.parseInt(value.substring(5, 7), 16);
+            color = new Color(red, green, blue);
+
+            fireEvent(new ColorChangeEvent((Component) field.getData(), color));
+        }
+    }
+
+    /**
+     * Called when the component is refreshing
+     */
+    @Override
+    protected String getCss(Component c) {
+        return "background: " + color.getCSS();
+    }
+}
\ No newline at end of file
diff --git a/server/src/com/vaadin/ui/components/colorpicker/ColorPickerSelect.java b/server/src/com/vaadin/ui/components/colorpicker/ColorPickerSelect.java
new file mode 100644 (file)
index 0000000..08747cc
--- /dev/null
@@ -0,0 +1,218 @@
+package com.vaadin.ui.components.colorpicker;
+
+import com.vaadin.data.Property.ValueChangeEvent;
+import com.vaadin.data.Property.ValueChangeListener;
+import com.vaadin.shared.ui.colorpicker.Color;
+import com.vaadin.ui.ComboBox;
+import com.vaadin.ui.CustomComponent;
+import com.vaadin.ui.VerticalLayout;
+
+/**
+ * A component that represents color selection swatches within a color picker.
+ * 
+ * @since 7.0.0
+ */
+public class ColorPickerSelect extends CustomComponent implements
+        ColorSelector, ValueChangeListener {
+
+    /** The range. */
+    private final ComboBox range;
+
+    /** The grid. */
+    private final ColorPickerGrid grid;
+
+    /**
+     * The Enum ColorRangePropertyId.
+     */
+    private enum ColorRangePropertyId {
+        ALL("All colors"), RED("Red colors"), GREEN("Green colors"), BLUE(
+                "Blue colors");
+
+        /** The caption. */
+        private String caption;
+
+        /**
+         * Instantiates a new color range property id.
+         * 
+         * @param caption
+         *            the caption
+         */
+        ColorRangePropertyId(String caption) {
+            this.caption = caption;
+        }
+
+        @Override
+        public String toString() {
+            return caption;
+        }
+    }
+
+    /**
+     * Instantiates a new color picker select.
+     * 
+     * @param rows
+     *            the rows
+     * @param columns
+     *            the columns
+     */
+    public ColorPickerSelect() {
+
+        VerticalLayout layout = new VerticalLayout();
+        setCompositionRoot(layout);
+
+        setStyleName("colorselect");
+        setWidth("100%");
+
+        range = new ComboBox();
+        range.setImmediate(true);
+        range.setImmediate(true);
+        range.setNullSelectionAllowed(false);
+        range.setNewItemsAllowed(false);
+        range.setWidth("100%");
+        range.addValueChangeListener(this);
+
+        for (ColorRangePropertyId id : ColorRangePropertyId.values()) {
+            range.addItem(id);
+        }
+        range.select(ColorRangePropertyId.ALL);
+
+        layout.addComponent(range);
+
+        grid = new ColorPickerGrid(createAllColors(14, 10));
+        grid.setWidth("100%");
+
+        layout.addComponent(grid);
+    }
+
+    /**
+     * Creates the all colors.
+     * 
+     * @param rows
+     *            the rows
+     * @param columns
+     *            the columns
+     * 
+     * @return the color[][]
+     */
+    private Color[][] createAllColors(int rows, int columns) {
+        Color[][] colors = new Color[rows][columns];
+
+        for (int row = 0; row < rows; row++) {
+            for (int col = 0; col < columns; col++) {
+
+                // Create the color grid by varying the saturation and value
+                if (row < (rows - 1)) {
+                    // Calculate new hue value
+                    float hue = ((float) col / (float) columns);
+                    float saturation = 1f;
+                    float value = 1f;
+
+                    // For the upper half use value=1 and variable
+                    // saturation
+                    if (row < (rows / 2)) {
+                        saturation = ((row + 1f) / (rows / 2f));
+                    } else {
+                        value = 1f - ((row - (rows / 2f)) / (rows / 2f));
+                    }
+
+                    colors[row][col] = new Color(Color.HSVtoRGB(hue,
+                            saturation, value));
+                }
+
+                // The last row should have the black&white gradient
+                else {
+                    float hue = 0f;
+                    float saturation = 0f;
+                    float value = 1f - ((float) col / (float) columns);
+
+                    colors[row][col] = new Color(Color.HSVtoRGB(hue,
+                            saturation, value));
+                }
+            }
+        }
+
+        return colors;
+    }
+
+    /**
+     * Creates the color.
+     * 
+     * @param color
+     *            the color
+     * @param rows
+     *            the rows
+     * @param columns
+     *            the columns
+     * 
+     * @return the color[][]
+     */
+    private Color[][] createColors(Color color, int rows, int columns) {
+        Color[][] colors = new Color[rows][columns];
+
+        float[] hsv = color.getHSV();
+
+        float hue = hsv[0];
+        float saturation = 1f;
+        float value = 1f;
+
+        for (int row = 0; row < rows; row++) {
+            for (int col = 0; col < columns; col++) {
+
+                int index = row * columns + col;
+                saturation = 1f;
+                value = 1f;
+
+                if (index <= ((rows * columns) / 2)) {
+                    saturation = index
+                            / (((float) rows * (float) columns) / 2f);
+                } else {
+                    index -= ((rows * columns) / 2);
+                    value = 1f - index
+                            / (((float) rows * (float) columns) / 2f);
+                }
+
+                colors[row][col] = new Color(Color.HSVtoRGB(hue, saturation,
+                        value));
+            }
+        }
+
+        return colors;
+    }
+
+    @Override
+    public Color getColor() {
+        return grid.getColor();
+    }
+
+    @Override
+    public void setColor(Color color) {
+        grid.getColor();
+    }
+
+    @Override
+    public void addColorChangeListener(ColorChangeListener listener) {
+        grid.addColorChangeListener(listener);
+    }
+
+    @Override
+    public void removeColorChangeListener(ColorChangeListener listener) {
+        grid.removeColorChangeListener(listener);
+    }
+
+    @Override
+    public void valueChange(ValueChangeEvent event) {
+        if (grid == null) {
+            return;
+        }
+
+        if (event.getProperty().getValue() == ColorRangePropertyId.ALL) {
+            grid.setColorGrid(createAllColors(14, 10));
+        } else if (event.getProperty().getValue() == ColorRangePropertyId.RED) {
+            grid.setColorGrid(createColors(new Color(0xFF, 0, 0), 14, 10));
+        } else if (event.getProperty().getValue() == ColorRangePropertyId.GREEN) {
+            grid.setColorGrid(createColors(new Color(0, 0xFF, 0), 14, 10));
+        } else if (event.getProperty().getValue() == ColorRangePropertyId.BLUE) {
+            grid.setColorGrid(createColors(new Color(0, 0, 0xFF), 14, 10));
+        }
+    }
+}
\ No newline at end of file
diff --git a/server/src/com/vaadin/ui/components/colorpicker/ColorSelector.java b/server/src/com/vaadin/ui/components/colorpicker/ColorSelector.java
new file mode 100644 (file)
index 0000000..8468666
--- /dev/null
@@ -0,0 +1,28 @@
+package com.vaadin.ui.components.colorpicker;
+
+import java.io.Serializable;
+
+import com.vaadin.shared.ui.colorpicker.Color;
+
+/**
+ * An interface for a color selector.
+ * 
+ * @since 7.0.0
+ */
+public interface ColorSelector extends Serializable, HasColorChangeListener {
+
+    /**
+     * Sets the color.
+     * 
+     * @param color
+     *            the new color
+     */
+    public void setColor(Color color);
+
+    /**
+     * Gets the color.
+     * 
+     * @return the color
+     */
+    public Color getColor();
+}
\ No newline at end of file
diff --git a/server/src/com/vaadin/ui/components/colorpicker/HasColorChangeListener.java b/server/src/com/vaadin/ui/components/colorpicker/HasColorChangeListener.java
new file mode 100644 (file)
index 0000000..25b3d3c
--- /dev/null
@@ -0,0 +1,19 @@
+package com.vaadin.ui.components.colorpicker;
+
+public interface HasColorChangeListener {
+
+    /**
+     * Adds a {@link ColorChangeListener} to the component.
+     * 
+     * @param listener
+     */
+    void addColorChangeListener(ColorChangeListener listener);
+
+    /**
+     * Removes a {@link ColorChangeListener} from the component.
+     * 
+     * @param listener
+     */
+    void removeColorChangeListener(ColorChangeListener listener);
+
+}
\ No newline at end of file
diff --git a/shared/src/com/vaadin/shared/ui/colorpicker/Color.java b/shared/src/com/vaadin/shared/ui/colorpicker/Color.java
new file mode 100644 (file)
index 0000000..6028213
--- /dev/null
@@ -0,0 +1,378 @@
+package com.vaadin.shared.ui.colorpicker;
+
+/**
+ * Default implementation for color.
+ * 
+ * @since 7.0.0
+ */
+public class Color {
+
+    public static final Color WHITE = new Color(255, 255, 255);
+    public static final Color BLACK = new Color(0, 0, 0);
+    public static final Color RED = new Color(255, 0, 0);
+    public static final Color GREEN = new Color(0, 255, 0);
+    public static final Color BLUE = new Color(0, 0, 255);
+    public static final Color YELLOW = new Color(255, 255, 0);
+    public static final Color MAGENTA = new Color(255, 0, 255);
+    public static final Color CYAN = new Color(0, 255, 255);
+
+    private int red;
+    private int green;
+    private int blue;
+    private int alpha;
+
+    private String OUTOFRANGE = "Value must be within the range [0-255]. Was: ";
+
+    /**
+     * Creates a color that has the specified red, green, blue, and alpha values
+     * within the range [0 - 255].
+     * 
+     * @throws IllegalArgumentException
+     *             if <code>red</code>, <code>green</code>, <code>blue</code> or
+     *             <code>alpha</code> fall outside of the inclusive range from 0
+     *             to 255
+     * @param red
+     *            the red value
+     * @param green
+     *            the green value
+     * @param blue
+     *            the blue value
+     * @param alpha
+     *            the alpha value
+     */
+    public Color(int red, int green, int blue, int alpha) {
+        checkRange(red, green, blue, alpha);
+        this.red = red;
+        this.green = green;
+        this.blue = blue;
+        this.alpha = alpha;
+    }
+
+    /**
+     * Creates a color that has the specified red, green, and blue values within
+     * the range [0 - 255]. Alpha gets the default value of 255.
+     * 
+     * @throws IllegalArgumentException
+     *             if <code>red</code>, <code>green</code> or <code>blue</code>
+     *             fall outside of the inclusive range from 0 to 255
+     * @param red
+     *            the red value
+     * @param green
+     *            the green value
+     * @param blue
+     *            the blue value
+     */
+    public Color(int red, int green, int blue) {
+        this(red, green, blue, 255);
+    }
+
+    /**
+     * Creates a color based on an RGB value.
+     * 
+     * @throws IllegalArgumentException
+     *             if converted values of <code>red</code>, <code>green</code>,
+     *             <code>blue</code> or <code>alpha</code> fall outside of the
+     *             inclusive range from 0 to 255
+     * 
+     * @param rgb
+     *            the RGB value
+     */
+    public Color(int rgb) {
+        int value = 0xff000000 | rgb;
+        int red = (value >> 16) & 0xFF;
+        int green = (value >> 8) & 0xFF;
+        int blue = (value >> 0) & 0xFF;
+        int alpha = (value >> 24) & 0xff;
+
+        checkRange(red, green, blue, alpha);
+
+        this.red = red;
+        this.green = green;
+        this.blue = blue;
+        this.alpha = alpha;
+    }
+
+    /**
+     * Checks that all values are within the acceptable range of [0, 255].
+     * 
+     * @throws IllegalArgumentException
+     *             if any of the values fall outside of the range
+     * 
+     * @param red
+     * @param green
+     * @param blue
+     * @param alpha
+     */
+    private void checkRange(int red, int green, int blue, int alpha) {
+        if (!withinRange(red) || !withinRange(green) || !withinRange(blue)
+                || !withinRange(alpha)) {
+
+            String errorMessage = "All values must fall within range [0-255]. (red: "
+                    + red
+                    + ", green: "
+                    + green
+                    + ", blue: "
+                    + blue
+                    + ", alpha: " + alpha + ")";
+            throw new IllegalArgumentException(errorMessage);
+        }
+    }
+
+    /**
+     * Checks whether the value is within the acceptable range of [0, 255].
+     * 
+     * @param value
+     * @return true if the value falls within the range, false otherwise
+     */
+    private boolean withinRange(int value) {
+        if (value < 0 || value > 255) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns the red value of the color.
+     * 
+     */
+    public int getRed() {
+        return red;
+    }
+
+    /**
+     * Sets the red value of the color. Value must be within the range [0, 255].
+     * 
+     * @param red
+     *            new red value
+     */
+    public void setRed(int red) {
+        if (withinRange(red)) {
+            this.red = red;
+        } else {
+            throw new IllegalArgumentException(OUTOFRANGE + red);
+        }
+    }
+
+    /**
+     * Returns the green value of the color.
+     * 
+     */
+    public int getGreen() {
+        return green;
+    }
+
+    /**
+     * Sets the green value of the color. Value must be within the range [0,
+     * 255].
+     * 
+     * @param green
+     *            new green value
+     */
+    public void setGreen(int green) {
+        if (withinRange(green)) {
+            this.green = green;
+        } else {
+            throw new IllegalArgumentException(OUTOFRANGE + green);
+        }
+    }
+
+    /**
+     * Returns the blue value of the color.
+     * 
+     */
+    public int getBlue() {
+        return blue;
+    }
+
+    /**
+     * Sets the blue value of the color. Value must be within the range [0,
+     * 255].
+     * 
+     * @param blue
+     *            new blue value
+     */
+    public void setBlue(int blue) {
+        if (withinRange(blue)) {
+            this.blue = blue;
+        } else {
+            throw new IllegalArgumentException(OUTOFRANGE + blue);
+        }
+    }
+
+    /**
+     * Returns the alpha value of the color.
+     * 
+     */
+    public int getAlpha() {
+        return alpha;
+    }
+
+    /**
+     * Sets the alpha value of the color. Value must be within the range [0,
+     * 255].
+     * 
+     * @param alpha
+     *            new alpha value
+     */
+    public void setAlpha(int alpha) {
+        if (withinRange(alpha)) {
+            this.alpha = alpha;
+        } else {
+            throw new IllegalArgumentException(OUTOFRANGE + alpha);
+        }
+    }
+
+    /**
+     * Returns CSS representation of the Color, e.g. #000000.
+     */
+    public String getCSS() {
+        String redString = Integer.toHexString(red);
+        redString = redString.length() < 2 ? "0" + redString : redString;
+
+        String greenString = Integer.toHexString(green);
+        greenString = greenString.length() < 2 ? "0" + greenString
+                : greenString;
+
+        String blueString = Integer.toHexString(blue);
+        blueString = blueString.length() < 2 ? "0" + blueString : blueString;
+
+        return "#" + redString + greenString + blueString;
+    }
+
+    /**
+     * Returns RGB value of the color.
+     */
+    public int getRGB() {
+        return ((alpha & 0xFF) << 24) | ((red & 0xFF) << 16)
+                | ((green & 0xFF) << 8) | ((blue & 0xFF) << 0);
+    }
+
+    /**
+     * Returns converted HSV components of the color.
+     * 
+     */
+    public float[] getHSV() {
+        float[] hsv = new float[3];
+
+        int maxColor = (red > green) ? red : green;
+        if (blue > maxColor) {
+            maxColor = blue;
+        }
+        int minColor = (red < green) ? red : green;
+        if (blue < minColor) {
+            minColor = blue;
+        }
+
+        float value = maxColor / 255.0f;
+
+        float saturation = 0;
+        if (maxColor != 0) {
+            saturation = ((float) (maxColor - minColor)) / ((float) maxColor);
+        }
+
+        float hue = 0;
+        if (saturation != 0) {
+            float redF = ((float) (maxColor - red))
+                    / ((float) (maxColor - minColor));
+            float greenF = ((float) (maxColor - green))
+                    / ((float) (maxColor - minColor));
+            float blueF = ((float) (maxColor - blue))
+                    / ((float) (maxColor - minColor));
+
+            if (red == maxColor) {
+                hue = blueF - greenF;
+            } else if (green == maxColor) {
+                hue = 2.0f + redF - blueF;
+            } else {
+                hue = 4.0f + greenF - redF;
+            }
+
+            hue = hue / 6.0f;
+            if (hue < 0) {
+                hue = hue + 1.0f;
+            }
+        }
+
+        hsv[0] = hue;
+        hsv[1] = saturation;
+        hsv[2] = value;
+        return hsv;
+    }
+
+    @Override
+    public int hashCode() {
+        return getRGB();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return obj instanceof Color && ((Color) obj).getRGB() == getRGB();
+    }
+
+    /**
+     * <p>
+     * Converts HSV's hue, saturation and value into an RGB value.
+     * <p>
+     * The <code>saturation</code> and <code>value</code> components should be
+     * floating-point values within the range [0.0-1.0].
+     * <p>
+     * 
+     * @param hue
+     *            the hue of the color
+     * @param saturation
+     *            the saturation of the color
+     * @param value
+     *            the value of the color
+     * @return the RGB value of corresponding color
+     */
+    public static int HSVtoRGB(float hue, float saturation, float value) {
+        int red = 0;
+        int green = 0;
+        int blue = 0;
+
+        if (saturation == 0) {
+            red = green = blue = (int) (value * 255.0f + 0.5f);
+        } else {
+            float h = (hue - (float) Math.floor(hue)) * 6.0f;
+            float f = h - (float) java.lang.Math.floor(h);
+            float p = value * (1.0f - saturation);
+            float q = value * (1.0f - saturation * f);
+            float t = value * (1.0f - (saturation * (1.0f - f)));
+
+            switch ((int) h) {
+            case 0:
+                red = (int) (value * 255.0f + 0.5f);
+                green = (int) (t * 255.0f + 0.5f);
+                blue = (int) (p * 255.0f + 0.5f);
+                break;
+            case 1:
+                red = (int) (q * 255.0f + 0.5f);
+                green = (int) (value * 255.0f + 0.5f);
+                blue = (int) (p * 255.0f + 0.5f);
+                break;
+            case 2:
+                red = (int) (p * 255.0f + 0.5f);
+                green = (int) (value * 255.0f + 0.5f);
+                blue = (int) (t * 255.0f + 0.5f);
+                break;
+            case 3:
+                red = (int) (p * 255.0f + 0.5f);
+                green = (int) (q * 255.0f + 0.5f);
+                blue = (int) (value * 255.0f + 0.5f);
+                break;
+            case 4:
+                red = (int) (t * 255.0f + 0.5f);
+                green = (int) (p * 255.0f + 0.5f);
+                blue = (int) (value * 255.0f + 0.5f);
+                break;
+            case 5:
+                red = (int) (value * 255.0f + 0.5f);
+                green = (int) (p * 255.0f + 0.5f);
+                blue = (int) (q * 255.0f + 0.5f);
+                break;
+            }
+        }
+
+        return 0xff000000 | (red << 16) | (green << 8) | (blue << 0);
+    }
+}
diff --git a/shared/src/com/vaadin/shared/ui/colorpicker/ColorPickerGradientServerRpc.java b/shared/src/com/vaadin/shared/ui/colorpicker/ColorPickerGradientServerRpc.java
new file mode 100644 (file)
index 0000000..e5cf016
--- /dev/null
@@ -0,0 +1,21 @@
+package com.vaadin.shared.ui.colorpicker;
+
+import com.vaadin.shared.communication.ServerRpc;
+
+/**
+ * RPC interface for ColorPickerGradient.
+ * 
+ * @since 7.0.0
+ * 
+ */
+public interface ColorPickerGradientServerRpc extends ServerRpc {
+
+    /**
+     * ColorPickerGradient mouseUp event.
+     * 
+     * @param cursorX
+     * @param cursorY
+     */
+    public void select(int cursorX, int cursorY);
+
+}
diff --git a/shared/src/com/vaadin/shared/ui/colorpicker/ColorPickerGradientState.java b/shared/src/com/vaadin/shared/ui/colorpicker/ColorPickerGradientState.java
new file mode 100644 (file)
index 0000000..d8d2a55
--- /dev/null
@@ -0,0 +1,18 @@
+package com.vaadin.shared.ui.colorpicker;
+
+import com.vaadin.shared.AbstractComponentState;
+
+/**
+ * Default shared state implementation for ColorPickerGradient.
+ * 
+ * @since 7.0.0
+ */
+public class ColorPickerGradientState extends AbstractComponentState {
+
+    public int cursorX;
+
+    public int cursorY;
+
+    public String bgColor;
+
+}
diff --git a/shared/src/com/vaadin/shared/ui/colorpicker/ColorPickerGridServerRpc.java b/shared/src/com/vaadin/shared/ui/colorpicker/ColorPickerGridServerRpc.java
new file mode 100644 (file)
index 0000000..e56b19c
--- /dev/null
@@ -0,0 +1,26 @@
+package com.vaadin.shared.ui.colorpicker;
+
+import com.vaadin.shared.communication.ServerRpc;
+
+/**
+ * RPC interface for ColorPickerGrid.
+ * 
+ * @since 7.0.0
+ * 
+ */
+public interface ColorPickerGridServerRpc extends ServerRpc {
+
+    /**
+     * ColorPickerGrid click event.
+     * 
+     * @param x
+     * @param y
+     */
+    public void select(int x, int y);
+
+    /**
+     * Call to refresh the grid.
+     */
+    public void refresh();
+
+}
diff --git a/shared/src/com/vaadin/shared/ui/colorpicker/ColorPickerGridState.java b/shared/src/com/vaadin/shared/ui/colorpicker/ColorPickerGridState.java
new file mode 100644 (file)
index 0000000..e79a3c2
--- /dev/null
@@ -0,0 +1,22 @@
+package com.vaadin.shared.ui.colorpicker;
+
+import com.vaadin.shared.AbstractComponentState;
+
+/**
+ * Default shared state implementation for ColorPickerGrid.
+ * 
+ * @since 7.0.0
+ */
+public class ColorPickerGridState extends AbstractComponentState {
+
+    public int rowCount;
+
+    public int columnCount;
+
+    public String[] changedX;
+
+    public String[] changedY;
+
+    public String[] changedColor;
+
+}
diff --git a/shared/src/com/vaadin/shared/ui/colorpicker/ColorPickerServerRpc.java b/shared/src/com/vaadin/shared/ui/colorpicker/ColorPickerServerRpc.java
new file mode 100644 (file)
index 0000000..7269923
--- /dev/null
@@ -0,0 +1,21 @@
+package com.vaadin.shared.ui.colorpicker;
+
+import com.vaadin.shared.communication.ServerRpc;
+
+/**
+ * RPC interface for AbstractColorPicker.
+ * 
+ * @since 7.0.0
+ * 
+ */
+public interface ColorPickerServerRpc extends ServerRpc {
+
+    /**
+     * ColorPicker click event.
+     * 
+     * @param openPopup
+     * 
+     */
+    public void openPopup(boolean openPopup);
+
+}
diff --git a/shared/src/com/vaadin/shared/ui/colorpicker/ColorPickerState.java b/shared/src/com/vaadin/shared/ui/colorpicker/ColorPickerState.java
new file mode 100644 (file)
index 0000000..1a914cf
--- /dev/null
@@ -0,0 +1,25 @@
+package com.vaadin.shared.ui.colorpicker;
+
+import com.vaadin.shared.AbstractComponentState;
+import com.vaadin.shared.annotations.DelegateToWidget;
+
+/**
+ * Default shared state implementation for AbstractColorPicker.
+ * 
+ * @since 7.0.0
+ */
+public class ColorPickerState extends AbstractComponentState {
+    {
+        primaryStyleName = "v-colorpicker";
+    }
+
+    @DelegateToWidget("setOpen")
+    public boolean popupVisible = false;
+
+    @DelegateToWidget("setColor")
+    public String color = null;
+
+    public boolean showDefaultCaption;
+
+    public boolean htmlContentAllowed;
+}
diff --git a/uitest/src/com/vaadin/tests/components/colorpicker/ColorPickerTest.html b/uitest/src/com/vaadin/tests/components/colorpicker/ColorPickerTest.html
new file mode 100644 (file)
index 0000000..fb8669b
--- /dev/null
@@ -0,0 +1,339 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!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" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost:8888/run/com.vaadin.tests.components.colorpicker.ColorPickerTest?restartApplication" />
+<title>ColorPickerTest</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">ColorPickerTest</td></tr>
+</thead><tbody>
+<tr>
+       <td>open</td>
+       <td>/run/com.vaadin.tests.components.colorpicker.ColorPickerTest?restartApplication</td>
+       <td></td>
+</tr>
+
+<!-- change foreground color -->
+<tr>
+       <td>click</td>
+       <td>colorpicker1</td>
+       <td></td>
+</tr>
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_Srgb-gradient/domChild[1]/domChild[4]</td>
+       <td>190,87</td>
+</tr>
+<tr>
+       <td>click</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[3]/VHorizontalLayout[0]/VOrderedLayout$Slot[0]/VButton[0]/domChild[0]/domChild[0]</td>
+       <td></td>
+</tr>
+
+<!-- change background color -->
+<tr>
+       <td>click</td>
+       <td>colorpicker2</td>
+       <td></td>
+</tr>
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_Srgb-gradient/domChild[1]/domChild[4]</td>
+       <td>51,33</td>
+</tr>
+<tr>
+       <td>click</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[3]/VHorizontalLayout[0]/VOrderedLayout$Slot[0]/VButton[0]/domChild[0]/domChild[0]</td>
+       <td></td>
+</tr>
+<tr>
+       <td>screenCapture</td>
+       <td></td>
+       <td>pink-on-yellow</td>
+</tr>
+
+<!-- change foreground color with area button -->
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_Scolorpicker5/domChild[1]</td>
+       <td>10,15</td>
+</tr>
+<!-- expand history -->
+<tr>
+       <td>click</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[2]/VButton[0]/domChild[0]</td>
+       <td></td>
+</tr>
+<!-- choose from history -->
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VCustomComponent[0]/VColorPickerGrid[0]/domChild[0]/domChild[1]/domChild[0]/domChild[3]</td>
+       <td>9,9</td>
+</tr>
+<tr>
+       <td>click</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[3]/VHorizontalLayout[0]/VOrderedLayout$Slot[0]/VButton[0]/domChild[0]/domChild[0]</td>
+       <td></td>
+</tr>
+
+<!-- change background color with area button -->
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_Scolorpicker6/domChild[1]</td>
+       <td>12,24</td>
+</tr>
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VCustomComponent[0]/VColorPickerGrid[0]/domChild[0]/domChild[1]/domChild[0]/domChild[1]</td>
+       <td>9,12</td>
+</tr>
+<!-- choose from history -->
+<tr>
+       <td>click</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[3]/VHorizontalLayout[0]/VOrderedLayout$Slot[0]/VButton[0]/domChild[0]</td>
+       <td></td>
+</tr>
+<tr>
+       <td>screenCapture</td>
+       <td></td>
+       <td>black-on-white</td>
+</tr>
+
+<!-- open and close -->
+<tr>
+       <td>click</td>
+       <td>colorpicker1</td>
+       <td></td>
+</tr>
+<tr>
+       <td>click</td>
+       <td>colorpicker1</td>
+       <td></td>
+</tr>
+
+<!-- open and close using area button -->
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_Scolorpicker5/domChild[1]</td>
+       <td>11,17</td>
+</tr>
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_Scolorpicker5/domChild[1]</td>
+       <td>11,17</td>
+</tr>
+
+<!-- open background (using area button) to display HSV effects -->
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_Scolorpicker6/domChild[1]</td>
+       <td>21,15</td>
+</tr>
+<!-- HSV tab -->
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0]/domChild[0]/domChild[0]</td>
+       <td>12,6</td>
+</tr>
+<!-- hue slider -->
+<tr>
+       <td>dragAndDrop</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[2]/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VSlider[0]/domChild[2]/domChild[0]</td>
+       <td>136,0</td>
+</tr>
+<!-- saturation slider -->
+<tr>
+       <td>dragAndDrop</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[2]/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VSlider[0]/domChild[2]/domChild[0]</td>
+       <td>100,0</td>
+</tr>
+<!-- value slider -->
+<tr>
+       <td>dragAndDrop</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[2]/VVerticalLayout[0]/VOrderedLayout$Slot[2]/VSlider[0]/domChild[2]/domChild[0]</td>
+       <td>-60,0</td>
+</tr>
+<!-- Swatches tab (choose color) -->
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2]/domChild[0]/domChild[0]/domChild[0]</td>
+       <td>43,8</td>
+</tr>
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VCustomComponent[0]/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VColorPickerGrid[0]/domChild[0]/domChild[1]/domChild[11]/domChild[9]</td>
+       <td>12,10</td>
+</tr>
+<tr>
+       <td>click</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[3]/VHorizontalLayout[0]/VOrderedLayout$Slot[0]/VButton[0]/domChild[0]/domChild[0]</td>
+       <td></td>
+</tr>
+<tr>
+       <td>screenCapture</td>
+       <td></td>
+       <td>black-on-purple</td>
+</tr>
+
+<!-- open foreground (using area button) to display checkbox effects -->
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_Scolorpicker5/domChild[1]</td>
+       <td>14,25</td>
+</tr>
+<!-- remove Swatches tab -->
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_SswaBox/domChild[0]</td>
+       <td>9,6</td>
+</tr>
+<!-- remove css field -->
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_StxtBox/domChild[0]</td>
+       <td>6,9</td>
+</tr>
+<!-- remove history -->
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_ShisBox/domChild[0]</td>
+       <td>6,7</td>
+</tr>
+<!-- remove RGB tab -->
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_SrgbBox/domChild[0]</td>
+       <td>6,7</td>
+</tr>
+<!-- return RGB tab -->
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_SrgbBox/domChild[0]</td>
+       <td>6,7</td>
+</tr>
+<!-- remove HSV tab -->
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_ShsvBox/domChild[0]</td>
+       <td>6,9</td>
+</tr>
+<!-- return HSV tab -->
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_ShsvBox/domChild[0]</td>
+       <td>6,9</td>
+</tr>
+<!-- return css field -->
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_StxtBox/domChild[0]</td>
+       <td>8,8</td>
+</tr>
+<!-- return history -->
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_ShisBox/domChild[0]</td>
+       <td>6,8</td>
+</tr>
+<!-- return Swatches tab -->
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_SswaBox/domChild[0]</td>
+       <td>4,8</td>
+</tr>
+<!-- close without choosing a new color -->
+<tr>
+       <td>click</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[3]/VHorizontalLayout[0]/VOrderedLayout$Slot[1]/VButton[0]/domChild[0]</td>
+       <td></td>
+</tr>
+
+<!-- change color of the first shade button -->
+<tr>
+       <td>click</td>
+       <td>shadebutton_1</td>
+       <td></td>
+</tr>
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_Srgb-gradient/domChild[1]/domChild[4]</td>
+       <td>148,127</td>
+</tr>
+<tr>
+       <td>click</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[3]/VHorizontalLayout[0]/VOrderedLayout$Slot[0]/VButton[0]/domChild[0]/domChild[0]</td>
+       <td></td>
+</tr>
+<tr>
+       <td>screenCapture</td>
+       <td></td>
+       <td>blue-first-button</td>
+</tr>
+
+<!-- reset the color back to black -->
+<tr>
+       <td>click</td>
+       <td>shadebutton_1</td>
+       <td></td>
+</tr>
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VCustomComponent[0]/VColorPickerGrid[0]/domChild[0]/domChild[1]/domChild[0]/domChild[6]</td>
+       <td>10,7</td>
+</tr>
+<tr>
+       <td>click</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[3]/VHorizontalLayout[0]/VOrderedLayout$Slot[0]/VButton[0]/domChild[0]/domChild[0]</td>
+       <td></td>
+</tr>
+
+<!-- change color of the last shade area -->
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_Sshadearea_16/domChild[1]</td>
+       <td>10,-65</td>
+</tr>
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_Srgb-gradient/domChild[1]/domChild[4]</td>
+       <td>36,93</td>
+</tr>
+<tr>
+       <td>click</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[3]/VHorizontalLayout[0]/VOrderedLayout$Slot[0]/VButton[0]/domChild[0]/domChild[0]</td>
+       <td></td>
+</tr>
+<tr>
+       <td>screenCapture</td>
+       <td></td>
+       <td>yellow-last-area</td>
+</tr>
+
+<!-- reset the color back to white -->
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::PID_Sshadearea_16/domChild[1]</td>
+       <td>19,-65</td>
+</tr>
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VCustomComponent[0]/VColorPickerGrid[0]/domChild[0]/domChild[1]/domChild[0]/domChild[4]</td>
+       <td>6,7</td>
+</tr>
+<tr>
+       <td>click</td>
+       <td>vaadin=runcomvaadintestscomponentscolorpickerColorPickerTest::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/VOrderedLayout$Slot[3]/VHorizontalLayout[0]/VOrderedLayout$Slot[0]/VButton[0]/domChild[0]</td>
+       <td></td>
+</tr>
+<tr>
+       <td>screenCapture</td>
+       <td></td>
+       <td>final</td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/uitest/src/com/vaadin/tests/components/colorpicker/ColorPickerTest.java b/uitest/src/com/vaadin/tests/components/colorpicker/ColorPickerTest.java
new file mode 100644 (file)
index 0000000..0f7e639
--- /dev/null
@@ -0,0 +1,492 @@
+package com.vaadin.tests.components.colorpicker;
+
+import java.awt.Graphics;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.imageio.ImageIO;
+
+import com.vaadin.data.Property.ValueChangeEvent;
+import com.vaadin.server.StreamResource;
+import com.vaadin.shared.ui.colorpicker.Color;
+import com.vaadin.shared.ui.label.ContentMode;
+import com.vaadin.tests.components.TestBase;
+import com.vaadin.ui.AbstractColorPicker;
+import com.vaadin.ui.Alignment;
+import com.vaadin.ui.CheckBox;
+import com.vaadin.ui.ColorPicker;
+import com.vaadin.ui.ColorPickerArea;
+import com.vaadin.ui.Embedded;
+import com.vaadin.ui.GridLayout;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.Panel;
+import com.vaadin.ui.VerticalLayout;
+import com.vaadin.ui.components.colorpicker.ColorChangeEvent;
+import com.vaadin.ui.components.colorpicker.ColorChangeListener;
+
+public class ColorPickerTest extends TestBase implements ColorChangeListener {
+
+    @Override
+    protected String getDescription() {
+        return "Vaadin 7 compatible ColorPicker";
+    }
+
+    @Override
+    protected Integer getTicketNumber() {
+        return 9201;
+    }
+
+    /** The foreground color. */
+    private Color foregroundColor = Color.BLACK; // The currently selected
+
+    /** The background color. */
+    private Color backgroundColor = Color.WHITE; // The currently selected
+
+    // The display box where the image is rendered
+    /** The display. */
+    private Embedded display;
+
+    private AbstractColorPicker colorpicker1;
+    private AbstractColorPicker colorpicker2;
+    private AbstractColorPicker colorpicker3;
+    private AbstractColorPicker colorpicker4;
+    private AbstractColorPicker colorpicker5;
+    private AbstractColorPicker colorpicker6;
+
+    private boolean rgbVisible = true;
+    private boolean hsvVisible = true;
+    private boolean swaVisible = true;
+    private boolean historyVisible = true;
+    private boolean txtfieldVisible = true;
+
+    private final CheckBox rgbBox = new CheckBox("RGB tab visible");
+    private final CheckBox hsvBox = new CheckBox("HSV tab visible");
+    private final CheckBox swaBox = new CheckBox("Swatches tab visible");
+    private final CheckBox hisBox = new CheckBox("History visible");
+    private final CheckBox txtBox = new CheckBox("CSS field visible");
+
+    /**
+     * This class is used to represent the preview of the color selection.
+     */
+    public class MyImageSource implements StreamResource.StreamSource {
+
+        /** The imagebuffer. */
+        private java.io.ByteArrayOutputStream imagebuffer = null;
+
+        /** The bg color. */
+        private final java.awt.Color bgColor;
+
+        /** The fg color. */
+        private final java.awt.Color fgColor;
+
+        /**
+         * Instantiates a new my image source.
+         * 
+         * @param fg
+         *            the foreground
+         * @param bg
+         *            the background
+         */
+        public MyImageSource(java.awt.Color fg, java.awt.Color bg) {
+            fgColor = fg;
+            bgColor = bg;
+        }
+
+        /* Must implement this method that returns the resource as a stream. */
+        @Override
+        public InputStream getStream() {
+
+            /* Create an image and draw something on it. */
+            BufferedImage image = new BufferedImage(270, 270,
+                    BufferedImage.TYPE_INT_RGB);
+            Graphics drawable = image.getGraphics();
+            drawable.setColor(bgColor);
+            drawable.fillRect(0, 0, 270, 270);
+            drawable.setColor(fgColor);
+            drawable.fillOval(25, 25, 220, 220);
+            drawable.setColor(java.awt.Color.blue);
+            drawable.drawRect(0, 0, 269, 269);
+            drawable.setColor(java.awt.Color.black);
+            drawable.drawString(
+                    "r=" + String.valueOf(fgColor.getRed()) + ",g="
+                            + String.valueOf(fgColor.getGreen()) + ",b="
+                            + String.valueOf(fgColor.getBlue()), 50, 100);
+            drawable.drawString(
+                    "r=" + String.valueOf(bgColor.getRed()) + ",g="
+                            + String.valueOf(bgColor.getGreen()) + ",b="
+                            + String.valueOf(bgColor.getBlue()), 5, 15);
+
+            try {
+                /* Write the image to a buffer. */
+                imagebuffer = new ByteArrayOutputStream();
+                ImageIO.write(image, "png", imagebuffer);
+
+                /* Return a stream from the buffer. */
+                return new ByteArrayInputStream(imagebuffer.toByteArray());
+            } catch (IOException e) {
+                return null;
+            }
+        }
+    }
+
+    private void setPopupVisibilities() {
+
+        rgbBox.setEnabled(!(rgbVisible && !hsvVisible && !swaVisible));
+        hsvBox.setEnabled(!(!rgbVisible && hsvVisible && !swaVisible));
+        swaBox.setEnabled(!(!rgbVisible && !hsvVisible && swaVisible));
+
+        colorpicker1.setRGBVisibility(rgbVisible);
+        colorpicker2.setRGBVisibility(rgbVisible);
+        colorpicker3.setRGBVisibility(rgbVisible);
+        colorpicker4.setRGBVisibility(rgbVisible);
+        colorpicker5.setRGBVisibility(rgbVisible);
+        colorpicker6.setRGBVisibility(rgbVisible);
+
+        colorpicker1.setHSVVisibility(hsvVisible);
+        colorpicker2.setHSVVisibility(hsvVisible);
+        colorpicker3.setHSVVisibility(hsvVisible);
+        colorpicker4.setHSVVisibility(hsvVisible);
+        colorpicker5.setHSVVisibility(hsvVisible);
+        colorpicker6.setHSVVisibility(hsvVisible);
+
+        colorpicker1.setSwatchesVisibility(swaVisible);
+        colorpicker2.setSwatchesVisibility(swaVisible);
+        colorpicker3.setSwatchesVisibility(swaVisible);
+        colorpicker4.setSwatchesVisibility(swaVisible);
+        colorpicker5.setSwatchesVisibility(swaVisible);
+        colorpicker6.setSwatchesVisibility(swaVisible);
+
+        colorpicker1.setHistoryVisibility(historyVisible);
+        colorpicker2.setHistoryVisibility(historyVisible);
+        colorpicker3.setHistoryVisibility(historyVisible);
+        colorpicker4.setHistoryVisibility(historyVisible);
+        colorpicker5.setHistoryVisibility(historyVisible);
+        colorpicker6.setHistoryVisibility(historyVisible);
+
+        colorpicker1.setTextfieldVisibility(txtfieldVisible);
+        colorpicker2.setTextfieldVisibility(txtfieldVisible);
+        colorpicker3.setTextfieldVisibility(txtfieldVisible);
+        colorpicker4.setTextfieldVisibility(txtfieldVisible);
+        colorpicker5.setTextfieldVisibility(txtfieldVisible);
+        colorpicker6.setTextfieldVisibility(txtfieldVisible);
+    }
+
+    @Override
+    protected void setup() {
+        getLayout().setWidth("1000px");
+        getLayout().setHeight(null);
+        getLayout().addStyleName("colorpicker-mainwindow-content");
+
+        // Create an instance of the preview and add it to the window
+        display = new Embedded("Color preview");
+        display.setWidth("270px");
+        display.setHeight("270px");
+
+        // Add the foreground and background colorpickers to a layout
+        HorizontalLayout mainLayout = new HorizontalLayout();
+        mainLayout.addStyleName("colorpicker-mainlayout");
+        mainLayout.setWidth("100%");
+        mainLayout.setHeight(null);
+        mainLayout.setMargin(true);
+        mainLayout.setSpacing(true);
+        getLayout().addComponent(mainLayout);
+
+        VerticalLayout layoutLeft = new VerticalLayout();
+        layoutLeft.setWidth("450px");
+        layoutLeft.setHeight(null);
+        layoutLeft.setSpacing(true);
+
+        GridLayout optLayout = new GridLayout(3, 2);
+        optLayout.setWidth("100%");
+        optLayout.setHeight(null);
+        optLayout.setMargin(true);
+        optLayout.setSpacing(true);
+
+        rgbBox.setValue(rgbVisible);
+        rgbBox.addValueChangeListener(new CheckBox.ValueChangeListener() {
+            @Override
+            public void valueChange(ValueChangeEvent event) {
+                rgbVisible = (Boolean) event.getProperty().getValue();
+                setPopupVisibilities();
+            }
+        });
+        rgbBox.setImmediate(true);
+        rgbBox.setId("rgbBox");
+        optLayout.addComponent(rgbBox);
+
+        hsvBox.setValue(hsvVisible);
+        hsvBox.addValueChangeListener(new CheckBox.ValueChangeListener() {
+            @Override
+            public void valueChange(ValueChangeEvent event) {
+                hsvVisible = (Boolean) event.getProperty().getValue();
+                setPopupVisibilities();
+            }
+        });
+        hsvBox.setImmediate(true);
+        hsvBox.setId("hsvBox");
+        optLayout.addComponent(hsvBox);
+
+        swaBox.setValue(swaVisible);
+        swaBox.addValueChangeListener(new CheckBox.ValueChangeListener() {
+            @Override
+            public void valueChange(ValueChangeEvent event) {
+                swaVisible = (Boolean) event.getProperty().getValue();
+                setPopupVisibilities();
+            }
+        });
+        swaBox.setImmediate(true);
+        swaBox.setId("swaBox");
+        optLayout.addComponent(swaBox);
+
+        hisBox.setValue(historyVisible);
+        hisBox.addValueChangeListener(new CheckBox.ValueChangeListener() {
+            @Override
+            public void valueChange(ValueChangeEvent event) {
+                historyVisible = (Boolean) event.getProperty().getValue();
+                setPopupVisibilities();
+            }
+        });
+        hisBox.setImmediate(true);
+        hisBox.setId("hisBox");
+        optLayout.addComponent(hisBox);
+
+        txtBox.setValue(txtfieldVisible);
+        txtBox.addValueChangeListener(new CheckBox.ValueChangeListener() {
+            @Override
+            public void valueChange(ValueChangeEvent event) {
+                txtfieldVisible = (Boolean) event.getProperty().getValue();
+                setPopupVisibilities();
+            }
+        });
+        txtBox.setImmediate(true);
+        txtBox.setId("txtBox");
+        optLayout.addComponent(txtBox);
+
+        Panel optPanel = new Panel("Customize the color picker popup window",
+                optLayout);
+        layoutLeft.addComponent(optPanel);
+
+        HorizontalLayout layout1 = createHorizontalLayout();
+
+        colorpicker1 = new ColorPicker("Foreground", foregroundColor);
+        colorpicker1.setHtmlContentAllowed(true);
+        colorpicker1.addColorChangeListener(this);
+        colorpicker1.setId("colorpicker1");
+        layout1.addComponent(colorpicker1);
+        layout1.setComponentAlignment(colorpicker1, Alignment.MIDDLE_CENTER);
+
+        colorpicker2 = new ColorPicker("Background", backgroundColor);
+        colorpicker2.addColorChangeListener(this);
+        colorpicker2.setId("colorpicker2");
+        layout1.addComponent(colorpicker2);
+        layout1.setComponentAlignment(colorpicker2, Alignment.MIDDLE_CENTER);
+
+        Panel panel1 = new Panel(
+                "Button-like colorpicker with current color and CSS code",
+                layout1);
+        layoutLeft.addComponent(panel1);
+
+        HorizontalLayout layout2 = createHorizontalLayout();
+
+        colorpicker3 = new ColorPicker("Foreground", foregroundColor);
+        colorpicker3.addColorChangeListener(this);
+        colorpicker3.setWidth("120px");
+        colorpicker3.setCaption("Foreground");
+        colorpicker3.setId("colorpicker3");
+        layout2.addComponent(colorpicker3);
+        layout2.setComponentAlignment(colorpicker3, Alignment.MIDDLE_CENTER);
+
+        colorpicker4 = new ColorPicker("Background", backgroundColor);
+        colorpicker4.addColorChangeListener(this);
+        colorpicker4.setWidth("120px");
+        colorpicker4.setCaption("Background");
+        colorpicker4.setId("colorpicker4");
+        layout2.addComponent(colorpicker4);
+        layout2.setComponentAlignment(colorpicker4, Alignment.MIDDLE_CENTER);
+
+        Panel panel2 = new Panel(
+                "Button-like colorpicker with current color and custom caption",
+                layout2);
+        layoutLeft.addComponent(panel2);
+
+        HorizontalLayout layout3 = createHorizontalLayout();
+
+        colorpicker5 = new ColorPickerArea("Foreground", foregroundColor);
+        colorpicker5.setCaption("Foreground");
+        colorpicker5.addColorChangeListener(this);
+        colorpicker5.setId("colorpicker5");
+        layout3.addComponent(colorpicker5);
+        layout3.setComponentAlignment(colorpicker5, Alignment.MIDDLE_CENTER);
+
+        colorpicker6 = new ColorPickerArea("Background", backgroundColor);
+        colorpicker6.setCaption("Background");
+        colorpicker6.setDefaultCaptionEnabled(false);
+        colorpicker6.addColorChangeListener(this);
+        colorpicker6.setId("colorpicker6");
+        layout3.addComponent(colorpicker6);
+        layout3.setComponentAlignment(colorpicker6, Alignment.MIDDLE_CENTER);
+
+        Panel panel3 = new Panel("Color area colorpicker with caption", layout3);
+        panel3.setWidth("100%");
+        panel3.setHeight(null);
+        layoutLeft.addComponent(panel3);
+
+        Label divider1 = new Label("<hr>", ContentMode.HTML);
+        layoutLeft.addComponent(divider1);
+
+        Label divider2 = new Label("<hr>", ContentMode.HTML);
+        layoutLeft.addComponent(divider2);
+
+        HorizontalLayout layout4 = createHorizontalLayout();
+
+        addShadeButton(new Color(Integer.parseInt("000000", 16)), layout4);
+        addShadeButton(new Color(Integer.parseInt("333333", 16)), layout4);
+        addShadeButton(new Color(Integer.parseInt("666666", 16)), layout4);
+        addShadeButton(new Color(Integer.parseInt("999999", 16)), layout4);
+        addShadeButton(new Color(Integer.parseInt("cccccc", 16)), layout4);
+        addShadeButton(new Color(Integer.parseInt("ffffff", 16)), layout4);
+
+        Panel panel4 = new Panel(
+                "Button-like colorpickers with disabled caption (no effect on fg/bg colors)",
+                layout4);
+        layoutLeft.addComponent(panel4);
+
+        HorizontalLayout layout5 = createHorizontalLayout();
+
+        addShadeArea(new Color(Integer.parseInt("000000", 16)), layout5);
+        addShadeArea(new Color(Integer.parseInt("111111", 16)), layout5);
+        addShadeArea(new Color(Integer.parseInt("222222", 16)), layout5);
+        addShadeArea(new Color(Integer.parseInt("333333", 16)), layout5);
+        addShadeArea(new Color(Integer.parseInt("444444", 16)), layout5);
+        addShadeArea(new Color(Integer.parseInt("555555", 16)), layout5);
+        addShadeArea(new Color(Integer.parseInt("666666", 16)), layout5);
+        addShadeArea(new Color(Integer.parseInt("777777", 16)), layout5);
+        addShadeArea(new Color(Integer.parseInt("888888", 16)), layout5);
+        addShadeArea(new Color(Integer.parseInt("999999", 16)), layout5);
+        addShadeArea(new Color(Integer.parseInt("aaaaaa", 16)), layout5);
+        addShadeArea(new Color(Integer.parseInt("bbbbbb", 16)), layout5);
+        addShadeArea(new Color(Integer.parseInt("cccccc", 16)), layout5);
+        addShadeArea(new Color(Integer.parseInt("dddddd", 16)), layout5);
+        addShadeArea(new Color(Integer.parseInt("eeeeee", 16)), layout5);
+        addShadeArea(new Color(Integer.parseInt("ffffff", 16)), layout5);
+
+        Panel panel5 = new Panel(
+                "Area colorpickers with no given caption (no effect on fg/bg colors)",
+                layout5);
+        layoutLeft.addComponent(panel5);
+
+        mainLayout.addComponent(layoutLeft);
+
+        mainLayout.addComponent(display);
+
+        updateDisplay(foregroundColor, backgroundColor);
+    }
+
+    private HorizontalLayout createHorizontalLayout() {
+        HorizontalLayout layout = new HorizontalLayout();
+        layout.setWidth("100%");
+        layout.setHeight(null);
+        layout.setMargin(true);
+        return layout;
+    }
+
+    private int shadeButtonCounter = 1;
+
+    private void addShadeButton(Color color, HorizontalLayout layout) {
+        AbstractColorPicker colorPicker = new ColorPicker(color.toString(),
+                color);
+        colorPicker.setDefaultCaptionEnabled(false);
+        colorPicker.setWidth("41px");
+        colorPicker.setId("shadebutton_" + shadeButtonCounter);
+        layout.addComponent(colorPicker);
+        layout.setComponentAlignment(colorPicker, Alignment.MIDDLE_CENTER);
+
+        ++shadeButtonCounter;
+    }
+
+    private int shadeAreaCounter = 1;
+
+    private void addShadeArea(Color color, HorizontalLayout layout) {
+        AbstractColorPicker colorPicker = new ColorPickerArea(color.toString(),
+                color);
+        colorPicker.setWidth("20px");
+        colorPicker.setHeight("20px");
+        colorPicker.setId("shadearea_" + shadeAreaCounter);
+        layout.addComponent(colorPicker);
+        layout.setComponentAlignment(colorPicker, Alignment.MIDDLE_CENTER);
+
+        ++shadeAreaCounter;
+    }
+
+    // This is called whenever a colorpicker popup is closed
+    /**
+     * Update display.
+     * 
+     * @param fg
+     *            the fg
+     * @param bg
+     *            the bg
+     */
+    public void updateDisplay(Color fg, Color bg) {
+        java.awt.Color awtFg = new java.awt.Color(fg.getRed(), fg.getGreen(),
+                fg.getBlue());
+        java.awt.Color awtBg = new java.awt.Color(bg.getRed(), bg.getGreen(),
+                bg.getBlue());
+        StreamResource.StreamSource imagesource = new MyImageSource(awtFg,
+                awtBg);
+
+        Date now = new Date();
+        SimpleDateFormat format = new SimpleDateFormat("hhmmss");
+
+        StreamResource imageresource = new StreamResource(imagesource,
+                "myimage" + format.format(now) + ".png");
+        imageresource.setCacheTime(0);
+
+        display.setSource(imageresource);
+    }
+
+    @Override
+    public void colorChanged(ColorChangeEvent event) {
+        if (event.getSource() == colorpicker1
+                || event.getSource() == colorpicker3
+                || event.getSource() == colorpicker5) {
+            foregroundColor = event.getColor();
+
+            if (event.getSource() != colorpicker1) {
+                colorpicker1.setColor(event.getColor());
+            }
+            if (event.getSource() != colorpicker3) {
+                colorpicker3.setColor(event.getColor());
+            }
+            if (event.getSource() != colorpicker5) {
+                colorpicker5.setColor(event.getColor());
+            }
+
+        } else if (event.getSource() == colorpicker2
+                || event.getSource() == colorpicker4
+                || event.getSource() == colorpicker6) {
+            backgroundColor = event.getColor();
+
+            if (event.getSource() != colorpicker2) {
+                colorpicker2.setColor(event.getColor());
+            }
+            if (event.getSource() != colorpicker4) {
+                colorpicker4.setColor(event.getColor());
+            }
+            if (event.getSource() != colorpicker6) {
+                colorpicker6.setColor(event.getColor());
+            }
+
+        } else {
+            return;
+        }
+
+        updateDisplay(foregroundColor, backgroundColor);
+    }
+}