[kune-commits] r1748 - in trunk: . img/icons img/icons/custom-iconic src/main/java/cc/kune/common/client/resources src/main/java/cc/kune/core/client/resources src/main/java/cc/kune/events/client/actions src/main/java/cc/kune/events/client/viewer src/main/java/cc/kune/events/shared src/main/java/org/waveprotocol/wave/client src/main/java/org/waveprotocol/wave/client/gadget src/main/java/org/waveprotocol/wave/client/gadget/renderer src/main/resources src/main/resources/icons src/main/webapp/others

Vicente J. Ruiz Jurado vjrj_ at ourproject.org
Thu Mar 8 16:05:47 CET 2012


Author: vjrj_
Date: 2012-03-08 16:05:45 +0100 (Thu, 08 Mar 2012)
New Revision: 1748

Added:
   trunk/img/icons/custom-iconic/
   trunk/img/icons/custom-iconic/barters.svg
   trunk/img/icons/custom-iconic/blog.svg
   trunk/img/icons/custom-iconic/chain-alternate.svg
   trunk/img/icons/custom-iconic/chain-close.svg
   trunk/img/icons/custom-iconic/chain-open.svg
   trunk/img/icons/custom-iconic/document_stroke-mod.svg
   trunk/img/icons/custom-iconic/lists.svg
   trunk/img/icons/custom-iconic/location.svg
   trunk/img/icons/custom-iconic/picture.svg
   trunk/img/icons/custom-iconic/reply.svg
   trunk/img/icons/custom-iconic/wiki_stroke-mod.svg
   trunk/src/main/java/org/waveprotocol/wave/client/gadget/
   trunk/src/main/java/org/waveprotocol/wave/client/gadget/renderer/
   trunk/src/main/java/org/waveprotocol/wave/client/gadget/renderer/Gadget.css
   trunk/src/main/java/org/waveprotocol/wave/client/gadget/renderer/GadgetWidget.java
   trunk/src/main/java/org/waveprotocol/wave/client/gadget/renderer/GadgetWidgetUi.java
   trunk/src/main/java/org/waveprotocol/wave/client/gadget/renderer/GadgetWidgetUi.ui.xml
   trunk/src/main/resources/icons/
   trunk/src/main/resources/icons/chain-close-black.png
   trunk/src/main/resources/icons/chain-close-white.png
   trunk/src/main/resources/icons/chain-closed-grey.png
   trunk/src/main/resources/icons/chain-open-black.png
   trunk/src/main/resources/icons/chain-open-grey.png
   trunk/src/main/resources/icons/chain-open-white.png
   trunk/src/main/resources/icons/picture-black.png
   trunk/src/main/resources/icons/picture-grey.png
   trunk/src/main/resources/icons/picture-white.png
   trunk/src/main/resources/icons/pref-black.png
   trunk/src/main/resources/icons/pref-grey.png
   trunk/src/main/resources/icons/pref-white.png
Removed:
   trunk/src/main/java/cc/kune/core/client/resources/pref-black.png
   trunk/src/main/java/cc/kune/core/client/resources/pref-grey.png
   trunk/src/main/java/cc/kune/core/client/resources/pref-white.png
Modified:
   trunk/.classpath
   trunk/pom.xml
   trunk/src/main/java/cc/kune/common/client/resources/CommonResources.java
   trunk/src/main/java/cc/kune/core/client/resources/CoreResources.java
   trunk/src/main/java/cc/kune/events/client/actions/EventAddMenuItem.java
   trunk/src/main/java/cc/kune/events/client/actions/EventsClientActions.java
   trunk/src/main/java/cc/kune/events/client/viewer/CalendarViewerPanel.java
   trunk/src/main/java/cc/kune/events/shared/EventsClientConversionUtil.java
   trunk/src/main/webapp/others/kune-client-actions.xml
Log:
Calendar improvements and fixes. Gadget width fix. New icons.

Modified: trunk/.classpath
===================================================================
--- trunk/.classpath	2012-03-07 13:08:17 UTC (rev 1747)
+++ trunk/.classpath	2012-03-08 15:05:45 UTC (rev 1748)
@@ -22,6 +22,7 @@
   <classpathentry kind="var" path="M2_REPO/org/waveprotocol/client/0.3.12/client-0.3.12.jar"/>
   <classpathentry kind="var" path="M2_REPO/org/waveprotocol/client-common/0.3.12/client-common-0.3.12.jar"/>
   <classpathentry kind="var" path="M2_REPO/org/waveprotocol/client-common-src/0.3.12/client-common-src-0.3.12.jar" sourcepath="M2_REPO/org/waveprotocol/client-common-src/0.3.12/client-common-src-0.3.12-sources.jar"/>
+  <classpathentry kind="var" path="M2_REPO/org/waveprotocol/client-scheduler/0.3.12/client-scheduler-0.3.12.jar"/>
   <classpathentry kind="var" path="M2_REPO/org/waveprotocol/client-scheduler-src/0.3.12/client-scheduler-src-0.3.12.jar" sourcepath="M2_REPO/org/waveprotocol/client-scheduler-src/0.3.12/client-scheduler-src-0.3.12-sources.jar"/>
   <classpathentry kind="var" path="M2_REPO/org/waveprotocol/client-src/0.3.12/client-src-0.3.12.jar" sourcepath="M2_REPO/org/waveprotocol/client-src/0.3.12/client-src-0.3.12-sources.jar"/>
   <classpathentry kind="var" path="M2_REPO/org/cobogw/gwt/cobogw/1.3.1/cobogw-1.3.1.jar"/>

Added: trunk/img/icons/custom-iconic/barters.svg
===================================================================
--- trunk/img/icons/custom-iconic/barters.svg	                        (rev 0)
+++ trunk/img/icons/custom-iconic/barters.svg	2012-03-08 15:05:45 UTC (rev 1748)
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363)  -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   x="0px"
+   y="0px"
+   width="32px"
+   height="32px"
+   viewBox="0 0 32 32"
+   enable-background="new 0 0 32 32"
+   xml:space="preserve"
+   id="svg2"
+   inkscape:version="0.48.2 r9819"
+   sodipodi:docname="barters.svg"><metadata
+   id="metadata20"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+   id="defs18" /><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="1078"
+   inkscape:window-height="919"
+   id="namedview16"
+   showgrid="false"
+   inkscape:zoom="1"
+   inkscape:cx="16"
+   inkscape:cy="16"
+   inkscape:window-x="0"
+   inkscape:window-y="972"
+   inkscape:window-maximized="0"
+   inkscape:current-layer="svg2" />
+<g
+   id="Layer_1">
+</g>
+<g
+   id="fullscreen_x5F_exit_x5F_32x32">
+	<g
+   id="g6">
+		<polygon
+   points="27.414,24.586 32,20 20,20 20,32 24.586,27.414 29.172,32 32,29.172 "
+   id="polygon8"
+   transform="matrix(-1,0,0,1,52,0)"
+   style="fill:#231f20" />
+		<polygon
+   points="7.414,4.586 2.875,0.043 0.047,2.871 4.586,7.414 0,12 12,12 12,0 "
+   id="polygon10"
+   transform="matrix(-1,0,0,-1,32,12)"
+   style="fill:#231f20" />
+		<polygon
+   points="0,20 4.586,24.586 0,29.172 2.828,32 7.414,27.414 12,32 12,20 "
+   id="polygon12"
+   style="fill:#231f20"
+   transform="matrix(1,0,0,-1,0,52)" />
+		<polygon
+   points="20,12 32,12 27.414,7.414 31.961,2.871 29.133,0.043 24.586,4.586 20,0 "
+   id="polygon14"
+   transform="translate(-20,0)"
+   style="fill:#231f20" />
+	</g>
+</g>
+</svg>
\ No newline at end of file

Added: trunk/img/icons/custom-iconic/blog.svg
===================================================================
--- trunk/img/icons/custom-iconic/blog.svg	                        (rev 0)
+++ trunk/img/icons/custom-iconic/blog.svg	2012-03-08 15:05:45 UTC (rev 1748)
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363)  -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="Layer_1"
+   x="0px"
+   y="0px"
+   width="32px"
+   height="32.023px"
+   viewBox="0 0 32 32.023"
+   style="enable-background:new 0 0 32 32.023;"
+   xml:space="preserve"
+   inkscape:version="0.48.2 r9819"
+   sodipodi:docname="blog.svg"><metadata
+   id="metadata9"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
+   id="defs7" /><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="1078"
+   inkscape:window-height="919"
+   id="namedview5"
+   showgrid="true"
+   inkscape:zoom="5.5975395"
+   inkscape:cx="16"
+   inkscape:cy="10.652002"
+   inkscape:window-x="0"
+   inkscape:window-y="972"
+   inkscape:window-maximized="0"
+   inkscape:current-layer="Layer_1"><inkscape:grid
+     type="xygrid"
+     id="grid3005" /></sodipodi:namedview>
+
+<path
+   style="fill:#231f20"
+   d="M 0 0 L 0 32.03125 L 32 32.03125 L 32 10 L 29.0625 7.0625 C 29.645385 8.2530281 29.96875 9.5850786 29.96875 11 C 29.96875 13.112978 29.25243 15.059907 28.03125 16.59375 C 28.028366 19.812263 28 28.03125 28 28.03125 L 4 28.03125 C 4 28.03125 4 6.5804999 4 3.9375 L 15.4375 3.9375 C 16.965886 2.7328831 18.902941 2.03125 21 2.03125 C 22.424999 2.03125 23.771869 2.3467994 24.96875 2.9375 L 22.03125 0 L 0 0 z "
+   id="path3" /><path
+   inkscape:connector-curvature="0"
+   style="fill:#231f20"
+   d="M 25.750001,13.115998 22.328,9.6899993 l 0,-3.6670003 -4.023,0 c 0,3.8454005 0,4.507 0,4.507 0,0.602 0.277,1.122 0.695,1.493 l 3.922001,3.921999 z"
+   id="path7"
+   sodipodi:nodetypes="ccccsccc" /><path
+   transform="matrix(3.277217,0,0,3.277217,-40.416133,-58.744255)"
+   d="m 21.480664,21.282667 a 2.7403326,2.7403326 0 1 1 -5.480665,0 2.7403326,2.7403326 0 1 1 5.480665,0 z"
+   sodipodi:ry="2.7403326"
+   sodipodi:rx="2.7403326"
+   sodipodi:cy="21.282667"
+   sodipodi:cx="18.740332"
+   id="path3005"
+   style="color:#000000;fill:none;stroke:#231f20;stroke-width:1.2205478;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+   sodipodi:type="arc" /></svg>
\ No newline at end of file

Added: trunk/img/icons/custom-iconic/chain-alternate.svg
===================================================================
--- trunk/img/icons/custom-iconic/chain-alternate.svg	                        (rev 0)
+++ trunk/img/icons/custom-iconic/chain-alternate.svg	2012-03-08 15:05:45 UTC (rev 1748)
@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363)  -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="Layer_1"
+   x="0px"
+   y="0px"
+   width="32"
+   height="32"
+   viewBox="0 0 32 32"
+   xml:space="preserve"
+   inkscape:version="0.48.2 r9819"
+   sodipodi:docname="chain-close.svg"><metadata
+   id="metadata9"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+   id="defs7" /><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="1078"
+   inkscape:window-height="919"
+   id="namedview5"
+   showgrid="true"
+   inkscape:zoom="11.313708"
+   inkscape:cx="-14.194054"
+   inkscape:cy="5.099319"
+   inkscape:window-x="0"
+   inkscape:window-y="972"
+   inkscape:window-maximized="0"
+   inkscape:current-layer="Layer_1"><inkscape:grid
+     type="xygrid"
+     id="grid2986"
+     empspacing="5"
+     visible="true"
+     enabled="true"
+     snapvisiblegridlinesonly="true" /></sodipodi:namedview>
+
+<path
+   sodipodi:nodetypes="sssssssssssccs"
+   inkscape:connector-curvature="0"
+   id="path2994"
+   d="m 22,18 -12,0 c -1.105,0 -2,0.896 -2,2 l 0,4.084746 C 8,28.501746 11.582,32 16,32 c 4.418,0 8,-3.498254 8,-7.915254 L 24,20 c 0,-1.104 -0.895,-2 -2,-2 z m -2,6.084746 c 0,0 0,3.915254 -4,3.915254 -4,0 -4,-3.656755 -4,-4 0,-0.08475 0,-0.08475 0,-2 l 8,0 c 0,0 0,1.084746 0,2.084746 z"
+   style="fill:#231f20" /><path
+   style="fill:#231f20"
+   d="M 22,14 10,14 C 8.895,14 8,13.104 8,12 L 8,7.915254 C 8,3.498254 11.582,0 16,0 c 4.418,0 8,3.498254 8,7.915254 L 24,12 c 0,1.104 -0.895,2 -2,2 z M 20,7.915254 C 20,7.915254 20,4 16,4 c -4,0 -4,3.656755 -4,4 0,0.08475 0,0.08475 0,2 l 8,0 c 0,0 0,-1.084746 0,-2.084746 z"
+   id="path2996"
+   inkscape:connector-curvature="0"
+   sodipodi:nodetypes="sssssssssssccs" /><rect
+   style="color:#000000;fill:#231f20;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:24;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+   id="rect3017"
+   width="4"
+   height="12"
+   x="14"
+   y="10" /><path
+   sodipodi:type="arc"
+   style="color:#000000;fill:#231f20;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:24;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+   id="path3019"
+   sodipodi:cx="17"
+   sodipodi:cy="10"
+   sodipodi:rx="2"
+   sodipodi:ry="2"
+   d="m 19,10 a 2,2 0 1 1 -4,0 2,2 0 1 1 4,0 z"
+   transform="translate(-1,0)" /><path
+   transform="translate(-1,12)"
+   d="m 19,10 a 2,2 0 1 1 -4,0 2,2 0 1 1 4,0 z"
+   sodipodi:ry="2"
+   sodipodi:rx="2"
+   sodipodi:cy="10"
+   sodipodi:cx="17"
+   id="path3022"
+   style="color:#000000;fill:#231f20;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:24;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+   sodipodi:type="arc" /><rect
+   style="color:#000000;fill:none;stroke:#231f20;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+   id="rect3813"
+   width="12"
+   height="10"
+   x="-13"
+   y="2"
+   ry="1"
+   rx="0.5" /><rect
+   rx="0.5"
+   ry="1"
+   y="20"
+   x="-13"
+   height="10"
+   width="12"
+   id="rect3817"
+   style="color:#000000;fill:none;stroke:#231f20;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /><rect
+   y="10"
+   x="-9"
+   height="12"
+   width="4"
+   id="rect3819"
+   style="color:#000000;fill:#231f20;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:24;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /><path
+   transform="translate(-24,0)"
+   d="m 19,10 a 2,2 0 1 1 -4,0 2,2 0 1 1 4,0 z"
+   sodipodi:ry="2"
+   sodipodi:rx="2"
+   sodipodi:cy="10"
+   sodipodi:cx="17"
+   id="path3821"
+   style="color:#000000;fill:#231f20;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:24;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+   sodipodi:type="arc" /><path
+   sodipodi:type="arc"
+   style="color:#000000;fill:#231f20;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:24;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+   id="path3823"
+   sodipodi:cx="17"
+   sodipodi:cy="10"
+   sodipodi:rx="2"
+   sodipodi:ry="2"
+   d="m 19,10 a 2,2 0 1 1 -4,0 2,2 0 1 1 4,0 z"
+   transform="translate(-24,12)" /><rect
+   rx="0.5"
+   ry="1"
+   y="2"
+   x="-33"
+   height="10"
+   width="12"
+   id="rect3825"
+   style="color:#000000;fill:none;stroke:#231f20;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /><rect
+   style="color:#000000;fill:none;stroke:#231f20;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:2;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+   id="rect3827"
+   width="12"
+   height="10"
+   x="-33"
+   y="20"
+   ry="1"
+   rx="0.5" /><rect
+   style="color:#000000;fill:#231f20;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:24;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+   id="rect3829"
+   width="4"
+   height="12"
+   x="-49"
+   y="10" /><path
+   sodipodi:type="arc"
+   style="color:#000000;fill:#231f20;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:24;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+   id="path3831"
+   sodipodi:cx="17"
+   sodipodi:cy="10"
+   sodipodi:rx="2"
+   sodipodi:ry="2"
+   d="m 19,10 a 2,2 0 1 1 -4,0 2,2 0 1 1 4,0 z"
+   transform="translate(-64,0)" /><path
+   transform="translate(-64,12)"
+   d="m 19,10 a 2,2 0 1 1 -4,0 2,2 0 1 1 4,0 z"
+   sodipodi:ry="2"
+   sodipodi:rx="2"
+   sodipodi:cy="10"
+   sodipodi:cx="17"
+   id="path3833"
+   style="color:#000000;fill:#231f20;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:24;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+   sodipodi:type="arc" /></svg>
\ No newline at end of file

