[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