Added: trunk/img/icons/custom-iconic/chain-close.svg
===================================================================
--- trunk/img/icons/custom-iconic/chain-close.svg	                        (rev 0)
+++ trunk/img/icons/custom-iconic/chain-close.svg	2012-03-08 15:05:45 UTC (rev 1748)
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363)  -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="Layer_1"
+   x="0px"
+   y="0px"
+   width="32"
+   height="32"
+   viewBox="0 0 32 32"
+   xml:space="preserve"
+   inkscape:version="0.48.2 r9819"
+   sodipodi:docname="chain-close.svg"><metadata
+   id="metadata9"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
+   id="defs7" /><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="1078"
+   inkscape:window-height="919"
+   id="namedview5"
+   showgrid="true"
+   inkscape:zoom="11.313708"
+   inkscape:cx="9.6518354"
+   inkscape:cy="14.221054"
+   inkscape:window-x="0"
+   inkscape:window-y="972"
+   inkscape:window-maximized="0"
+   inkscape:current-layer="Layer_1"><inkscape:grid
+     type="xygrid"
+     id="grid2986"
+     empspacing="5"
+     visible="true"
+     enabled="true"
+     snapvisiblegridlinesonly="true" /></sodipodi:namedview>
+
+<path
+   style="fill:#231f20"
+   d="M 16 0 C 11.582 -5.9211895e-16 8 3.48925 8 7.90625 L 8 12 C 8 13.104 8.895 14 10 14 L 14 14 L 14 18 L 10 18 C 8.895 18 8 18.896 8 20 L 8 24.09375 C 8 28.51075 11.582 32 16 32 C 20.418 32 24 28.51075 24 24.09375 L 24 20 C 24 18.896 23.105 18 22 18 L 18 18 L 18 14 L 22 14 C 23.105 14 24 13.104 24 12 L 24 7.90625 C 24 3.48925 20.418 0 16 0 z M 16 4 C 20 4 20 7.90625 20 7.90625 L 20 10 L 18 10 C 18 8.8954305 17.104569 8 16 8 C 14.895431 8 14 8.8954305 14 10 L 12 10 L 12 8 C 12 7.656755 12 4 16 4 z M 12 22 L 14 22 C 14 23.104569 14.895431 24 16 24 C 17.104569 24 18 23.104569 18 22 L 20 22 L 20 24.09375 C 20 24.09375 20 28 16 28 C 12 28 12 24.343245 12 24 L 12 22 z "
+   id="path2994" /></svg>
\ No newline at end of file

Added: trunk/img/icons/custom-iconic/chain-open.svg
===================================================================
--- trunk/img/icons/custom-iconic/chain-open.svg	                        (rev 0)
+++ trunk/img/icons/custom-iconic/chain-open.svg	2012-03-08 15:05:45 UTC (rev 1748)
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363)  -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="Layer_1"
+   x="0px"
+   y="0px"
+   width="32"
+   height="32"
+   viewBox="0 0 32 32"
+   xml:space="preserve"
+   inkscape:version="0.48.2 r9819"
+   sodipodi:docname="chain-open.svg"><metadata
+   id="metadata9"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
+   id="defs7" /><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="1078"
+   inkscape:window-height="919"
+   id="namedview5"
+   showgrid="true"
+   inkscape:zoom="22.40625"
+   inkscape:cx="16"
+   inkscape:cy="16"
+   inkscape:window-x="0"
+   inkscape:window-y="972"
+   inkscape:window-maximized="0"
+   inkscape:current-layer="Layer_1"><inkscape:grid
+     type="xygrid"
+     id="grid2986"
+     empspacing="5"
+     visible="true"
+     enabled="true"
+     snapvisiblegridlinesonly="true" /></sodipodi:namedview>
+
+<path
+   sodipodi:nodetypes="sssssssssssccs"
+   inkscape:connector-curvature="0"
+   id="path2994"
+   d="m 22,18 -12,0 c -1.105,0 -2,0.896 -2,2 l 0,4.084746 C 8,28.501746 11.582,32 16,32 c 4.418,0 8,-3.498254 8,-7.915254 L 24,20 c 0,-1.104 -0.895,-2 -2,-2 z m -2,6.084746 c 0,0 0,3.915254 -4,3.915254 -4,0 -4,-3.656755 -4,-4 0,-0.08475 0,-0.08475 0,-2 l 8,0 c 0,0 0,1.084746 0,2.084746 z"
+   style="fill:#231f20" /><path
+   style="fill:#231f20"
+   d="M 22,14 10,14 C 8.895,14 8,13.104 8,12 L 8,7.915254 C 8,3.498254 11.582,0 16,0 c 4.418,0 8,3.498254 8,7.915254 L 24,12 c 0,1.104 -0.895,2 -2,2 z M 20,7.915254 C 20,7.915254 20,4 16,4 c -4,0 -4,3.656755 -4,4 0,0.08475 0,0.08475 0,2 l 8,0 c 0,0 0,-1.084746 0,-2.084746 z"
+   id="path2996"
+   inkscape:connector-curvature="0"
+   sodipodi:nodetypes="sssssssssssccs" /></svg>
\ No newline at end of file

Added: trunk/img/icons/custom-iconic/document_stroke-mod.svg
===================================================================
--- trunk/img/icons/custom-iconic/document_stroke-mod.svg	                        (rev 0)
+++ trunk/img/icons/custom-iconic/document_stroke-mod.svg	2012-03-08 15:05:45 UTC (rev 1748)
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363)  -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="Layer_1"
+   x="0px"
+   y="0px"
+   width="32px"
+   height="32.023px"
+   viewBox="0 0 32 32.023"
+   style="enable-background:new 0 0 32 32.023;"
+   xml:space="preserve"
+   inkscape:version="0.48.2 r9819"
+   sodipodi:docname="document_stroke-mod.svg"><metadata
+   id="metadata9"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+   id="defs7" /><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="1078"
+   inkscape:window-height="919"
+   id="namedview5"
+   showgrid="true"
+   inkscape:zoom="20.844669"
+   inkscape:cx="16"
+   inkscape:cy="16.011499"
+   inkscape:window-x="0"
+   inkscape:window-y="972"
+   inkscape:window-maximized="0"
+   inkscape:current-layer="Layer_1"><inkscape:grid
+     type="xygrid"
+     id="grid3005" /></sodipodi:namedview>
+<path
+   style="fill:#231f20"
+   d="M 22.016,0 0,0 -5.9466538e-7,32.023 C 13,32.023 19,32.023 32.000001,32.023 L 32,10.008 z M 28,28.023 c -10.907572,0 -10.783442,0 -24.0000004,0 0,0 0.006,-21.441 0.006,-24.084 2.1,0 7.807,0 12.006,0 l 0,12.01 12.01,0 C 28.021,18.682 28,28.023 28,28.023 z M 20.016,11.945 c 0,-1.543 0,-5.918 0,-8.006 1.615,1.619 6.402,6.404 8.006,8.006 -2.047,0 -6.454,0 -8.006,0 z"
+   id="path3"
+   inkscape:connector-curvature="0"
+   sodipodi:nodetypes="ccccccccccccccccc" />
+</svg>
\ No newline at end of file

Added: trunk/img/icons/custom-iconic/lists.svg
===================================================================
--- trunk/img/icons/custom-iconic/lists.svg	                        (rev 0)
+++ trunk/img/icons/custom-iconic/lists.svg	2012-03-08 15:05:45 UTC (rev 1748)
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363)  -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="Layer_1"
+   x="0px"
+   y="0px"
+   width="32px"
+   height="32px"
+   viewBox="0 0 32 32"
+   style="enable-background:new 0 0 32 32;"
+   xml:space="preserve"
+   inkscape:version="0.48.2 r9819"
+   sodipodi:docname="lists.svg"><metadata
+   id="metadata19"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
+   id="defs17">
+	
+	
+	
+	
+	
+</defs><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="1078"
+   inkscape:window-height="919"
+   id="namedview15"
+   showgrid="true"
+   inkscape:zoom="11.203125"
+   inkscape:cx="17.097985"
+   inkscape:cy="13.124115"
+   inkscape:window-x="0"
+   inkscape:window-y="972"
+   inkscape:window-maximized="0"
+   inkscape:current-layer="Layer_1"><inkscape:grid
+     type="xygrid"
+     id="grid3015" /></sodipodi:namedview>
+<g
+   id="g3000"><polygon
+     transform="matrix(-1,0,0,1,32,0)"
+     style="fill:#231f20"
+     points="32,0 32,4 32,4 0,4 0,0 "
+     id="polygon5" /><polygon
+     transform="matrix(-1.4,0,0,1,32,0)"
+     style="fill:#231f20"
+     points="20,12 0,12 0,8 20,8 20,12 "
+     id="polygon7" /><polygon
+     transform="matrix(-0.875,0,0,1,32,0)"
+     style="fill:#231f20"
+     points="32,24 0,24 0,20 32,20 32,24 "
+     id="polygon9" /><polygon
+     transform="matrix(-1,0,0,1,32,0)"
+     style="fill:#231f20"
+     points="24,28 24,32 24,32 0,32 0,28 "
+     id="polygon11" /></g>
+</svg>
\ No newline at end of file

Added: trunk/img/icons/custom-iconic/location.svg
===================================================================
--- trunk/img/icons/custom-iconic/location.svg	                        (rev 0)
+++ trunk/img/icons/custom-iconic/location.svg	2012-03-08 15:05:45 UTC (rev 1748)
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363)  -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="Layer_1"
+   x="0px"
+   y="0px"
+   width="32"
+   height="32"
+   viewBox="0 0 31.999999 32"
+   xml:space="preserve"
+   inkscape:version="0.48.2 r9819"
+   sodipodi:docname="link.svg"><metadata
+   id="metadata13"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
+   id="defs11" /><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="1078"
+   inkscape:window-height="919"
+   id="namedview9"
+   showgrid="true"
+   inkscape:zoom="22.40625"
+   inkscape:cx="16"
+   inkscape:cy="16"
+   inkscape:window-x="0"
+   inkscape:window-y="972"
+   inkscape:window-maximized="0"
+   inkscape:current-layer="Layer_1"><inkscape:grid
+     type="xygrid"
+     id="grid3009"
+     empspacing="5"
+     visible="true"
+     enabled="true"
+     snapvisiblegridlinesonly="true" /></sodipodi:namedview>
+<g
+   id="g3"
+   transform="translate(0,2)">
+	<path
+   style="fill:#231f20"
+   d="m 16.024,4.007 c 3.32,0 6.019,2.7 6.019,6.017 0,1.894 -0.867,3.636 -2.379,4.773 C 18.042084,16.123195 17,17.96875 16,19.53125 15,17.96875 14,16 12.39,14.797 10.875,13.66 10.008,11.918 10.008,10.024 c 0,-3.317 2.699,-6.017 6.016,-6.017 M 16.024,0 C 10.488,0 6,4.488 6,10.024 c 0,3.28 1.547,6.147 3.977,7.976 1.121,0.844 3.1235,4 3.57325,5 C 14,24 15.331475,26.335992 16,28 c 0.734517,-1.69468 1.812,-4 2.406,-5 0.594,-1 2.547,-4.156 3.668,-5 2.43,-1.829 3.977,-4.696 3.977,-7.976 C 26.051,4.488 21.562,0 16.024,0 z"
+   id="path5"
+   inkscape:connector-curvature="0"
+   sodipodi:nodetypes="cscccsccsczczcsc" />
+	
+</g>
+</svg>
\ No newline at end of file

Added: trunk/img/icons/custom-iconic/picture.svg
===================================================================
--- trunk/img/icons/custom-iconic/picture.svg	                        (rev 0)
+++ trunk/img/icons/custom-iconic/picture.svg	2012-03-08 15:05:45 UTC (rev 1748)
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363)  -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="Layer_1"
+   x="0px"
+   y="0px"
+   width="32px"
+   height="32.023px"
+   viewBox="0 0 32 32.023"
+   style="enable-background:new 0 0 32 32.023;"
+   xml:space="preserve"
+   inkscape:version="0.48.2 r9819"
+   sodipodi:docname="picture.svg"
+   inkscape:export-filename="/tmp/picture-black.png"
+   inkscape:export-xdpi="45"
+   inkscape:export-ydpi="45"><metadata
+   id="metadata9"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+   id="defs7" /><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="1078"
+   inkscape:window-height="919"
+   id="namedview5"
+   showgrid="true"
+   inkscape:zoom="11.195079"
+   inkscape:cx="15.028131"
+   inkscape:cy="14.592141"
+   inkscape:window-x="0"
+   inkscape:window-y="972"
+   inkscape:window-maximized="0"
+   inkscape:current-layer="Layer_1"><inkscape:grid
+     type="xygrid"
+     id="grid3005" /></sodipodi:namedview>
+
+<path
+   style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#231f20;fill-opacity:1;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"
+   d="M 1.8125 0.03125 A 2.0001999 2.0001999 0 0 0 0 2.03125 L 0 30.03125 A 2.0001999 2.0001999 0 0 0 2 32.03125 L 30 32.03125 A 2.0001999 2.0001999 0 0 0 32 30.03125 L 32 2.03125 A 2.0001999 2.0001999 0 0 0 30 0.03125 L 2 0.03125 A 2.0001999 2.0001999 0 0 0 1.8125 0.03125 z M 4 4.03125 L 28 4.03125 L 28 14.03125 C 28 14.03125 26.974092 14.034149 25.65625 14.0625 C 25.581508 12.992164 25.258639 11.966366 24.71875 11.03125 C 22.797598 7.7037175 18.483783 6.5475981 15.15625 8.46875 C 12.362378 10.081793 11.133571 13.357304 11.9375 16.34375 C 9.6691205 17.30296 7.6457826 18.345626 4 19 L 4 4.03125 z M 18.875 11.5 C 19.832223 11.564262 20.72483 12.121629 21.25 13.03125 C 21.469627 13.411655 21.613234 13.817196 21.65625 14.25 C 19.617583 14.395505 17.468728 14.646833 15.71875 15.0625 C 15.483749 13.844461 16.007763 12.569329 17.15625 11.90625 C 17.702023 11.591148 18.300666 11.461443 18.875 11.5 z M 28 18.03125 L 28 28.03125 L 4 28.03125 L 4 23.03125 A 2.0001999 2.0001999 0 0 0 4.34375 23 C 10.699207 21.940757 12.956538 19.854616 16.5 18.96875 C 19.891283 18.120929 28 18.03125 28 18.03125 z "
+   id="rect3004" /></svg>
\ No newline at end of file

Added: trunk/img/icons/custom-iconic/reply.svg
===================================================================
--- trunk/img/icons/custom-iconic/reply.svg	                        (rev 0)
+++ trunk/img/icons/custom-iconic/reply.svg	2012-03-08 15:05:45 UTC (rev 1748)
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363)  -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="Layer_1"
+   x="0px"
+   y="0px"
+   width="32px"
+   height="31.985px"
+   viewBox="0 0 32 31.985"
+   style="enable-background:new 0 0 32 31.985;"
+   xml:space="preserve"
+   inkscape:version="0.48.2 r9819"
+   sodipodi:docname="reply.svg"><metadata
+   id="metadata13"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
+   id="defs11" /><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="1078"
+   inkscape:window-height="919"
+   id="namedview9"
+   showgrid="false"
+   inkscape:zoom="20.869432"
+   inkscape:cx="16"
+   inkscape:cy="15.9925"
+   inkscape:window-x="0"
+   inkscape:window-y="972"
+   inkscape:window-maximized="0"
+   inkscape:current-layer="Layer_1" />
+<g
+   id="g3"
+   transform="matrix(-1,0,0,-1,31.98626,31.985377)">
+	<path
+   style="fill:#231f20"
+   d="M 28.156,3.72 C 23.191259,-1.3561979 13.995754,-1.2081334 9.2406719,4.087821 7.4768618,5.8148852 5.7546981,7.5837628 4,9.32 4,7.547 4,5.774 4,4.001 c -1.3333333,0 -2.6666667,0 -4,0 0,4 0,8 0,12 4,0 8,0 12,0 0,-1.333333 0,-2.666667 0,-4 -1.675,0 -3.35,0 -5.025,0 2.3669706,-2.2196714 4.439909,-4.7779711 7.05347,-6.7189446 4.151835,-2.59042 10.352865,-1.2404923 12.749396,3.1216783 2.157583,3.6209673 1.362664,8.6925193 -1.835853,11.4513573 -1.001785,0.993873 -3.033417,2.323732 -2.233621,2.558459 4.283435,-0.590885 8.428779,-3.795214 9.173842,-8.201008 C 32.416151,10.382846 30.852862,6.4282488 28.156,3.72 z"
+   id="path5"
+   inkscape:connector-curvature="0"
+   sodipodi:nodetypes="ccccccccccccccc" />
+	
+		
+</g>
+</svg>
\ No newline at end of file

Added: trunk/img/icons/custom-iconic/wiki_stroke-mod.svg
===================================================================
--- trunk/img/icons/custom-iconic/wiki_stroke-mod.svg	                        (rev 0)
+++ trunk/img/icons/custom-iconic/wiki_stroke-mod.svg	2012-03-08 15:05:45 UTC (rev 1748)
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363)  -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="Layer_1"
+   x="0px"
+   y="0px"
+   width="32px"
+   height="32.023px"
+   viewBox="0 0 32 32.023"
+   style="enable-background:new 0 0 32 32.023;"
+   xml:space="preserve"
+   inkscape:version="0.48.2 r9819"
+   sodipodi:docname="wiki_stroke-mod.svg"><metadata
+   id="metadata9"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+   id="defs7" /><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="1078"
+   inkscape:window-height="919"
+   id="namedview5"
+   showgrid="true"
+   inkscape:zoom="4"
+   inkscape:cx="16"
+   inkscape:cy="13.87808"
+   inkscape:window-x="0"
+   inkscape:window-y="972"
+   inkscape:window-maximized="0"
+   inkscape:current-layer="Layer_1"><inkscape:grid
+     type="xygrid"
+     id="grid3005" /></sodipodi:namedview>
+<path
+   style="fill:#231f20"
+   d="m 23,0.02299881 c 0,0 -12,0 -19.0000004,-0.02299881 C 3.9999996,8.0229991 -5.9466538e-7,32.023 -5.9466538e-7,32.023 13,32.023 21.5,32.023 28,32.023 31.000001,20.023 31.000001,18.022999 31.984,10.030999 28,7.0229991 23,0.02299881 23,0.02299881 z M 25,28.023 c -10.907572,0 -13.911982,0.09595 -20.5202614,0.09595 C 5.9999996,21.023 6.9999997,12.022999 7.4797387,4.022999 c 2.1000003,0 5.3212613,0 9.5202613,0 0,0 0,9 -0.988,11.926001 C 21,16.517683 27,16.022999 27,16.022999 26.999,18.755999 25,28.023 25,28.023 z M 20.431765,12.687182 C 21.415765,8.7651811 21,6.110999 21,4.022999 c 1,2 5.418,6.320001 7.022,7.922001 -3.022,1.077999 -6.038235,0.742182 -7.590235,0.742182 z"
+   id="path3"
+   inkscape:connector-curvature="0"
+   sodipodi:nodetypes="ccccccccccccccccc" />
+</svg>
\ No newline at end of file

Modified: trunk/pom.xml
===================================================================
--- trunk/pom.xml	2012-03-07 13:08:17 UTC (rev 1747)
+++ trunk/pom.xml	2012-03-08 15:05:45 UTC (rev 1748)
@@ -638,6 +638,11 @@
     </dependency>
     <dependency>
       <groupId>org.waveprotocol</groupId>
+      <artifactId>client-scheduler</artifactId>
+      <version>${wiab.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.waveprotocol</groupId>
       <artifactId>client-scheduler-src</artifactId>
       <version>${wiab.version}</version>
     </dependency>

Modified: trunk/src/main/java/cc/kune/common/client/resources/CommonResources.java
===================================================================
--- trunk/src/main/java/cc/kune/common/client/resources/CommonResources.java	2012-03-07 13:08:17 UTC (rev 1747)
+++ trunk/src/main/java/cc/kune/common/client/resources/CommonResources.java	2012-03-08 15:05:45 UTC (rev 1748)
@@ -71,7 +71,34 @@
 
   @Source("cross.png")
   ImageResource redCross();
+  
+  @Source("icons/chain-close-black.png")
+  ImageResource chainCloseBlack();
 
+  @Source("icons/chain-close-white.png")
+  ImageResource chainCloseWhite();
+
+  @Source("icons/chain-closed-grey.png")
+  ImageResource chainClosedGrey();
+
+  @Source("icons/chain-open-black.png")
+  ImageResource chainOpenBlack();
+
+  @Source("icons/chain-open-grey.png")
+  ImageResource chainOpenGrey();
+
+  @Source("icons/chain-open-white.png")
+  ImageResource chainOpenWhite();
+
+  @Source("icons/picture-black.png")
+  ImageResource pictureBlack();
+
+  @Source("icons/picture-grey.png")
+  ImageResource pictureGrey();
+
+  @Source("icons/picture-white.png")
+  ImageResource pictureWhite();
+  
   @Source("unchecked.png")
   ImageResource unChecked();
 }

Modified: trunk/src/main/java/cc/kune/core/client/resources/CoreResources.java
===================================================================
--- trunk/src/main/java/cc/kune/core/client/resources/CoreResources.java	2012-03-07 13:08:17 UTC (rev 1747)
+++ trunk/src/main/java/cc/kune/core/client/resources/CoreResources.java	2012-03-08 15:05:45 UTC (rev 1748)
@@ -19,11 +19,12 @@
  */
 package cc.kune.core.client.resources;
 
-import com.google.gwt.resources.client.ClientBundle;
+import cc.kune.common.client.resources.CommonResources;
+
 import com.google.gwt.resources.client.CssResource;
 import com.google.gwt.resources.client.ImageResource;
 
-public interface CoreResources extends ClientBundle {
+public interface CoreResources extends CommonResources {
   public interface Style extends CssResource {
     String loadingSpiner();
   }
@@ -442,16 +443,16 @@
   @Source("person-def.png")
   ImageResource personDef();
 
-  @Source("pref-black.png")
+  @Source("icons/pref-black.png")
   ImageResource prefBlack();
 
-  @Source("pref-grey.png")
+  @Source("icons/pref-grey.png")
   ImageResource prefGrey();
 
   @Source("prefs.png")
   ImageResource prefs();
 
-  @Source("pref-white.png")
+  @Source("icons/pref-white.png")
   ImageResource prefWhite();
 
   @Source("question32.png")
@@ -547,4 +548,6 @@
   @Source("wave-icon.png")
   ImageResource waveIcon();
 
+
+
 }

Deleted: trunk/src/main/java/cc/kune/core/client/resources/pref-black.png
===================================================================
(Binary files differ)

Deleted: trunk/src/main/java/cc/kune/core/client/resources/pref-grey.png
===================================================================
(Binary files differ)

Deleted: trunk/src/main/java/cc/kune/core/client/resources/pref-white.png
===================================================================
(Binary files differ)

Modified: trunk/src/main/java/cc/kune/events/client/actions/EventAddMenuItem.java
===================================================================
--- trunk/src/main/java/cc/kune/events/client/actions/EventAddMenuItem.java	2012-03-07 13:08:17 UTC (rev 1747)
+++ trunk/src/main/java/cc/kune/events/client/actions/EventAddMenuItem.java	2012-03-08 15:05:45 UTC (rev 1748)
@@ -75,7 +75,7 @@
               contService.get().addNewContentWithGadgetAndState(session.getUserHash(),
                   session.getContainerState().getStateToken(),
                   EventsConstants.TYPE_MEETING_DEF_GADGETNAME, EventsConstants.TYPE_MEETING, title,
-                  title, EventsClientConversionUtil.toMap(appt), new AsyncCallbackSimple<StateContentDTO>() {
+                  "", EventsClientConversionUtil.toMap(appt), new AsyncCallbackSimple<StateContentDTO>() {
                     @Override
                     public void onFailure(final Throwable caught) {
                       super.onFailure(caught);

Modified: trunk/src/main/java/cc/kune/events/client/actions/EventsClientActions.java
===================================================================
--- trunk/src/main/java/cc/kune/events/client/actions/EventsClientActions.java	2012-03-07 13:08:17 UTC (rev 1747)
+++ trunk/src/main/java/cc/kune/events/client/actions/EventsClientActions.java	2012-03-08 15:05:45 UTC (rev 1748)
@@ -60,8 +60,8 @@
       final Provider<Calendar7DaysViewSelectBtn> cal7DaysBtn,
       final Provider<CalendarMonthViewSelectBtn> calMonthBtn,
       final Provider<EventAddMenuItem> eventAddMenuItem,
+      final Provider<EventOpenMenuItem> eventOpenMenuItem, final Provider<CalendarGoPrevBtn> calPrevBtn,
       final Provider<EventRemoveMenuItem> eventRemoveMenuItem,
-      final Provider<EventOpenMenuItem> eventOpenMenuItem, final Provider<CalendarGoPrevBtn> calPrevBtn,
       final Provider<CalendarGoNextBtn> calNextBtn, final CalendarOnOverMenu onOverMenu,
       final Provider<CalendarGoTodayBtn> goToday, final Provider<RefreshContentMenuItem> refresh) {
     super(session, stateManager, i18n, registry);
@@ -82,8 +82,8 @@
     // On over calendar menu
     actionsRegistry.addAction(ActionGroups.TOOLBAR, onOverMenu, containers);
 
+    eventOpenMenuItem.get();
     eventAddMenuItem.get();
-    eventOpenMenuItem.get();
     eventRemoveMenuItem.get();
 
     // For now, commented:

Modified: trunk/src/main/java/cc/kune/events/client/viewer/CalendarViewerPanel.java
===================================================================
--- trunk/src/main/java/cc/kune/events/client/viewer/CalendarViewerPanel.java	2012-03-07 13:08:17 UTC (rev 1747)
+++ trunk/src/main/java/cc/kune/events/client/viewer/CalendarViewerPanel.java	2012-03-08 15:05:45 UTC (rev 1748)
@@ -22,6 +22,7 @@
 import com.bradrydzewski.gwt.calendar.client.Appointment;
 import com.bradrydzewski.gwt.calendar.client.Calendar;
 import com.bradrydzewski.gwt.calendar.client.CalendarSettings;
+import com.bradrydzewski.gwt.calendar.client.CalendarSettings.Click;
 import com.bradrydzewski.gwt.calendar.client.CalendarViews;
 import com.bradrydzewski.gwt.calendar.client.event.CreateHandler;
 import com.bradrydzewski.gwt.calendar.client.event.DateRequestHandler;
@@ -65,6 +66,7 @@
     final CalendarSettings settings = new CalendarSettings();
     settings.setIntervalsPerHour(4);
     settings.setPixelsPerInterval(10);
+    settings.setTimeBlockClickNumber(Click.Single);
     // settings.setOffsetHourLabels(true);
     settings.setScrollToHour(8);
     calendar = new Calendar();

Modified: trunk/src/main/java/cc/kune/events/shared/EventsClientConversionUtil.java
===================================================================
--- trunk/src/main/java/cc/kune/events/shared/EventsClientConversionUtil.java	2012-03-07 13:08:17 UTC (rev 1747)
+++ trunk/src/main/java/cc/kune/events/shared/EventsClientConversionUtil.java	2012-03-08 15:05:45 UTC (rev 1748)
@@ -23,6 +23,9 @@
     if (end != null) {
       app.setEnd(DateUtils.toDate(end));
     }
+    String allDay = properties.get(ICalConstants._ALL_DAY);
+    if (allDay != null)
+      app.setAllDay(Boolean.parseBoolean(allDay));
     return app;
   }
 
@@ -30,6 +33,7 @@
     final Map<String, String> properties = EventsSharedConversionUtil.toMap(app);
     properties.put(ICalConstants.DATE_TIME_START, DateUtils.toString(app.getStart()));
     properties.put(ICalConstants.DATE_TIME_END, DateUtils.toString(app.getEnd()));
+    properties.put(ICalConstants._ALL_DAY, Boolean.toString(app.isAllDay()));
     return properties;
   }
 

Added: trunk/src/main/java/org/waveprotocol/wave/client/gadget/renderer/Gadget.css
===================================================================
--- trunk/src/main/java/org/waveprotocol/wave/client/gadget/renderer/Gadget.css	                        (rev 0)
+++ trunk/src/main/java/org/waveprotocol/wave/client/gadget/renderer/Gadget.css	2012-03-08 15:05:45 UTC (rev 1748)
@@ -0,0 +1,136 @@
+/*
+ * StyleInjector CSS file for Gadget container.
+ * Author: vadimg at google.com (Vadim Gerasimov)
+ */
+
+.panel {
+  position: relative; /* Positioned container */
+  zoom: 1;            /* Fixes IE positioning bug. */
+  overflow: hidden;   /* Expand to size of floated contents. */
+  font-size: 0pt;
+  border: none;
+  clear: left;
+}
+
+.inline {
+  display: inline-block;
+  vertical-align: bottom;
+  width: 100%;
+}
+
+.title {
+  display: none;
+}
+
+.gadgetFrame {
+  margin-top: 0px;
+  margin-bottom: 0px;
+  font-size: 0pt;
+  display: block;
+  position: relative;
+}
+
+.loadingGadgetFrame {
+  border: 1px solid #b8c6d9;
+}
+
+.loadedGadgetFrame {
+  border: 1px solid transparent;
+}
+
+ at sprite .loadingGadgetLargeThrobber {
+  gwt-image: 'loadingGadgetLarge';
+  background-color: #e6ebf2;
+  background-repeat: no-repeat;
+  line-height: value('loadingGadgetLarge.getHeight', 'px');
+  background-position: center center;
+  display: block;
+}
+
+ at sprite .loadingGadgetSmallThrobber {
+  gwt-image: 'loadingGadgetSmall';
+  background-color: #e6ebf2;
+  background-repeat: no-repeat;
+  line-height: value('loadingGadgetSmall.getHeight', 'px');
+  background-position: center center;
+  display: block;
+}
+
+ at url brokenGadgetUrl brokenGadget;
+.brokenGadgetIcon {
+  background-color: #e6ebf2;
+  background-image: brokenGadgetUrl;
+  background-repeat: no-repeat;
+  background-position: center center;
+  display: block;
+}
+
+.loadedGadget {
+  display: block;
+}
+
+.iframeDiv {
+  position: relative;
+  left: 0px;
+  top: 0px;
+}
+
+/* Buttons on each widget. */
+ at eval metaButtonTransitionTime RESOURCES.FADE_DELAY_STRING;
+ at sprite .metaButtons {
+  position: absolute;
+  right: 14px;
+  top: 6px;
+  z-index: 1;
+  gwt-image: 'metaMid';
+  overflow: visible;     /* GWT's sprite rules stick in an overflow:hidden which we don't want. */
+  font-size: 0pt;
+
+  /** animate fade-in and fade-out of the meta buttons */
+  \-webkit-transition: opacity metaButtonTransitionTime ease-in-out;
+  \-moz-transition: opacity metaButtonTransitionTime ease-in-out;
+  transition: opacity metaButtonTransitionTime ease-in-out;
+}
+
+/*
+ * The left and right
+ */
+
+ at eval metaLeftMargin RESOURCES.META_LEFT_WIDTH;
+ at eval metaRightMargin RESOURCES.META_RIGHT_WIDTH;
+
+ at sprite .metaRight {
+  gwt-image: 'metaRightImage';
+  float: left;
+  margin-right: metaRightMargin;
+}
+
+ at sprite .metaLeft {
+  gwt-image: 'metaLeftImage';
+  float: left;
+  margin-left: metaLeftMargin;
+}
+
+.metaButtonsPanel {
+  float: left;
+}
+
+.metaButton {
+  /* Push the buttons down to align nicely with the mid, right and left background images. */
+  margin-top: 3px;
+  float: left;
+}
+
+.metaButton.disabled {
+  opacity: 0.5;
+}
+
+ at if user.agent ie6 {
+  .metaButton.disabled {
+    filter: "alpha(opacity = 50)";
+  }
+}
+
+ at sprite .more {
+  gwt-image: 'moreImage';
+}

Added: trunk/src/main/java/org/waveprotocol/wave/client/gadget/renderer/GadgetWidget.java
===================================================================
--- trunk/src/main/java/org/waveprotocol/wave/client/gadget/renderer/GadgetWidget.java	                        (rev 0)
+++ trunk/src/main/java/org/waveprotocol/wave/client/gadget/renderer/GadgetWidget.java	2012-03-08 15:05:45 UTC (rev 1748)
@@ -0,0 +1,2036 @@
+// @formatter:off
+/**
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.waveprotocol.wave.client.gadget.renderer;
+
+import static org.waveprotocol.wave.model.gadget.GadgetConstants.AUTHOR_ATTRIBUTE;
+import static org.waveprotocol.wave.model.gadget.GadgetConstants.ID_ATTRIBUTE;
+import static org.waveprotocol.wave.model.gadget.GadgetConstants.IFRAME_URL_ATTRIBUTE;
+import static org.waveprotocol.wave.model.gadget.GadgetConstants.LAST_KNOWN_HEIGHT_ATTRIBUTE;
+import static org.waveprotocol.wave.model.gadget.GadgetConstants.LAST_KNOWN_WIDTH_ATTRIBUTE;
+import static org.waveprotocol.wave.model.gadget.GadgetConstants.PREFS_ATTRIBUTE;
+import static org.waveprotocol.wave.model.gadget.GadgetConstants.SNIPPET_ATTRIBUTE;
+import static org.waveprotocol.wave.model.gadget.GadgetConstants.STATE_ATTRIBUTE;
+import static org.waveprotocol.wave.model.gadget.GadgetConstants.TITLE_ATTRIBUTE;
+import static org.waveprotocol.wave.model.gadget.GadgetConstants.URL_ATTRIBUTE;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gwt.core.client.Duration;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.ScriptElement;
+import com.google.gwt.http.client.URL;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Random;
+import com.google.gwt.user.client.Window.Location;
+
+import org.waveprotocol.wave.client.account.ProfileManager;
+import org.waveprotocol.wave.client.common.util.UserAgent;
+import org.waveprotocol.wave.client.editor.content.AnnotationPainter;
+import org.waveprotocol.wave.client.editor.content.CMutableDocument;
+import org.waveprotocol.wave.client.editor.content.ContentElement;
+import org.waveprotocol.wave.client.editor.content.ContentNode;
+import org.waveprotocol.wave.client.gadget.GadgetLog;
+import org.waveprotocol.wave.client.gadget.StateMap;
+import org.waveprotocol.wave.client.gadget.StateMap.Each;
+import org.waveprotocol.wave.client.scheduler.ScheduleCommand;
+import org.waveprotocol.wave.client.scheduler.ScheduleTimer;
+import org.waveprotocol.wave.client.scheduler.Scheduler;
+import org.waveprotocol.wave.client.scheduler.Scheduler.Task;
+import org.waveprotocol.wave.model.conversation.ConversationBlip;
+import org.waveprotocol.wave.model.conversation.ObservableConversation;
+import org.waveprotocol.wave.model.document.util.Point;
+import org.waveprotocol.wave.model.document.util.XmlStringBuilder;
+import org.waveprotocol.wave.model.gadget.GadgetXmlUtil;
+import org.waveprotocol.wave.model.id.ModernIdSerialiser;
+import org.waveprotocol.wave.model.id.WaveletName;
+import org.waveprotocol.wave.model.supplement.ObservableSupplementedWave;
+import org.waveprotocol.wave.model.util.CollectionUtils;
+import org.waveprotocol.wave.model.util.ReadableStringMap.ProcV;
+import org.waveprotocol.wave.model.util.ReadableStringSet;
+import org.waveprotocol.wave.model.util.StringMap;
+import org.waveprotocol.wave.model.wave.ParticipantId;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Class to implement gadget widgets rendered in the client.
+ *
+ *
+ *         TODO(user): Modularize the gadget APIs (base, Podium, Wave, etc).
+ *
+ *         TODO(user): Refactor the common RPC call code.
+ */
+public class GadgetWidget extends ObservableSupplementedWave.ListenerImpl
+    implements GadgetRpcListener, GadgetWaveletListener, GadgetUiListener {
+
+  private static final String GADGET_RELAY_PATH = "gadgets/files/container/rpc_relay.html";
+  private static final int DEFAULT_HEIGHT_PX = 100;
+  private static final int DEFAULT_WIDTH_PX = -1;
+
+  /**
+   * Helper class to analyze element changes in the gadget state and prefs.
+   */
+  private abstract class ElementChangeTask {
+    /**
+     * Runs processChange() wrapped in code that detects and submits changes in
+     * the gadget state and prefs.
+     *
+     * @param node The node being processed or null if not defined.
+     */
+    void run(ContentNode node) {
+      if (!isActive()) {
+        log("Element change event in removed node: ignoring.");
+        return;
+      }
+      StateMap oldState = StateMap.create();
+      oldState.copyFrom(state);
+      final StateMap oldPrefs = StateMap.create();
+      oldPrefs.copyFrom(userPrefs);
+      processChange(node);
+      if (!state.compare(oldState)) {
+        gadgetStateSubmitter.submit();
+      }
+      // TODO(user): Optimize prefs updates.
+      if (!userPrefs.compare(oldPrefs)) {
+        userPrefs.each(new StateMap.Each() {
+          @Override
+          public void apply(String key, String value) {
+            if (!oldPrefs.has(key) || !value.equals(oldPrefs.get(key))) {
+              setGadgetPref(key, value);
+            }
+          }
+        });
+      }
+    }
+
+    /**
+     * Processes the changes in the elements.
+     *
+     * @param node The node being processed or null if not defined.
+     */
+    abstract void processChange(ContentNode node);
+  }
+
+  /**
+   * Podium state is stored as a part of the wave gadget state and can be
+   * visible to the Gadget via both Wave and Podium RPC interfaces.
+   */
+  private static final String PODIUM_STATE_NAME = "podiumState";
+
+  /**
+   * Gadget RPC path: location of the RPC JavaScript code to be loaded into the
+   * client code. This is the standard Gadget library to support RPCs.
+   */
+  static final String GADGET_RPC_PATH = "/gadgets/js/core:rpc.js";
+
+  /**
+   * Gadget name prefix: the common part of the gadget IFrame ID and name. The
+   * numeric gadget ID is appended to this prefix.
+   */
+  static final String GADGET_NAME_PREFIX = "wgadget_iframe_";
+
+  /** Primary view for gadgets. */
+  static final String GADGET_PRIMARY_VIEW = "canvas";
+
+  /** Default view for gadgets. */
+  static final String GADGET_DEFAULT_VIEW = "default";
+
+  /**
+   * Time in milliseconds to wait for the RPC script to load before logging a
+   * warning.
+   */
+  private static final int GADGET_RPC_LOAD_WARNING_TIMEOUT_MS = 30000;
+
+  /** Time granularity to check for the Gadget RPC library load state. */
+  private static final int GADGET_RPC_LOAD_TIMER_MS = 250;
+
+  /** Editing mode polling timer. */
+  private static final int EDITING_POLLING_TIMER_MS = 200;
+
+  /** Blip submit delay in milliseconds. */
+  private static final int BLIP_SUBMIT_TIMEOUT_MS = 30;
+
+  /** Gadget state send delay in milliseconds. */
+  private static final int STATE_SEND_TIMEOUT_MS = 30;
+
+  /** The Wave API version supported by the gadget container. */
+  private static final String WAVE_API_VERSION = "1";
+
+  /** The key for the playback state in the wave gadget state map. */
+  private static final String PLAYBACK_MODE_KEY = "${playback}";
+
+  /** The key for the edit state in the wave gadget state map. */
+  private static final String EDIT_MODE_KEY = "${edit}";
+
+  /** Gadget-loading frame border removal delay in ms. */
+  private static final int FRAME_BORDER_REMOVE_DELAY_MS = 3000;
+
+  /** Delay before sending one more participant information update in ms. */
+  private static final int REPEAT_PARTICIPANT_INFORMATION_SEND_DELAY_MS = 5000;
+
+  /**  Object that manages Gadget UI HTML elements. */
+  private GadgetWidgetUi ui;
+
+  /** Gadget title element. */
+  private GadgetElementChild titleElement;
+
+  /** The gadget spec URL. */
+  private String source;
+
+  /** Gadget instance ID counter (local for each client). */
+  private static int nextClientInstanceId = 0;
+
+  /** Gadget instance ID. Non-final for testing. */
+  private int clientInstanceId;
+
+  /** Gadget iframe URL. */
+  private String iframeUrl;
+
+  /** Gadget RPC token.*/
+  private final String rpcToken;
+
+  /** Gadget security token. */
+  private String securityToken;
+
+  /** Gadget user preferences. */
+  private GadgetUserPrefs userPrefs;
+
+  /**
+   * Gadget state element map. Maps state keys to the corresponding elements.
+   */
+  private final StringMap<GadgetElementChild> prefElements;
+
+  /**
+   * Widget active flag: true after the widget is created, false after it is
+   * destroyed.
+   */
+  private boolean active = false;
+
+  /** ID of the gadget's wave/let. */
+  private WaveletName waveletName;
+
+  /** Host blip of this gadget. */
+  private ConversationBlip blip;
+
+  /** Blip submitter. */
+  private Submitter blipSubmitter;
+
+  /** Gadget state submitter. */
+  private Submitter gadgetStateSubmitter;
+
+  /** Private gadget state submitter. */
+  private Submitter privateGadgetStateSubmitter;
+
+  /** ContentElement in the wave that corresponds to this gadget. */
+  private ContentElement element;
+
+  /** Indicator for gadget's blip editing state. */
+  private EditingIndicator editingIndicator;
+
+  /** Participant information. */
+  private ParticipantInformation participants;
+
+  /** Gadget state. */
+  private StateMap state;
+
+  /** User id of the current logged in user. */
+  private String loginName;
+
+  /**
+   * Gadget state element map. Maps state keys to the corresponding elements.
+   */
+  private final StringMap<GadgetElementChild> stateElements;
+
+  /** Indicates whether the gadget is known to support the Wave API. */
+  private boolean waveEnabled = false;
+
+  /** Version of Wave API that is used by the gadget-side code. */
+  private String waveApiVersion = "";
+
+  /** Per-user wavelet to store private gadget data. */
+  private ObservableSupplementedWave supplement;
+
+  /** Provides profile information. */
+  private ProfileManager profileManager;
+
+  /** Wave client locale. */
+  private Locale locale;
+
+  /** Gadget library initialization flag. */
+  private static boolean initialized = false;
+
+  /**
+   * Gadget element child that defines what nodes to check for redundancy in the
+   * removeRedundantNodeTask. Only a single task can be scheduled at a time.
+   */
+  private GadgetElementChild redundantNodeCheckChild = null;
+
+  /**
+   * Indicates whether the gadget has performed a document mutation on behalf of
+   * the user. This flag is checked when the gadget tries to perform
+   * non-essential modifications of the document such as duplicate node cleanup
+   * or height attribute update. Performing such operations may generate
+   * unnecessary playback frames and attribute modifications to a user who did
+   * not use the gadget. The flag is set when the gadget modifies state, prefs,
+   * title, or any other elements that normally are linked to user actions in
+   * the gadget.
+   */
+  private boolean documentModified = false;
+
+  /**
+   * Indicates that the iframe URL attribute should be updated when the gadget
+   * modifies the document in response to a user action.
+   */
+  private boolean toUpdateIframeUrl = false;
+
+  private final String clientInstanceLogLabel;
+
+  // Note that the following regex expressions are strings rather than compiled patterns because GWT
+  // does not (yet) support those. Consider using the new GWT RegExp class in the future.
+
+  /**
+   * Pattern to match rpc token, security token, and user preference parameters
+   * in a URL fragment. Used to remove all these parameters.
+   */
+  private final static String FRAGMENT_CLEANING_PATTERN = "(^|&)(rpctoken=|st=|up_)[^&]*";
+
+  /**
+   * Pattern to match module ID and security token parameters a URL. Used to
+   * remove all these parameters.
+   */
+  private final static String URL_CLEANING_PATTERN = "&(mid=|st=|lang=|country=|debug=)[^&]*";
+
+  /**
+   * Pattern to match and remove URL fragment including the #.
+   */
+  private final static String FRAGMENT_PATTERN = "#.*";
+
+  /**
+   * Pattern to match and remove URL part before fragment including the #.
+   */
+  private final static String BEFORE_FRAGMENT_PATTERN = "[^#]*#";
+
+  /**
+   * Pattern to validate URL fragment.
+   */
+  private final static String FRAGMENT_VALIDATION_PATTERN =
+      "([\\w~!&@\\$\\-\\.\\'\\(\\)\\*\\+\\,\\;\\=\\?\\:]|%[0-9a-fA-F]{2})+";
+
+  /**
+   * Pattern to match iframe host in the beginning of a URL. This is not a
+   * validation check. The user can choose their own host.  This simply serves
+   * to extract the iframe segment of the URL
+   */
+  private final static String IFRAME_HOST_PATTERN =
+      "^\\/\\/(https?:\\/\\/)?[^\\/]+\\/";
+
+  /**
+   * Pattern to remove XML-unsafe characters. Snippeting fails on some of those
+   * symbol combinations due to a potential bug in XML attribute processing.
+   * Theoretically all those symbols should be tolerated and displayed in
+   * snippets without any special processing in this class.
+   *
+   * TODO(user): Investigate/test this later to remove sanitization.
+   */
+  private final static String SNIPPET_SANITIZER_PATTERN = "[<>\\\"\\'\\&]";
+
+
+  /**
+   * Constructs GadgetWidget for testing.
+   */
+  private GadgetWidget() {
+    clientInstanceId = nextClientInstanceId++;
+    clientInstanceLogLabel = "[" + clientInstanceId + "]";
+    prefElements = CollectionUtils.createStringMap();
+    stateElements = CollectionUtils.createStringMap();
+    rpcToken = "" +
+        ((Long.valueOf(Random.nextInt()) << 32) | (Long.valueOf(Random.nextInt()) & 0xFFFFFFFFL));
+  }
+
+  private static native boolean gadgetLibraryLoaded() /*-{
+    return ($wnd.gadgets && $wnd.gadgets.rpc) ? true : false;
+  }-*/;
+
+  /**
+   * Preloads the libraries and initializes them on the first use.
+   */
+  private static void initializeGadgets() {
+    if (!initialized && !gadgetLibraryLoaded()) {
+      GadgetLog.log("Initializing Gadget RPC script tag.");
+      loadGadgetRpcScript();
+      initialized = true;
+      GadgetLog.log("Gadgets RPC script tag initialized.");
+    }
+    // TODO(user): Remove the css hacks once CAJA is fixed.
+    if (!initialized && !gadgetLibraryLoaded()) {
+      // HACK(user): NOT reachable, but GWT thinks it is.
+      excludeCssName();
+    }
+  }
+
+  /**
+   * Utility function to convert a Gadget StateMap to a string to be stored as
+   * an attribute value.
+   *
+   * @param state JSON object to be converted to string.
+   * @return string to be saved as an attribute value.
+   */
+  private static String stateToAttribute(StateMap state) {
+    if (state == null) {
+      return URL.encodeComponent("{}");
+    }
+    return URL.encodeComponent(state.toJson());
+  }
+
+  /**
+   * Utility function to convert an attribute string to a Gadget StateMap.
+   *
+   * @param attribute attribute value string.
+   * @return StateMap constructed from the attribute value.
+   */
+  private StateMap attributeToState(String attribute) {
+    StateMap result = StateMap.create();
+    if ((attribute != null) && !attribute.equals("")) {
+      log("Unescaped attribute: ", URL.decodeComponent(attribute));
+      result.fromJson(URL.decodeComponent(attribute));
+      log("State map: ", result.toJson());
+    }
+    return result;
+  }
+
+  /**
+   * Returns the gadget name that identifies the gadget and its frame.
+   *
+   * @return gadget name.
+   */
+  private String getGadgetName() {
+    return GADGET_NAME_PREFIX + clientInstanceId;
+  }
+
+  private void updatePrefsFromAttribute(String prefAttribute) {
+    if (!stateToAttribute(userPrefs).equals(prefAttribute)) {
+      StateMap prefState = attributeToState(prefAttribute);
+      userPrefs.parse(prefState, true);
+      log("Updating user prefs: ", userPrefs.toJson());
+      prefState.each(new StateMap.Each() {
+        @Override
+        public void apply(String key, String value) {
+          setGadgetPref(key, value);
+        }
+      });
+    }
+  }
+
+  /**
+   * Processes changes in the gadget element attributes.
+   * TODO(user): move some of this code to the handler.
+   *
+   * @param name attribute name.
+   * @param value new attribute value.
+   */
+  public void onAttributeModified(String name, String value) {
+    log("Attribute '", name, "' changed to '", value, "'");
+    if (userPrefs == null) {
+      log("Attribute changed before the gadget is initialized.");
+      return;
+    }
+
+    if (name.equals(URL_ATTRIBUTE)) {
+      source = (value == null) ? "" : value;
+    } else if  (name.equals(TITLE_ATTRIBUTE)) {
+      String title = (value == null) ? "" : URL.decodeComponent(value);
+      if (!title.equals(ui.getTitleLabelText())) {
+        log("Updating title: ", title);
+        ui.setTitleLabelText(title);
+      }
+    } else if (name.equals(PREFS_ATTRIBUTE)) {
+      updatePrefsFromAttribute(value);
+    } else if (name.equals(STATE_ATTRIBUTE)) {
+      StateMap newState = attributeToState(value);
+      if (!state.compare(newState)) {
+        String podiumState = newState.get(PODIUM_STATE_NAME);
+        if ((podiumState != null) && (!podiumState.equals(state.get(PODIUM_STATE_NAME)))) {
+          sendPodiumOnStateChangedRpc(getGadgetName(), podiumState);
+        }
+        state.clear();
+        state.copyFrom(newState);
+        log("Updating gadget state: ", state.toJson());
+        gadgetStateSubmitter.submit();
+      }
+    }
+  }
+
+  /**
+   * Loads Gadget RPC library script.
+   */
+  private static void loadGadgetRpcScript() {
+    ScriptElement script = Document.get().createScriptElement();
+    script.setType("text/javascript");
+    script.setSrc(GADGET_RPC_PATH);
+    Document.get().getBody().appendChild(script);
+  }
+
+  /**
+   * Appends tokens to the iframe URI fragment.
+   *
+   * @param fragment Original parameter fragment of the gadget URI.
+   * @return Updated parameter fragment with new RPC and security tokens.
+   */
+  private String updateGadgetUriFragment(String fragment) {
+    fragment = "rpctoken=" + rpcToken +
+          (fragment.isEmpty() || (fragment.charAt(0) == '&') ? "" : "&") + fragment;
+    if ((securityToken != null) && !securityToken.isEmpty()) {
+      fragment += "&st=" + URL.encodeComponent(securityToken);
+    }
+    return fragment;
+  }
+
+  @VisibleForTesting
+  static String cleanUrl(String url) {
+    String baseUrl = url;
+    String fragment = "";
+    int fragmentIndex = url.indexOf("#");
+    if (fragmentIndex >= 0) {
+      fragment = (url.substring(fragmentIndex + 1)).replaceAll(FRAGMENT_CLEANING_PATTERN, "");
+      if (fragment.startsWith("&")) {
+        fragment = fragment.substring(1);
+      }
+      baseUrl = url.substring(0, fragmentIndex);
+    }
+    baseUrl = baseUrl.replaceAll(URL_CLEANING_PATTERN, "");
+    return baseUrl + (fragment.isEmpty() ? "" : "#" + fragment);
+  }
+
+  /**
+   * Constructs IFrame URI of this gadget.
+   *
+   * @param instanceId instance to encode in the URI.
+   * @param url URL template.
+   * @return IFrame URI of this gadget.
+   */
+  String buildIframeUrl(int instanceId, String url) {
+    final StringBuilder builder =  new StringBuilder();
+    String fragment = "";
+    int fragmentIndex = url.indexOf("#");
+    if (fragmentIndex >= 0) {
+      fragment = url.substring(fragmentIndex + 1);
+      url = url.substring(0, fragmentIndex);
+    }
+    builder.append(url);
+
+    boolean enableGadgetCache = false;
+
+    builder.append("&nocache=" + (enableGadgetCache ? "0" : "1"));
+    builder.append("&mid=" + instanceId);
+    builder.append("&lang=" + locale.getLanguage());
+    builder.append("&country=" + locale.getCountry());
+    String href = getUrlPrefix();
+    // TODO(user): Parent is normally the last non-hash parameter. It is moved
+    // as a temp fix for kitchensinky. Move it back when the kitchensinky is
+    // working wihout this workaround.
+    builder.append("&parent=" + URL.encode(href));
+    builder.append("&wave=" + WAVE_API_VERSION);
+    builder.append("&waveId=" + URL.encodeQueryString(
+        ModernIdSerialiser.INSTANCE.serialiseWaveId(waveletName.waveId)));
+    fragment = updateGadgetUriFragment(fragment);
+    if (!fragment.isEmpty()) {
+      builder.append("#" + fragment);
+      log("Appended fragment: ", fragment);
+    }
+    if (userPrefs != null) {
+      userPrefs.each(new StateMap.Each() {
+        @Override
+        public void apply(String key, String value) {
+          if (value != null) {
+            builder.append("&up_");
+            builder.append(URL.encodeQueryString(key));
+            builder.append('=');
+            builder.append(URL.encodeQueryString(value));
+          }
+        }
+      });
+    }
+    return builder.toString();
+  }
+
+  /**
+   * Verifies that the gadget has non-empty attribute.
+   *
+   * @param name attribute name.
+   * @return true if non-empty height attribute exists, flase otherwise.
+   */
+  private boolean hasAttribute(String name) {
+    if (element.hasAttribute(name)) {
+      String value = element.getAttribute(name);
+      if (!"".equals(value)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Updates the gadget attribute in a deferred command if the panel is
+   * editable.
+   *
+   * @param attributeName attribute name.
+   * @param value new attribute value.
+   */
+  private void scheduleGadgetAttributeUpdate(final String attributeName, final String value) {
+    ScheduleCommand.addCommand(new Scheduler.Task() {
+      @Override
+      public void execute() {
+        if (canModifyDocument() && documentModified) {
+          String oldValue = element.getAttribute(attributeName);
+          if (!value.equals(oldValue)) {
+            element.getMutableDoc().setElementAttribute(element, attributeName, value);
+          }
+        }
+      }
+    });
+  }
+
+  /**
+   * Updates gadget IFrame attributes.
+   *
+   * @param url URL template for the iframe.
+   * @param width preferred width of the iframe.
+   * @param height preferred height of the iframe.
+   */
+  private void updateGadgetIframe(String url, long width, long height) {
+    if (!isActive()) {
+      return;
+    }
+    iframeUrl = url;
+    if (hasAttribute(LAST_KNOWN_WIDTH_ATTRIBUTE)) {
+      setSavedIframeWidth();
+    } else if (width != 0) {
+      ui.setIframeWidth(width + "px");
+      ui.makeInline();
+      scheduleGadgetAttributeUpdate(LAST_KNOWN_WIDTH_ATTRIBUTE, Long.toString(width));
+    } else {
+      ui.setIframeWidth("100%");
+    }
+    if (!hasAttribute(LAST_KNOWN_HEIGHT_ATTRIBUTE) && (height != 0)) {
+      ui.setIframeHeight(height);
+      scheduleGadgetAttributeUpdate(LAST_KNOWN_HEIGHT_ATTRIBUTE, Long.toString(height));
+    }
+    String ifr = buildIframeUrl(getInstanceId(), url);
+    log("ifr: ", ifr);
+    ui.setIframeSource(ifr);
+  }
+
+  private int parseSizeString(String heightString) throws NumberFormatException {
+    if (heightString.endsWith("px")) {
+      return Integer.parseInt(heightString.substring(0, heightString.length() - 2));
+    } else {
+      return Integer.parseInt(heightString);
+    }
+  }
+
+  /**
+   * Updates gadget iframe height if the gadget has the height attribute.
+   */
+  private void setSavedIframeHeight() {
+    if (hasAttribute(LAST_KNOWN_HEIGHT_ATTRIBUTE)) {
+      String savedHeight = element.getAttribute(LAST_KNOWN_HEIGHT_ATTRIBUTE);
+      try {
+        int height = parseSizeString(savedHeight);
+        ui.setIframeHeight(height);
+      } catch (NumberFormatException e) {
+        log("Invalid saved height attribute (ignored): ", savedHeight);
+      }
+    }
+  }
+
+  /**
+   * Updates gadget iframe height if the gadget has the height attribute.
+   */
+  private void setSavedIframeWidth() {    
+    if (hasAttribute(LAST_KNOWN_WIDTH_ATTRIBUTE)) {
+      String savedWidth = element.getAttribute(LAST_KNOWN_WIDTH_ATTRIBUTE);
+      try {
+        int width = parseSizeString(savedWidth);
+        ui.setIframeWidth(width + "px");
+        ui.makeInline();
+      } catch (NumberFormatException e) {
+        log("Invalid saved width attribute (ignored): ", savedWidth);
+      }
+    }
+  }
+
+  /**
+   * Creates a display widget for the gadget.
+   *
+   * @param element ContentElement from the wave.
+   * @param blip gadget blip.
+   * @return display widget for the gadget.
+   */
+  public static GadgetWidget createGadgetWidget(ContentElement element, WaveletName waveletName,
+      ConversationBlip blip, ObservableSupplementedWave supplement,
+      ProfileManager profileManager, Locale locale, String loginName) {
+
+    final GadgetWidget widget = GWT.create(GadgetWidget.class);
+
+    widget.element = element;
+    widget.editingIndicator =
+      new BlipEditingIndicator(element.getRenderedContentView().getDocumentElement());
+    widget.ui = new GadgetWidgetUi(widget.getGadgetName(), widget.editingIndicator);
+    widget.state = StateMap.create();
+    initializeGadgets();
+    widget.blip = blip;
+    widget.initializeGadgetContainer();
+    widget.ui.setGadgetUiListener(widget);
+    widget.waveletName = waveletName;
+    widget.supplement = supplement;
+    widget.profileManager = profileManager;
+    widget.locale = locale;
+    widget.loginName = loginName;
+    supplement.addListener(widget);
+    return widget;
+  }
+
+  /**
+   * @return the actual GWT widget
+   */
+  public GadgetWidgetUi getWidget() {
+    return ui;
+  }
+
+  @Override
+  public void setTitle(String title) {
+    if (!isActive()) {
+      return;
+    }
+    final String newTitle = (title == null) ? "" : title;
+    log("Set title '", XmlStringBuilder.createText(newTitle), "'");
+    if (titleElement == null) {
+      onModifyingDocument();
+      GadgetElementChild.create(element.getMutableDoc().insertXml(
+          Point.end((ContentNode) element), GadgetXmlUtil.constructTitleXml(newTitle)));
+      blipSubmitter.submit();
+    } else {
+      if (!title.equals(titleElement.getValue())) {
+        onModifyingDocument();
+        titleElement.setValue(newTitle);
+        blipSubmitter.submit();
+      }
+    }
+  }
+
+  @Override
+  public void logMessage(String message) {
+    GadgetLog.developerLog(message);
+  }
+
+  private String sanitizeSnippet(String snippet) {
+    return snippet.replaceAll(SNIPPET_SANITIZER_PATTERN, " ");
+  }
+
+  @Override
+  public void setSnippet(String snippet) {
+    if (!canModifyDocument()) {
+      return;
+    }
+    String safeSnippet = sanitizeSnippet(snippet);
+    log("Snippet changed: " + safeSnippet);
+    scheduleGadgetAttributeUpdate(SNIPPET_ATTRIBUTE, safeSnippet);
+  }
+
+  /**
+   * Gets the attribute value from the mutable document associated with the
+   * gadget.
+   *
+   * @param attributeName name of the attribute
+   * @return attribute value or empty string if attribute is missing
+   */
+  private String getAttribute(String attributeName) {
+    return element.hasAttribute(attributeName) ? element.getAttribute(attributeName) : "";
+  }
+
+  @VisibleForTesting
+  static String getIframeHost(String url) {
+    // Ideally this should be done with regex matcher which is not supported in GWT.
+    String iframeHostMatcher = url.replaceFirst(IFRAME_HOST_PATTERN, "");
+    if (iframeHostMatcher.length() != url.length()) {
+      return url.substring(0, url.length() - iframeHostMatcher.length());
+    } else {
+      return "";
+    }
+  }
+
+  /**
+   * Controller registration task.
+   *
+   * @param url URL template of the gadget iframe.
+   * @param width preferred iframe width.
+   * @param height preferred iframe height.
+   */
+  private void controllerRegistration(String url, long width, long height) {
+    Controller controller = Controller.getInstance();
+    String iframeHost = getIframeHost(url);
+    String relayUrl = iframeHost + GADGET_RELAY_PATH;
+    controller.setRelayUrl(getGadgetName(), relayUrl);
+    controller.registerGadgetListener(getGadgetName(), GadgetWidget.this);
+    controller.setRpcToken(getGadgetName(), rpcToken);
+    updateGadgetIframe(url, width, height);
+    removeFrameBorder();
+
+    delayedPodiumInitialization();
+    log("Gadget ", getGadgetName(), " is registered, relayUrl=", relayUrl,
+        ", RPC token=", rpcToken);
+  }
+
+  private void registerWithController(String url, long width, long height) {
+    if (gadgetLibraryLoaded()) {
+      controllerRegistration(url, width, height);
+    } else {
+      scheduleControllerRegistration(url, width, height);
+    }
+  }
+
+  /**
+   * Registers the Gadget object as RPC event listener with the Gadget RPC
+   * Controller after waiting for the Gadget RPC library to load.
+   */
+  private void scheduleControllerRegistration(
+      final String url, final long width, final long height) {
+    new ScheduleTimer() {
+      private double loadWarningTime =
+          Duration.currentTimeMillis() + GADGET_RPC_LOAD_WARNING_TIMEOUT_MS;
+      @Override
+      public void run() {
+        if (!isActive()) {
+          cancel();
+          log("Not active.");
+          return;
+        } else if (gadgetLibraryLoaded()) {
+          cancel();
+          controllerRegistration(url, width, height);
+        } else {
+          if (Duration.currentTimeMillis() > loadWarningTime) {
+            log("Gadget RPC script failed to load on time.");
+            loadWarningTime += GADGET_RPC_LOAD_WARNING_TIMEOUT_MS;
+          }
+        }
+      }
+    }.scheduleRepeating(GADGET_RPC_LOAD_TIMER_MS);
+  }
+
+  private void initializeGadgetContainer() {
+    userPrefs = GadgetUserPrefs.create();
+    blipSubmitter = new Submitter(BLIP_SUBMIT_TIMEOUT_MS, new Submitter.SubmitTask() {
+      @Override public void doSubmit() {
+        // TODO: send a playback frame signal.
+        log("Blip submitted.");
+      }
+    });
+    gadgetStateSubmitter = new Submitter(STATE_SEND_TIMEOUT_MS, new Submitter.SubmitTask() {
+      @Override public void doSubmit() {
+        sendGadgetState();
+        log("Gadget state sent.");
+      }
+    });
+    privateGadgetStateSubmitter = new Submitter(STATE_SEND_TIMEOUT_MS, new Submitter.SubmitTask() {
+      @Override public void doSubmit() {
+        sendPrivateGadgetState();
+        log("Private gadget state sent.");
+      }
+    });
+  }
+
+  private void initializePodium() {
+    if (!isActive()) {
+      // If the widget does not exist, exit.
+      return;
+    }
+    for (ParticipantId participant : blip.getConversation().getParticipantIds()) {
+      String myId = participants.getMyId();
+      if ((myId != null) && !participant.getAddress().equals(myId)) {
+        String opponentId = participant.getAddress();
+        try {
+          sendPodiumOnInitializedRpc(getGadgetName(), myId, opponentId);
+          log("Sent Podium initialization: " + myId + " " + opponentId);
+          String podiumState = state.get(PODIUM_STATE_NAME);
+          if (podiumState != null) {
+            sendPodiumOnStateChangedRpc(getGadgetName(), podiumState);
+            log("Sent Podium state update.");
+          }
+        } catch (Exception e) {
+          // This is a catch to avoid sending RPCs to deleted gadgets.
+          log("Podium initialization failure");
+        }
+        return;
+      }
+    }
+    log("Podium is not initialized: less than two participants.");
+  }
+
+  private void delayedPodiumInitialization() {
+    // TODO(user): This is a hack to delay Podium initialization.
+    // Define an initialization protocol for Podium to avoid this.
+    new ScheduleTimer() {
+      @Override
+      public void run() {
+        initializePodium();
+      }
+    }.schedule(3000);
+  }
+
+  private void removeFrameBorder() {
+    new ScheduleTimer() {
+      @Override
+      public void run() {
+        ui.removeThrobber();
+      }
+    }.schedule(FRAME_BORDER_REMOVE_DELAY_MS);
+  }
+
+  private void constructGadgetFromMetadata(GadgetMetadata metadata, String view, String token) {
+    log("Received metadata: ", metadata.getIframeUrl(view));
+    String url = cleanUrl(metadata.getIframeUrl(view));
+    if (url.equals(iframeUrl) && ((token == null) || token.isEmpty())) {
+      log("Received metadata matches the cached information.");
+      constructGadgetSizeFromMetadata(metadata, view, url);
+      return;
+    }
+    // NOTE(user): Technically we should not save iframe URLs for gadgets with security tokens,
+    // but some gadgets, such as YNM, that depend on opensocial libraries get security tokens they
+    // never use. Also to enable gadgets in Ripple and other light Wave clients it's desirable to
+    // to always have the iframe URL at least for rudimentary rendering.
+    if (canModifyDocument() && documentModified) {
+      scheduleGadgetAttributeUpdate(IFRAME_URL_ATTRIBUTE, url);
+    } else {
+      toUpdateIframeUrl = true;
+    }
+    securityToken = token;
+    if ("".equals(ui.getTitleLabelText()) && metadata.hasTitle()) {
+      ui.setTitleLabelText(metadata.getTitle());
+    }
+    constructGadgetSizeFromMetadata(metadata, view, url);
+  }
+
+  private void constructGadgetSizeFromMetadata(GadgetMetadata metadata, String view, String url) {
+    int height =
+        (int) (metadata.hasHeight() ? metadata.getHeight() : metadata.getPreferredHeight(view));
+    int width =
+        (int) (metadata.hasWidth() ? metadata.getWidth() : metadata.getPreferredWidth(view));    
+    registerWithController(url, width, height);
+    if (height > 0) {
+      setIframeHeight(String.valueOf(height));
+    } else {
+      setIframeHeight(String.valueOf(DEFAULT_HEIGHT_PX));
+    }
+    if (width > 0){
+      setIframeWidth(String.valueOf(width));
+    } else {
+      setIframeWidth(String.valueOf(DEFAULT_WIDTH_PX));
+    }
+  }
+
+  /**
+   * This function generates a gadget instance ID for generating gadget metadata
+   * and security tokens. The ID should be 1. hard to guess; 2. same for the
+   * same gadget element for the same participant in the same wave every time
+   * the wave is rendered in the same client; 3. preferably, but not necessarily
+   * different for different gadget elements and different participants.
+   *
+   * Condition 2 is needed to achieve consistent behavior in gadgets that, for
+   * example, request special permissions using OAuth/OpenSocial.
+   *
+   * This function satisfies those conditions, except the ID is going to be
+   * always the same for the same type of the gadget in the same wavelet for the
+   * same participant. This poses minimal risk (in terms of matching domains and
+   * security tokens) because the gadgets with matching IDs would be rendered
+   * for the same person in the same wave.
+   *
+   * NOTE(user): Instance ID should be non-negative number to work around a
+   * bug in GGS and/or Linux libraries that produces non-renderable iframe URLs
+   * for negative instance IDs. The domain name starts with dash "-". Browsers
+   * in Windows and Mac OS tolerate this, but browsers in Linux fail to render
+   * such URLs.
+   *
+   * @return instance ID for the gadget.
+   */
+  private int getInstanceId() {
+    String name = ModernIdSerialiser.INSTANCE.serialiseWaveletName(waveletName);
+    String instanceDescriptor = name + loginName + source;
+    int hash = instanceDescriptor.hashCode();
+    return (hash < 0) ? ~hash : hash;
+  }
+
+  private void showBrokenGadget(String message) {
+    ui.showBrokenGadget(message);
+    log("Broken gadget: ", message);
+  }
+
+  private boolean validIframeUrl(String url) {
+    return (url != null) && !url.isEmpty() && !getIframeHost(url).isEmpty();
+  }
+
+  private void scheduleGadgetIdUpdate() {
+    ScheduleCommand.addCommand(new Scheduler.Task() {
+      @Override
+      public void execute() {
+        generateAndSetGadgetId();
+      }
+    });
+  }
+
+  private void allowModificationOfNewlyCreatedGadget() {
+    // Missing height attribute indicates freshly added gadget. Assume that the
+    // document is modified for the purpose of updating attributes.
+    if (!hasAttribute(LAST_KNOWN_HEIGHT_ATTRIBUTE) && editingIndicator.isEditing()) {
+      scheduleGadgetIdUpdate();
+      onModifyingDocument();
+    }
+  }
+
+  /**
+   * Creates a widget to render the gadget.
+   */
+  public void createWidget() {
+    if (isActive()) {
+      log("Repeated attempt to create gadget widget.");
+      return;
+    }
+
+    active = true;
+    log("Creating Gadget Widget ", getGadgetName());
+
+    ui.enableMenu();
+    allowModificationOfNewlyCreatedGadget();
+    setSavedIframeHeight();
+    setSavedIframeWidth();
+
+    source = getAttribute(URL_ATTRIBUTE);
+    String title = getAttribute(TITLE_ATTRIBUTE);
+    ui.setTitleLabelText((title == null) ? "" : URL.decodeComponent(title));
+    updatePrefsFromAttribute(getAttribute(PREFS_ATTRIBUTE));
+    refreshParticipantInformation();
+
+    // HACK(anorth): This event routing should happen outside the widget.
+    ObservableConversation conv = (ObservableConversation) blip.getConversation();
+    conv.addListener(new WaveletListenerAdapter(blip, this));
+    log("Requesting Gadget metadata: ", source);
+    String cachedIframeUrl = getAttribute(IFRAME_URL_ATTRIBUTE);
+    if (validIframeUrl(cachedIframeUrl)) {
+      registerWithController(cleanUrl(cachedIframeUrl), 0, 0);
+    }
+    GadgetDataStoreImpl.getInstance().getGadgetData(source, waveletName, getInstanceId(),
+        new GadgetDataStore.DataCallback() {
+          @Override
+          public void onError(String message, Throwable t) {
+            if ((t != null) && (t.getMessage() != null)) {
+              message += " " + t.getMessage();
+            }
+            showBrokenGadget(message);
+          }
+
+          @Override
+          public void onDataReady(GadgetMetadata metadata, String securityToken) {
+            if (isActive()) {
+              ReadableStringSet views = metadata.getViewSet();
+              String view =  null;
+              if (views.contains(GADGET_PRIMARY_VIEW)) {
+                view = GADGET_PRIMARY_VIEW;
+              } else if (views.contains(GADGET_DEFAULT_VIEW)) {
+                view = GADGET_DEFAULT_VIEW;
+              } else if (!views.isEmpty()) {
+                view = views.someElement();
+              } else {
+                showBrokenGadget("Gadget has no view to render.");
+                return;
+              }
+              String url = metadata.getIframeUrl(view);
+              if (validIframeUrl(url)) {
+                constructGadgetFromMetadata(metadata, view, securityToken);
+              } else {
+                showBrokenGadget("Invalid IFrame URL " + url);
+              }
+            }
+          }
+    });
+  }
+
+  /**
+   * Utility function to send setPref RPC to the gadget.
+   *
+   * @param target the gadget frame ID.
+   * @param name name of the preference to set.
+   * @param value value of the preference.
+   */
+  public native void sendGadgetPrefRpc(String target, String name, String value) /*-{
+    try {
+      $wnd.gadgets.rpc.call(target, 'set_pref', null, 0, name, value);
+    } catch (e) {
+      // HACK(user): Ignoring any failure for now.
+      @org.waveprotocol.wave.client.gadget.GadgetLog::log(Ljava/lang/String;)
+      ('set_pref RPC failed');
+    }
+  }-*/;
+
+  /**
+   * Utility function to send initialization RPC to Podium gadget.
+   *
+   * @param target the gadget frame ID.
+   * @param id Podium ID of this client.
+   * @param otherId Podium ID of the opponent client.
+   */
+  public native void sendPodiumOnInitializedRpc(String target, String id, String otherId) /*-{
+    try {
+      $wnd.gadgets.rpc.call(target, 'onInitialized', null, id, otherId);
+    } catch (e) {
+      // HACK(user): Ignoring any failure for now.
+      @org.waveprotocol.wave.client.gadget.GadgetLog::log(Ljava/lang/String;)
+      ('onInitialized RPC failed');
+    }
+  }-*/;
+
+  /**
+   * Utility function to send state change RPC to Podium gadget.
+   *
+   * @param target the gadget frame ID.
+   * @param state Podium gadget state.
+   */
+  public native void sendPodiumOnStateChangedRpc(String target, String state) /*-{
+    try {
+      $wnd.gadgets.rpc.call(target, 'onStateChanged', null, state);
+    } catch (e) {
+      // HACK(user): Ignoring any failure for now.
+      @org.waveprotocol.wave.client.gadget.GadgetLog::log(Ljava/lang/String;)
+      ('onStateChanged RPC failed');
+    }
+  }-*/;
+
+  /**
+   * Utility function to send title to the embedding container.
+   *
+   * @param title the title value for the container.
+   */
+  public native void sendEmbeddedRpc(String title) /*-{
+    try {
+      $wnd.gadgets.rpc.call(null, 'set_title', null, title);
+    } catch (e) {
+      // HACK(user): Ignoring any failure for now.
+      @org.waveprotocol.wave.client.gadget.GadgetLog::log(Ljava/lang/String;)
+      ('set_title RPC failed');
+    }
+  }-*/;
+
+  /**
+   * Utility function to send participant information to Wave gadget.
+   *
+   * @param target the gadget frame ID.
+   * @param participants JSON string of Wavelet participants.
+   */
+  public native void sendParticipantsRpc(String target, JavaScriptObject participants) /*-{
+    try {
+      $wnd.gadgets.rpc.call(target, 'wave_participants', null, participants);
+    } catch (e) {
+      // HACK(user): Ignoring any failure for now.
+      @org.waveprotocol.wave.client.gadget.GadgetLog::log(Ljava/lang/String;)
+      ('wave_participants RPC failed');
+    }
+  }-*/;
+
+  /**
+   * Utility function to send Gadget state to Wave gadget.
+   *
+   * @param target the gadget frame ID.
+   * @param state JSON string of Gadget state.
+   */
+  public native void sendGadgetStateRpc(String target, JavaScriptObject state) /*-{
+    try {
+      $wnd.gadgets.rpc.call(target, 'wave_gadget_state', null, state);
+    } catch (e) {
+      // HACK(user): Ignoring any failure for now.
+      @org.waveprotocol.wave.client.gadget.GadgetLog::log(Ljava/lang/String;)
+      ('wave_gadget_state RPC failed');
+    }
+  }-*/;
+
+  /**
+   * Utility function to send private Gadget state to Wave gadget.
+   *
+   * @param target the gadget frame ID.
+   * @param state JSON string of Gadget state.
+   */
+  public native void sendPrivateGadgetStateRpc(String target, JavaScriptObject state) /*-{
+    try {
+      $wnd.gadgets.rpc.call(target, 'wave_private_gadget_state', null, state);
+    } catch (e) {
+      // HACK(user): Ignoring any failure for now.
+      @org.waveprotocol.wave.client.gadget.GadgetLog::log(Ljava/lang/String;)
+      ('wave_private_gadget_state RPC failed');
+    }
+  }-*/;
+
+  /**
+   * Utility function to send Gadget mode to Wave gadget.
+   *
+   * @param target the gadget frame ID.
+   * @param mode JSON string of Gadget state.
+   */
+  public native void sendModeRpc(String target, JavaScriptObject mode) /*-{
+    try {
+      $wnd.gadgets.rpc.call(target, 'wave_gadget_mode', null, mode);
+    } catch (e) {
+      // HACK(user): Ignoring any failure for now.
+      @org.waveprotocol.wave.client.gadget.GadgetLog::log(Ljava/lang/String;)
+      ('wave_gadget_mode RPC failed');
+    }
+  }-*/;
+
+  /**
+   * Sends the gadget state to the wave gadget. Injects the playback state value
+   * into the state.
+   */
+  public void sendGadgetState() {
+    if (waveEnabled) {
+      log("Sending gadget state: ", state.toJson());
+      sendGadgetStateRpc(getGadgetName(), state.asJavaScriptObject());
+    }
+  }
+
+  /**
+   * Sends the private gadget state to the wave gadget.
+   */
+  public void sendPrivateGadgetState() {
+    if (waveEnabled) {
+      String gadgetId = getGadgetId();
+      StateMap privateState = StateMap.createFromStringMap(gadgetId != null ?
+          supplement.getGadgetState(gadgetId) : CollectionUtils.<String> emptyMap());
+      log("Sending private gadget state: ", privateState.toJson());
+      sendPrivateGadgetStateRpc(getGadgetName(), privateState.asJavaScriptObject());
+    }
+  }
+
+  /**
+   * Sends the gadget mode to the wave gadget.
+   */
+  public void sendMode() {
+    if (waveEnabled) {
+      StateMap mode = StateMap.create();
+      mode.put(PLAYBACK_MODE_KEY, "0");
+      mode.put(EDIT_MODE_KEY, editingIndicator.isEditing() ? "1" : "0");
+      log("Sending gadget mode: ", mode.toJson());
+      sendModeRpc(getGadgetName(), mode.asJavaScriptObject());
+    }
+  }
+
+  /**
+   * Returns the ID of the user who added the gadget as defined in the author
+   * attribute. If the attribute is not defined returns the blip author instead
+   * (as the best guess for the author for backward compatibility).
+   *
+   * @return author ID of the user who added the gadget to the wave
+   */
+  private String getAuthor() {
+    String author = element.getAttribute(AUTHOR_ATTRIBUTE);
+    return (author != null) ? author : blip.getAuthorId().getAddress();
+  }
+
+  /**
+   * Builds a map of participants from two lists of participant ids.
+   */
+  private StringMap<ParticipantId> getParticipantsForIds(
+      Collection<ParticipantId> list1, Collection<ParticipantId> list2) {
+    StringMap<ParticipantId> mergedMap = CollectionUtils.createStringMap();
+    for (ParticipantId p : list1) {
+        mergedMap.put(p.getAddress(), p);
+    }
+    for (ParticipantId p : list2) {
+        mergedMap.put(p.getAddress(), p);
+    }
+    return mergedMap;
+  }
+
+  /**
+   * Refreshes the participant information.
+   */
+  private void refreshParticipantInformation() {
+    StringMap<ParticipantId> waveletParticipants = getParticipantsForIds(
+        blip.getConversation().getParticipantIds(), blip.getContributorIds());
+    ParticipantId viewerId = new ParticipantId(loginName);
+    waveletParticipants.put(viewerId.getAddress(), viewerId);
+    List<ParticipantId> participantList = CollectionUtils.newJavaList(waveletParticipants);
+    participants = ParticipantInformation.create(
+        viewerId.getAddress(), getAuthor(), participantList, getUrlPrefix(), profileManager);
+    final StringBuilder builder = new StringBuilder();
+    builder.append("Participants: ");
+    builder.append("I am " + participants.getMyId());
+    for (ParticipantId participant : participantList) {
+      builder.append("; " + participant);
+    }
+
+    log(builder.toString());
+  }
+
+  /**
+   * Refreshes and sends participant information to wave-enabled gadget.
+   */
+  private void sendCurrentParticipantInformation() {
+    if (waveEnabled) {
+      refreshParticipantInformation();
+      sendParticipantsRpc(getGadgetName(), participants);
+      log("Sent participants: ", participants);
+    }
+  }
+
+  /**
+   * Utility function to perform setPref RPC to the gadget.
+   *
+   * @param name name of the preference to set.
+   * @param value value of the preference.
+   */
+  public void setGadgetPref(final String name, final String value) {
+    ScheduleCommand.addCommand(new Task() {
+      @Override
+      public void execute() {
+        if (isActive()) {
+          sendGadgetPrefRpc(getGadgetName(), name, value);
+        }
+      }
+    });
+  }
+
+  /**
+   * Marks the Widget as inactive after the gadget node is removed from the
+   * parent.
+   */
+  public void setInactive() {
+    log("Gadget node removed.");
+    supplement.removeListener(this);
+    active = false;
+  }
+
+  @Override
+  public void setIframeHeight(String height) {
+    if (!isActive()) {
+      return;
+    }
+    log("Set IFrame height ", height);
+    try {
+      int heightValue = parseSizeString(height);
+      ui.setIframeHeight(heightValue);
+      scheduleGadgetAttributeUpdate(LAST_KNOWN_HEIGHT_ATTRIBUTE, Long.toString(heightValue));
+    } catch (NumberFormatException e) {
+      log("Invalid height (ignored): ", height);
+    }
+  }
+
+  public void setIframeWidth(String width) {
+    if (!isActive()) {
+      return;
+    }
+    log("Set IFrame width ", width);
+    try {
+      int widthValue = parseSizeString(width);
+      if (widthValue <= 0) {
+        ui.setIframeWidth("100%");
+      } else {
+        ui.setIframeWidth(widthValue + "px");
+      }
+      ui.makeInline();
+      scheduleGadgetAttributeUpdate(LAST_KNOWN_WIDTH_ATTRIBUTE, Long.toString(widthValue));
+    } catch (NumberFormatException e) {
+      log("Invalid width (ignored): ", width);
+    }
+  }
+
+  @Override
+  public void requestNavigateTo(String url) {
+    log("Requested navigate to: ", url);
+    // NOTE(user): Currently only allow the gadgets to change the fragment part of the URL.
+    String newFragment = url.replaceFirst(BEFORE_FRAGMENT_PATTERN, "");
+    if (newFragment.matches(FRAGMENT_VALIDATION_PATTERN)) {
+      Location.replace(Location.getHref().replaceFirst(FRAGMENT_PATTERN, "") + "#" + newFragment);
+    } else {
+      log("Navigate request denied.");
+    }
+  }
+
+  @Override
+  public void updatePodiumState(String podiumState) {
+    if (isActive()) {
+      modifyState(PODIUM_STATE_NAME, podiumState);
+      blipSubmitter.submit();
+    }
+  }
+
+  private void setPref(String key, String value) {
+    if (!canModifyDocument() || (key == null) || (value == null)) {
+      return;
+    }
+    userPrefs.put(key, value);
+    if (prefElements.containsKey(key)) {
+      if (!prefElements.get(key).getValue().equals(value)) {
+        log("Updating preference '", key, "'='", value, "'");
+        onModifyingDocument();
+        prefElements.get(key).setValue(value);
+        blipSubmitter.submit();
+      }
+    } else {
+      log("New preference '", key, "'='", value, "'");
+      onModifyingDocument();
+      element.getMutableDoc().insertXml(
+          Point.end((ContentNode)element), GadgetXmlUtil.constructPrefXml(key, value));
+      blipSubmitter.submit();
+    }
+
+  }
+
+  @Override
+  public void setPrefs(String ... keyValue) {
+    // Ignore callbacks from the gadget in playback mode.
+    if (!canModifyDocument()) {
+      return;
+    }
+    // Ignore the last key if its value is missing.
+    for (int i = 0; i < keyValue.length - 1; i+=2) {
+      setPref(keyValue[i], keyValue[i + 1]);
+    }
+  }
+
+  /**
+   * Sets up a polling loop to check the edit mode state and send it to the
+   * gadget.
+   *
+   * TODO(user): Add edit mode change events to the client and find a way to
+   * relay them to the gadget containers.
+   */
+  private void setupModePolling() {
+    new ScheduleTimer() {
+      private boolean wasEditing = editingIndicator.isEditing();
+
+      @Override
+      public void run() {
+        if (!isActive()) {
+          cancel();
+          return;
+        } else {
+          boolean newEditing = editingIndicator.isEditing();
+          if (wasEditing != newEditing) {
+            sendMode();
+            wasEditing = newEditing;
+          }
+        }
+      }
+    }.scheduleRepeating(EDITING_POLLING_TIMER_MS);
+  }
+
+  /**
+   * HACK: This is a workaround for Firefox bug
+   * https://bugzilla.mozilla.org/show_bug.cgi?id=498904 Due to this bug the
+   * gadget RPCs may be sent to a dead iframe. Changing the iframe ID fixes
+   * container-to-gadget communication. Non-wave gadgets may have other issues
+   * associated with this bug. But most wave-enabled gadgets should work when
+   * the iframe ID is updated in the waveEnable call.
+   */
+  private void substituteIframeId() {
+    clientInstanceId = nextClientInstanceId++;
+    ui.setIframeId(getGadgetName());
+    controllerRegistration(iframeUrl, 0, 0);
+  }
+
+  @Override
+  public void waveEnable(String waveApiVersion) {
+    if (!isActive()) {
+      return;
+    }
+
+    // HACK: See substituteIframeId() description.
+    // TODO(user): Remove when the Firefox bug is fixed.
+    if (UserAgent.isFirefox()) {
+      substituteIframeId();
+    }
+
+    waveEnabled = true;
+    this.waveApiVersion = waveApiVersion;
+    log("Wave-enabled gadget registered with API version ", waveApiVersion);
+    sendWaveGadgetInitialization();
+    setupModePolling();
+  }
+
+  @Override
+  public void waveGadgetStateUpdate(final JavaScriptObject delta) {
+    // Return if in playback mode. isEditable indicates playback.
+    if (!canModifyDocument()) {
+      return;
+    }
+
+    final StateMap deltaState = StateMap.create();
+    deltaState.fromJsonObject(delta);
+    // Defer state modifications to avoid RPC failure in Safari 3. The
+    // intermittent failure is caused by RPC called from received RPC
+    // callback.
+    // TODO(user): Remove this workaround once this is fixed in GGS.
+    ScheduleCommand.addCommand(new Task() {
+      @Override
+      public void execute() {
+        deltaState.each(new Each() {
+          @Override
+          public void apply(final String key, final String value) {
+            if (value != null) {
+              modifyState(key, value);
+            } else {
+              deleteState(key);
+            }
+          }
+        });
+        log("Applied delta ", delta.toString(), " new state ", state.toJson());
+        gadgetStateSubmitter.triggerScheduledSubmit();
+        blipSubmitter.submitImmediately();
+      }
+    });
+  }
+
+  /**
+   * Generates a unique gadget ID.
+   * TODO(user): Replace with proper MD5-based UUID.
+   *
+   * @return a unique gadget ID.
+   */
+  private String generateGadgetId() {
+    String name = ModernIdSerialiser.INSTANCE.serialiseWaveletName(waveletName);
+    String instanceDescriptor = name + getAuthor() + source;
+    String prefix = Integer.toHexString(instanceDescriptor.hashCode());
+    String time = Integer.toHexString(new Date().hashCode());
+    String version = Long.toHexString(blip.getLastModifiedVersion());
+    return prefix + time + version;
+  }
+
+  private String generateAndSetGadgetId() {
+    if (!canModifyDocument()) {
+      return null;
+    }
+    String id = generateGadgetId();
+    element.getMutableDoc().setElementAttribute(element, ID_ATTRIBUTE, id);
+    return id;
+  }
+
+  private String getGadgetId() {
+    return element.getAttribute(ID_ATTRIBUTE);
+  }
+
+  private String getOrGenerateGadgetId() {
+    String id = getGadgetId();
+    if ((id == null) || id.isEmpty()) {
+      id = generateAndSetGadgetId();
+    }
+    return id;
+  }
+
+  @Override
+  public void wavePrivateGadgetStateUpdate(JavaScriptObject delta) {
+    // Return if in playback mode. isEditable indicates playback.
+    if (!canModifyDocument()) {
+      return;
+    }
+
+    StateMap deltaState = StateMap.create();
+    deltaState.fromJsonObject(delta);
+    final String gadgetId = getOrGenerateGadgetId();
+    if (gadgetId != null) {
+      deltaState.each(new Each() {
+        @Override
+        public void apply(final String key, final String value) {
+          supplement.setGadgetState(gadgetId, key, value);
+        }
+      });
+      log("Applied private delta ", deltaState.toJson());
+      privateGadgetStateSubmitter.triggerScheduledSubmit();
+    } else {
+      log("Unable to get gadget ID to update private state. Delta ", deltaState.toJson());
+    }
+  }
+
+  private void modifyState(String key, String value) {
+    if (!canModifyDocument()) {
+      log("Unable to modify state ", key, " ", value);
+    } else {
+      log("Modifying state ", key, " ", value);
+      if (stateElements.containsKey(key)) {
+        if (!stateElements.get(key).getValue().equals(value)) {
+          onModifyingDocument();
+          stateElements.get(key).setValue(value);
+        }
+      }  else {
+        onModifyingDocument();
+        element.getMutableDoc().insertXml(
+            Point.end((ContentNode)element), GadgetXmlUtil.constructStateXml(key, value));
+      }
+    }
+  }
+
+  private void deleteState(String key) {
+    if (!canModifyDocument()) {
+      log("Unable to remove state ", key);
+    } else {
+      log("Removing state ", key);
+      if (stateElements.containsKey(key)) {
+        onModifyingDocument();
+        element.getMutableDoc().deleteNode(stateElements.get(key).getElement());
+      }
+    }
+  }
+
+  private void sendWaveGadgetInitialization() {
+    sendMode();
+    sendCurrentParticipantInformation();
+    gadgetStateSubmitter.submitImmediately();
+    privateGadgetStateSubmitter.submitImmediately();
+    // Send participant information one more time as participant pictures may be
+    // loaded with a delay. There is no callback to get the picture update
+    // event.
+    new ScheduleTimer() {
+      @Override
+      public void run() {
+        if (isActive()) {
+          sendCurrentParticipantInformation();
+        }
+      }
+    }.schedule(REPEAT_PARTICIPANT_INFORMATION_SEND_DELAY_MS);
+  }
+
+  private void updateElementMaps(
+      GadgetElementChild child, StringMap<GadgetElementChild> childMap, StateMap stateMap) {
+    if (child.getKey() == null) {
+      log("Missing key attribute: element ignored.");
+      return;
+    }
+    if (childMap.containsKey(child.getKey())) {
+      logFine("Old value: ", childMap.get(child.getKey()));
+    }
+    childMap.put(child.getKey(), child);
+    stateMap.put(child.getKey(), child.getValue());
+    logFine("Updated element ", child.getKey(), " : ", child.getValue());
+  }
+
+  private void processTitleChild(GadgetElementChild child) {
+    titleElement = child;
+    String newTitleValue = child.getValue();
+    if (newTitleValue == null) {
+      newTitleValue = "";
+    }
+    if (!newTitleValue.equals(ui.getTitleLabelText())) {
+      ui.setTitleLabelText(newTitleValue);
+    }
+  }
+
+  private void removeChildFromMaps(
+      GadgetElementChild child, StringMap<GadgetElementChild> childMap, StateMap stateMap) {
+    String key = child.getKey();
+    if (childMap.containsKey(key)) {
+      stateMap.remove(key);
+      childMap.remove(key);
+      logFine("Removed element ", key);
+    }
+  }
+
+  private void processChild(GadgetElementChild child) {
+    if (child == null) {
+      return;
+    }
+    logFine("Processing: ", child);
+    switch (child.getType()) {
+      case STATE:
+        updateElementMaps(child, stateElements, state);
+        break;
+      case PREF:
+        updateElementMaps(child, prefElements, userPrefs);
+        break;
+      case TITLE:
+        processTitleChild(child);
+        break;
+      case CATEGORIES:
+        logFine("Categories element ignored.");
+        break;
+      default:
+        // Note(user): editor may add/remove selection and cursor nodes.
+        logFine("Unexpected gadget node ", child.getTag());
+    }
+  }
+
+  /**
+   * Finds the first copy of the given child in the sibling sequence starting at
+   * the given node.
+   *
+   * @param child Child to find next copy of.
+   * @param node Node to scan from.
+   * @return Next copy of the child or null if not found.
+   */
+  private static GadgetElementChild findNextChildCopy(GadgetElementChild child, ContentNode node) {
+    if (child == null) {
+      return null;
+    }
+    while (node != null) {
+      GadgetElementChild gadgetChild = GadgetElementChild.create(node);
+      if (child.isDuplicate(gadgetChild)) {
+        return gadgetChild;
+      }
+      node = node.getNextSibling();
+    }
+    return null;
+  }
+
+  /**
+   * Task removes redundant nodes that match redundantNodeCheckChild.
+   */
+  private final Scheduler.Task removeRedundantNodesTask = new Scheduler.Task() {
+    @Override
+    public void execute() {
+      if (!canModifyDocument()) {
+        return;
+      }
+      if (redundantNodeCheckChild != null) {
+        GadgetElementChild firstMatchingNode = findNextChildCopy(
+            redundantNodeCheckChild, element.getFirstChild());
+        GadgetElementChild lastSeenNode = firstMatchingNode;
+        while (lastSeenNode != null) {
+          lastSeenNode = findNextChildCopy(
+              redundantNodeCheckChild, firstMatchingNode.getElement().getNextSibling());
+          if (lastSeenNode != null) {
+            log("Removing: ", lastSeenNode);
+            element.getMutableDoc().deleteNode(lastSeenNode.getElement());
+          }
+        }
+      } else {
+        log("Undefined redundant node check child.");
+      }
+      redundantNodeCheckChild = null;
+    }
+  };
+
+  /**
+   * Scans nodes and removes duplicate copies of the given child leaving only
+   * the first copy.
+   * TODO(user): Unit test for node manipulations.
+   *
+   * @param child Child to delete the duplicates of.
+   */
+  private void removeRedundantNodes(final GadgetElementChild child) {
+    if (!documentModified || (child == null)) {
+      return;
+    }
+    if (redundantNodeCheckChild == null) {
+      redundantNodeCheckChild = child;
+      ScheduleCommand.addCommand(removeRedundantNodesTask);
+    } else {
+      log("Overlapping redundant node check requests.");
+    }
+  }
+
+  private final ElementChangeTask childAddedTask = new ElementChangeTask() {
+    @Override
+    void processChange(ContentNode node) {
+      GadgetElementChild child = GadgetElementChild.create(node);
+      log("Added: ", child);
+      if (child != null) {
+        removeRedundantNodes(child);
+        processChild(child);
+      }
+    }
+  };
+
+  /**
+   * Processes an add child event.
+   *
+   * @param node the child added to the gadget node.
+   */
+  public void onChildAdded(ContentNode node) {
+    childAddedTask.run(node);
+  }
+
+  private final ElementChangeTask childRemovedTask = new ElementChangeTask() {
+    @Override
+    void processChange(ContentNode node) {
+      GadgetElementChild child = GadgetElementChild.create(node);
+      log("Removed: ", child);
+      switch (child.getType()) {
+        case STATE:
+          removeChildFromMaps(child, stateElements, state);
+          break;
+        case PREF:
+          removeChildFromMaps(child, prefElements, userPrefs);
+          break;
+        case TITLE:
+          log("Removing title is not supported");
+          break;
+        case CATEGORIES:
+          log("Removing categories is not supported");
+          break;
+        default:
+          // Note(user): editor may add/remove selection and cursor nodes.
+          log("Unexpected gadget node removed ", child.getTag());
+      }
+    }
+  };
+
+  /**
+   * Processes a remove child event.
+   *
+   * @param node
+   */
+  public void onRemovingChild(ContentNode node) {
+    childRemovedTask.run(node);
+  }
+
+  /**
+   * Rescans all gadget children to update the values stored in the gadget
+   * object.
+   */
+  private void rescanGadgetXmlElements() {
+    log("Rescanning elements");
+    ContentNode childNode = element.getFirstChild();
+    while (childNode != null) {
+      processChild(GadgetElementChild.create(childNode));
+      childNode = childNode.getNextSibling();
+    }
+  }
+
+  private final ElementChangeTask descendantsMutatedTask = new ElementChangeTask() {
+    @Override
+    void processChange(ContentNode node) {
+      rescanGadgetXmlElements();
+    }
+  };
+
+  private final Scheduler.Task schedulableMutationTask = new Scheduler.Task() {
+    @Override
+    public void execute() {
+      descendantsMutatedTask.run(null);
+    }
+  };
+
+  /**
+   * Processes a mutation event.
+   */
+  public void onDescendantsMutated() {
+    log("Descendants mutated.");
+    ScheduleCommand.addCommand(schedulableMutationTask);
+  }
+
+  @Override
+  public void onBlipContributorAdded(ParticipantId contributor) {
+    if (isActive()) {
+      log("Contributor added ", contributor);
+      sendCurrentParticipantInformation();
+    } else {
+      log("Contributor added event in deleted node.");
+    }
+  }
+
+  @Override
+  public void onBlipContributorRemoved(ParticipantId contributor) {
+    if (isActive()) {
+      log("Contributor removed ", contributor);
+      sendCurrentParticipantInformation();
+    } else {
+      log("Contributor removed event in deleted node.");
+    }
+  }
+
+  @Override
+  public void onParticipantAdded(ParticipantId participant) {
+    if (isActive()) {
+      log("Participant added ", participant);
+      sendCurrentParticipantInformation();
+    } else {
+      log("Participant added event in deleted node.");
+    }
+  }
+
+  @Override
+  public void onParticipantRemoved(ParticipantId participant) {
+    if (isActive()) {
+      log("Participant removed ", participant);
+      sendCurrentParticipantInformation();
+    } else {
+      log("Participant removed event in deleted node.");
+    }
+  }
+
+  private Object[] expandArgs(Object object, Object ... objects) {
+    Object[] args = new Object[objects.length + 1];
+    args[0] = object;
+    System.arraycopy(objects, 0, args, 1, objects.length);
+    return args;
+  }
+
+  private void log(Object ... objects) {
+    if (GadgetLog.shouldLog()) {
+      GadgetLog.logLazy(expandArgs(clientInstanceLogLabel, objects));
+    }
+  }
+
+  private void logFine(Object ... objects) {
+    if (GadgetLog.shouldLogFine()) {
+      GadgetLog.logFineLazy(expandArgs(clientInstanceLogLabel, objects));
+    }
+  }
+
+  /**
+   * Returns the URL of the client including protocol and host.
+   *
+   * @return URL of the client.
+   */
+  private String getUrlPrefix() {
+    return Location.getProtocol() + "//" + Location.getHost();
+  }
+
+  /**
+   * Returns the UI element.
+   *
+   * @return UI element.
+   */
+  Element getElement() {
+    return ui.getElement();
+  }
+
+  private boolean isActive() {
+    return active;
+  }
+
+  private boolean canModifyDocument() {
+    return isActive();
+  }
+
+  @Override
+  public void deleteGadget() {
+    if (canModifyDocument()) {
+      element.getMutableDoc().deleteNode(element);
+    }
+  }
+
+  @Override
+  public void selectGadget() {
+    if (isActive()) {
+      CMutableDocument doc = element.getMutableDoc();
+      element.getSelectionHelper().setSelectionPoints(
+          Point.before(doc, element), Point.after(doc, element));
+    }
+  }
+
+  @Override
+  public void resetGadget() {
+    if (canModifyDocument()) {
+      state.each(new Each() {
+        @Override
+        public void apply(String key, String value) {
+          deleteState(key);
+        }
+      });
+      gadgetStateSubmitter.submit();
+      final String gadgetId = getGadgetId();
+      if (gadgetId != null) {
+        supplement.getGadgetState(gadgetId).each(new ProcV<String>() {
+          @Override
+          public void apply(String key, String value) {
+            supplement.setGadgetState(gadgetId, key, null);
+          }
+        });
+        privateGadgetStateSubmitter.submit();
+      }
+    }
+  }
+
+  private static native void excludeCssName() /*-{
+		css();
+  }-*/;
+
+  private static class BlipEditingIndicator implements EditingIndicator {
+    private final ContentElement element;
+
+    /**
+     * Constructs editing indicator for the gadget's blip.
+     */
+    BlipEditingIndicator(ContentElement element) {
+      this.element = element;
+    }
+
+    /**
+     * Returns the current edit state of the blip.
+     * TODO(user): add event-driven update of the edit state.
+     *
+     * @return whether the blip is in edit state.
+     */
+    @Override
+    public boolean isEditing() {
+      return (element != null)
+          ? AnnotationPainter.isInEditingDocument(ContentElement.ELEMENT_MANAGER, element) : false;
+    }
+  }
+
+  @Override
+  public void onMaybeGadgetStateChanged(String gadgetId) {
+    if (gadgetId != null) {
+      String myId = getGadgetId();
+      if (gadgetId.equals(myId)) {
+        privateGadgetStateSubmitter.submitImmediately();
+      }
+    }
+  }
+
+  /**
+   * Executes when the document is being modified in response to a user action.
+   */
+  private void onModifyingDocument() {
+    documentModified = true;
+    if (toUpdateIframeUrl) {
+      scheduleGadgetAttributeUpdate(IFRAME_URL_ATTRIBUTE, iframeUrl);
+      toUpdateIframeUrl = false;
+    }
+  }
+
+  /**
+   * Creates GadgetWidget instance with preset fields for testing.
+   *
+   * TODO(user): Refactor to remove test code.
+   *
+   * @param id client instance ID
+   * @param userPrefs user prederences
+   * @param waveletName wavelet name
+   * @param securityToken security token
+   * @param locale locale
+   * @return test instance of the widget
+   */
+  @VisibleForTesting
+  static GadgetWidget createForTesting(int id, GadgetUserPrefs userPrefs, WaveletName waveletName,
+      String securityToken, Locale locale) {
+    GadgetWidget widget = new GadgetWidget();
+    widget.clientInstanceId = id;
+    widget.userPrefs = userPrefs;
+    widget.waveletName = waveletName;
+    widget.securityToken = securityToken;
+    widget.locale = locale;
+    return widget;
+  }
+
+  /**
+   * @return RPC token for testing
+   */
+  @VisibleForTesting
+  String getRpcToken() {
+    return rpcToken;
+  }
+}

Added: trunk/src/main/java/org/waveprotocol/wave/client/gadget/renderer/GadgetWidgetUi.java
===================================================================
--- trunk/src/main/java/org/waveprotocol/wave/client/gadget/renderer/GadgetWidgetUi.java	                        (rev 0)
+++ trunk/src/main/java/org/waveprotocol/wave/client/gadget/renderer/GadgetWidgetUi.java	2012-03-08 15:05:45 UTC (rev 1748)
@@ -0,0 +1,499 @@
+// @formatter:off
+/**
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.waveprotocol.wave.client.gadget.renderer;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.IFrameElement;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.dom.client.StyleInjector;
+import com.google.gwt.event.dom.client.DomEvent;
+import com.google.gwt.event.dom.client.MouseOutEvent;
+import com.google.gwt.event.dom.client.MouseOutHandler;
+import com.google.gwt.event.dom.client.MouseOverEvent;
+import com.google.gwt.event.dom.client.MouseOverHandler;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.DataResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.NamedFrame;
+import com.google.gwt.user.client.ui.Widget;
+
+import org.waveprotocol.wave.client.common.util.DomHelper;
+import org.waveprotocol.wave.client.editor.EditorStaticDeps;
+import org.waveprotocol.wave.client.scheduler.ScheduleTimer;
+import org.waveprotocol.wave.client.widget.common.ClickableDivPanel;
+import org.waveprotocol.wave.client.widget.common.HoverHelper;
+import org.waveprotocol.wave.client.widget.common.HoverHelper.Hoverable;
+import org.waveprotocol.wave.client.widget.menu.MenuButton;
+
+/**
+ * GadgetWidget UI implementation.
+ *
+ */
+public class GadgetWidgetUi extends Composite implements Hoverable {
+  /**
+   * Class that exposes protected addDomHandler method from NamedFrame as
+   * addHandler. Serves as the Gadget IFrame widget class.
+   */
+  private static class GadgetIFrame extends NamedFrame {
+    GadgetIFrame(String name) {
+      super(name);
+    }
+
+    <H extends EventHandler> HandlerRegistration addHandler(
+        final H handler, DomEvent.Type<H> type) {
+      return addDomHandler(handler, type);
+    }
+  }
+
+  /**
+   * CSS resource for the style injector.
+   */
+  public interface Resources extends ClientBundle {
+    /** The singleton instance of our CSS resources. */
+    static final Resources RESOURCES = GWT.<Resources> create(Resources.class);
+
+    @Source("Gadget.css")
+    public Css css();
+
+    @Source("broken_gadget.png")
+    DataResource brokenGadget();
+
+    @Source("loading_gadget.gif")
+    ImageResource loadingGadgetLarge();
+
+    @Source("loading_gadget_small.gif")
+    ImageResource loadingGadgetSmall();
+
+    @Source("meta_more.png")
+    ImageResource moreImage();
+
+    // Normal
+    @Source("meta_left.png")
+    ImageResource metaLeftImage();
+
+    @Source("meta_right.png")
+    ImageResource metaRightImage();
+
+    static final String META_LEFT_WIDTH = "-" + RESOURCES.metaLeftImage().getWidth() + "px";
+    static final String META_RIGHT_WIDTH = "-" + RESOURCES.metaRightImage().getWidth() + "px";
+    static final String FADE_DELAY_STRING = FADE_DELAY_MS + "ms";
+
+    @Source("meta_mid.png")
+    @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+    ImageResource metaMid();
+
+    // Down
+    @Source("meta_left_down.png")
+    ImageResource metaLeftDown();
+
+    @Source("meta_right_down.png")
+    ImageResource metaRightDown();
+
+    @Source("meta_mid_down.png")
+    ImageResource metaMidDown();
+
+    // Hover
+    @Source("meta_left_hover.png")
+    ImageResource metaLeftHover();
+
+    @Source("meta_right_hover.png")
+    ImageResource metaRightHover();
+
+    @Source("meta_mid_hover.png")
+    ImageResource metaMidHover();
+  }
+
+  /** CSS interface with style string methods. */
+  interface Css extends CssResource {
+    String panel();
+    String inline();
+    String title();
+    String gadgetFrame();
+    String iframeDiv();
+    String loadedGadgetFrame();
+    String loadingGadgetFrame();
+    String loadingGadgetSmallThrobber();
+    String loadingGadgetLargeThrobber();
+    String loadedGadget();
+    String brokenGadgetIcon();
+    String more();
+    String metaButton();
+    String metaRight();
+    String metaButtonsPanel();
+    String metaLeft();
+    String metaButtons();
+    String disabled();
+  }
+
+  /** The singleton instance of our CSS resources. */
+  static final Css CSS = Resources.RESOURCES.css();
+
+  /**
+   * Static initializer: injects stylesheet.
+   */
+  static {
+    StyleInjector.inject(CSS.getText());
+  }
+
+  @UiField ClickableDivPanel enclosingBox;
+
+  @UiField ClickableDivPanel metaButtons;
+
+  @UiField ClickableDivPanel metaLeft;
+
+  @UiField ClickableDivPanel metaButtonsPanel;
+
+  @UiField ClickableDivPanel metaRight;
+
+  @UiField Label titleLabel;
+
+  @UiField ClickableDivPanel gadgetFrame;
+
+  @UiField ClickableDivPanel iframeDiv;
+
+  interface Binder extends UiBinder<ClickableDivPanel, GadgetWidgetUi> {}
+
+  private static final Binder BINDER = GWT.create(Binder.class);
+
+  /**
+   * The maximum height of the gadget frame that should use the small throbber
+   * icon.
+   */
+  private static final int MAX_SMALL_GADGET_HEIGHT = 63;
+
+  private static final int FADE_DELAY_MS = 300;
+
+  /** Gadget IFrame. */
+  private final GadgetIFrame gadgetIframe;
+
+  private GadgetUiListener listener = null;
+
+  private MenuButton menu;
+
+  private boolean menuEnabled;
+
+  private final EditingIndicator editingIndicator;
+
+  private static enum ThrobberState {
+    SMALL,
+    LARGE,
+    BROKEN,
+    DISABLED
+  }
+
+  private ThrobberState throbberState = ThrobberState.SMALL;
+
+  /**
+   * Constructs gadget widget UI.
+   */
+  public GadgetWidgetUi(String gadgetName, EditingIndicator editingIndicator) {
+    this.editingIndicator = editingIndicator;
+    menuEnabled = false;
+    initWidget(BINDER.createAndBindUi(this));
+    gadgetIframe = new GadgetIFrame(gadgetName);
+    buildIFrame(gadgetName);
+    buildMenu();
+    hideMetaButtons();
+    gadgetFrame.addStyleName(CSS.loadingGadgetFrame());
+    makeWidgetsUnselectable();
+    HoverHelper.getInstance().setup(this);
+  }
+
+  private void makeWidgetsUnselectable() {
+    makeUnselectable(enclosingBox);
+    makeUnselectable(titleLabel);
+    makeUnselectable(gadgetFrame);
+    makeUnselectable(gadgetIframe);
+    makeUnselectable(metaButtons);
+    makeUnselectable(metaLeft);
+    makeUnselectable(metaButtonsPanel);
+    makeUnselectable(metaRight);
+    makeUnselectable(menu.getButton());
+  }
+
+  private void makeUnselectable(Widget widget) {
+    DomHelper.setContentEditable(widget.getElement(), false, false);
+    DomHelper.makeUnselectable(widget.getElement());
+  }
+
+  private void buildIFrame(String gadgetName) {
+    gadgetIframe.getElement().setId(gadgetName);
+    
+    int height = 0;
+    switch (throbberState) {
+      case SMALL:
+        gadgetIframe.addStyleName(CSS.loadingGadgetSmallThrobber());
+        height = Resources.RESOURCES.loadingGadgetSmall().getHeight();
+        break;
+      case LARGE:
+        gadgetIframe.addStyleName(CSS.loadingGadgetLargeThrobber());
+        height = Resources.RESOURCES.loadingGadgetLarge().getHeight();
+        break;
+    }
+    
+    IFrameElement iframe = getIframeElement();
+    iframe.setAttribute("vspace", "0");
+    iframe.setAttribute("hspace", "0");
+    iframe.setAttribute("frameBorder", "no");
+    iframe.setAttribute("moduleId", gadgetName);
+    iframe.setAttribute("display", "block");
+    iframe.setAttribute("height", height + "px");
+    // TODO(user): scrolling policy/settings for the wave gadgets.
+    iframe.setScrolling("no");
+    iframeDiv.add(gadgetIframe);
+  }
+
+  private void buildMenu() {
+    // TODO(user): Use menu builder.
+    menu = new MenuButton(CSS.metaButton() + " " + CSS.more(), "Gadget menu");
+    menu.addItem("Delete gadget", new Command() {
+      @Override
+      public void execute() {
+        if (listener != null) {
+          listener.deleteGadget();
+        }
+      }
+    }, true);
+    menu.addItem("Select", new Command() {
+      @Override
+      public void execute() {
+        if (listener != null) {
+          listener.selectGadget();
+        }
+      }
+    }, true);
+    menu.addItem("Reset", new Command() {
+      @Override
+      public void execute() {
+        if (listener != null) {
+          listener.resetGadget();
+        }
+      }
+    }, true);
+    metaButtonsPanel.add(menu.getButton());
+  }
+
+  /**
+   * Task to set display:none to deactivate the menu. Note that opacity 0 leaves
+   * invisible, but active element.
+   */
+  private final ScheduleTimer disableMenuTask = new ScheduleTimer() {
+    @Override
+    public void run() {
+      metaButtons.getElement().getStyle().setDisplay(Display.NONE);
+    }
+  };
+
+  private void hideMetaButtons() {
+    metaButtons.getElement().getStyle().setOpacity(0);
+    disableMenuTask.schedule(FADE_DELAY_MS);
+  }
+
+  private void showMetaButtons() {
+    disableMenuTask.cancel();
+    metaButtons.getElement().getStyle().clearDisplay();
+    metaButtons.getOffsetWidth(); // Force the browser to render the first frame.
+    metaButtons.getElement().getStyle().clearOpacity();
+  }
+
+  /**
+   * Gets the gadget iframe element.
+   *
+   * @return Gadget iframe element.
+   */
+  public IFrameElement getIframeElement() {
+    return IFrameElement.as(gadgetIframe.getElement());
+  }
+
+  /**
+   * Sets the iframe height attribute.
+   *
+   * @param height New iframe height.
+   */
+  public void setIframeHeight(long height) {
+    getIframeElement().setAttribute("height", height + "px");
+    if (height > MAX_SMALL_GADGET_HEIGHT) {
+      if (throbberState == ThrobberState.SMALL) {
+        gadgetIframe.removeStyleName(CSS.loadingGadgetSmallThrobber());
+        gadgetIframe.addStyleName(CSS.loadingGadgetLargeThrobber());
+        throbberState = ThrobberState.LARGE;
+      }
+    } else {
+      if (throbberState == ThrobberState.LARGE) {
+        gadgetIframe.removeStyleName(CSS.loadingGadgetLargeThrobber());
+        gadgetIframe.addStyleName(CSS.loadingGadgetSmallThrobber());
+        throbberState = ThrobberState.SMALL;
+      }
+    }
+  }
+
+  /**
+   * Sets the iframe width attribute.
+   *
+   * @param width New iframe width.
+   */
+  public void setIframeWidth(String width) {
+    getIframeElement().setAttribute("width", width);
+    iframeDiv.setWidth(width);
+    gadgetFrame.setWidth(width);
+  }
+
+  /**
+   * Sets gadget iframe ID.
+   *
+   * @param newId new gadget iframe id.
+   */
+  public void setIframeId(String newId) {
+    gadgetIframe.getElement().setId(newId);
+  }
+
+  /**
+   * Sets the iframe source attribute.
+   *
+   * @param source New iframe source.
+   */
+  public void setIframeSource(String source) {
+    if ((source != null) && !source.equals(getIframeElement().getSrc())) {
+      getIframeElement().setSrc(source);
+    }
+  }
+
+  /**
+   * Removes the gadget loading throbber by replacing the iframe CSS.
+   */
+  public void removeThrobber() {
+    switch (throbberState) {
+      case SMALL:
+        gadgetIframe.removeStyleName(CSS.loadingGadgetSmallThrobber());
+        break;
+      case LARGE:
+        gadgetIframe.removeStyleName(CSS.loadingGadgetLargeThrobber());
+        break;
+    }
+    throbberState = ThrobberState.DISABLED;
+    gadgetIframe.addStyleName(CSS.loadedGadget());
+    gadgetFrame.removeStyleName(CSS.loadingGadgetFrame());
+    gadgetFrame.addStyleName(CSS.loadedGadgetFrame());
+  }
+
+  /**
+   * Shows broken gadget icon and error message.
+   */
+  public void showBrokenGadget(String error) {
+    switch (throbberState) {
+      case SMALL:
+        gadgetIframe.removeStyleName(CSS.loadingGadgetSmallThrobber());
+        getIframeElement().setAttribute("height", (MAX_SMALL_GADGET_HEIGHT + 1) + "px");
+        break;
+      case LARGE:
+        gadgetIframe.removeStyleName(CSS.loadingGadgetLargeThrobber());
+        break;
+    }
+    throbberState = ThrobberState.BROKEN;
+    gadgetIframe.addStyleName(CSS.brokenGadgetIcon());
+  }
+
+  public String getTitleLabelText() {
+    return titleLabel.getText();
+  }
+
+  /**
+   * Sets the text of the title label.
+   *
+   * @param text New title label text.
+   */
+  public void setTitleLabelText(String text) {
+    // TODO(user): Remove when the editor ignores mutations from doodads.
+    EditorStaticDeps.startIgnoreMutations();
+    try {
+      titleLabel.setText(text);
+    } finally {
+      EditorStaticDeps.endIgnoreMutations();
+    }
+  }
+
+  public void setGadgetUiListener(GadgetUiListener listener) {
+    this.listener = listener;
+  }
+
+  /**
+   * Enables the overlay Gadget menu.
+   */
+  public void enableMenu() {
+    menuEnabled = true;
+  }
+
+  /**
+   * Disables the overlay Gadget menu.
+   */
+  public void disableMenu() {
+    hideMetaButtons();
+    menu.onOff(menu.getButton());
+    menuEnabled = false;
+  }
+
+  public void makeInline() {
+    enclosingBox.addStyleName(CSS.inline());
+  }
+
+  @Override
+  public void onMouseEnter() {
+    if (!menuEnabled || !editingIndicator.isEditing()) {
+      return;
+    }
+    showMetaButtons();
+  }
+
+  @Override
+  public void onMouseLeave() {
+    hideMetaButtons();
+  }
+
+  @Override
+  public void addHandlers(MouseOverHandler mouseOverHandler, MouseOutHandler mouseOutHandler) {
+    // TODO(user): Investigate why the event propagation does not work and
+    // remove unnecessary handler setup.
+    addDomHandler(mouseOverHandler, MouseOverEvent.getType());
+    addDomHandler(mouseOutHandler, MouseOutEvent.getType());
+    enclosingBox.addMouseOverHandler(mouseOverHandler);
+    gadgetFrame.addMouseOverHandler(mouseOverHandler);
+    iframeDiv.addMouseOverHandler(mouseOverHandler);
+    gadgetIframe.addHandler(mouseOverHandler, MouseOverEvent.getType());
+    metaButtons.addMouseOverHandler(mouseOverHandler);
+    metaLeft.addMouseOverHandler(mouseOverHandler);
+    metaButtonsPanel.addMouseOverHandler(mouseOverHandler);
+    metaRight.addMouseOverHandler(mouseOverHandler);
+    menu.getButton().addMouseOverHandler(mouseOverHandler);
+  }
+
+  @Override
+  public boolean isOrHasChild(Element e) {
+    return enclosingBox.getElement().isOrHasChild(e);
+  }
+}

Added: trunk/src/main/java/org/waveprotocol/wave/client/gadget/renderer/GadgetWidgetUi.ui.xml
===================================================================
--- trunk/src/main/java/org/waveprotocol/wave/client/gadget/renderer/GadgetWidgetUi.ui.xml	                        (rev 0)
+++ trunk/src/main/java/org/waveprotocol/wave/client/gadget/renderer/GadgetWidgetUi.ui.xml	2012-03-08 15:05:45 UTC (rev 1748)
@@ -0,0 +1,49 @@
+<!--
+  UI binder for Gadget Container.
+  Author: vadimg at google.com (Vadim Gerasimov)
+-->
+<ui:UiBinder
+  xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:gwt='urn:import:com.google.gwt.user.client.ui'
+  xmlns:wb='urn:import:org.waveprotocol.wave.client.widget.common'
+>
+  <ui:with
+    field='res'
+    type='org.waveprotocol.wave.client.gadget.renderer.GadgetWidgetUi.Resources'
+  />
+  <wb:ClickableDivPanel
+    ui:field='enclosingBox'
+    addStyleNames='{res.css.panel}'
+  >
+    <!-- Gadget title label. -->
+    <gwt:Label ui:field='titleLabel' addStyleNames='{res.css.title}'/>
+    <wb:ClickableDivPanel
+      ui:field='gadgetFrame'
+      addStyleNames='{res.css.gadgetFrame}'
+    >
+      <!-- Main gadget iframe. -->
+      <wb:ClickableDivPanel
+        ui:field='iframeDiv'
+        addStyleNames='{res.css.iframeDiv}'
+      />
+      <!-- Mouse-over overlay menu. -->
+      <wb:ClickableDivPanel
+        ui:field='metaButtons'
+        addStyleNames='{res.css.metaButtons}'
+      >
+        <wb:ClickableDivPanel
+          ui:field='metaLeft'
+          addStyleNames='{res.css.metaLeft}'
+        />
+        <wb:ClickableDivPanel
+          ui:field='metaButtonsPanel'
+          addStyleNames='{res.css.metaButtonsPanel}'
+        />
+        <wb:ClickableDivPanel
+          ui:field='metaRight'
+          addStyleNames='{res.css.metaRight}'
+        />
+      </wb:ClickableDivPanel>
+    </wb:ClickableDivPanel>
+  </wb:ClickableDivPanel>
+</ui:UiBinder>

Added: trunk/src/main/resources/icons/chain-close-black.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/main/resources/icons/chain-close-black.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/main/resources/icons/chain-close-white.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/main/resources/icons/chain-close-white.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/main/resources/icons/chain-closed-grey.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/main/resources/icons/chain-closed-grey.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/main/resources/icons/chain-open-black.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/main/resources/icons/chain-open-black.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/main/resources/icons/chain-open-grey.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/main/resources/icons/chain-open-grey.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/main/resources/icons/chain-open-white.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/main/resources/icons/chain-open-white.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/main/resources/icons/picture-black.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/main/resources/icons/picture-black.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/main/resources/icons/picture-grey.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/main/resources/icons/picture-grey.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/src/main/resources/icons/picture-white.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/main/resources/icons/picture-white.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Copied: trunk/src/main/resources/icons/pref-black.png (from rev 1747, trunk/src/main/java/cc/kune/core/client/resources/pref-black.png)
===================================================================
(Binary files differ)

Copied: trunk/src/main/resources/icons/pref-grey.png (from rev 1747, trunk/src/main/java/cc/kune/core/client/resources/pref-grey.png)
===================================================================
(Binary files differ)

Copied: trunk/src/main/resources/icons/pref-white.png (from rev 1747, trunk/src/main/java/cc/kune/core/client/resources/pref-white.png)
===================================================================
(Binary files differ)

Modified: trunk/src/main/webapp/others/kune-client-actions.xml
===================================================================
--- trunk/src/main/webapp/others/kune-client-actions.xml	2012-03-07 13:08:17 UTC (rev 1747)
+++ trunk/src/main/webapp/others/kune-client-actions.xml	2012-03-08 15:05:45 UTC (rev 1748)
@@ -424,39 +424,6 @@
     </guiActionDescriptor>
 
 
-    <guiActionDescriptor>
-      <type>wave-gadget</type>
-      <extensionName>WavePlan</extensionName>
-      <name>New meet planer</name>
-      <path>New Gadget»Calendar</path>
-      <description>This gadget helps you to coordinate multi-participant schedules and make decisions in an intuitive manner.
-      </description>
-      <new-content-title>New meet planner</new-content-title>
-      <new-content-textintro></new-content-textintro>
-      <enabled>true</enabled>
-      <typeIds>
-        <typeId origTypeId="docs.folder" destTypeId="docs.doc"/>
-        <typeId origTypeId="docs.root" destTypeId="docs.doc"/>
-        <typeId origTypeId="blogs.blog" destTypeId="blogs.post"/>
-        <typeId origTypeId="lists.list" destTypeId="lists.post"/>
-                <!-- Add gadget in docs directly: -->
-        <typeId origTypeId="docs.doc" destTypeId="docs.doc"/>
-        <typeId origTypeId="blogs.post" destTypeId="blogs.post"/>
-        <typeId origTypeId="lists.post" destTypeId="lists.post"/>
-        <typeId origTypeId="wiki.wikipage" destTypeId="wiki.wikipage"/>
-        <typeId origTypeId="tasks.task" destTypeId="tasks.task"/>
-      </typeIds>
-      <rol>
-        <!-- Administrator, Editor, Viewer -->
-        <rolRequired>Editor</rolRequired>
-        <authNeed>true</authNeed>
-      </rol>
-      <!-- only for new waves: anyone, onlymembers, onlyadmins, ask (not
-        used yet) -->
-      <participants>ask</participants>
-    </guiActionDescriptor>
-
-
     <!-- Media ================================================== -->
 
     <guiActionDescriptor>




More information about the kune-commits mailing list