[Commits] r966 - in sandbox/opengeo/geoexplorer: build examples lib lib/GeoExt/data lib/GeoExt/widgets lib/GeoExt/widgets/form lib/GeoExt/widgets/tips lib/GeoExt/widgets/tree tests tests/lib/GeoExt/data tests/lib/GeoExt/widgets tests/lib/GeoExt/widgets/form

commits at geoext.org commits at geoext.org
Tue Jun 2 23:10:46 CEST 2009


Author: tschaub
Date: 2009-06-02 23:10:46 +0200 (Tue, 02 Jun 2009)
New Revision: 966

Added:
   sandbox/opengeo/geoexplorer/examples/mappanel-viewport.html
   sandbox/opengeo/geoexplorer/examples/mappanel-viewport.js
   sandbox/opengeo/geoexplorer/examples/toolbar.html
   sandbox/opengeo/geoexplorer/examples/toolbar.js
   sandbox/opengeo/geoexplorer/examples/zoomslider.html
   sandbox/opengeo/geoexplorer/examples/zoomslider.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/Action.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/LegendImage.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/LegendWMS.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/ZoomSlider.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/form.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tips/ZoomSliderTip.js
   sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/Action.html
   sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/ZoomSlider.html
   sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/form.html
Removed:
   sandbox/opengeo/geoexplorer/build/apidoc_config/
Modified:
   sandbox/opengeo/geoexplorer/examples/legendpanel.html
   sandbox/opengeo/geoexplorer/examples/legendpanel.js
   sandbox/opengeo/geoexplorer/examples/popup.js
   sandbox/opengeo/geoexplorer/examples/tree.html
   sandbox/opengeo/geoexplorer/examples/wms-capabilities.js
   sandbox/opengeo/geoexplorer/lib/GeoExt.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/data/FeatureRecord.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/data/LayerReader.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/data/LayerRecord.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/data/LayerStore.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/data/ProtocolProxy.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/data/ScaleStore.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/data/WMSCapabilitiesReader.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/data/WMSCapabilitiesStore.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/LegendPanel.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/MapPanel.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/Popup.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/form/SearchAction.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tips/SliderTip.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tree/BaseLayerContainer.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tree/LayerContainer.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tree/LayerNode.js
   sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tree/OverlayLayerContainer.js
   sandbox/opengeo/geoexplorer/tests/lib/GeoExt/data/LayerStore.html
   sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/LegendPanel.html
   sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/MapPanel.html
   sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/Popup.html
   sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/form/SearchAction.html
   sandbox/opengeo/geoexplorer/tests/list-tests.html
Log:
merge up to r965 from trunk

Modified: sandbox/opengeo/geoexplorer/examples/legendpanel.html
===================================================================
--- sandbox/opengeo/geoexplorer/examples/legendpanel.html	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/examples/legendpanel.html	2009-06-02 21:10:46 UTC (rev 966)
@@ -1,26 +1,26 @@
-<html>
-    <head>
-        <script type="text/javascript" src="http://dev.geoext.org/trunk/ext/adapter/ext/ext-base.js"></script>
-        <script type="text/javascript" src="http://dev.geoext.org/trunk/ext/ext-all.js"></script>
-        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
-        <link rel="stylesheet" type="text/css" href="http://extjs.com/deploy/dev/examples/shared/examples.css"></link>
-        <script src="http://openlayers.org/api/2.8-rc2/OpenLayers.js"></script>
-        <script type="text/javascript" src="../lib/GeoExt.js"></script>
-
-        <script type="text/javascript" src="legendpanel.js"></script>
-
-        <style type="text/css">
-        .mylabel {
-            font-weight: bold;
-            color: red;
-        }
-        </style>
-    </head>
-    <body>
-        <h1>GeoExt.LegendPanel</h1>
-        <p>This example shows the how to create a LegendPanel that autopopulates with legends from a map
-        that has already been created.</p>
-        <p>The js is not minified so it is readable. See <a href="legendpanel.js">legendpanel.js</a>.</p>
-        <div id="view"></div>
-    </body>
-</html>
+<html>
+    <head>
+        <script type="text/javascript" src="http://dev.geoext.org/trunk/ext/adapter/ext/ext-base.js"></script>
+        <script type="text/javascript" src="http://dev.geoext.org/trunk/ext/ext-all.js"></script>
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
+        <link rel="stylesheet" type="text/css" href="http://extjs.com/deploy/dev/examples/shared/examples.css"></link>
+        <script src="http://openlayers.org/api/2.8-rc2/OpenLayers.js"></script>
+        <script type="text/javascript" src="../lib/GeoExt.js"></script>
+
+        <script type="text/javascript" src="legendpanel.js"></script>
+
+        <style type="text/css">
+        .mylabel {
+            font-weight: bold;
+            color: red;
+        }
+        </style>
+    </head>
+    <body>
+        <h1>GeoExt.LegendPanel</h1>
+        <p>This example shows the how to create a LegendPanel that autopopulates with legends from a map
+        that has already been created.</p>
+        <p>The js is not minified so it is readable. See <a href="legendpanel.js">legendpanel.js</a>.</p>
+        <div id="view"></div>
+    </body>
+</html>
\ No newline at end of file

Modified: sandbox/opengeo/geoexplorer/examples/legendpanel.js
===================================================================
--- sandbox/opengeo/geoexplorer/examples/legendpanel.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/examples/legendpanel.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -1,87 +1,91 @@
-
-var mapPanel;
-
-Ext.onReady(function() {
-    var map = new OpenLayers.Map({allOverlays: true});
-    map.addLayers([
-        new OpenLayers.Layer.WMS(
-            "Tasmania",
-            "http://publicus.opengeo.org/geoserver/wms?",
-            {layers: 'topp:tasmania_state_boundaries', format: 'image/png', transparent: true},
-            {singleTile: true}),
-        new OpenLayers.Layer.WMS(
-            "Cities and Roads",
-            "http://publicus.opengeo.org/geoserver/wms?",
-            {layers: 'topp:tasmania_cities,topp:tasmania_roads', format: 'image/png', transparent: true},
-            {singleTile: true}),
-        new OpenLayers.Layer.Vector('Polygons', {styleMap: new OpenLayers.StyleMap({
-                "default": new OpenLayers.Style({
-                    pointRadius: 8,
-                    fillColor: "#00ffee",
-                    strokeColor: "#000000",
-                    strokeWidth: 2
-                }) }) })
-    ]);
-    map.addControl(new OpenLayers.Control.LayerSwitcher());
-
-    addLayer = function() {
-        var wmslayer = new OpenLayers.Layer.WMS("Bodies of Water",
-            "http://publicus.opengeo.org/geoserver/wms?",
-            {layers: 'topp:tasmania_water_bodies', format: 'image/png', transparent: true},
-            {singleTile: true});
-        mapPanel.map.addLayer(wmslayer);
-    };
-
-    removeLayer = function() {
-        mapPanel.map.removeLayer(mapPanel.map.layers[1]);
-    };
-
-    moveLayer = function(idx) {
-        mapPanel.map.setLayerIndex(mapPanel.map.layers[0], idx);
-    };
-
-    toggleVisibility = function() {
-        mapPanel.map.layers[1].setVisibility(!mapPanel.map.layers[1].getVisibility());
-    };
-
-    updateHideInLegend = function() {
-        mapPanel.layers.getAt(1).set("hideInLegend", true);
-    };
-
-    updateLegendUrl = function() {
-        mapPanel.layers.getAt(0).set("legendURL", "http://www.geoext.org//trac/geoext/chrome/site/img/GeoExt.png");
-    };
-
-    mapPanel = new GeoExt.MapPanel({
-        region: 'center',
-        height: 400,
-        width: 600,
-        map: map,
-        center: new OpenLayers.LonLat(146.4, -41.6),
-        zoom: 7});
-
-    legendPanel = new GeoExt.LegendPanel({
-        labelCls: 'mylabel',
-        bodyStyle: 'padding:5px',
-        width: 350,
-        tbar: new Ext.Toolbar({items: [
-            new Ext.Button({text: 'add', handler: addLayer}),
-            new Ext.Button({text: 'remove', handler: removeLayer}),
-            new Ext.Button({text: 'movetotop', handler: function() { moveLayer(10); } }),
-            new Ext.Button({text: 'moveup', handler: function() { moveLayer(1); } }),
-            new Ext.Button({text: 'togglevis', handler: toggleVisibility}),
-            new Ext.Button({text: 'hide', handler: updateHideInLegend}),
-            new Ext.Button({text: 'legendurl', handler: updateLegendUrl})
-            ]}),
-        autoScroll: true,
-        region: 'west'});
-
-     new Ext.Panel({
-        title: "GeoExt LegendPanel Demo",
-        layout: 'border',
-        renderTo: 'view',
-        height: 400,
-        width: 800,
-        items: [legendPanel, mapPanel] });
-});
-
+
+var mapPanel;
+
+Ext.onReady(function() {
+    var map = new OpenLayers.Map({allOverlays: true});
+    map.addLayers([
+        new OpenLayers.Layer.WMS(
+            "Tasmania",
+            "http://publicus.opengeo.org/geoserver/wms?",
+            {layers: 'topp:tasmania_state_boundaries', format: 'image/png', transparent: true},
+            {singleTile: true}),
+        new OpenLayers.Layer.WMS(
+            "Cities and Roads",
+            "http://publicus.opengeo.org/geoserver/wms?",
+            {layers: 'topp:tasmania_cities,topp:tasmania_roads', format: 'image/png', transparent: true},
+            {singleTile: true}),
+        new OpenLayers.Layer.Vector('Polygons', {styleMap: new OpenLayers.StyleMap({
+                "default": new OpenLayers.Style({
+                    pointRadius: 8,
+                    fillColor: "#00ffee",
+                    strokeColor: "#000000",
+                    strokeWidth: 2
+                }) }) })
+    ]);
+    map.addControl(new OpenLayers.Control.LayerSwitcher());
+
+    var addLayer = function() {
+        var wmslayer = new OpenLayers.Layer.WMS("Bodies of Water",
+            "http://publicus.opengeo.org/geoserver/wms?",
+            {layers: 'topp:tasmania_water_bodies', format: 'image/png', transparent: true},
+            {singleTile: true});
+        mapPanel.map.addLayer(wmslayer);
+    };
+
+    var removeLayer = function() {
+        mapPanel.map.removeLayer(mapPanel.map.layers[1]);
+    };
+
+    var moveLayer = function(idx) {
+        mapPanel.map.setLayerIndex(mapPanel.map.layers[0], idx);
+    };
+
+    var toggleVisibility = function() {
+        mapPanel.map.layers[1].setVisibility(!mapPanel.map.layers[1].getVisibility());
+    };
+
+    var updateHideInLegend = function() {
+        mapPanel.layers.getAt(1).set("hideInLegend", true);
+    };
+
+    var updateLegendUrl = function() {
+        mapPanel.layers.getAt(0).set("legendURL", "http://www.geoext.org/trac/geoext/chrome/site/img/GeoExt.png");
+    };
+
+    var mapPanel = new GeoExt.MapPanel({
+        region: 'center',
+        height: 400,
+        width: 600,
+        map: map,
+        center: new OpenLayers.LonLat(146.4, -41.6),
+        zoom: 7
+    });
+
+    var legendPanel = new GeoExt.LegendPanel({
+        labelCls: 'mylabel',
+        bodyStyle: 'padding:5px',
+        width: 350,
+        autoScroll: true,
+        region: 'west'
+    });
+
+    new Ext.Panel({
+        title: "GeoExt LegendPanel Demo",
+        layout: 'border',
+        renderTo: 'view',
+        height: 400,
+        width: 800,
+        tbar: new Ext.Toolbar({
+            items: [
+                {text: 'add', handler: addLayer},
+                {text: 'remove', handler: removeLayer},
+                {text: 'movetotop', handler: function() { moveLayer(10); } },
+                {text: 'moveup', handler: function() { moveLayer(1); } },
+                {text: 'togglevis', handler: toggleVisibility},
+                {text: 'hide', handler: updateHideInLegend},
+                {text: 'legendurl', handler: updateLegendUrl}
+            ]
+        }),
+        items: [legendPanel, mapPanel]
+    });
+});

Added: sandbox/opengeo/geoexplorer/examples/mappanel-viewport.html
===================================================================
--- sandbox/opengeo/geoexplorer/examples/mappanel-viewport.html	                        (rev 0)
+++ sandbox/opengeo/geoexplorer/examples/mappanel-viewport.html	2009-06-02 21:10:46 UTC (rev 966)
@@ -0,0 +1,40 @@
+<html>
+    <head>
+        <title>GeoExt MapPanel in an Ext Viewport</title>
+
+        <script type="text/javascript" src="http://extjs.cachefly.net/builds/ext-cdn-771.js"></script>
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
+        <link rel="stylesheet" type="text/css" href="http://extjs.com/deploy/dev/examples/shared/examples.css"></link>
+
+        <!-- Google Maps API for "localhost" -->
+        <!--
+        <script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAjpkAC9ePGem0lIq5XcMiuhR_wWLPFku8Ix9i2SXYRVK3e45q1BQUd_beF8dtzKET_EteAjPdGDwqpQ'></script>
+        -->
+        <!-- Google Maps API for "dev.geoext.org" -->
+        <script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAA_5ak-hsiH4j5bQQn7-k66xTWxvN8zH6Ta_pgIhhU0TB7bG8iAhS99ituPif4lG-2CHXoZ3qenLnK1g'></script>
+
+        <script src="http://openlayers.org/api/2.8-rc4/OpenLayers.js"></script>
+        <!--
+        <script type="text/javascript" src="../../openlayers/lib/OpenLayers.js"></script>
+        -->
+
+        <script type="text/javascript" src="../lib/GeoExt.js"></script>
+        
+        <script type="text/javascript" src="mappanel-viewport.js"></script>
+
+    </head>
+    <body>
+        <div id="title">
+            <h1>GeoExt.MapPanel in an Ext.Viewport</h1>
+        </div>
+
+        <div id="description">
+            <p>This example shows how to place a MapPanel as a region in a
+            container using a border layout, the container is a Viewport in
+            this example.</p>
+            
+            <p>The js is not minified so it is readable. See <a
+            href="mappanel-viewport.js">mappanel-viewport.js</a>.</p>
+        </div>
+    </body>
+</html>

Added: sandbox/opengeo/geoexplorer/examples/mappanel-viewport.js
===================================================================
--- sandbox/opengeo/geoexplorer/examples/mappanel-viewport.js	                        (rev 0)
+++ sandbox/opengeo/geoexplorer/examples/mappanel-viewport.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -0,0 +1,68 @@
+var mapPanel;
+
+Ext.onReady(function() {
+
+    // if true a google layer is used, if false
+    // the bluemarble WMS layer is used
+    var google = false;
+
+    var options, layer;
+    var extent = new OpenLayers.Bounds(-5, 35, 15, 55);
+
+    if (google) {
+
+        options = {
+            projection: new OpenLayers.Projection("EPSG:900913"),
+            units: "m",
+            numZoomLevels: 18,
+            maxResolution: 156543.0339,
+            maxExtent: new OpenLayers.Bounds(-20037508, -20037508,
+                                             20037508, 20037508.34)
+        };
+
+        layer = new OpenLayers.Layer.Google(
+            "Google Satellite",
+            {type: G_SATELLITE_MAP, sphericalMercator: true}
+        );
+
+        extent.transform(
+            new OpenLayers.Projection("EPSG:4326"), options.projection
+        );
+
+    } else {
+        layer = new OpenLayers.Layer.WMS(
+            "bluemarble",
+            "http://sigma.openplans.org/geoserver/wms?",
+            {layers: 'bluemarble'},
+            {isBaseLayer: true}
+        );
+    }
+
+    var map = new OpenLayers.Map(options);
+
+    new Ext.Viewport({
+        layout: "border",
+        items: [{
+            region: "north",
+            contentEl: "title",
+            height: 50
+        }, {
+            region: "center",
+            id: "mappanel",
+            title: "Map",
+            xtype: "gx_mappanel",
+            map: map,
+            layers: [layer],
+            extent: extent,
+            split: true
+        }, {
+            region: "east",
+            title: "Description",
+            contentEl: "description",
+            width: 200,
+            split: true
+        }]
+    });
+
+    mapPanel = Ext.getCmp("mappanel");
+});

Modified: sandbox/opengeo/geoexplorer/examples/popup.js
===================================================================
--- sandbox/opengeo/geoexplorer/examples/popup.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/examples/popup.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -36,7 +36,7 @@
                 }
             }
         });
-        popup.addToMapPanel(mapPanel);
+        mapPanel.add(popup);
     }
 
     // create popup on "featureselected"

Added: sandbox/opengeo/geoexplorer/examples/toolbar.html
===================================================================
--- sandbox/opengeo/geoexplorer/examples/toolbar.html	                        (rev 0)
+++ sandbox/opengeo/geoexplorer/examples/toolbar.html	2009-06-02 21:10:46 UTC (rev 966)
@@ -0,0 +1,51 @@
+<html>
+    <head>
+        <title>GeoExt Toolbar Example</title>
+
+        <script type="text/javascript" src="http://extjs.cachefly.net/builds/ext-cdn-771.js"></script>
+        <!--
+        <script type="text/javascript" src="../../ext/adapter/ext/ext-base.js"></script>
+        <script type="text/javascript" src="../../ext/ext-all-debug.js"></script>
+        <link rel="stylesheet" type="text/css" href="../../ext/resources/css/ext-all.css" />
+        <link rel="stylesheet" type="text/css" href="../../ext/examples/shared/examples.css"></link>
+        -->
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
+        <link rel="stylesheet" type="text/css" href="http://extjs.com/deploy/dev/examples/shared/examples.css"></link>
+        <script src="http://openlayers.org/api/2.8-rc2/OpenLayers.js"></script>
+        <!--
+        <script src="../../openlayers/lib/OpenLayers.js"></script>
+        -->
+        <script type="text/javascript" src="../lib/GeoExt.js"></script>
+
+        <script type="text/javascript" src="toolbar.js"></script>
+
+        <style type="text/css">
+            /* work around an Ext bug that makes the rendering
+               of menu items not as one would expect */
+            .ext-ie .x-menu-item-icon {
+                left: -24px;
+            }
+            .ext-strict .x-menu-item-icon {
+                left: 3px;
+            }
+            .ext-ie6 .x-menu-item-icon {
+                left: -24px;
+            }
+            .ext-ie7 .x-menu-item-icon {
+                left: -24px;
+            }
+        </style>
+    </head>
+    <body>
+        <h1>OpenLayers controls in an Ext toolbar</h1>
+
+        <p>This example shows how to add OpenLayers controls in an Ext toolbar.
+        GeoExt provides the GeoExt.Action class for adapating a control to an
+        object that can be inserted in a toolbar or in a menu.</p>
+
+        <p>The js is not minified so it is readable. See <a
+        href="toolbar.js">toolbar.js</a>.</p>
+
+        <div id="mappanel"></div>
+    </body>
+</html>

Added: sandbox/opengeo/geoexplorer/examples/toolbar.js
===================================================================
--- sandbox/opengeo/geoexplorer/examples/toolbar.js	                        (rev 0)
+++ sandbox/opengeo/geoexplorer/examples/toolbar.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -0,0 +1,140 @@
+
+Ext.onReady(function() {
+    var map = new OpenLayers.Map();
+    var wms = new OpenLayers.Layer.WMS(
+        "bluemarble",
+        "http://sigma.openplans.org/geoserver/wms?",
+        {layers: 'bluemarble'}
+    );
+    var vector = new OpenLayers.Layer.Vector("vector");
+    map.addLayers([wms, vector]);
+    
+    var ctrl, toolbarItems = [], action, actions = {};
+
+    // ZoomToMaxExtent control, a "button" control
+    action = new GeoExt.Action({
+        control: new OpenLayers.Control.ZoomToMaxExtent(),
+        map: map,
+        text: "max extent"
+    });
+    actions["max_extent"] = action;
+    toolbarItems.push(action);
+    toolbarItems.push("-");
+
+    // Navigation control and DrawFeature controls
+    // in the same toggle group
+    action = new GeoExt.Action({
+        text: "nav",
+        control: new OpenLayers.Control.Navigation(),
+        map: map,
+        // button options
+        toggleGroup: "draw",
+        allowDepress: false,
+        pressed: true,
+        // check item options
+        group: "draw",
+        checked: true
+    });
+    actions["nav"] = action;
+    toolbarItems.push(action);
+
+    action = new GeoExt.Action({
+        text: "draw poly",
+        control: new OpenLayers.Control.DrawFeature(
+            vector, OpenLayers.Handler.Polygon
+        ),
+        map: map,
+        // button options
+        toggleGroup: "draw",
+        allowDepress: false,
+        // check item options
+        group: "draw"
+    });
+    actions["draw_poly"] = action;
+    toolbarItems.push(action);
+
+    action = new GeoExt.Action({
+        text: "draw line",
+        control: new OpenLayers.Control.DrawFeature(
+            vector, OpenLayers.Handler.Path
+        ),
+        map: map,
+        // button options
+        toggleGroup: "draw",
+        allowDepress: false,
+        // check item options
+        group: "draw"
+    });
+    actions["draw_line"] = action;
+    toolbarItems.push(action);
+    toolbarItems.push("-");
+
+    // SelectFeature control, a "toggle" control
+    action = new GeoExt.Action({
+        text: "select",
+        control: new OpenLayers.Control.SelectFeature(vector, {
+            type: OpenLayers.Control.TYPE_TOGGLE,
+            hover: true
+        }),
+        map: map,
+        // button options
+        enableToggle: true
+    });
+    actions["select"] = action;
+    toolbarItems.push(action);
+    toolbarItems.push("-");
+
+    // Navigation history - two "button" controls
+    ctrl = new OpenLayers.Control.NavigationHistory();
+    map.addControl(ctrl);
+
+    action = new GeoExt.Action({
+        text: "previous",
+        control: ctrl.previous,
+        disabled: true
+    });
+    actions["previous"] = action;
+    toolbarItems.push(action);
+
+    action = new GeoExt.Action({
+        text: "next",
+        control: ctrl.next,
+        disabled: true
+    });
+    actions["next"] = action;
+    toolbarItems.push(action);
+    toolbarItems.push("->");
+
+    // Reuse the GeoExt.Action objects created above
+    // as menu items
+    toolbarItems.push({
+        text: "menu",
+        menu: new Ext.menu.Menu({
+            items: [
+                // ZoomToMaxExtent
+                actions["max_extent"],
+                // Nav
+                new Ext.menu.CheckItem(actions["nav"]),
+                // Draw poly
+                new Ext.menu.CheckItem(actions["draw_poly"]),
+                // Draw line
+                new Ext.menu.CheckItem(actions["draw_line"]),
+                // Select control
+                new Ext.menu.CheckItem(actions["select"]),
+                // Navigation history control
+                actions["previous"],
+                actions["next"]
+            ]
+        })
+    });
+
+    var mapPanel = new GeoExt.MapPanel({
+        renderTo: "mappanel",
+        height: 400,
+        width: 600,
+        map: map,
+        center: new OpenLayers.LonLat(5, 45),
+        zoom: 4,
+        tbar: toolbarItems
+    });
+});

Modified: sandbox/opengeo/geoexplorer/examples/tree.html
===================================================================
--- sandbox/opengeo/geoexplorer/examples/tree.html	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/examples/tree.html	2009-06-02 21:10:46 UTC (rev 966)
@@ -5,8 +5,8 @@
         <script type="text/javascript" src="http://extjs.cachefly.net/builds/ext-cdn-771.js"></script>
         <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
         <link rel="stylesheet" type="text/css" href="http://extjs.com/deploy/dev/examples/shared/examples.css"></link>
+        <script src="http://openlayers.org/api/2.8-rc2/OpenLayers.js"></script>
         <script type="text/javascript" src="../lib/GeoExt.js"></script>
-        <script src="http://openlayers.org/api/2.8-rc2/OpenLayers.js"></script>
 
         <script type="text/javascript" src="tree.js"></script>
 
@@ -16,7 +16,7 @@
             <h1>GeoExt.tree Components</h1>
             <p>This example shows how to work with layer tree.  The basic
             component for building layer trees is the LayerNode, and there are
-            differnt types of containers for automatically adding a map's
+            different types of containers for automatically adding a map's
             layers to the tree. The tree configuration of this example is pure
             JSON and can be edited live by clicking on the "Show/Edit Tree Config"
             button below the layers panel.<p>
@@ -24,4 +24,4 @@
             <a href="tree.js">tree.js</a>.</p>
         </div>
     </body>
-</html>
\ No newline at end of file
+</html>

Modified: sandbox/opengeo/geoexplorer/examples/wms-capabilities.js
===================================================================
--- sandbox/opengeo/geoexplorer/examples/wms-capabilities.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/examples/wms-capabilities.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -36,6 +36,7 @@
             title: "Preview: " + record.get("title"),
             width: 512,
             height: 256,
+            layout: "fit",
             items: [{
                 xtype: "gx_mappanel",
                 layers: [layer],

Added: sandbox/opengeo/geoexplorer/examples/zoomslider.html
===================================================================
--- sandbox/opengeo/geoexplorer/examples/zoomslider.html	                        (rev 0)
+++ sandbox/opengeo/geoexplorer/examples/zoomslider.html	2009-06-02 21:10:46 UTC (rev 966)
@@ -0,0 +1,22 @@
+<html>
+    <head>
+        <title>GeoExt ZoomSlider</title>
+
+        <script type="text/javascript" src="http://extjs.cachefly.net/builds/ext-cdn-771.js"></script>
+        <link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-2.2.1/resources/css/ext-all.css" />
+        <link rel="stylesheet" type="text/css" href="http://extjs.com/deploy/dev/examples/shared/examples.css"></link>
+        <script src="http://openlayers.org/api/2.8-rc2/OpenLayers.js"></script>
+        <script type="text/javascript" src="../lib/GeoExt.js"></script>
+
+        <script type="text/javascript" src="zoomslider.js"></script>
+    </head>
+    <body>
+        <h1>GeoExt.ZoomSlider</h1>
+        <p>The ZoomSlider allows control of the map scale using an Ext.Slider.
+        It is also possible to add a special tooltip plugin, ZoomSliderTip, which
+        will show the zoom level, scale and resolution while dragging the slider
+        (the content is configurable).<p>
+        <p>The js is not minified so it is readable. See <a href="zoomslider.js">zoomslider.js</a>.</p>
+        <div id="map-container"></div>
+    </body>
+</html>

Added: sandbox/opengeo/geoexplorer/examples/zoomslider.js
===================================================================
--- sandbox/opengeo/geoexplorer/examples/zoomslider.js	                        (rev 0)
+++ sandbox/opengeo/geoexplorer/examples/zoomslider.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -0,0 +1,40 @@
+var panel, slider;
+
+Ext.onReady(function() {
+    
+    // create a map panel with an embedded slider
+    panel = new GeoExt.MapPanel({
+        title: "Map",
+        renderTo: "map-container",
+        height: 300,
+        width: 400,
+        map: {
+            controls: [new OpenLayers.Control.Navigation()]
+        },
+        layers: [new OpenLayers.Layer.WMS(
+            "Global Imagery",
+            "http://demo.opengeo.org/geoserver/wms",
+            {layers: 'bluemarble'}
+        )],
+        extent: [-5, 35, 15, 55],
+        items: [{
+            xtype: "gx_zoomslider",
+            vertical: true,
+            height: 100,
+            x: 10,
+            y: 20,
+            plugins: new GeoExt.ZoomSliderTip()
+        }]
+    });
+    
+    // create a separate slider bound to the map but displayed elsewhere
+    slider = new GeoExt.ZoomSlider({
+        map: panel.map,
+        width: 200,
+        plugins: new GeoExt.ZoomSliderTip({
+            template: "<div>Zoom Level: {zoom}</div>"
+        }),
+        renderTo: document.body
+    });
+
+});

Modified: sandbox/opengeo/geoexplorer/lib/GeoExt/data/FeatureRecord.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/data/FeatureRecord.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/data/FeatureRecord.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -27,18 +27,14 @@
     {name: "feature"}, {name: "state"}, {name: "fid"}
 ]);
 
-/**
- * APIFunction: GeoExt.data.FeatureRecord.create
- * Creates a constructor for a FeatureRecord, optionally with additional
- * fields.
- * 
- * Parameters:
- * o - {Array} Field definition as in {Ext.data.Record.create}. Can be omitted
- *     if no additional fields are required (records will always have fields
- *     {OpenLayers.Feature} "feature", {String} "state" and {String} "fid".
- *
- * Returns:
- * {Function} A specialized {<GeoExt.data.FeatureRecord>} constructor.
+/** api: classmethod[create]
+ *  :param o: ``Array`` Field definition as in ``Ext.data.Record.create``. Can
+ *      be omitted if no additional fields are required.
+ *  :return: ``Function`` A specialized :class:`GeoExt.data.FeatureRecord`
+ *      constructor.
+ *  
+ *  Creates a constructor for a :class:`GeoExt.data.FeatureRecord`, optionally
+ *  with additional fields.
  */
 GeoExt.data.FeatureRecord.create = function(o) {
     var f = Ext.extend(GeoExt.data.FeatureRecord, {});

Modified: sandbox/opengeo/geoexplorer/lib/GeoExt/data/LayerReader.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/data/LayerReader.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/data/LayerReader.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -9,30 +9,31 @@
  * @include GeoExt/data/LayerRecord.js
  */
 
+/** api: (define)
+ *  module = GeoExt.data
+ *  class = LayerReader
+ *  base_link = `Ext.data.DataReader <http://extjs.com/deploy/dev/docs/?class=Ext.data.DataReader>`_
+ */
 Ext.namespace("GeoExt", "GeoExt.data");
 
-/**
- * Class: GeoExt.data.LayerReader
- *      LayerReader is a specific Ext.data.DataReader for converting
- *      layers into layer records, i.e. {OpenLayers.Layer} objects
- *      into {GeoExt.data.LayerRecor} objects.
- *
- * Usage example:
- * (start code)
- *         var reader = new GeoExt.data.LayerReader();
- *         var layerData = reader.readRecords(map.layers);
- *         var numRecords = layerData.totalRecords;
- *         var layerRecords = layerData.records;
- * (end)
- *
- * Inherits from:
- *  - {Ext.data.DataReader}
+/** api: example
+ *  Sample using a reader to create records from an array of layers:
+ * 
+ *  .. code-block:: javascript
+ *     
+ *      var reader = new GeoExt.data.LayerReader();
+ *      var layerData = reader.readRecords(map.layers);
+ *      var numRecords = layerData.totalRecords;
+ *      var layerRecords = layerData.records;
  */
 
-/**
- * Constructor: GeoExt.data.LayerReader
- *      Create a layer reader. The arguments passed are similar to those
- *      passed to {Ext.data.DataReader} constructor.
+/** api: constructor
+ *  .. class:: LayerReader(meta, recordType)
+ *  
+ *      Data reader class to create an array of
+ *      :class:`GeoExt.data.LayerRecord` objects from an array of 
+ *      ``OpenLayers.Layer`` objects for use in a
+ *      :class:`GeoExt.data.LayerStore` object.
  */
 GeoExt.data.LayerReader = function(meta, recordType) {
     meta = meta || {};
@@ -46,24 +47,19 @@
 
 Ext.extend(GeoExt.data.LayerReader, Ext.data.DataReader, {
 
-    /**
-     * APIProperty: totalRecords
-     * {Integer}
+    /** private: property[totalRecords]
+     *  ``Integer``
      */
     totalRecords: null,
 
-    /**
-     * APIMethod: readRecords
-     *      From an array of {OpenLayers.Layer} objects create a data block
-     *      containing {<GeoExt.data.LayerRecord>} objects.
-     *
-     * Parameters:
-     * layers - {Array({OpenLayers.Layer})} Array of layers.
-     *
-     * Returns:
-     * {Object} An object with two properties. The value of the property "records"
-     *      is the array of layer records. The value of the property "totalRecords"
-     *      is the number of records in the array.
+    /** api: method[readRecords]
+     *  :param layers: ``Array(OpenLayers.Layer)`` List of layers for creating
+     *      records.
+     *  :return: ``Object``  An object with ``records`` and ``totalRecords``
+     *      properties.
+     *  
+     *  From an array of {OpenLayers.Layer} objects create a data block
+     *  containing :class:`GeoExt.data.LayerRecord` objects.
      */
     readRecords : function(layers) {
         var records = [];

Modified: sandbox/opengeo/geoexplorer/lib/GeoExt/data/LayerRecord.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/data/LayerRecord.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/data/LayerRecord.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -5,42 +5,35 @@
  * 
  * pending approval */
 
+/** api: (define)
+ *  module = GeoExt.data
+ *  class = LayerRecord
+ *  base_link = `Ext.data.Record <http://extjs.com/deploy/dev/docs/?class=Ext.data.Record>`_
+ */
 Ext.namespace("GeoExt.data");
 
-/**
- * Class: GeoExt.data.LayerRecord
- * A subclass of {Ext.data.Record} which provides a special record that
- * represents an {OpenLayers.Layer}. This record will always have at least a
- * layer and a title field in its data. The id of this record will be the same
- * as the id of the layer it represents.
- * 
- * Inherits from
- * - {Ext.data.Record}
+/** api: constructor
+ *  .. class:: LayerRecord
+ *  
+ *      A record that represents an ``OpenLayers.Layer``. This record
+ *      will always have at least the following fields:
+ *
+ *      * layer ``OpenLayers.Layer``
+ *      * title ``String``
  */
-/**
- * Constructor: GeoExt.data.LayerRecord
- * 
- * Parameters:
- * data - {Object} data object for the record
- * id - {String} id of the record
- */
 GeoExt.data.LayerRecord = Ext.data.Record.create([
     {name: "layer"},
     {name: "title", type: "string", mapping: "name"}
 ]);
 
-/**
- * APIFunction: GeoExt.data.LayerRecord.create
- * Creates a constructor for a LayerRecord, optionally with additional
- * fields.
- * 
- * Parameters:
- * o - {Array} Field definition as in {Ext.data.Record.create}. Can be omitted
- *     if no additional fields are required (records will always have a
- *     {OpenLayers.Layer} layer and a {String} title field).
- *
- * Returns:
- * {Function} A specialized {<GeoExt.data.LayerRecord>} constructor.
+/** api: classmethod[create]
+ *  :param o: ``Array`` Field definition as in ``Ext.data.Record.create``. Can
+ *      be omitted if no additional fields are required.
+ *  :return: ``Function`` A specialized :class:`GeoExt.data.LayerRecord`
+ *      constructor.
+ *  
+ *  Creates a constructor for a :class:`GeoExt.data.LayerRecord`, optionally
+ *  with additional fields.
  */
 GeoExt.data.LayerRecord.create = function(o) {
     var f = Ext.extend(GeoExt.data.LayerRecord, {});

Modified: sandbox/opengeo/geoexplorer/lib/GeoExt/data/LayerStore.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/data/LayerStore.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/data/LayerStore.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -9,69 +9,82 @@
  * @include GeoExt/data/LayerReader.js
  */
 
+/** api: (define)
+ *  module = GeoExt.data
+ *  class = LayerStore
+ *  base_link = `Ext.data.DataStore <http://extjs.com/deploy/dev/docs/?class=Ext.data.DataStore>`_
+ */
 Ext.namespace("GeoExt.data");
 
-/**
- * Class: GeoExt.data.LayerStoreMixin
- * A store that synchronizes a layers array of an {OpenLayers.Map} with a
- * layer store holding {<GeoExt.data.LayerRecord>} entries.
+/** private: constructor
+ *  .. class:: LayerStoreMixin
+ *      A store that synchronizes a layers array of an {OpenLayers.Map} with a
+ *      layer store holding {<GeoExt.data.LayerRecord>} entries.
  * 
- * This class can not be instantiated directly. Instead, it is meant to extend
- * {Ext.data.Store} or a subclass of it:
- * (start code)
- * var store = new (Ext.extend(Ext.data.Store, GeoExt.data.LayerStoreMixin))({
- *     map: myMap,
- *     layers: myLayers
- * });
- * (end)
+ *      This class can not be instantiated directly. Instead, it is meant to
+ *      extend ``Ext.data.Store`` or a subclass of it.
+ */
+
+/** private: example
+ *  Sample code to extend a store with the LayerStoreMixin.
+ *
+ *  .. code-block:: javascript
+ *  
+ *      var store = new (Ext.extend(Ext.data.Store, GeoExt.data.LayerStoreMixin))({
+ *          map: myMap,
+ *          layers: myLayers
+ *      });
  * 
- * For convenience, a {<GeoExt.data.LayerStore>} class is available as a
- * shortcut to the Ext.extend sequence in the above code snippet. The above
- * is equivalent to:
- * (start code)
- * var store = new GeoExt.data.LayerStore({
- *     map: myMap,
- *     layers: myLayers
- * });
- * (end)
+ *  For convenience, a :class:`GeoExt.data.LayerStore` class is available as a
+ *  shortcut to the ``Ext.extend`` sequence in the above code snippet.
  */
+
 GeoExt.data.LayerStoreMixin = {
-    /**
-     * APIProperty: map
-     * {OpenLayers.Map} Map that this store will be in sync with.
+
+    /** api: config[map]
+     *  ``OpenLayers.Map``
+     *  Map that this store will be in sync with.
      */
+    
+    /** api: property[map]
+     *  ``OpenLayers.Map``
+     *  Map that the store is synchronized with.
+     */
     map: null,
+    
+    /** api: config[layers]
+     *  ``Array(OpenLayers.Layer)``
+     *  Layers that will be added to the store (and the map, depending on the
+     *  value of the ``initDir`` option.
+     */
+    
+    /** api: config[initDir]
+     *  ``Number``
+     *  Bitfields specifying the direction to use for the initial sync between
+     *  the map and the store, if set to 0 then no initial sync is done.
+     *  Defaults to ``GeoExt.data.LayerStore.MAP_TO_STORE|GeoExt.data.LayerStore.STORE_TO_MAP``
+     */
 
-    /**
-     * APIProperty: reader
-     * {<GeoExt.data.LayerReader>} The reader used to get
-     *     <GeoExt.data.LayerRecord> objects from {OpenLayers.Layer}
-     *     objects.
+    /** api: config[fields]
+     *  ``Array``
+     *  If provided a custom layer record type with additional fields will be
+     *  used. Default fields for every layer record are `layer`
+     *  (``OpenLayers.Layer``) `title` (``String``). The value of this option is
+     *  either a field definition objects as passed to the
+     *  :meth:`GeoExt.data.LayerRecord.create` function or a
+     *  :class:`GeoExt.data.LayerRecord` constructor created using
+     *  :meth:`GeoExt.data.LayerRecord.create`.
      */
+
+    /** api: config[reader]
+     *  ``Ext.data.DataReader`` The reader used to produce
+     *  :class:`GeoExt.data.LayerRecord` objects from ``OpenLayers.Layer``
+     *  objects.  If not provided, a :class:`GeoExt.data.LayerReader` will be
+     *  used.
+     */
     reader: null,
 
-    /**
-     * Constructor: GeoExt.data.LayerStoreMixin
-     * 
-     * Parameters:
-     * config - {Object}
-     * 
-     * Valid config options:
-     * map - {OpenLayers.Map|<GeoExt.MapPanel>} map to sync the layer store
-     *     with.
-     * layers - {Array(OpenLayers.Layer)} Layers that will be added to the
-     *     store (and the map, depending on the value of the initDir option).
-     * fields - {Array} If provided a custom layer record type with additional
-     *     fields will be used. Default fields for every layer record are
-     *     {OpenLayers.Layer} layer and {String} title. The value of this
-     *     option is either a field definition objects as passed to the
-     *     GeoExt.data.LayerRecord.create function or a
-     *     {<GeoExt.data.LayerRecord>} constructor created using
-     *     GeoExt.data.LayerRecord.create.
-     * initDir - {Number} Bitfields specifying the direction to use for the
-     *     initial sync between the map and the store, if set to 0 then no
-     *     initial sync is done. Defaults to
-     *     <GeoExt.data.LayerStore.MAP_TO_STORE>|<GeoExt.data.LayerStore.STORE_TO_MAP>.
+    /** private: method[constructor]
      */
     constructor: function(config) {
         config = config || {};
@@ -96,20 +109,12 @@
         }
     },
 
-    /**
-     * APIMethod: bind
-     * Bind this store to a map instance, once bound the store
-     * is synchronized with the map and vice-versa.
-     * 
-     * Parameters:
-     * map - {OpenLayers.Map} The map instance.
-     * options - {Object}
-     *
-     * Valid config options:
-     * initDir - {Number} Bitfields specifying the direction to use for the
-     *     initial sync between the map and the store, if set to 0 then no
-     *     initial sync is done. Defaults to
-     *     <GeoExt.data.LayerStore.MAP_TO_STORE>|<GeoExt.data.LayerStore.STORE_TO_MAP>.
+    /** private: method[bind]
+     *  :param map: ``OpenLayers.Map`` The map instance.
+     *  :param options: ``Object``
+     *  
+     *  Bind this store to a map instance, once bound the store
+     *  is synchronized with the map and vice-versa.
      */
     bind: function(map, options) {
         if(this.map) {
@@ -157,9 +162,8 @@
         });
     },
 
-    /**
-     * APIMethod: unbind
-     * Unbind this store from the map it is currently bound.
+    /** private: method[unbind]
+     *  Unbind this store from the map it is currently bound.
      */
     unbind: function() {
         if(this.map) {
@@ -180,25 +184,23 @@
         }
     },
     
-    /**
-     * Method: onChangeLayer
-     * Handler for layer changes.  When layer order changes, this moves the
-     *     appropriate record within the store.
-     *
-     * Parameters:
-     * evt - {Object}
+    /** private: method[onChangeLayer]
+     *  :param evt: ``Object``
+     * 
+     *  Handler for layer changes.  When layer order changes, this moves the
+     *  appropriate record within the store.
      */
     onChangeLayer: function(evt) {
         var layer = evt.layer;
-        if(evt.property === "order") {
-            if(!this._adding && !this._removing) {
-                var layerIndex = this.map.getLayerIndex(layer);
-                var recordIndex = this.findBy(function(rec, id) {
-                    return rec.get("layer") === layer;
-                });
-                if(recordIndex > -1) {
+        var recordIndex = this.findBy(function(rec, id) {
+            return rec.get("layer") === layer;
+        });
+        if(recordIndex > -1) {
+            var record = this.getAt(recordIndex);
+            if(evt.property === "order") {
+                if(!this._adding && !this._removing) {
+                    var layerIndex = this.map.getLayerIndex(layer);
                     if(layerIndex !== recordIndex) {
-                        var record = this.getAt(recordIndex);
                         this._removing = true;
                         this.remove(record);
                         delete this._removing;
@@ -207,6 +209,8 @@
                         delete this._adding;
                     }
                 }
+            } else {
+                this.fireEvent("update", this, record, Ext.data.Record.EDIT);
             }
         } else if (evt.property === "visibility") {
             var layerIndex = this.map.getLayerIndex(layer);
@@ -220,12 +224,10 @@
          }
     },
    
-    /**
-     * Method: onAddLayer
-     * Handler for a map's addlayer event
-     * 
-     * Parameters:
-     * evt - {Object}
+    /** private: method[onAddLayer]
+     *  :param evt: ``Object``
+     *  
+     *  Handler for a map's addlayer event
      */
     onAddLayer: function(evt) {
         if(!this._adding) {
@@ -236,12 +238,10 @@
         }
     },
     
-    /**
-     * Method: onRemoveLayer
-     * Handler for a map's removelayer event
+    /** private: method[onRemoveLayer]
+     *  :param evt: ``Object``
      * 
-     * Parameters:
-     * evt - {Object}
+     *  Handler for a map's removelayer event
      */
     onRemoveLayer: function(evt){
         if(!this._removing) {
@@ -252,14 +252,12 @@
         }
     },
     
-    /**
-     * Method: onLoad
-     * Handler for a store's load event
+    /** private: method[onLoad]
+     *  :param store: ``Ext.data.Store``
+     *  :param records: ``Array(Ext.data.Record)``
+     *  :param options: ``Object``
      * 
-     * Parameters:
-     * store - {<Ext.data.Store>}
-     * records - {Array(Ext.data.Record)}
-     * options - {Object}
+     *  Handler for a store's load event
      */
     onLoad: function(store, records, options) {
         if (!Ext.isArray(records)) {
@@ -286,12 +284,10 @@
         }
     },
     
-    /**
-     * Method: onClear
-     * Handler for a store's clear event
+    /** private: method[onClear]
+     *  :param store: ``Ext.data.Store``
      * 
-     * Parameters:
-     * store - {<Ext.data.Store>}
+     *  Handler for a store's clear event
      */
     onClear: function(store) {
         this._removing = true;
@@ -301,14 +297,12 @@
         delete this._removing;
     },
     
-    /**
-     * Method: onAdd
-     * Handler for a store's add event
+    /** private: method[onAdd]
+     *  :param store: ``Ext.data.Store``
+     *  :param records: ``Array(Ext.data.Record)``
+     *  :param index: ``Number``
      * 
-     * Parameters:
-     * store - {<Ext.data.Store>}
-     * records - {Array(Ext.data.Record)}
-     * index - {Number}
+     *  Handler for a store's add event
      */
     onAdd: function(store, records, index) {
         if(!this._adding) {
@@ -325,14 +319,12 @@
         }
     },
     
-    /**
-     * Method: onRemove
-     * Handler for a store's remove event
+    /** private: method[onRemove]
+     *  :param store: ``Ext.data.Store``
+     *  :param record: ``Ext.data.Record``
+     *  :param index: ``Number``
      * 
-     * Parameters:
-     * store - {<Ext.data.Store>}
-     * records - {Array(Ext.data.Record)}
-     * index - {Number}
+     *  Handler for a store's remove event
      */
     onRemove: function(store, record, index){
         if(!this._removing) {
@@ -345,47 +337,46 @@
         }
     },
 
-    /**
-     * Method: removeMapLayers
-     * Removes a record's layer from the bound map.
-     * 
-     * Parameters:
-     * record - {<Ext.data.Record>}
+    /** private: method[removeMapLayer]
+     *  :param record: ``Ext.data.Record``
+     *  
+     *  Removes a record's layer from the bound map.
      */
     removeMapLayer: function(record){
         this.map.removeLayer(record.get("layer"));
     },
 
-    /**
-     * Method: onReplace
-     * Handler for a store's data collections' replace event
-     * 
-     * Parameters:
-     * key - {String}
-     * oldRecord - {Object} In this case, a record that has been replaced.
-     * newRecord - {Object} In this case, a record that is replacing oldRecord.
+    /** private: method[onReplace]
+     *  :param key: ``String``
+     *  :param oldRecord: ``Object`` In this case, a record that has been
+     *      replaced.
+     *  :param newRecord: ``Object`` In this case, a record that is replacing
+     *      oldRecord.
+
+     *  Handler for a store's data collections' replace event
      */
     onReplace: function(key, oldRecord, newRecord){
         this.removeMapLayer(oldRecord);
     }
 };
 
-/**
- * Class: GeoExt.data.LayerStore
- * Default implementation of an {Ext.data.Store} extended with
- * {<GeoExt.data.LayerStoreMixin>}
- * 
- * Inherits from:
- * - {Ext.data.Store}
- * - {<GeoExt.data.LayerStoreMixin>}
+/** api: example
+ *  Sample to create a new store containing a cache of
+ *  :class:`GeoExt.data.LayerRecord` instances derived from map layers.
+ *
+ *  .. code-block:: javascript
+ *  
+ *      var store = new GeoExt.data.LayerStore({
+ *          map: myMap,
+ *          layers: myLayers
+ *      });
  */
-/**
- * Constructor: GeoExt.data.LayerStore
- * 
- * Parameters:
- * config - {Object} See {<GeoExt.data.LayerStoreMixin>} and 
- * http://extjs.com/deploy/dev/docs/?class=Ext.data.Store for valid config
- *     options. 
+
+/** api: constructor
+ *  .. class:: LayerStore
+ *
+ *      A store that contains a cache of :class:`GeoExt.data.LayerRecord`
+ *      objects.
  */
 GeoExt.data.LayerStore = Ext.extend(
     Ext.data.Store,

Modified: sandbox/opengeo/geoexplorer/lib/GeoExt/data/ProtocolProxy.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/data/ProtocolProxy.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/data/ProtocolProxy.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -17,59 +17,57 @@
  * along with GeoExt.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+/** api: (define)
+ *  module = GeoExt.data
+ *  class = ProtocolProxy
+ *  base_link = `Ext.data.DataProxy <http://extjs.com/deploy/dev/docs/?class=Ext.data.DataProxy>`_
+ */
 Ext.namespace('GeoExt', 'GeoExt.data');
 
-/**
- * Class: GeoExt.data.ProtocolProxy
- */
-
-/**
- * Constructor: GeoExt.data.ProtocolProxy
- *
- * Parameters:
- * config - {Object} Config object
- */
 GeoExt.data.ProtocolProxy = function(config) {
     GeoExt.data.ProtocolProxy.superclass.constructor.call(this);
     Ext.apply(this, config);
 };
 
+/** api: constructor
+ *  .. class:: ProtocolProxy
+ *   
+ *      A data proxy for use with ``OpenLayers.Proxy`` objects.
+ */
 Ext.extend(GeoExt.data.ProtocolProxy, Ext.data.DataProxy, {
-    /**
-     * APIProperty: protocol
-     * {<OpenLayers.Protocol>} The protocol used to fetch features.
+
+    /** api: config[protocol]
+     *  ``OpenLayers.Protocol``
+     *  The protocol used to fetch features.
      */
     protocol: null,
 
-    /**
-     * APIProperty: abortPrevious
-     * {Boolean} Whether to abort the previous request or not, defaults
-     * to true.
+    /** api: config[abortPrevious]
+     *  ``Boolean``
+     *  Abort any previous request before issuing another.  Default is ``true``.
      */
     abortPrevious: true,
 
-    /**
-     * Property: response
-     * {<OpenLayers.Protocol.Response>} The response returned by
-     * the read call on the protocol.
+    /** private: property[response]
+     *  ``OpenLayers.Protocol.Response``
+     *  The response returned by the read call on the protocol.
      */
     response: null,
 
-    /**
-     * Method: load
+    /** private: method[load]
+     *  :param params: ``Object`` An object containing properties which are to
+     *      be used as HTTP parameters for the request to the remote server.
+     *  :param reader: ``Ext.data.DataReader`` The Reader object which converts
+     *      the data object into a block of ``Ext.data.Records``.
+     *  :param callback: ``Function`` The function into which to pass the block
+     *      of ``Ext.data.Records``. The function is passed the Record block
+     *      object, the ``args`` argument passed to the load function, and a
+     *      boolean success indicator.
+     *  :param scope: ``Object`` The scope in which to call the callback.
+     *  :param arg: ``Object`` An optional argument which is passed to the
+     *      callback as its second parameter.
      *
-     * Parameters:
-     * params - {Object} An object containing properties which are to be used
-     *     as HTTP parameters for the request to the remote server.
-     * reader - {Ext.data.DataReader} The Reader object which converts the data
-     *     object into a block of Ext.data.Records.
-     * callback - {Function} The function into which to pass the block of
-     *     Ext.data.Records. The function is passed the Record block object,
-     *     the "args" argument passed to the load function, and a boolean
-     *     success indicator
-     * scope - {Object} The scope in which to call the callback
-     * arg - {Object} An optional argument which is passed to the callback
-     *     as its second parameter.
+     *  Calls ``read`` on the protocol.
      */
     load: function(params, reader, callback, scope, arg) {
         if (this.fireEvent("beforeload", this, params) !== false) {
@@ -98,9 +96,8 @@
         }
     },
 
-    /**
-     * Method: abortRequest
-     * Called to abort any ongoing request.
+    /** private: method[abortRequest]
+     *  Called to abort any ongoing request.
      */
     abortRequest: function() {
         // FIXME really we should rely on the protocol itself to
@@ -116,13 +113,11 @@
         }
     },
 
-    /**
-     * Method: loadResponse
-     * Handle response from the protocol
-     *
-     * Parameters:
-     * o - {Object} 
-     * response - {<OpenLayers.Protocol.Response>} 
+    /** private: method[loadResponse]
+     *  :param o: ``Object``
+     *  :param response: ``OpenLayers.Protocol.Response``
+     *  
+     *  Handle response from the protocol
      */
     loadResponse: function(o, response) {
         if (response.success()) {

Modified: sandbox/opengeo/geoexplorer/lib/GeoExt/data/ScaleStore.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/data/ScaleStore.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/data/ScaleStore.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -5,29 +5,37 @@
  * 
  * ¹ pending approval */
 
+/** api: (define)
+ *  module = GeoExt.data
+ *  class = ScaleStore
+ *  base_link = `Ext.data.DataStore <http://extjs.com/deploy/dev/docs/?class=Ext.data.DataStore>`_
+ */
 Ext.namespace("GeoExt.data");
 
-/**
- *  Class: GeoExt.data.ScaleStore
- *  This store maintains a list of available zoom levels, optionally keeping it synchronized with 
- *  a Map or MapPanel instance.   The entries in the list have the following fields: 
- *  zoom - the number of the zoom level
- *  scale - the scale denominator for the zoom level
- *  resolution - the map resolution when the zoom level is active.
+/** api: constructor
+ *  .. class:: ScaleStore
+ *
+ *      A store that contains a cache of available zoom levels.  The store can
+ *      optionally be kept synchronized with an ``OpenLayers.Map`` or
+ *      :class:`GeoExt.MapPanel` object.
+ *
+ *      Records have the following fields:
+ *
+ *      * zoom - ``Number``  The zoom level.
+ *      * scale - ``Number`` The scale denominator.
+ *      * resolution - ``Number`` The map units per pixel.
  */
 GeoExt.data.ScaleStore = Ext.extend(Ext.data.Store, {
-    /**
-     * Property: map
-     * The OpenLayers.Map instance to which the store is bound, if any.
+
+    /** api: config[map]
+     *  ``OpenLayers.Map`` or :class:`GeoExt.MapPanel`
+     *  Optional map or map panel from which to derive scale values.
      */
     map: null,
 
-    /**
-     * Constructor: GeoExt.data.ScaleStore
-     * Construct a ScaleStore from a configuration.  The ScaleStore accepts some custom parameters 
-     * addition to the fields accepted by Ext.Store.
-     * Additional options:
-     * map - the GeoExt.MapPanel or OpenLayers.Map instance the store should stay sync'ed with
+    /** private: method[constructor]
+     *  Construct a ScaleStore from a configuration.  The ScaleStore accepts
+     *  some custom parameters addition to the fields accepted by Ext.Store.
      */
     constructor: function(config) {
         var map = (config.map instanceof GeoExt.MapPanel ? config.map.map : config.map);
@@ -43,15 +51,14 @@
         if (map) this.bind(map);
     },
 
-    /**
-     * APIMethod: bind
-     * Bind this store to a map; that is, maintain the zoom list in sync with the map's current 
-     * configuration.  If the map does not currently have a set scale list, then the store will 
-     * remain empty until the map is configured with one.
-     *
-     * Parameters: 
-     * map - the GeoExt.MapPanel or OpenLayers.Map to which we should bind
-     * options - additional parameters for the bind operation (optional, currently unused)
+    /** api: method[bind]
+     *  :param map: :class`GeoExt.MapPanel` or ``OpenLayers.Map`` Panel or map
+     *      to which we should bind.
+     *  
+     *  Bind this store to a map; that is, maintain the zoom list in sync with
+     *  the map's current configuration.  If the map does not currently have a
+     *  set scale list, then the store will remain empty until the map is
+     *  configured with one.
      */
     bind: function(map, options) {
         this.map = (map instanceof GeoExt.MapPanel ? map.map : map);
@@ -63,10 +70,10 @@
         }
     },
 
-    /**
-     * APIMethod: unbind
-     * Un-bind this store from the map to which it is currently bound.  The currently stored zoom 
-     * levels will remain, but no further changes from the map will affect it.
+    /** api: method[unbind]
+     *  Un-bind this store from the map to which it is currently bound.  The
+     *  currently stored zoom levels will remain, but no further changes from
+     *  the map will affect it.
      */
     unbind: function() {
         if (this.map) {
@@ -76,13 +83,12 @@
         }
     },
 
-    /**
-     * Method: populateOnAdd
-     * This method handles the case where we have bind() called on a not-fully-configured map so 
-     * that the zoom levels can be detected when a baselayer is finally added.
-     *
-     * Parameters:
-     * evt - the OpenLayers event
+    /** private: method[populateOnAdd]
+     *  :param evt: ``Object``
+     *  
+     *  This method handles the case where we have bind() called on a
+     *  not-fully-configured map so that the zoom levels can be detected when a
+     *  baselayer is finally added.
      */
     populateOnAdd: function(evt) {
         if (evt.layer.isBaseLayer) {
@@ -91,10 +97,9 @@
         }
     },
 
-    /**
-     * Method: populateFromMap
-     * This method actually loads the zoom level information from the OpenLayers.Map and converts 
-     * it to Ext Records.
+    /** private: method[populateFromMap]
+     *  This method actually loads the zoom level information from the
+     *  OpenLayers.Map and converts it to Ext Records.
      */
     populateFromMap: function() {
         var zooms = [];

Modified: sandbox/opengeo/geoexplorer/lib/GeoExt/data/WMSCapabilitiesReader.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/data/WMSCapabilitiesReader.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/data/WMSCapabilitiesReader.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -2,32 +2,25 @@
  * Copyright (c) 2008 The Open Planning Project
  */
 
+/** api: (define)
+ *  module = GeoExt.data
+ *  class = WMSCapabilitiesReader
+ *  base_link = `Ext.data.DataReader <http://extjs.com/deploy/dev/docs/?class=Ext.data.DataReader>`_
+ */
 Ext.namespace("GeoExt.data");
 
-/**
- * Class: GeoExt.data.WMSCapabilitiesReader
- * Data reader class to provide an array of {Ext.data.Record} objects given
- *     a WMS GetCapabilities response for use by an {Ext.data.Store}
- *     object.
- *
- * Extends:
- *  - Ext.data.DataReader
+/** api: constructor
+ *  .. class:: WMSCapabilitiesReader(meta, recordType)
+ *  
+ *      :param meta: ``Object`` Reader configuration.
+ *      :param recordType: ``Array | Ext.data.Record`` An array of field
+ *          configuration objects or a record object.  Default is
+ *          :class:`GeoExt.data.LayerRecord`.
+ *   
+ *      Data reader class to create an array of
+ *      :class:`GeoExt.data.LayerRecord` objects from a WMS GetCapabilities
+ *      response.
  */
-
-/**
- * Constructor: GeoExt.data.WMSCapabilitiesReader
- * Create a new attributes reader object.
- *
- * Parameters:
- * meta - {Object} Reader configuration.
- * recordType - {Array | Ext.data.Record} An array of field configuration
- *     objects or a record object.  Default is <GeoExt.data.LayerRecord>.
- *
- * Configuration options (meta properties):
- * format - {OpenLayers.Format} A parser for transforming the XHR response
- *     into an array of objects representing attributes.  Defaults to
- *     an {OpenLayers.Format.WMSCapabilities} parser.
- */
 GeoExt.data.WMSCapabilitiesReader = function(meta, recordType) {
     meta = meta || {};
     if(!meta.format) {
@@ -55,18 +48,11 @@
 
 Ext.extend(GeoExt.data.WMSCapabilitiesReader, Ext.data.DataReader, {
 
-    /**
-     * Method: read
-     * This method is only used by a DataProxy which has retrieved data from a
-     *     remote server.
-     *
-     * Parameters:
-     * request - {Object} The XHR object which contains the parsed XML
-     *     document.
-     * 
-     * Returns:
-     * {Object} A data block which is used by an {Ext.data.Store} as a cache
-     *     of Ext.data.Records.
+    /** private: method[read]
+     *  :param request: ``Object`` The XHR object which contains the parsed XML
+     *      document.
+     *  :return: ``Object`` A data block which is used by an ``Ext.data.Store``
+     *      as a cache of ``Ext.data.Record`` objects.
      */
     read: function(request) {
         var data = request.responseXML;
@@ -76,19 +62,16 @@
         return this.readRecords(data);
     },
 
-    /**
-     * Method: readRecords
-     * Create a data block containing Ext.data.Records from an XML document.
-     *
-     * Parameters:
-     * data - {DOMElement | Strint | Object} A document element or XHR response
-     *     string.  As an alternative to fetching capabilities data from a remote
-     *     source, an object representing the capabilities can be provided given
-     *     that the structure mirrors that returned from the capabilities parser.
-     *
-     * Returns:
-     * {Object} A data block which is used by an {Ext.data.Store} as a cache of
-     *     Ext.data.Records.
+    /** private: method[readRecords]
+     *  :param data: ``DOMElement | Strint | Object`` A document element or XHR
+     *      response string.  As an alternative to fetching capabilities data
+     *      from a remote source, an object representing the capabilities can
+     *      be provided given that the structure mirrors that returned from the
+     *      capabilities parser.
+     *  :return: ``Object`` A data block which is used by an ``Ext.data.Store``
+     *      as a cache of ``Ext.data.Record`` objects.
+     *  
+     *  Create a data block containing Ext.data.Records from an XML document.
      */
     readRecords: function(data) {
         

Modified: sandbox/opengeo/geoexplorer/lib/GeoExt/data/WMSCapabilitiesStore.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/data/WMSCapabilitiesStore.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/data/WMSCapabilitiesStore.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -6,36 +6,39 @@
  * @include GeoExt/data/WMSCapabilitiesReader.js
  */
 
+/** api: (define)
+ *  module = GeoExt.data
+ *  class = WMSCapabilitiesStore
+ *  base_link = `Ext.data.DataStore <http://extjs.com/deploy/dev/docs/?class=Ext.data.DataStore>`_
+ */
 Ext.namespace("GeoExt.data");
-/**
- * Class: GeoExt.data.WMSCapabilitiesStore
- * Small helper class to make creating stores for remote WMS layer data easier.
- *     WMSCapabilitiesStore is pre-configured with a built-in
- *     {Ext.data.HttpProxy} and {GeoExt.data.WMSCapabilitiesReader}.  The
- *     HttpProxy is configured to allow caching (disableCaching: false) and uses
- *     GET.  If you require some other proxy/reader combination then you'll have
- *     to configure this with your own proxy or create a basic
- *     GeoExt.data.LayerStore and configure as needed.
- *
- * Extends:
- *  - GeoExt.data.Store
+
+/** api: constructor
+ *  .. class:: WMSCapabilitiesStore
+ *  
+ *      Small helper class to make creating stores for remote WMS layer data
+ *      easier.  The store is pre-configured with a built-in
+ *      ``Ext.data.HttpProxy`` and :class:`GeoExt.data.WMSCapabilitiesReader`.
+ *      The proxy is configured to allow caching and issues requests via GET.
+ *      If you require some other proxy/reader combination then you'll have to
+ *      configure this with your own proxy or create a basic
+ *      :class:`GeoExt.data.LayerStore` and configure as needed.
  */
 
-/**
- * Constructor: GeoExt.data.WMSCapabilitiesStore
- * Create a new WMS capabilities store object.
- *
- * Parameters:
- * config - {Object} Store configuration.
- *
- * Configuration options:
- * format - {OpenLayers.Format} A parser for transforming the XHR response into
- *     an array of objects representing attributes.  Defaults to an
- *     {OpenLayers.Format.WMSCapabilities} parser.
- * fields - {Array | Function} Either an Array of field definition objects as
- *     passed to Ext.data.Record.create, or a Record constructor created using
- *     Ext.data.Record.create.  Defaults to ["name", "type"]. 
+/** api: config[format]
+ *  ``OpenLayers.Format``
+ *  A parser for transforming the XHR response into an array of objects
+ *  representing attributes.  Defaults to an ``OpenLayers.Format.WMSCapabilities``
+ *  parser.
  */
+
+/** api: config[fields]
+ *  ``Array | Function``
+ *  Either an Array of field definition objects as passed to
+ *  ``Ext.data.Record.create``, or a record constructor created using
+ *  ``Ext.data.Record.create``.  Defaults to ``["name", "type"]``. 
+ */
+
 GeoExt.data.WMSCapabilitiesStore = function(c) {
     c = c || {};
     GeoExt.data.WMSCapabilitiesStore.superclass.constructor.call(

Added: sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/Action.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/Action.js	                        (rev 0)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/Action.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -0,0 +1,211 @@
+/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation
+ * Published under the BSD license.
+ * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * of the license.
+ * 
+ * pending approval */
+
+/** api: (define)
+ *  module = GeoExt
+ *  class = Action
+ *  base_link = `Ext.Action <http://extjs.com/deploy/dev/docs/?class=Ext.Action>`_
+ */
+Ext.namespace("GeoExt");
+
+/** api: example
+ *  Sample code to create a toolbar with an OpenLayers control into it.
+ * 
+ *  .. code-block:: javascript
+ *  
+ *      var ctrl = new OpenLayers.Control.ZoomToMaxExtent();
+ *      var action = new GeoExt.Action(ctrl, {
+ *          text: "max extent"
+ *          control: ctrl,
+ *          map: map
+ *      });
+ *      var toolbar = new Ext.Toolbar([action]);
+ */
+
+/** api: constructor
+ *  .. class:: Action(config)
+ *  
+ *      Create a GeoExt.Action instance. A GeoExt.Action is created
+ *      to insert an OpenLayers control in a toolbar as a button or
+ *      in a menu as a menu item. A GeoExt.Action instance can be
+ *      used like a regular Ext.Action, look at the Ext.Action API
+ *      doc for more detail.
+ */
+GeoExt.Action = Ext.extend(Ext.Action, {
+
+    /** api: config[control]
+     *  ``OpenLayers.Control`` The OpenLayers control wrapped in this action.
+     */
+    control: null,
+
+    /** private: property[uScope]
+     *  ``Object`` The user-provided scope, used when calling uHandler,
+     *  uToggleHandler, and uCheckHandler.
+     */
+    uScope: null,
+
+    /** private: property[uHandler]
+     *  ``Function`` References the function the user passes through
+     *  the "handler" property.
+     */
+    uHandler: null,
+
+    /** private: property[uToggleHandler]
+     *  ``Function`` References the function the user passes through
+     *  the "toggleHandler" property.
+     */
+    uToggleHandler: null,
+
+    /** private: property[uCheckHandler]
+     *  ``Function`` References the function the user passes through
+     *  the "checkHandler" property.
+     */
+    uCheckHandler: null,
+
+    /** private */
+    constructor: function(config) {
+        
+        // store the user scope and handlers
+        this.uScope = config.scope;
+        this.uHandler = config.handler;
+        this.uToggleHandler = config.toggleHandler;
+        this.uCheckHandler = config.checkHandler;
+
+        config.scope = this;
+        config.handler = this.pHandler;
+        config.toggleHandler = this.pToggleHandler;
+        config.checkHandler = this.pCheckHandler;
+
+        // set control in the instance, the Ext.Action
+        // constructor won't do it for us
+        var ctrl = this.control = config.control;
+        delete config.control;
+
+        // register "activate" and "deactivate" listeners
+        // on the control
+        if(ctrl) {
+            // If map is provided in config, add control to map.
+            if(config.map) {
+                config.map.addControl(ctrl);
+            }
+            ctrl.events.on({
+                activate: this.onCtrlActivate,
+                deactivate: this.onCtrlDeactivate,
+                scope: this
+            });
+        }
+
+        arguments.callee.superclass.constructor.call(this, config);
+    },
+
+    /** private: method[pHandler]
+     *  :param cmp: ``Ext.Component`` The component that triggers the handler.
+     *
+     *  The private handler.
+     */
+    pHandler: function(cmp) {
+        var ctrl = this.control;
+        if(ctrl &&
+           ctrl.type == OpenLayers.Control.TYPE_BUTTON) {
+            ctrl.trigger();
+        }
+        if(this.uHandler) {
+            this.uHandler.apply(this.uScope, arguments);
+        }
+    },
+
+    /** private: method[pTogleHandler]
+     *  :param cmp: ``Ext.Component`` The component that triggers the toggle handler.
+     *  :param state: ``Boolean`` The state of the toggle.
+     *
+     *  The private toggle handler.
+     */
+    pToggleHandler: function(cmp, state) {
+        this.changeControlState(state);
+        if(this.uToggleHandler) {
+            this.uToggleHandler.apply(this.uScope, arguments);
+        }
+    },
+
+    /** private: method[pCheckHandler]
+     *  :param cmp: ``Ext.Component`` The component that triggers the check handler.
+     *  :param state: ``Boolean`` The state of the toggle.
+     *
+     *  The private check handler.
+     */
+    pCheckHandler: function(cmp, state) {
+        this.changeControlState(state);
+        if(this.uCheckHandler) {
+            this.uCheckHandler.apply(this.uScope, arguments);
+        }
+    },
+
+    /** private: method[changeControlState]
+     *  :param state: ``Boolean`` The state of the toggle.
+     *
+     *  Change the control state depending on the state boolean.
+     */
+    changeControlState: function(state) {
+        if(state) {
+            if(!this._activating) {
+                this._activating = true;
+                this.control.activate();
+                this._activating = false;
+            }
+        } else {
+            if(!this._deactivating) {
+                this._deactivating = true;
+                this.control.deactivate();
+                this._deactivating = false;
+            }
+        }
+    },
+
+    /** private: method[onCtrlActivate]
+     *  
+     *  Called when this action's control is activated.
+     */
+    onCtrlActivate: function() {
+        var ctrl = this.control;
+        if(ctrl.type == OpenLayers.Control.TYPE_BUTTON) {
+            this.enable();
+        } else {
+            // deal with buttons
+            this.safeCallEach("toggle", [true]);
+            // deal with check items
+            this.safeCallEach("setChecked", [true]);
+        }
+    },
+
+    /** private: method[onCtrlDeactivate]
+     *  
+     *  Called when this action's control is deactivated.
+     */
+    onCtrlDeactivate: function() {
+        var ctrl = this.control;
+        if(ctrl.type == OpenLayers.Control.TYPE_BUTTON) {
+            this.disable();
+        } else {
+            // deal with buttons
+            this.safeCallEach("toggle", [false]);
+            // deal with check items
+            this.safeCallEach("setChecked", [false]);
+        }
+    },
+
+    /** private: method[safeCallEach]
+     *
+     */
+    safeCallEach: function(fnName, args) {
+        var cs = this.items;
+        for(var i = 0, len = cs.length; i < len; i++){
+            if(cs[i][fnName]) {
+                cs[i][fnName].apply(cs[i], args);
+            }
+        }
+    }
+});

Added: sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/LegendImage.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/LegendImage.js	                        (rev 0)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/LegendImage.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -0,0 +1,80 @@
+/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation
+ * Published under the BSD license.
+ * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * of the license.
+ * 
+ * pending approval */
+
+/** api: (define)
+ *  module = GeoExt
+ *  class = LegendImage
+ */
+
+Ext.namespace('GeoExt');
+
+/** api: constructor
+ *  .. class:: LegendImage(config)
+ *
+ *  Show a legend image in a BoxComponent and make sure load errors are dealt
+ *  with.
+ */
+GeoExt.LegendImage = Ext.extend(Ext.BoxComponent, {
+
+    /** api: config[url]
+     *  ``String``  The url of the image to load
+     */
+    url: null,
+
+    /** api: config[imgCls]
+     *  ``String``  Optional css class to apply to img tag
+     */
+    imgCls: null,
+
+    /** private: method[initComponent]
+     *  Initializes the legend image component. 
+     */
+    initComponent: function() {
+        GeoExt.LegendImage.superclass.initComponent.call(this);
+        this.autoEl = {tag: 'img',
+            'class': (this.imgCls ? this.imgCls : ''), src: this.url};
+    },
+
+    /** api: method[setUrl]
+     *  :param url: ``String`` The new url of the image.
+     *  
+     *  Sets the url of the image.
+     */
+    setUrl: function(url) {
+        var el = this.getEl();
+        if (el) {
+            el.dom.src = url;
+        }
+    },
+
+    /** private: method[onRender]
+     *  Private method called when the legend image component is being
+     *  rendered.
+     */
+    onRender: function(ct, position) {
+        GeoExt.LegendImage.superclass.onRender.call(this, ct, position);
+        this.getEl().on('error', this.onImageLoadError, this);
+    },
+
+    /** private: method[onDestroy]
+     *  Private method called during the destroy sequence.
+     */
+    onDestroy: function() {
+        this.getEl().un('error', this.onImageLoadError, this);
+        GeoExt.LegendImage.superclass.onDestroy.apply(this, arguments);
+    },
+    
+    /** private: method[onImageLoadError]
+     *  Private method called if the legend image fails loading.
+     */
+    onImageLoadError: function() {
+        this.getEl().dom.src = Ext.BLANK_IMAGE_URL;
+    }
+
+});
+
+Ext.reg('gx_legendimage', GeoExt.LegendImage);

Modified: sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/LegendPanel.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/LegendPanel.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/LegendPanel.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -1,280 +1,281 @@
-/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation
- * Published under the BSD license.
- * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
- * of the license.
- *
- * pending approval */
-
-/** api: (define)
- *  module = GeoExt
- *  class = LegendPanel
- */
-
-Ext.namespace('GeoExt');
-
-/** api: constructor
- *  .. class:: LegendPanel(config)
- *
- *  A panel showing legends of all layers in a layer store.
- *  Depending on the layer type, a legend renderer will be chosen.
- */
-GeoExt.LegendPanel = Ext.extend(Ext.Panel, {
-
-    /** api: config[dynamic]
-     *  ``Boolean``
-     *  If false the LegendPanel will not listen to the add, remove and change 
-     *  events of the LayerStore. So it will load with the initial state of
-     *  the LayerStore and not change anymore. 
-     */
-    dynamic: true,
-    
-    /** api: config[showTitle]
-     *  ``Boolean``
-     *  Whether or not to show the title of a layer. This can be a global
-     *  setting for the whole panel, or it can be overridden on the LayerStore 
-     *  record using the hideInLegend property.
-     */
-    showTitle: true,
-
-    /** api: config[labelCls]
-     *  ``String``
-     *  Optional css class to use for the layer title labels.
-     */
-    labelCls: null,
-
-    /** api:config[bodyStyle]
-     *  ``String``
-     *  Optional style to apply to the body of the legend panels.
-     */
-    bodyStyle: '',
-
-    /** api: config[layerStore]
-     *  ``GeoExt.data.LayerStore``
-     *  The layer store containing layers to be displayed in the legend 
-     *  container. If not provided it will be taken from the MapPanel.
-     */
-    layerStore: null,
-
-    /** private: method[initComponent]
-     *  Initializes the legend panel.
-     */
-    initComponent: function() {
-        GeoExt.LegendPanel.superclass.initComponent.call(this);
-    },
-    
-    /** private: method[onRender]
-     *  Private method called when the legend panel is being rendered.
-     */
-    onRender: function() {
-        GeoExt.LegendPanel.superclass.onRender.apply(this, arguments);
-        if(!this.layerStore) {
-            this.layerStore = GeoExt.MapPanel.guess().layers;
-        }
-        this.layerStore.each(function(record) {
-                this.addLegend(record);
-            }, this);
-        if (this.dynamic) {
-            this.layerStore.on({
-                "add": this.onStoreAdd,
-                "remove": this.onStoreRemove,
-                "update": this.onStoreUpdate,
-                scope: this
-            });
-        }
-        this.doLayout();
-    },
-
-    /** private: method[recordIndexToPanelIndex]
-     *  Private method to get the panel index for a layer represented by a
-     *  record.
-     *
-     *  :param index ``Integer`` The index of the record in the store.
-     *
-     *  :return: ``Integer`` The index of the sub panel in this panel.
-     */
-    recordIndexToPanelIndex: function(index) {
-        var store = this.layerStore;
-        var count = store.getCount();
-        var panelIndex = -1;
-        for(var i=count-1; i>=0; --i) {
-            var layer = store.getAt(i).get("layer");
-            var legendGenerator = GeoExt.legend[layer.CLASS_NAME.replace(
-                'OpenLayers.Layer.', '')];
-            if(layer.displayInLayerSwitcher && legendGenerator &&
-                (store.getAt(i).get("hideInLegend") !== true)) {
-                    ++panelIndex;
-                    if(index === i) {
-                        break;
-                    }
-            }
-        };
-        return panelIndex;
-    },
-
-    /** private: method[onStoreUpdate]
-     *  Update a layer within the legend panel. Gets called when the store
-     *  fires the update event. This usually means the visibility of the layer
-     *  has changed.
-     *
-     *  :param store: ``Ext.data.Store`` The store in which the record was
-     *      changed.
-     *  :param record: ``Ext.data.Record`` The record object corresponding
-     *      to the updated layer.
-     *  :param operation: ``String`` The type of operation.
-     */
-    onStoreUpdate: function(store, record, operation) {
-        var layer = record.get('layer');
-        
-        if(this.items){
-            var legend = this.getComponent(layer.id);
-            
-            if (legend) {
-                legend.setVisible(layer.getVisibility() && 
-                                  layer.displayInLayerSwitcher && !record.get('hideInLegend'));
-                if (record.get('legendURL')) {
-                    var items = legend.findByType('gx_legendimage');
-                    for (var i=0, len=items.length; i<len; i++) {
-                        items[i].setUrl(record.get('legendURL'));
-                    }
-                }}
-        }
-    },
-
-    /** private: method[onStoreAdd]
-     *  Private method called when a layer is added to the store.
-     *
-     *  :param store: ``Ext.data.Store`` The store to which the record(s) was 
-     *      added.
-     *  :param record: ``Ext.data.Record`` The record object(s) corresponding
-     *      to the added layers.
-     *  :param index: ``Integer`` The index of the inserted record.
-     */
-    onStoreAdd: function(store, records, index) {
-        var panelIndex = this.recordIndexToPanelIndex(index);
-        for (var i=0, len=records.length; i<len; i++) {
-            this.addLegend(records[i], panelIndex);
-        }
-        this.doLayout();
-    },
-
-    /** private: method[onStoreRemove]
-     *  Private method called when a layer is removed from the store.
-     *
-     *  :param store: ``Ext.data.Store`` The store from which the record(s) was
-     *      removed.
-     *  :param record: ``Ext.data.Record`` The record object(s) corresponding
-     *      to the removed layers.
-     *  :param index: ``Integer`` The index of the removed record.
-     */
-    onStoreRemove: function(store, record, index) {
-        this.removeLegend(record);
-    },
-
-    /** private: method[removeLegend]
-     *  Remove the legend of a layer.
-     *  :param record: ``Ext.data.Record`` The record object from the layer 
-     *      store to remove.
-     */
-    removeLegend: function(record) {
-        var legend = this.getComponent(record.get('layer').id);
-        if (legend) {
-            this.remove(legend, true);
-            this.doLayout();
-        }
-    },
-
-    /** private: method[createLegendSubpanel]
-     *  Create a legend sub panel for the layer.
-     *
-     *  :param record: ``Ext.data.Record`` The record object from the layer
-     *      store.
-     *
-     *  :return: ``Ext.Panel`` The created panel per layer
-     */
-    createLegendSubpanel: function(record) {
-        var layer = record.get('layer');
-        var mainPanel = this.createMainPanel(record);
-        if (mainPanel !== null) {
-            // the default legend can be overridden by specifying a
-            // legendURL property
-            if (record.get('legendURL')) {
-                var legend = new GeoExt.legend.Image({url:
-                    record.get('legendURL')});
-                mainPanel.add(legend);
-            } else {
-                var legendGenerator = GeoExt.legend[layer.CLASS_NAME.replace(
-                    'OpenLayers.Layer.', '')];
-                if (legendGenerator) {
-                    var legend = new legendGenerator({layer: layer});
-                    mainPanel.add(legend);
-                }
-            }
-        }
-        return mainPanel;
-    },
-
-    /** private: method[addLegend]
-     *  Add a legend for the layer.
-     *
-     *  :param record: ``Ext.data.Record`` The record object from the layer 
-     *      store.
-     *  :param index: ``Integer`` The position at which to add the legend.
-     */
-    addLegend: function(record, index) {
-        index = index || 0;
-        var layer = record.get('layer');
-        var legendSubpanel = this.createLegendSubpanel(record);
-        if (legendSubpanel !== null) {
-           legendSubpanel.setVisible(layer.getVisibility());
-           this.insert(index, legendSubpanel);
-        }
-    },
-
-    /** private: method[createMainPanel]
-     *  Creates the main panel with a title for the layer.
-     *
-     *  :param record: ``Ext.data.Record`` The record object from the layer
-     *      store.
-     *
-     *  :return: ``Ext.Panel`` The created main panel with a label.
-     */
-    createMainPanel: function(record) {
-        var layer = record.get('layer');
-        var panel = null;
-        var legendGenerator = GeoExt.legend[layer.CLASS_NAME.replace(
-            'OpenLayers.Layer.', '')];
-        if (layer.displayInLayerSwitcher && !record.get('hideInLegend') && 
-          legendGenerator) {
-            var panelConfig = {
-                id: layer.id,
-                border: false,
-                bodyBorder: false,
-                bodyStyle: this.bodyStyle,
-                items: [
-                    new Ext.form.Label({
-                        text: (this.showTitle && !record.get('hideTitle')) ? 
-                            layer.name : '',
-                        cls: 'x-form-item x-form-item-label' +
-                            (this.labelCls ? ' ' + this.labelCls : '')
-                    })
-                ]
-            };
-            panel = new Ext.Panel(panelConfig);
-        }
-        return panel;
-    },
-
-    /** private: method[onDestroy]
-     *  Private method called during the destroy sequence.
-     */
-    onDestroy: function() {
-        if(this.layerStore) {
-            this.layerStore.un("add", this.onStoreAdd, this);
-            this.layerStore.un("remove", this.onStoreRemove, this);
-        }
-        GeoExt.LegendPanel.superclass.onDestroy.apply(this, arguments);
-    }
-    
-});
-
-Ext.reg('gx_legendpanel', GeoExt.LegendPanel);
+/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation
+ * Published under the BSD license.
+ * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * of the license.
+ *
+ * pending approval */
+
+/** api: (define)
+ *  module = GeoExt
+ *  class = LegendPanel
+ */
+
+Ext.namespace('GeoExt');
+
+/** api: constructor
+ *  .. class:: LegendPanel(config)
+ *
+ *  A panel showing legends of all layers in a layer store.
+ *  Depending on the layer type, a legend renderer will be chosen.
+ */
+GeoExt.LegendPanel = Ext.extend(Ext.Panel, {
+
+    /** api: config[dynamic]
+     *  ``Boolean``
+     *  If false the LegendPanel will not listen to the add, remove and change 
+     *  events of the LayerStore. So it will load with the initial state of
+     *  the LayerStore and not change anymore. 
+     */
+    dynamic: true,
+    
+    /** api: config[showTitle]
+     *  ``Boolean``
+     *  Whether or not to show the title of a layer. This can be a global
+     *  setting for the whole panel, or it can be overridden on the LayerStore 
+     *  record using the hideInLegend property.
+     */
+    showTitle: true,
+
+    /** api: config[labelCls]
+     *  ``String``
+     *  Optional css class to use for the layer title labels.
+     */
+    labelCls: null,
+
+    /** api: config[bodyStyle]
+     *  ``String``
+     *  Optional style to apply to the body of the legend panels.
+     */
+    bodyStyle: '',
+
+    /** api: config[layerStore]
+     *  ``GeoExt.data.LayerStore``
+     *  The layer store containing layers to be displayed in the legend 
+     *  container. If not provided it will be taken from the MapPanel.
+     */
+    layerStore: null,
+
+    /** private: method[initComponent]
+     *  Initializes the legend panel.
+     */
+    initComponent: function() {
+        GeoExt.LegendPanel.superclass.initComponent.call(this);
+    },
+    
+    /** private: method[onRender]
+     *  Private method called when the legend panel is being rendered.
+     */
+    onRender: function() {
+        GeoExt.LegendPanel.superclass.onRender.apply(this, arguments);
+        if(!this.layerStore) {
+            this.layerStore = GeoExt.MapPanel.guess().layers;
+        }
+        this.layerStore.each(function(record) {
+                this.addLegend(record);
+            }, this);
+        if (this.dynamic) {
+            this.layerStore.on({
+                "add": this.onStoreAdd,
+                "remove": this.onStoreRemove,
+                "update": this.onStoreUpdate,
+                scope: this
+            });
+        }
+        this.doLayout();
+    },
+
+    /** private: method[recordIndexToPanelIndex]
+     *  Private method to get the panel index for a layer represented by a
+     *  record.
+     *
+     *  :param index ``Integer`` The index of the record in the store.
+     *
+     *  :return: ``Integer`` The index of the sub panel in this panel.
+     */
+    recordIndexToPanelIndex: function(index) {
+        var store = this.layerStore;
+        var count = store.getCount();
+        var panelIndex = -1;
+        for(var i=count-1; i>=0; --i) {
+            var layer = store.getAt(i).get("layer");
+            var legendGenerator = GeoExt[
+                "Legend" + layer.CLASS_NAME.split(".").pop()
+            ];
+            if(layer.displayInLayerSwitcher && legendGenerator &&
+                (store.getAt(i).get("hideInLegend") !== true)) {
+                    ++panelIndex;
+                    if(index === i) {
+                        break;
+                    }
+            }
+        }
+        return panelIndex;
+    },
+
+    /** private: method[onStoreUpdate]
+     *  Update a layer within the legend panel. Gets called when the store
+     *  fires the update event. This usually means the visibility of the layer
+     *  has changed.
+     *
+     *  :param store: ``Ext.data.Store`` The store in which the record was
+     *      changed.
+     *  :param record: ``Ext.data.Record`` The record object corresponding
+     *      to the updated layer.
+     *  :param operation: ``String`` The type of operation.
+     */
+    onStoreUpdate: function(store, record, operation) {
+        var layer = record.get('layer');
+        var legend = this.getComponent(layer.id);
+        if (legend) {
+            legend.setVisible(layer.getVisibility() && 
+                layer.displayInLayerSwitcher && !record.get('hideInLegend'));
+            if (record.get('legendURL')) {
+                var items = legend.findByType('gx_legendimage');
+                for (var i=0, len=items.length; i<len; i++) {
+                    items[i].setUrl(record.get('legendURL'));
+                }
+            }
+        }
+    },
+
+    /** private: method[onStoreAdd]
+     *  Private method called when a layer is added to the store.
+     *
+     *  :param store: ``Ext.data.Store`` The store to which the record(s) was 
+     *      added.
+     *  :param record: ``Ext.data.Record`` The record object(s) corresponding
+     *      to the added layers.
+     *  :param index: ``Integer`` The index of the inserted record.
+     */
+    onStoreAdd: function(store, records, index) {
+        var panelIndex = this.recordIndexToPanelIndex(index);
+        for (var i=0, len=records.length; i<len; i++) {
+            this.addLegend(records[i], panelIndex);
+        }
+        this.doLayout();
+    },
+
+    /** private: method[onStoreRemove]
+     *  Private method called when a layer is removed from the store.
+     *
+     *  :param store: ``Ext.data.Store`` The store from which the record(s) was
+     *      removed.
+     *  :param record: ``Ext.data.Record`` The record object(s) corresponding
+     *      to the removed layers.
+     *  :param index: ``Integer`` The index of the removed record.
+     */
+    onStoreRemove: function(store, record, index) {
+        this.removeLegend(record);
+    },
+
+    /** private: method[removeLegend]
+     *  Remove the legend of a layer.
+     *  :param record: ``Ext.data.Record`` The record object from the layer 
+     *      store to remove.
+     */
+    removeLegend: function(record) {
+        var legend = this.getComponent(record.get('layer').id);
+        if (legend) {
+            this.remove(legend, true);
+            this.doLayout();
+        }
+    },
+
+    /** private: method[createLegendSubpanel]
+     *  Create a legend sub panel for the layer.
+     *
+     *  :param record: ``Ext.data.Record`` The record object from the layer
+     *      store.
+     *
+     *  :return: ``Ext.Panel`` The created panel per layer
+     */
+    createLegendSubpanel: function(record) {
+        var layer = record.get('layer');
+        var mainPanel = this.createMainPanel(record);
+        if (mainPanel !== null) {
+            // the default legend can be overridden by specifying a
+            // legendURL property
+            var legend;
+            if (record.get('legendURL')) {
+                legend = new GeoExt.LegendImage({url: record.get('legendURL')});
+                mainPanel.add(legend);
+            } else {
+                var legendGenerator = GeoExt[
+                    "Legend" + layer.CLASS_NAME.split(".").pop()
+                ];
+                if (legendGenerator) {
+                    legend = new legendGenerator({layer: layer});
+                    mainPanel.add(legend);
+                }
+            }
+        }
+        return mainPanel;
+    },
+
+    /** private: method[addLegend]
+     *  Add a legend for the layer.
+     *
+     *  :param record: ``Ext.data.Record`` The record object from the layer 
+     *      store.
+     *  :param index: ``Integer`` The position at which to add the legend.
+     */
+    addLegend: function(record, index) {
+        index = index || 0;
+        var layer = record.get('layer');
+        var legendSubpanel = this.createLegendSubpanel(record);
+        if (legendSubpanel !== null) {
+           legendSubpanel.setVisible(layer.getVisibility());
+           this.insert(index, legendSubpanel);
+        }
+    },
+
+    /** private: method[createMainPanel]
+     *  Creates the main panel with a title for the layer.
+     *
+     *  :param record: ``Ext.data.Record`` The record object from the layer
+     *      store.
+     *
+     *  :return: ``Ext.Panel`` The created main panel with a label.
+     */
+    createMainPanel: function(record) {
+        var layer = record.get('layer');
+        var panel = null;
+        var legendGenerator = GeoExt[
+            "Legend" + layer.CLASS_NAME.split(".").pop()
+        ];
+        if (layer.displayInLayerSwitcher && !record.get('hideInLegend') &&
+            legendGenerator) {
+            var panelConfig = {
+                id: layer.id,
+                border: false,
+                bodyBorder: false,
+                bodyStyle: this.bodyStyle,
+                items: [
+                    new Ext.form.Label({
+                        text: (this.showTitle && !record.get('hideTitle')) ? 
+                            layer.name : '',
+                        cls: 'x-form-item x-form-item-label' +
+                            (this.labelCls ? ' ' + this.labelCls : '')
+                    })
+                ]
+            };
+            panel = new Ext.Panel(panelConfig);
+        }
+        return panel;
+    },
+
+    /** private: method[onDestroy]
+     *  Private method called during the destroy sequence.
+     */
+    onDestroy: function() {
+        if(this.layerStore) {
+            this.layerStore.un("add", this.onStoreAdd, this);
+            this.layerStore.un("remove", this.onStoreRemove, this);
+            this.layerStore.un("update", this.onStoreUpdate, this);
+        }
+        GeoExt.LegendPanel.superclass.onDestroy.apply(this, arguments);
+    }
+    
+});
+
+Ext.reg('gx_legendpanel', GeoExt.LegendPanel);
\ No newline at end of file

Added: sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/LegendWMS.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/LegendWMS.js	                        (rev 0)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/LegendWMS.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -0,0 +1,87 @@
+/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation
+ * Published under the BSD license.
+ * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * of the license.
+ *
+ * pending approval */
+
+/**
+ * @include GeoExt/widgets/LegendImage.js
+ */
+
+/** api: (define)
+ *  module = GeoExt
+ *  class = LegendWMS
+ */
+Ext.namespace('GeoExt');
+
+/** api: constructor
+ *  .. class:: LegendWMS(config)
+ *
+ *  Show a legend image for a WMS layer.
+ */
+GeoExt.LegendWMS = Ext.extend(Ext.Panel, {
+
+    /** api: config[imageFormat]
+     *  ``String``  
+     *  The image format to request the legend image in.
+     *  Defaults to image/png.
+     */
+    imageFormat: "image/gif",
+
+    /** api: config[layer]
+     *  ``OpenLayers.Layer.WMS``
+     *  The WMS layer to request the legend for.
+     */
+    layer: null,
+
+    /** api: config[bodyBorder]
+     *  ``Boolean``
+     *  Show a border around the legend image or not. Default is false.
+     */
+    bodyBorder: false,
+
+    /** private: method[initComponent]
+     *  Initializes the WMS legend. For group layers it will create multiple
+     *  image box components.
+     */
+    initComponent: function() {
+        GeoExt.LegendWMS.superclass.initComponent.call(this);
+        this.createLegend();
+    },
+
+    /** private: method[getLegendUrl]
+     *  :param layer: ``OpenLayers.Layer.WMS`` The OpenLayers WMS layer object
+     *  :param layerName: ``String`` The name of the layer 
+     *      (used in the LAYERS parameter)
+     *  :return: ``String`` The url of the SLD WMS GetLegendGraphic request.
+     *
+     *  Get the url for the SLD WMS GetLegendGraphic request.
+     */
+    getLegendUrl: function(layerName) {
+        return this.layer.getFullRequestString({
+            REQUEST: "GetLegendGraphic",
+            WIDTH: null,
+            HEIGHT: null,
+            EXCEPTIONS: "application/vnd.ogc.se_xml",
+            LAYER: layerName,
+            LAYERS: null,
+            SRS: null,
+            FORMAT: this.imageFormat
+        });
+    },
+
+    /** private: method[createLegend]
+     *  Add one BoxComponent per sublayer to this panel.
+     */
+    createLegend: function() {
+        var layers = this.layer.params.LAYERS.split(",");
+        for (var i = 0, len = layers.length; i < len; i++){
+            var layerName = layers[i];
+            var legend = new GeoExt.LegendImage({url:
+                this.getLegendUrl(layerName)});
+            this.add(legend);
+        }
+    }
+
+});
\ No newline at end of file

Modified: sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/MapPanel.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/MapPanel.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/MapPanel.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -125,29 +125,38 @@
     },
     
     /** private: method[updateMapSize]
-     *  Tell the map that it needs to recaculate its size and position.
+     *  Tell the map that it needs to recalculate its size and position.
      */
     updateMapSize: function() {
         if(this.map) {
             this.map.updateSize();
         }
     },
-    
-    /** private: method[onRender]
-     *  Private method called after the panel has been
-     *  rendered.
+
+    /** private: method[renderMap]
+     *  Private method called after the panel has been rendered or after it
+     *  has been laid out by its parent's layout.
      */
-    onRender: function() {
-        GeoExt.MapPanel.superclass.onRender.apply(this, arguments);
-        this.map.render(this.body.dom);
-        if(this.map.layers.length > 0) {
-            if(this.center) {
-                // zoom does not have to be defined
-                this.map.setCenter(this.center, this.zoom);
-            }  else if(this.extent) {
-                this.map.zoomToExtent(this.extent);
+    renderMap: function() {
+        var map = this.map;
+
+        // hack: prevent map.updateSize (called from within map.render) from 
+        // zooming to the map extent. This hack is a workaround for 
+        // <http://trac.openlayers.org/ticket/2105> and must be
+        // removed once this ticket is closed.
+        var setCenter = map.setCenter;
+        map.setCenter = function() {};
+        map.render(this.body.dom);
+        map.setCenter = setCenter;
+
+        if(map.layers.length > 0) {
+            if(this.center || this.zoom != null) {
+                // both do not have to be defined
+                map.setCenter(this.center, this.zoom);
+            } else if(this.extent) {
+                map.zoomToExtent(this.extent);
             } else {
-                this.map.zoomToMaxExtent();
+                map.zoomToMaxExtent();
             }
         }
     },
@@ -178,10 +187,19 @@
      */
     afterRender: function() {
         GeoExt.MapPanel.superclass.afterRender.apply(this, arguments);
-        if(this.ownerCt) {
+        if(!this.ownerCt) {
+            this.renderMap();
+        } else {
             this.ownerCt.on("move", this.updateMapSize, this);
+            this.ownerCt.on({
+                "afterlayout": {
+                    fn: this.renderMap,
+                    scope: this,
+                    single: true
+                }
+            });
         }
-    },    
+    },
 
     /** private: method[onResize]
      *  Private method called after the panel has been resized.
@@ -191,14 +209,46 @@
         this.updateMapSize();
     },
     
-    /** private: method[onDestroy]
+    /** private: method[onBeforeAdd]
+     *  Private method called before a component is added to the panel.
+     */
+    onBeforeAdd: function(item) {
+        if(typeof item.addToMapPanel === "function") {
+            item.addToMapPanel(this);
+        }
+        GeoExt.MapPanel.superclass.onBeforeAdd.apply(this, arguments);
+    },
+    
+    /** private: method[remove]
+     *  Private method called when a component is removed from the panel.
+     */
+    remove: function(item, autoDestroy) {
+        if(typeof item.removeFromMapPanel === "function") {
+            item.removeFromMapPanel(this);
+        }
+        GeoExt.MapPanel.superclass.remove.apply(this, arguments);
+    },
+
+    /** private: method[beforeDestroy]
      *  Private method called during the destroy sequence.
      */
-    onDestroy: function() {
+    beforeDestroy: function() {
         if(this.ownerCt) {
             this.ownerCt.un("move", this.updateMapSize, this);
         }
-        GeoExt.MapPanel.superclass.onDestroy.apply(this, arguments);
+        /**
+         * If this container was passed a map instance, it is the
+         * responsibility of the creator to destroy it.
+         */
+        if(!this.initialConfig.map ||
+           !(this.initialConfig.map instanceof OpenLayers.Map)) {
+            // we created the map, we destroy it
+            if(this.map && this.map.destroy) {
+                this.map.destroy();
+            }
+        }
+        delete this.map;
+        GeoExt.MapPanel.superclass.beforeDestroy.apply(this, arguments);
     }
 });
 

Modified: sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/Popup.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/Popup.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/Popup.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -146,34 +146,36 @@
         GeoExt.Popup.superclass.initTools.call(this);
     },
 
-    /** api: method[addToMapPanel]
+    /** private: method[addToMapPanel]
      *  :param mapPanel: :class:`MapPanel` The panel to which this popup should
      *      be added.
      *  
      *  Adds this popup to a :class:`MapPanel`.  Assumes that the
      *  MapPanel's map is already initialized and that the
-     *  Popup's feature is on the map.
+     *  Popup's feature is on the map.  This method is called by the MapPanel's
+     *  add method.
      */
     addToMapPanel: function(mapPanel) {
         this.mapPanel = mapPanel;
         this.map = this.mapPanel.map;
-
-        mapPanel.add(this);
-        mapPanel.doLayout();
-
-        this.position();
-
-        /* Anchoring */
-        if(this.anchored) {
-            this.anchorPopup();
-        }
-
-        this.show();
-
-        /* Panning */
-        if(this.panIn) {
-            this.panIntoView();
-        }
+        
+        mapPanel.on({
+            "add": {
+                fn: function() {
+                    mapPanel.doLayout();
+                    this.position();
+                    if(this.anchored) {
+                        this.anchorPopup();
+                    }
+                    this.show();
+                    if(this.panIn) {
+                        this.panIntoView();
+                    }
+                },
+                single: true,
+                scope: this
+            }
+        });
     },
 
     /** api: method[setSize]
@@ -249,7 +251,6 @@
      *  MapPanel and adds it to the page body.
      */
     unanchorPopup: function() {
-        this.unbindFromMapPanel();
 
         //make the window draggable
         this.draggable = true;
@@ -284,19 +285,25 @@
         }
     },
 
-    /** private: method[unbindFromMapPanel]
+    /** private: method[removeFromMapPanel]
      *  Utility method for unbinding events that call for popup repositioning.
+     *  Called from the panel during panel.remove(popup).
      */
-    unbindFromMapPanel: function() {
-        //stop position with feature
-        this.map.events.un({
-            "move" : this.position,
-            scope : this
-        });
+    removeFromMapPanel: function() {
+        if(this.map && this.map.events) {
+            //stop position with feature
+            this.map.events.un({
+                "move" : this.position,
+                scope : this
+            });
+        }
 
-        this.un("resize", this.position);
-        this.un("collapse", this.position);
-        this.un("expand", this.position);
+        this.un("resize", this.position, this);
+        this.un("collapse", this.position, this);
+        this.un("expand", this.position, this);
+
+        delete this.mapPanel;
+        delete this.map;
     },
 
     /** private: method[panIntoView]
@@ -352,7 +359,7 @@
      *  Cleanup events before destroying the popup.
      */
     beforeDestroy: function() {
-        this.unbindFromMapPanel();
+        this.removeFromMapPanel();
         GeoExt.Popup.superclass.beforeDestroy.call(this);
     }
 });

Added: sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/ZoomSlider.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/ZoomSlider.js	                        (rev 0)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/ZoomSlider.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -0,0 +1,261 @@
+/**
+ * Copyright (c) 2008 The Open Planning Project
+ */
+
+/**
+ * @include GeoExt/widgets/tips/ZoomSliderTip.js
+ */
+
+/** api: (define)
+ *  module = GeoExt
+ *  class = ZoomSlider
+ *  base_link = `Ext.Slider <http://extjs.com/deploy/dev/docs/?class=Ext.Slider>`_
+ */
+Ext.namespace("GeoExt");
+
+/** api: example
+ *  Sample code to render a slider outside the map viewport:
+ * 
+ *  .. code-block:: javascript
+ *     
+ *      var slider = new GeoExt.ZoomSlider({
+ *          renderTo: document.body,
+ *          width: 200,
+ *          map: map
+ *      });
+ *     
+ *  Sample code to add a slider to a map panel:
+ * 
+ *  .. code-block:: javascript
+ * 
+ *      var panel = new GeoExt.MapPanel({
+ *          renderTo: document.body,
+ *          height: 300,
+ *          width: 400,
+ *          map: {
+ *              controls: [new OpenLayers.Control.Navigation()]
+ *          },
+ *          layers: [new OpenLayers.Layer.WMS(
+ *              "Global Imagery",
+ *              "http://demo.opengeo.org/geoserver/wms",
+ *              {layers: "bluemarble"}
+ *          )],
+ *          extent: [-5, 35, 15, 55],
+ *          items: [{
+ *              xtype: "gx_zoomslider",
+ *              vertical: true,
+ *              height: 100,
+ *              x: 10,
+ *              y: 20
+ *          }]
+ *      });
+ */
+
+/** api: constructor
+ *  .. class:: ZoomSlider(config)
+ *   
+ *      Create a slider for controlling a map's zoom level.
+ */
+GeoExt.ZoomSlider = Ext.extend(Ext.Slider, {
+    
+    /** api: config[map]
+     *  ``OpenLayers.Map`` or :class:`GeoExt.MapPanel`
+     *  The map that the slider controls.
+     */
+    map: null,
+    
+    /** private: config[minValue]
+     *  ``Number``
+     */
+    minValue: null,
+    
+    /** private: config[minValue]
+     *  ``Number``
+     */
+    maxValue: null,
+    
+    /** api: config[baseCls]
+     *  ``String``
+     *  The CSS class name for the slider elements.  Default is "gx-zoomslider".
+     */
+    baseCls: "gx-zoomslider",
+    
+    /** private: property[updating]
+     *  ``Boolean``
+     *  The slider position is being updated by itself (based on map zoomend).
+     */
+    updating: false,
+    
+    /** private: method[initComponent]
+     *  Initialize the component.
+     */
+    initComponent: function() {
+        GeoExt.ZoomSlider.superclass.initComponent.call(this);
+        
+        if(this.map) {
+            if(this.map instanceof GeoExt.MapPanel) {
+                this.map = this.map.map;
+            }
+            this.bind(this.map);
+        }
+        this.on({
+            "changecomplete": this.changeHandler,
+            "beforedestroy": this.unbind,
+            scope: this
+        });
+        
+    },
+    
+    /** private: method[onRender]
+     *  Override onRender to set base css class.
+     */
+    onRender: function() {
+        GeoExt.ZoomSlider.superclass.onRender.apply(this, arguments);
+        this.el.addClass(this.baseCls);
+    },
+
+    /** private: method[afterRender]
+     *  Override afterRender because the render event is fired too early
+     *  to call update.
+     */
+    afterRender : function(){
+        Ext.Slider.superclass.afterRender.apply(this, arguments);
+        this.update();
+    },
+    
+    /** private: method[addToMapPanel]
+     *  :param panel: :class:`GeoExt.MapPanel`
+     *  
+     *  Called by a MapPanel if this component is one of the items in the panel.
+     */
+    addToMapPanel: function(panel) {
+        this.on({
+            render: function() {
+                var el = this.getEl();
+                el.setStyle({
+                    position: "absolute",
+                    zIndex: panel.map.Z_INDEX_BASE.Control
+                });
+                el.on({
+                    mousedown: this.stopMouseEvents,
+                    click: this.stopMouseEvents
+                });
+            },
+            scope: this
+        });
+        this.bind(panel.map);
+    },
+    
+    /** private: method[stopMouseEvents]
+     *  :param e: ``Object``
+     */
+    stopMouseEvents: function(e) {
+        e.stopEvent();
+    },
+    
+    /** private: method[removeFromMap]
+     *  :param panel: :class:`GeoExt.MapPanel`
+     *  
+     *  Called by a MapPanel if this component is one of the items in the panel.
+     */
+    removeFromMapPanel: function(panel) {
+        var el = this.getEl();
+        el.un("mousedown", this.stopMouseEvents, this);
+        el.un("click", this.stopMouseEvents, this);
+        this.unbind();
+    },
+    
+    /** private: method[bind]
+     *  :param map: ``OpenLayers.Map``
+     */
+    bind: function(map) {
+        this.map = map;
+        this.map.events.on({
+            zoomend: this.update,
+            changebaselayer: this.initZoomValues,
+            scope: this
+        });
+        if(this.map.baseLayer) {
+            this.initZoomValues();
+        }
+    },
+    
+    /** private: method[unbind]
+     */
+    unbind: function() {
+        if(this.map) {
+            this.map.events.un({
+                zoomend: this.update,
+                changebaselayer: this.initZoomValues,
+                scope: this
+            });
+        }
+    },
+    
+    /** private: method[initZoomValues]
+     *  Set the min/max values for the slider if not set in the config.
+     */
+    initZoomValues: function() {
+        var layer = this.map.baseLayer;
+        if(this.initialConfig.minValue === undefined) {
+            this.minValue = layer.minZoomLevel || 0;
+        }
+        if(this.initialConfig.maxValue === undefined) {
+            this.maxValue = layer.maxZoomLevel || layer.numZoomLevels - 1;
+        }
+    },
+    
+    /** api: method[getZoom]
+     *  :return: ``Number`` The map zoom level.
+     *  
+     *  Get the zoom level for the associated map based on the slider value.
+     */
+    getZoom: function() {
+        return this.getValue();
+    },
+    
+    /** api: method[getScale]
+     *  :return: ``Number`` The map scale denominator.
+     *  
+     *  Get the scale denominator for the associated map based on the slider value.
+     */
+    getScale: function() {
+        return OpenLayers.Util.getScaleFromResolution(
+            this.map.getResolutionForZoom(this.getValue()),
+            this.map.getUnits()
+        );
+    },
+    
+    /** api: method[getResolution]
+     *  :return: ``Number`` The map resolution.
+     *  
+     *  Get the resolution for the associated map based on the slider value.
+     */
+    getResolution: function() {
+        return this.map.getResolutionForZoom(this.getValue());
+    },
+    
+    /** private: method[changeHandler]
+     *  Registered as a listener for slider changecomplete.  Zooms the map.
+     */
+    changeHandler: function() {
+        if(this.map && !this.updating) {
+            this.map.zoomTo(this.getValue());
+        }
+    },
+    
+    /** private: method[update]
+     *  Registered as a listener for map zoomend.  Updates the value of the slider.
+     */
+    update: function() {
+        if(this.rendered && this.map) {
+            this.updating = true;
+            this.setValue(this.map.getZoom());
+            this.updating = false;
+        }
+    }
+
+});
+
+/** api: xtype = gx_zoomslider */
+Ext.reg('gx_zoomslider', GeoExt.ZoomSlider);

Modified: sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/form/SearchAction.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/form/SearchAction.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/form/SearchAction.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -11,6 +11,10 @@
  *  base_link = `Ext.form.Action <http://extjs.com/deploy/dev/docs/?class=Ext.form.Action>`_
  */
 
+/**
+ * @include GeoExt/widgets/form.js
+ */
+
 Ext.namespace("GeoExt.form");
  
 /** api: example
@@ -47,68 +51,6 @@
  *      });
  */
 
-/**
- * Function: GeoExt.form.filterFromForm
- * Create an {OpenLayers.Filter} object from a {Ext.form.BasicForm}
- *     instance or a {Ext.form.FormPanel}.
- *
- * Parameters:
- * form - {Ext.form.BasicForm|Ext.form.FormPanel}
- * logicalOp - {String} Either {OpenLayers.Filter.Logical.AND}
- *     or {OpenLayers.Filter.Logical.OR}, set to
- *     {OpenLayers.Filter.Logical.AND} if null or
- *     undefined.
- *
- * Returns:
- * {OpenLayers.Filter}
- */
-GeoExt.form.filterFromForm = function(form, logicalOp) {
-    if(form instanceof Ext.form.FormPanel) {
-        form = form.getForm();
-    }
-    var filters = [], values = form.getValues(false);
-    for(var prop in values) {
-        var s = prop.split("__");
-
-        var value = values[prop], type;
-
-        if(s.length > 1 && 
-           (type = GeoExt.form.filterFromForm.FILTER_MAP[s[1]]) !== undefined) {
-            prop = s[0];
-        } else {
-            type = OpenLayers.Filter.Comparison.EQUAL_TO;
-        }
-
-        filters.push(
-            new OpenLayers.Filter.Comparison({
-                type: type,
-                value: value,
-                property: prop
-            })
-        );
-    }
-
-    return new OpenLayers.Filter.Logical({
-        type: logicalOp || OpenLayers.Filter.Logical.AND,
-        filters: filters
-    });
-};
-
-/**
- * Constant: GeoExt.form.filterFromForm.FILTER_MAP
- * An object mapping operator strings as found in field names to
- *     {OpenLayers.Filter.Comparison} types.
- */
-GeoExt.form.filterFromForm.FILTER_MAP = {
-    "eq": OpenLayers.Filter.Comparison.EQUAL_TO,
-    "ne": OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
-    "lt": OpenLayers.Filter.Comparison.LESS_THAN,
-    "le": OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO,
-    "gt": OpenLayers.Filter.Comparison.GREATER_THAN,
-    "ge": OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO,
-    "like": OpenLayers.Filter.Comparison.LIKE
-};
-
 /** api: constructor
  *  .. class:: SearchAction(form, options)
  *
@@ -165,7 +107,7 @@
      */
     run: function() {
         var o = this.options;
-        var f = GeoExt.form.filterFromForm(this.form);
+        var f = GeoExt.form.toFilter(this.form);
         if(o.clientValidation === false || this.form.isValid()){
             this.response = o.protocol.read(
                 Ext.applyIf({

Added: sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/form.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/form.js	                        (rev 0)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/form.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -0,0 +1,70 @@
+/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation
+ * Published under the BSD license.
+ * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * of the license.
+ * 
+ * pending approval */
+
+Ext.namespace("GeoExt.form");
+
+/**
+ * Function: GeoExt.form.toFilter
+ * Create an {OpenLayers.Filter} object from a {Ext.form.BasicForm}
+ *     or a {Ext.form.FormPanel} instance.
+ *
+ * Parameters:
+ * form - {Ext.form.BasicForm|Ext.form.FormPanel}
+ * logicalOp - {String} Either {OpenLayers.Filter.Logical.AND}
+ *     or {OpenLayers.Filter.Logical.OR}, set to
+ *     {OpenLayers.Filter.Logical.AND} if null or
+ *     undefined.
+ *
+ * Returns:
+ * {OpenLayers.Filter}
+ */
+GeoExt.form.toFilter = function(form, logicalOp) {
+    if(form instanceof Ext.form.FormPanel) {
+        form = form.getForm();
+    }
+    var filters = [], values = form.getValues(false);
+    for(var prop in values) {
+        var s = prop.split("__");
+
+        var value = values[prop], type;
+
+        if(s.length > 1 && 
+           (type = GeoExt.form.toFilter.FILTER_MAP[s[1]]) !== undefined) {
+            prop = s[0];
+        } else {
+            type = OpenLayers.Filter.Comparison.EQUAL_TO;
+        }
+
+        filters.push(
+            new OpenLayers.Filter.Comparison({
+                type: type,
+                value: value,
+                property: prop
+            })
+        );
+    }
+
+    return new OpenLayers.Filter.Logical({
+        type: logicalOp || OpenLayers.Filter.Logical.AND,
+        filters: filters
+    });
+};
+
+/**
+ * Constant: GeoExt.form.omForm.FILTER_MAP
+ * An object mapping operator strings as found in field names to
+ *     {OpenLayers.Filter.Comparison} types.
+ */
+GeoExt.form.toFilter.FILTER_MAP = {
+    "eq": OpenLayers.Filter.Comparison.EQUAL_TO,
+    "ne": OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
+    "lt": OpenLayers.Filter.Comparison.LESS_THAN,
+    "le": OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO,
+    "gt": OpenLayers.Filter.Comparison.GREATER_THAN,
+    "ge": OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO,
+    "like": OpenLayers.Filter.Comparison.LIKE
+};

Modified: sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tips/SliderTip.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tips/SliderTip.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tips/SliderTip.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -1,34 +1,87 @@
+/**
+ * Copyright (c) 2008 The Open Planning Project
+ */
+
+/** api: (define)
+ *  module = GeoExt
+ *  class = SliderTip
+ *  base_link = `Ext.Tip <http://extjs.com/deploy/dev/docs/?class=Ext.Tip>`_
+ */
 Ext.namespace("GeoExt");
 
+/** api: example
+ *  Sample code to create a slider tip to display slider value on hover:
+ * 
+ *  .. code-block:: javascript
+ *     
+ *      var slider = new Ext.Slider({
+ *          renderTo: document.body,
+ *          width: 200,
+ *          plugins: new GeoExt.SliderTip()
+ *      });
+ */
+
+/** api: constructor
+ *  .. class:: SliderTip(config)
+ *   
+ *      Create a slider tip displaying ``Ext.Slider`` values over slider thumbs.
+ */
 GeoExt.SliderTip = Ext.extend(Ext.Tip, {
 
-    /**
-     * Property: hover
-     * {Boolean} Display the tip when hovering over the thumb.  If false, tip
-     *     will only be displayed while dragging.  Default is true.
+    /** api: config[hover]
+     *  ``Boolean``
+     *  Display the tip when hovering over the thumb.  If ``false``, tip will
+     *  only be displayed while dragging.  Default is ``true``.
      */
     hover: true,
     
-    /**
-     * Property: dragging
-     * {Boolean} The thumb is currently being dragged.
+    /** api: config[minWidth]
+     *  ``Number``
+     *  Minimum width of the tip.  Default is 10.
      */
-    dragging: false,
+    minWidth: 10,
 
+    /** api: config[minWidth]
+     *  ``Number``
+     *  Minimum width of the tip.  Default is 10.
+     */
     minWidth: 10,
-    minWidth: 10,
+    
+    /** api: config[offsets]
+     *  ``Array(Number)``
+     *  A two item list that provides x, y offsets for the tip.  Default is
+     *  [0, -10].
+     */
     offsets : [0, -10],
-    init : function(slider){
-        slider.on('dragstart', this.onSlide, this);
-        slider.on('drag', this.onSlide, this);
-        slider.on('dragend', this.hide, this);
-        slider.on('destroy', this.destroy, this);
+    
+    /** private: property[dragging]
+     *  ``Boolean``
+     *  The thumb is currently being dragged.
+     */
+    dragging: false,
+
+    /** private: method[init]
+     *  :param slider: ``Ext.Slider``
+     *  
+     *  Called when the plugin is initialized.
+     */
+    init: function(slider) {
+        slider.on({
+            dragstart: this.onSlide,
+            drag: this.onSlide,
+            dragend: this.hide,
+            destroy: this.destroy,
+            scope: this
+        });
         if(this.hover) {
-            slider.on('render', this.registerThumbListeners, this);
+            slider.on("render", this.registerThumbListeners, this);
         }
         this.slider = slider;
     },
 
+    /** private: method[registerThumbListeners]
+     *  Set as a listener for 'render' if hover is true.
+     */
     registerThumbListeners: function() {
         this.slider.thumb.on({
             "mouseover": function() {
@@ -44,7 +97,12 @@
         });
     },
 
-    onSlide : function(slider) {
+    /** private: method[onSlide]
+     *  :param slider: ``Ext.Slider``
+     *
+     *  Listener for dragstart and drag.
+     */
+    onSlide: function(slider) {
         this.dragging = true;
         this.show();
         this.body.update(this.getText(slider));
@@ -52,6 +110,11 @@
         this.el.alignTo(slider.thumb, 'b-t?', this.offsets);
     },
 
+    /** api: config[getText]
+     *  ``Function``
+     *  Function that generates the string value to be displayed in the tip.  By
+     *  default, the return from slider.getValue() is displayed.
+     */
     getText : function(slider) {
         return slider.getValue();
     }

Added: sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tips/ZoomSliderTip.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tips/ZoomSliderTip.js	                        (rev 0)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tips/ZoomSliderTip.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2008 The Open Planning Project
+ */
+
+/**
+ * @include GeoExt/widgets/tips/SliderTip.js
+ */
+
+/** api: (extends)
+ *  GeoExt/widgets/tips/SliderTip.js
+ */
+
+/** api: (define)
+ *  module = GeoExt
+ *  class = ZoomSliderTip
+ *  base_link = `Ext.Tip <http://extjs.com/deploy/dev/docs/?class=Ext.Tip>`_
+ */
+Ext.namespace("GeoExt");
+
+/** api: example
+ *  Sample code to create a slider tip to display scale and resolution:
+ * 
+ *  .. code-block:: javascript
+ *     
+ *      var slider = new GeoExt.ZoomSlider({
+ *          renderTo: document.body,
+ *          width: 200,
+ *          map: map,
+ *          plugins: new GeoExt.ZoomSliderTip({
+ *              template: "Scale: 1 : {scale}<br>Resolution: {resolution}"
+ *          })
+ *      });
+ */
+
+/** api: constructor
+ *  .. class:: ZoomSliderTip(config)
+ *   
+ *      Create a slider tip displaying :class:`GeoExt.ZoomSlider` values.
+ */
+GeoExt.ZoomSliderTip = Ext.extend(GeoExt.SliderTip, {
+    
+    /** api: config[template]
+     *  ``String``
+     *  Template for the tip. Can be customized using the following keywords in
+     *  curly braces:
+     *  
+     *  * ``zoom`` - the zoom level
+     *  * ``resolution`` - the resolution
+     *  * ``scale`` - the scale denominator
+     */
+    template: '<div>Zoom Level: {zoom}</div>' +
+        '<div>Resolution: {resolution}</div>' +
+        '<div>Scale: 1 : {scale}</div>',
+    
+    /** private: property[compiledTemplate]
+     *  ``Ext.Template``
+     *  The template compiled from the ``template`` string on init.
+     */
+    compiledTemplate: null,
+    
+    /** private: method[init]
+     *  Called to initialize the plugin.
+     */
+    init: function(slider) {
+        this.compiledTemplate = new Ext.Template(this.template);
+        GeoExt.ZoomSliderTip.superclass.init.call(this, slider);
+    },
+    
+    /** private: method[getText]
+     *  :param slider: ``Ext.Slider`` The slider this tip is attached to.
+     */
+    getText : function(slider) {
+        var data = {
+            zoom: slider.getZoom(),
+            resolution: slider.getResolution(),
+            scale: Math.round(slider.getScale()) 
+        };
+        return this.compiledTemplate.apply(data);
+    }
+});

Modified: sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tree/BaseLayerContainer.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tree/BaseLayerContainer.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tree/BaseLayerContainer.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -1,43 +1,49 @@
-/**
- * Copyright (c) 2008 The Open Planning Project
- */
+/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation ¹
+ * Published under the BSD license.
+ * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * of the license.
+ * 
+ * ¹ pending approval */
 
 /**
  * @requires GeoExt/widgets/tree/LayerContainer.js
  */
 Ext.namespace("GeoExt.tree");
 
-/**
- * Class: GeoExt.tree.BaseLayerContainer
+/** api: (define)
+ *  module = GeoExt.tree
+ *  class = BaseLayerContainer
+ */
+
+/** api: (extends)
+ * GeoExt/widgets/tree/LayerContainer.js
+ */
+
+/** api: constructor
+ *  .. class:: BaseLayerContainer
  * 
- * A layer container that will collect all base layers of an OpenLayers map.
- * Only layers that have displayInLayerSwitcher set to true will be included.
+ *     A layer container that will collect all base layers of an OpenLayers
+ *     map. Only layers that have displayInLayerSwitcher set to true will be
+ *     included.
  * 
- * To use this node type in JSON config, set nodeType to
- * "olBaseLayerContainer".
- * 
- * Inherits from:
- * - <GeoExt.tree.LayerContainer>
+ *     To use this node type in ``TreePanel`` config, set nodeType to
+ *     "gx_baselayercontainer".
  */
 GeoExt.tree.BaseLayerContainer = Ext.extend(GeoExt.tree.LayerContainer, {
 
-    /**
-     * Constructor: GeoExt.tree.BaseLayerContainer
-     * 
-     * Parameters:
-     * config - {Object}
+    /** private: method[constructor]
+     *  Private constructor override.
      */
     constructor: function(config) {
         config.text = config.text || "Base Layer";
         GeoExt.tree.BaseLayerContainer.superclass.constructor.apply(this, arguments);
     },
 
-    /**
-     * Method: addLayerNode
-     * Adds a child node representing a base layer of the map
+    /** private: method[addLayerNode]
+     *  :param layerRecord: ``Ext.data.Record`` the layer record to add a node
+     *      for
      * 
-     * Parameters:
-     * layerRecord - {Ext.data.Record} the layer record to add a node for
+     *  Adds a child node representing a base layer of the map.
      */
     addLayerNode: function(layerRecord) {
         var layer = layerRecord.get("layer");
@@ -47,12 +53,11 @@
         }
     },
     
-    /**
-     * Method: removeLayerNode
-     * Removes a child node representing a base layer of the map
-     * 
-     * Parameters:
-     * layerRecord - {Ext.data.Record} the layer record to remove the node for
+    /** private: method[removeLayerNode]
+     *  :param layerRecord: ``Ext.data.Record`` the layer record to remove the
+     *      node for
+     *
+     *  Removes a child node representing a base layer of the map.
      */
     removeLayerNode: function(layerRecord) {
         var layer = layerRecord.get("layer");

Modified: sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tree/LayerContainer.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tree/LayerContainer.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tree/LayerContainer.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -1,5 +1,12 @@
+/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation ¹
+ * Published under the BSD license.
+ * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * of the license.
+ * 
+ * ¹ pending approval */
+
 /**
- * Copyright (c) 2008 The Open Planning Project
+ * @include GeoExt/widgets/tree/LayerNode.js
  */
 
 /**
@@ -7,40 +14,39 @@
  */
 Ext.namespace("GeoExt.tree");
 
-/**
- * Class: GeoExt.tree.LayerContainer
+/** api: (define)
+ *  module = GeoExt.tree
+ *  class = LayerContainer
+ *  base_link = `Ext.tree.TreeNode <http://extjs.com/deploy/dev/docs/?class=Ext.tree.TreeNode>`_
+ */
+
+/** api: constructor
+ *  .. class:: LayerContainer
  * 
- * A subclass of {Ext.tree.TreeNode} that will collect all layers of an
- * OpenLayers map. Only layers that have displayInLayerSwitcher set to true
- * will be included. The childrens' iconCls will be set to "baselayer-icon"
- * for base layers, and to "layer-icon" for overlay layers.
+ *      A subclass of ``Ext.tree.TreeNode`` that will collect all layers of an
+ *      OpenLayers map. Only layers that have displayInLayerSwitcher set to true
+ *      will be included. The childrens' iconCls will be set to "baselayer-icon"
+ *      for base layers, and to "layer-icon" for overlay layers.
  * 
- * To use this node type in JSON config, set nodeType to "olLayerContainer".
- * 
- * Inherits from:
- * - <Ext.tree.TreeNode>
+ *      To use this node type in ``TreePanel`` config, set nodeType to
+ *      "gx_layercontainer".
  */
 GeoExt.tree.LayerContainer = Ext.extend(Ext.tree.TreeNode, {
     
-    /**
-     * APIProperty: layerStore
-     * {<GeoExt.data.LayerStore>} The layer store containing layers to be
-     *     displayed in the container.
+    /** api: config[layerStore]
+     *  :class:`GeoExt.data.LayerStore`
+     *  The layer store containing layers to be displayed in the container.
      */
     layerStore: null,
     
-    /**
-     * APIProperty: defaults
-     * {Object} a configuration object passed to all nodes that this
-     *     LayerContainer creates.
+    /** api: config[defaults]
+     *  ``Object``
+     *  A configuration object passed to all nodes that this container creates.
      */
     defaults: null,
 
-    /**
-     * Constructor: GeoExt.tree.LayerContainer
-     * 
-     * Parameters:
-     * config - {Object}
+    /** private: method[constructor]
+     *  Private constructor override.
      */
     constructor: function(config) {
         this.layerStore = config.layerStore;
@@ -48,11 +54,8 @@
         GeoExt.tree.LayerContainer.superclass.constructor.apply(this, arguments);
     },
 
-    /**
-     * Method: render
-     * 
-     * Parameters:
-     * bulkRender - {Boolean}
+    /** private: method[render]
+     *  :param bulkRender: ``Boolean``
      */
     render: function(bulkRender) {
         if (!this.rendered) {
@@ -71,14 +74,12 @@
         GeoExt.tree.LayerContainer.superclass.render.call(this, bulkRender);
     },
     
-    /**
-     * Method: onStoreAdd
-     * Listener for the store's add event.
-     *
-     * Parameters:
-     * store - {Ext.data.Store}
-     * records - {Array(Ext.data.Record)}
-     * index - {Number}
+    /** private: method[onStoreAdd]
+     *  :param store: ``Ext.data.Store``
+     *  :param records: ``Array(Ext.data.Record)``
+     *  :param index: ``Number``
+     *  
+     *  Listener for the store's add event.
      */
     onStoreAdd: function(store, records, index) {
         if(!this._reordering) {
@@ -89,14 +90,12 @@
         }
     },
     
-    /**
-     * Method: onStoreRemove
-     * Listener for the store's remove event.
-     *
-     * Parameters:
-     * store - {Ext.data.Store}
-     * record - {Ext.data.Record}
-     * index - {Number}
+    /** private: method[onStoreRemove]
+     *  :param store: ``Ext.data.Store``
+     *  :param record: ``Ext.data.Record``
+     *  :param index: ``Number``
+     *  
+     *  Listener for the store's remove event.
      */
     onStoreRemove: function(store, record, index) {
         if(!this._reordering) {
@@ -104,8 +103,7 @@
         }
     },
 
-    /**
-     * Method: onDestroy
+    /** private: method[onDestroy]
      */
     onDestroy: function() {
         if(this.layerStore) {
@@ -115,15 +113,9 @@
         GeoExt.tree.LayerContainer.superclass.onDestroy.apply(this, arguments);
     },
     
-    /**
-     * Method: recordIndexToNodeIndex
-     * Convert a record index into a child node index.
-     *
-     * Parameters:
-     * index - {Number} The record index in the layer store.
-     *
-     * Returns:
-     * {Number} The appropriate child node index for the record.
+    /** private: method[recordIndexToNodeIndex]
+     *  :param index: ``Number`` The record index in the layer store.
+     *  :return: ``Number`` The appropriate child node index for the record.
      */
     recordIndexToNodeIndex: function(index) {
         var store = this.layerStore;
@@ -140,15 +132,11 @@
         return nodeIndex;
     },
     
-    /**
-     * Method: nodeIndexToRecordIndex
-     * Convert a child node index to a record index.
-     *
-     * Parameters:
-     * index - {Number} The child node index.
-     *
-     * Returns:
-     * {Number} The appropriate record index for the node.
+    /** private: method[nodeIndexToRecordIndex]
+     *  :param index: ``Number`` The child node index.
+     *  :return: ``Number`` The appropriate record index for the node.
+     *  
+     *  Convert a child node index to a record index.
      */
     nodeIndexToRecordIndex: function(index) {
         var store = this.layerStore;
@@ -165,13 +153,12 @@
         return i;
     },
     
-    /**
-     * Method: addLayerNode
-     * Adds a child node representing a layer of the map
-     * 
-     * Parameters:
-     * layerRecord - {Ext.data.Record} the layer record to add the layer for
-     * index - {Number} Optional index for the new layer.  Default is 0.
+    /** private: method[addLayerNode]
+     *  :param layerRecord: ``Ext.data.Record`` The layer record containing the
+     *      layer to be added.
+     *  :param index: ``Number`` Optional index for the new layer.  Default is 0.
+     *  
+     *  Adds a child node representing a layer of the map
      */
     addLayerNode: function(layerRecord, index) {
         index = index || 0;
@@ -192,12 +179,11 @@
         }
     },
     
-    /**
-     * Method: removeLayerNode
-     * Removes a child node representing a layer of the map
+    /** private: method[removeLayerNode]
+     *  :param layerRecord: ``Ext.data.Record`` The layer record containing the
+     *      layer to be removed.
      * 
-     * Parameters:
-     * layerRecord - {Ext.data.Record} the layer record to remove the node for
+     *  Removes a child node representing a layer of the map
      */
     removeLayerNode: function(layerRecord) {
         var layer = layerRecord.get("layer");
@@ -212,18 +198,16 @@
     	}
     },
     
-    /**
-     * Method: onChildMove
-     * Listener for child node "move" events.  This updates the order of
-     *     records in the store based on new node order if the node has not
-     *     changed parents.
-     *
-     * Parameters:
-     * tree - {Ext.data.Tree}
-     * node - {Ext.tree.TreeNode}
-     * oldParent - {Ext.tree.TreeNode}
-     * newParent - {Ext.tree.TreeNode}
-     * index {Number}
+    /** private: method[onChildMove]
+     *  :param tree: ``Ext.data.Tree``
+     *  :param node: ``Ext.tree.TreeNode``
+     *  :param oldParent: ``Ext.tree.TreeNode``
+     *  :param newParent: ``Ext.tree.TreeNode``
+     *  :param index: ``Number``
+     *  
+     *  Listener for child node "move" events.  This updates the order of
+     *  records in the store based on new node order if the node has not
+     *  changed parents.
      */
     onChildMove: function(tree, node, oldParent, newParent, index) {
         if(oldParent === newParent) {

Modified: sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tree/LayerNode.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tree/LayerNode.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tree/LayerNode.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -1,53 +1,45 @@
-/**
- * Copyright (c) 2008 The Open Planning Project
- */
+/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation ¹
+ * Published under the BSD license.
+ * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * of the license.
+ * 
+ * ¹ pending approval */
+
 Ext.namespace("GeoExt.tree");
 
-/**
- * Class: GeoExt.tree.LayerNodeUI
- * 
- * Inherits from:
- * - Ext.tree.TreeNodeUI
+/** private: constructor
+ *  .. class:: LayerNodeUI
+ *
+ *      Place in a separate file if this should be documented.
  */
 GeoExt.tree.LayerNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
     
-    /**
-     * Property: radio
-     * {Ext.Element}
+    /** private: property[radio]
+     *  ``Ext.Element``
      */
     radio: null,
     
-    /**
-     * Constructor: GeoExt.tree.LayerNodeUI
-     * 
-     * Parameters:
-     * config - {Object}
+    /** private: method[constructor]
      */
     constructor: function(config) {
         GeoExt.tree.LayerNodeUI.superclass.constructor.apply(this, arguments);
     },
     
-    /**
-     * Method: render
-     * 
-     * Parameters:
-     * bulkRender - {Boolean}
+    /** private: method[render]
+     *  :param bulkRender: ``Boolean``
      */
     render: function(bulkRender) {
         GeoExt.tree.LayerNodeUI.superclass.render.call(this, bulkRender);
         var a = this.node.attributes;
-        if (a.radioGroup && this.radio !== undefined) {
+        if (a.radioGroup && this.radio === null) {
             this.radio = Ext.DomHelper.insertAfter(this.checkbox,
                 ['<input type="radio" class="x-tree-node-radio" name="',
                 a.radioGroup, '_radio"></input>'].join(""));
         }
     },
     
-    /**
-     * Method: onClick
-     * 
-     * Parameters:
-     * e - {Object}
+    /** private: method[onClick]
+     *  :param e: ``Object``
      */
     onClick: function(e) {
         if (e.getTarget('input[type=radio]', 1)) {
@@ -57,11 +49,8 @@
         }
     },
     
-    /**
-     * Method: toggleCheck
-     * 
-     * Parameters:
-     * value - {Boolean}
+    /** private: method[toggleCheck]
+     *  :param value: ``Boolean``
      */
     toggleCheck: function(value) {
         GeoExt.tree.LayerNodeUI.superclass.toggleCheck.call(this, value);
@@ -74,8 +63,7 @@
         node.visibilityChanging = false;
     },
     
-    /**
-     * Method: onDestroy
+    /** private: method[onDestroy]
      */
     onDestroy: function() {
         delete this.radio;
@@ -83,75 +71,78 @@
     }
 });
 
+/** api: (define)
+ *  module = GeoExt.tree
+ *  class = LayerNode
+ *  base_link = `Ext.tree.TreeNode <http://extjs.com/deploy/dev/docs/?class=Ext.tree.TreeNode>`_
+ */
 
-/**
- * Class: GeoExt.tree.LayerNode
+/** api: constructor
+ *  .. class:: LayerNode(config)
  * 
- * A subclass of {<GeoExt.tree.TristateCheckboxNode>} that is connected to an
- * {OpenLayers.Layer} by setting the node's layer property. Checking or
- * unchecking the checkbox of this node will directly affect the layer and
- * vice versa. The default iconCls for this node's icon is "layer-icon",
- * unless it has children.
+ *      A subclass of ``Ext.tree.TreeNode`` that is connected to an
+ *      ``OpenLayers.Layer`` by setting the node's layer property. Checking or
+ *      unchecking the checkbox of this node will directly affect the layer and
+ *      vice versa. The default iconCls for this node's icon is "layer-icon",
+ *      unless it has children.
  * 
- * Setting the node's layer property to a layer name instead of an object
- * will also work. As soon as a layer is found, it will be stored as layer
- * property in the attributes hash.
+ *      Setting the node's layer property to a layer name instead of an object
+ *      will also work. As soon as a layer is found, it will be stored as layer
+ *      property in the attributes hash.
  * 
- * The node's text property defaults to the layer name.
+ *      The node's text property defaults to the layer name.
  * 
- * If the node has a radioGroup attribute configured, the node will be
- * rendered with a radio button. This works like the checkbox with the
- * checked attribute, but radioGroup is a string that identifies the options
- * group. Clicking the radio button will fire a radioChange event.
+ *      If the node has a radioGroup attribute configured, the node will be
+ *      rendered with a radio button. This works like the checkbox with the
+ *      checked attribute, but radioGroup is a string that identifies the options
+ *      group. Clicking the radio button will fire a radioChange event.
  * 
- * To use this node type in a JSON config, set nodeType to "gx_layer".
- * 
- * Inherits from:
- * - Ext.tree.TreeNode
+ *      To use this node type in a ``TreePanel`` config, set ``nodeType`` to
+ *      "gx_layer".
  */
 GeoExt.tree.LayerNode = Ext.extend(Ext.tree.TreeNode, {
     
-    /**
-     * APIProperty: layer
-     * {OpenLayers.Layer|String} The layer that this layer node will
-     *     be bound to, or the name of the layer (has to match the layer's
-     *     name property). If a layer name is provided, <layerStore> also has
-     *     to be provided.
+    /** api: config[layer]
+     *  ``OpenLayers.Layer or String``
+     *  The layer that this layer node will
+     *  be bound to, or the name of the layer (has to match the layer's
+     *  name property). If a layer name is provided, ``layerStore`` also has
+     *  to be provided.
      */
+
+    /** api: property[layer]
+     *  ``OpenLayers.Layer``
+     *  The layer this node is bound to.
+     */
     layer: null,
     
-    /**
-     * APIProperty: layerStore
-     * {<GeoExt.data.LayerStore|"auto"} The layer store containing the layer
-     *     that this node represents. If set to "auto", the node will query
-     *     the ComponentManager for a <GeoExt.MapPanel>, take the first one it
-     *     finds and take its layer store. This property is only required
-     *     if <layer> is provided as a string.
+    /** api: config[layerStore]
+     *  :class:`GeoExt.data.LayerStore` ``or "auto"``
+     *  The layer store containing the layer that this node represents.  If set
+     *  to "auto", the node will query the ComponentManager for a
+     *  :class:`GeoExt.MapPanel`, take the first one it finds and take its layer
+     *  store. This property is only required if ``layer`` is provided as a
+     *  string.
      */
     layerStore: null,
     
-    /**
-     * APIProperty: childNodeType
-     * {Ext.tree.Node|String} node class or nodeType of childnodes for this
-     *     node. A node type provided here needs to have an add method, with
-     *     a scope argument. This method will be run by this node in the
-     *     context of this node, to create child nodes.
+    /** api: config[childNodeType]
+     *  ``Ext.tree.Node or String``
+     *  Node class or nodeType of childnodes for this node. A node type provided
+     *  here needs to have an add method, with a scope argument. This method
+     *  will be run by this node in the context of this node, to create child nodes.
      */
     childNodeType: null,
     
-    /**
-     * Property: visibilityChanging
+    /** private: property[visibilityChanging]
      * {Boolean} private property indicating layer visibility being changed
      *     by this node in order to prevent visibilitychanged events bouncing
      *     back and forth
      */
     visibilityChanging: false,
     
-    /**
-     * Constructor: GeoExt.tree.LayerNode
-     * 
-     * Parameters:
-     * config - {Object}
+    /** private: method[constructor]
+     *  Private constructor override.
      */
     constructor: function(config) {
         config.leaf = config.leaf || !config.children;
@@ -164,10 +155,9 @@
         
         this.defaultUI = this.defaultUI || GeoExt.tree.LayerNodeUI;
         this.addEvents(
-            /**
-             * Event: radiochange
-             * Notifies listener when a differnt radio button was selected.
-             * Will be called with the currently selected node as argument.
+            /** api: event[radiochange]
+             *  Notifies listener when a differnt radio button was selected.
+             *  Will be called with the currently selected node as argument.
              */
             "radiochange"
         );
@@ -180,12 +170,8 @@
         GeoExt.tree.LayerNode.superclass.constructor.apply(this, arguments);
     },
 
-    /**
-     * Method: render
-     * 
-     * Properties:
-     * bulkRender {Boolean} - optional
-     * layer {<OpenLayers.Layer>} - optional
+    /** private: method[render]
+     *  :param bulkRender: ``Boolean``
      */
     render: function(bulkRender) {
         var layer = this.layer instanceof OpenLayers.Layer && this.layer;
@@ -233,10 +219,9 @@
         GeoExt.tree.LayerNode.superclass.render.call(this, bulkRender);
     },
     
-    /**
-     * Method: addVisibilityHandlers
-     * Adds handlers that sync the checkbox state with the layer's visibility
-     * state
+    /** private: method[addVisibilityHandlers]
+     *  Adds handlers that sync the checkbox state with the layer's visibility
+     *  state
      */
     addVisibilityEventHandlers: function() {
         this.layer.events.register("visibilitychanged", this, function() {
@@ -256,10 +241,9 @@
         });
     },
     
-    /**
-     * Method: addStoreEventHandlers
-     * Adds handlers that make sure the node disappeares when the layer is
-     * removed from the store, and appears when it is re-added.
+    /** private: method[addStoreEventHandlers]
+     *  Adds handlers that make sure the node disappeares when the layer is
+     *  removed from the store, and appears when it is re-added.
      */
     addStoreEventHandlers: function() {
         this.layerStore.on({
@@ -287,10 +271,9 @@
         });
     },
     
-    /**
-     * Method: addChildNodes
-     * Calls the add method of a node type configured as <childNodeType>
-     * to add children.
+    /** private: method[addChildNodes]
+     *  Calls the add method of a node type configured as ``childNodeType``
+     *  to add children.
      */
     addChildNodes: function() {
         if(typeof this.childNodeType == "string") {

Modified: sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tree/OverlayLayerContainer.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tree/OverlayLayerContainer.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt/widgets/tree/OverlayLayerContainer.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -1,31 +1,38 @@
-/**
- * Copyright (c) 2008 The Open Planning Project
- */
+/* Copyright (C) 2008-2009 The Open Source Geospatial Foundation ¹
+ * Published under the BSD license.
+ * See http://geoext.org/svn/geoext/core/trunk/license.txt for the full text
+ * of the license.
+ * 
+ * ¹ pending approval */
 
 /**
  * @requires GeoExt/widgets/tree/LayerContainer.js
  */
 Ext.namespace("GeoExt.tree");
 
-/**
- * Class: GeoExt.tree.OverlayLayerContainer
+/** api: (define)
+ *  module = GeoExt.tree
+ *  class = OverlayLayerContainer
+ */
+
+/** api: (extends)
+ * GeoExt/widgets/tree/LayerContainer.js
+ */
+
+/** api: constructor
+ * .. class:: OverlayLayerContainer
  * 
- * A layer container that will collect all overlay layers of an OpenLayers map.
- * Only layers that have displayInLayerSwitcher set to true will be included.
+ *     A layer container that will collect all overlay layers of an OpenLayers
+ *     map. Only layers that have displayInLayerSwitcher set to true will be
+ *     included.
  * 
- * To use this node type in JSON config, set nodeType to
- * "olOverlayLayerContainer".
- * 
- * Inherits from:
- * - <GeoExt.tree.LayerContainer>
+ *     To use this node type in ``TreePanel`` config, set nodeType to
+ *     "gx_overlaylayerontainer".
  */
 GeoExt.tree.OverlayLayerContainer = Ext.extend(GeoExt.tree.LayerContainer, {
 
-    /**
-     * Constructor: GeoExt.tree.OverlayLayerContainer
-     * 
-     * Parameters:
-     * config - {Object}
+    /** private: method[constructor]
+     *  Private constructor override.
      */
     constructor: function(config) {
         config.text = config.text || "Overlays";
@@ -33,12 +40,11 @@
             arguments);
     },
 
-    /**
-     * Method: addLayerNode
-     * Adds a child node representing a overlay layer of the map
-     * 
-     * Parameters:
-     * layerRecord - {Ext.data.Record} the layer record to add a node for
+    /** private: method[addLayerNode]
+     *  :param layerRecord:  ``Ext.data.Record`` the layer record to add a node
+     *      for
+     *  
+     *  Adds a child node representing a overlay layer of the map.
      */
     addLayerNode: function(layerRecord) {
         var layer = layerRecord.get("layer");
@@ -48,12 +54,11 @@
         }
     },
     
-    /**
-     * Method: removeLayerNode
-     * Removes a child node representing an overlay layer of the map
-     * 
-     * Parameters:
-     * layerRecord - {Ext.data.Record} the layer record to remove the node for
+    /** private: method[removeLayerNode]
+     *  :param layerRecord: ``Ext.data.Record`` the layer record to remove the
+     *      node for
+     *      
+     * Removes a child node representing an overlay layer of the map.
      */
     removeLayerNode: function(layerRecord) {
         var layer = layerRecord.get("layer");

Modified: sandbox/opengeo/geoexplorer/lib/GeoExt.js
===================================================================
--- sandbox/opengeo/geoexplorer/lib/GeoExt.js	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/lib/GeoExt.js	2009-06-02 21:10:46 UTC (rev 966)
@@ -67,28 +67,24 @@
             "GeoExt/data/ScaleStore.js",
             "GeoExt/data/WMSCapabilitiesReader.js",
             "GeoExt/data/WMSCapabilitiesStore.js",
-            "GeoExt/data/ScaleStore.js",
+            "GeoExt/widgets/Action.js",
             "GeoExt/data/ProtocolProxy.js",
-            "GeoExt/data/WMSCapabilitiesReader.js",
-            "GeoExt/data/WMSCapabilitiesStore.js",
-            "GeoExt/widgets/LegendPanel.js",
             "GeoExt/widgets/MapPanel.js",
             "GeoExt/widgets/Popup.js",
-            "GeoExt/widgets/ScaleSlider.js",
-            "GeoExt/widgets/tips/SliderTip.js",
-            "GeoExt/widgets/tips/ScaleSliderTip.js",
-            "GeoExt/widgets/tree/LayerNode.js",
-            "GeoExt/widgets/tree/LayerContainer.js",
+            "GeoExt/widgets/form.js",
             "GeoExt/widgets/form/SearchAction.js",
             "GeoExt/widgets/form/BasicForm.js",
             "GeoExt/widgets/form/FormPanel.js",
+            "GeoExt/widgets/tips/SliderTip.js",
+            "GeoExt/widgets/tips/ZoomSliderTip.js",
             "GeoExt/widgets/tree/LayerNode.js",
             "GeoExt/widgets/tree/LayerContainer.js",
             "GeoExt/widgets/tree/BaseLayerContainer.js",
             "GeoExt/widgets/tree/OverlayLayerContainer.js",
-            "GeoExt/widgets/legend/Image.js",
-            "GeoExt/widgets/legend/WMS.js",
-            "GeoExt/widgets/LegendPanel.js"
+            "GeoExt/widgets/LegendImage.js",
+            "GeoExt/widgets/LegendWMS.js",
+            "GeoExt/widgets/LegendPanel.js",
+            "GeoExt/widgets/ZoomSlider.js"
         );
 
         var agent = navigator.userAgent;

Modified: sandbox/opengeo/geoexplorer/tests/lib/GeoExt/data/LayerStore.html
===================================================================
--- sandbox/opengeo/geoexplorer/tests/lib/GeoExt/data/LayerStore.html	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/tests/lib/GeoExt/data/LayerStore.html	2009-06-02 21:10:46 UTC (rev 966)
@@ -286,7 +286,92 @@
 
         }
         
+        function test_events(t) {
+            t.plan(21);
+            
+            var map = new OpenLayers.Map({div: "mappanel", allOverlays: true});
+            var a = new OpenLayers.Layer("a");
+            var b = new OpenLayers.Layer("b");
+            var c = new OpenLayers.Layer("c");
+            var d = new OpenLayers.Layer("d");
 
+            var store = new GeoExt.data.LayerStore({
+                map: map
+            });
+            
+            store.add(store.reader.readRecords([a, b, c]).records);
+            
+            var events = [];
+            function logEvent(type, args) {
+                events.push({type: type, args: args});
+            }
+            store.on({
+                add: function() {logEvent("add", arguments)},
+                remove: function() {logEvent("remove", arguments)},
+                update: function() {logEvent("update", arguments)}
+            });
+            
+            var batch, event, record;
+            
+            // confirm that we get an "add" on map.addLayer
+            batch = "map.addLayer";
+            map.addLayer(d);
+            t.eq(events.length, 1, "[" + batch + "] one event");
+            event = events[0];
+            t.eq(event.type, "add", "[" + batch + "] 'add' event");
+            t.eq(event.args[1].length, 1, "[" + batch + "] correct number of records");
+            record = event.args[1][0];
+            t.ok(record.get("layer") === d, "[" + batch + "] correct layer on record");
+            
+            // confirm that we get a "remove" on map.removeLayer
+            events = [];
+            batch = "map.removeLayer";
+            map.removeLayer(d);
+            t.eq(events.length, 1, "[" + batch + "] one event");
+            event = events[0];
+            t.eq(event.type, "remove", "[" + batch + "] 'remove' event");
+            t.ok(event.args[1].get("layer") === d, "[" + batch + "] correct layer on record");
+
+            // confirm that we get an "add" and "remove" on map.setLayerIndex
+            events = [];
+            batch = "map.setLayerIndex";
+            map.setLayerIndex(b, 0);
+            t.eq(events.length, 2, "[" + batch + "] two events");
+            event = events[0];
+            t.eq(event.type, "remove", "[" + batch + "] 'remove' event first");
+            t.ok(event.args[1].get("layer") === b, "[" + batch + "] correct layer on record");
+            event = events[1];
+            t.eq(event.type, "add", "[" + batch + "] 'add' event second");
+            t.eq(event.args[1].length, 1, "[" + batch + "] correct number of records");
+            record = event.args[1][0];
+            t.ok(record.get("layer") === b, "[" + batch + "] correct layer on record");
+
+            // confirm that we get an "update" on layer.setName
+            events = [];
+            batch = "layer.setName";
+            c.setName("c layer");
+            t.eq(events.length, 1, "[" + batch + "] one event");
+            event = events[0];
+            t.eq(event.type, "update", "[" + batch + "] 'update' event");
+            t.ok(event.args[1].get("layer") === c, "[" + batch + "] correct layer on record");
+            t.eq(event.args[2], Ext.data.Record.EDIT, "[" + batch + "] correct operation");
+            
+            // confirm that we get an "update" on layer.setVisibility
+            events = [];
+            batch = "layer.setVisibility";
+            a.setVisibility(false);
+            t.eq(events.length, 1, "[" + batch + "] one event");
+            event = events[0];
+            t.eq(event.type, "update", "[" + batch + "] 'update' event");
+            t.ok(event.args[1].get("layer") === a, "[" + batch + "] correct layer on record");
+            t.eq(event.args[2], Ext.data.Record.EDIT, "[" + batch + "] correct operation");
+
+
+            map.destroy();
+            
+        }
+        
+
     </script>
   </head>  
   <body>

Added: sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/Action.html
===================================================================
--- sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/Action.html	                        (rev 0)
+++ sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/Action.html	2009-06-02 21:10:46 UTC (rev 966)
@@ -0,0 +1,373 @@
+<!DOCTYPE html>
+<html debug="true">
+  <head>
+    <script type="text/javascript" src="../../../../../openlayers/lib/OpenLayers.js"></script>
+    <script type="text/javascript" src="../../../../../ext/adapter/ext/ext-base.js"></script>
+    <script type="text/javascript" src="../../../../../ext/ext-all-debug.js"></script>
+    <script type="text/javascript" src="../../../../lib/GeoExt.js"></script>
+
+    <script type="text/javascript">
+        function test_constructor(t) {
+            t.plan(11)
+
+            var ctrl, scope, handler, toggleHandler, checkHandler, cfg, action;
+
+            ctrl = new OpenLayers.Control();
+            
+            var map = new OpenLayers.Map();
+
+            scope = {}, handler = function() {};
+            toggleHandler = function() {}, checkHandler = function() {};
+
+            cfg = {
+                control: ctrl,
+                map: map,
+                scope: scope,
+                handler: handler,
+                toggleHandler: toggleHandler,
+                checkHandler: checkHandler
+            };
+
+            action = new GeoExt.Action(cfg);
+
+            t.ok(action.control == ctrl,
+                 "constructor sets control in the instance");
+            t.ok(action.control.map === map,
+                 "constructor adds control to map if provided");
+            t.ok(action.uScope == scope,
+                 "constructor sets this.uScope to user-provided scope");
+            t.ok(action.uHandler == handler,
+                 "constructor sets this.uHandler to user-provided handler");
+            t.ok(action.uToggleHandler == toggleHandler,
+                 "constructor sets this.uToggleHandler to user-provided toggleHandler");
+            t.ok(action.uCheckHandler == checkHandler,
+                 "constructor sets this.uCheckHandler to user-provided checkHandler");
+
+            t.eq(action.initialConfig.control, undefined,
+                 "constructor does not set control in the initial config");
+            t.ok(action.initialConfig.scope == action,
+                 "constructor sets scope to this in the initial config");
+            t.ok(action.initialConfig.handler == action.pHandler,
+                 "constructor sets handler to this.pHandler in the initial config");
+            t.ok(action.initialConfig.toggleHandler == action.pToggleHandler,
+                 "constructor sets toggleHandler to this.ptoggleHandler in the initial config");
+            t.ok(action.initialConfig.checkHandler == action.pCheckHandler,
+                 "constructor sets checkHandler to this.pCheckHandler in the initial config");
+            
+            map.destroy();
+        }
+
+        function test_button(t) {
+            t.plan(6);
+
+            var menu, menuBtn, tb, ctrl, triggerCnt, handlerCnt;
+            var action, btn, item;
+
+            var evt = {
+                preventDefault: function() {},
+                stopEvent: function() {},
+                button: 0
+            };
+
+            menu = new Ext.menu.Menu({
+                // with shadow set to its default value ("sides")
+                // IE throws "object: Invalid argument" exceptions
+                shadow: false
+            });
+            menuBtn = new Ext.Button({menu: menu});
+
+            tb = new Ext.Toolbar({
+                renderTo: "toolbar",
+                buttons: [menuBtn]
+            });
+
+            // the "button" control
+            ctrl = new OpenLayers.Control({
+                type: OpenLayers.Control.TYPE_BUTTON,
+                trigger: function() {
+                    triggerCnt++;
+                }
+            });
+            ctrl.activate();
+
+            action = new GeoExt.Action({
+                control: ctrl,
+                handler: function() {
+                    handlerCnt++;
+                }
+            });
+            
+            // create button from action and it to toolbar
+            btn = new Ext.Button(action);
+            tb.add(btn);
+
+            // simulate click on button
+            // 2 tests
+            // test that the control's trigger is called exactly once
+            // test that the action handler is called exactly once
+            triggerCnt = 0;
+            handlerCnt = 0;
+            btn.onClick(evt);
+            t.eq(triggerCnt, 1, "click on button calls control trigger once");
+            t.eq(handlerCnt, 1, "click on button calls action handler once");
+
+            // create menu item from action and it to menu
+            item = new Ext.menu.Item(action);
+            menu.add(item);
+
+            // simulate click on menu item
+            // 2 tests
+            // test that the control's trigger is called exactly once
+            // test that the action handler is called exactly once
+            triggerCnt = 0;
+            handlerCnt = 0;
+            menuBtn.showMenu();
+            item.onClick(evt);
+            menuBtn.hideMenu();
+            t.eq(triggerCnt, 1, "click on menu item calls control trigger once");
+            t.eq(handlerCnt, 1, "click on menu item calls action handler once");
+
+            // deactivate control
+            // 2 tests
+            // test that button is disabled
+            // test that menu item is disabled
+            ctrl.deactivate();
+            t.eq(btn.disabled, true, "deactivating control disables button");
+            t.eq(btn.disabled, true, "deactivating control disables menu item");
+        }
+
+        function test_toggle(t) {
+            t.plan(12);
+
+            var menu, menuBtn, tb, ctrl;
+            var activateCnt, deactivateCnt, toggleHandlerCnt, checkHandlerCnt;
+            var action, btn, item;
+
+            var evt = {
+                preventDefault: function() {},
+                stopEvent: function() {},
+                button: 0
+            };
+
+            menu = new Ext.menu.Menu({
+                // with shadow set to its default value ("sides")
+                // IE throws "object: Invalid argument" exceptions
+                shadow: false
+            });
+            menuBtn = new Ext.Button({menu: menu});
+
+            tb = new Ext.Toolbar({
+                renderTo: "toolbar",
+                buttons: [menuBtn]
+            });
+
+            // the control
+            ctrl = new OpenLayers.Control({
+                activate: function() {
+                    activateCnt++;
+                    return OpenLayers.Control.prototype.activate.call(this, arguments);
+                },
+                deactivate: function() {
+                    deactivateCnt++;
+                    return OpenLayers.Control.prototype.deactivate.call(this, arguments);
+                }
+            });
+            ctrl.activate();
+
+            action = new GeoExt.Action({
+                control: ctrl,
+                enableToggle: true,
+                toggleHandler: function() {
+                    toggleHandlerCnt++;
+                },
+                checkHandler: function() {
+                    checkHandlerCnt++;
+                }
+            });
+            
+            // create button from action and it to toolbar
+            btn = new Ext.Button(action);
+            tb.add(btn);
+
+            // simulate click on button
+            // 2 tests
+            // test that the control gets activated once
+            // test that the toggle handler is called exactly once
+            activateCnt = 0;
+            deactivateCnt = 0;
+            toggleHandlerCnt = 0;
+            btn.onClick(evt);
+            t.eq(activateCnt, 1, "click on button activates control once");
+            t.eq(deactivateCnt, 0, "click on button does not deactivate control");
+            t.eq(toggleHandlerCnt, 1, "click on button calls toggle handler once");
+
+            // simulate click on button
+            // 2 tests
+            // test that the control gets deactivated once
+            // test that the toggle handler is called exactly once
+            activateCnt = 0;
+            deactivateCnt = 0;
+            toggleHandlerCnt = 0;
+            btn.onClick(evt);
+            t.eq(activateCnt, 0, "click again on button does not activate control");
+            t.eq(deactivateCnt, 1, "click again on button deactivates control once");
+            t.eq(toggleHandlerCnt, 1, "click again on button calls toggle handler once");
+
+            // create menu item from action and it to menu
+            item = new Ext.menu.CheckItem(action);
+            menu.add(item);
+
+            // simulate click on menu item
+            // 2 tests
+            // test that the control gets activated once
+            // test that the toggle handler is called exactly once
+            activateCnt = 0;
+            deactivateCnt = 0;
+            checkHandlerCnt = 0;
+            menuBtn.showMenu();
+            item.onClick(evt);
+            menuBtn.hideMenu();
+            t.eq(activateCnt, 1, "click on menu item activates control once");
+            t.eq(deactivateCnt, 0, "click on menu item does not deactivate control");
+            t.eq(checkHandlerCnt, 1, "click on menu item calls check handler once");
+
+            // simulate click on menu item
+            // 2 tests
+            // test that the control gets deactivated once
+            // test that the toggle handler is called exactly once
+            activateCnt = 0;
+            deactivateCnt = 0;
+            checkHandlerCnt = 0;
+            menuBtn.showMenu();
+            item.onClick(evt);
+            menuBtn.hideMenu();
+            t.eq(activateCnt, 0, "click again on menu item does not activate control");
+            t.eq(deactivateCnt, 1, "click again on menu item deactivates control once");
+            t.eq(checkHandlerCnt, 1, "click again on menu item calls check handler once");
+        }
+
+        function test_toggle_group(t) {
+            t.plan(8);
+
+            var menu, menuBtn, tb, ctrl1, ctrl2;
+
+            var activateCtrl1Cnt, deactivateCtrl1Cnt;
+            var activateCtrl2Cnt, deactivateCtrl2Cnt;
+            
+            var action1, action2, btn1, btn2, item1, item2;
+
+            var evt = {
+                preventDefault: function() {},
+                stopEvent: function() {},
+                button: 0
+            };
+
+            menu = new Ext.menu.Menu({
+                // with shadow set to its default value ("sides")
+                // IE throws "object: Invalid argument" exceptions
+                shadow: false
+            });
+            menuBtn = new Ext.Button({menu: menu});
+
+            tb = new Ext.Toolbar({
+                renderTo: "toolbar",
+                buttons: [menuBtn]
+            });
+
+            // the controls
+            ctrl1 = new OpenLayers.Control({
+                activate: function() {
+                    activateCtrl1Cnt++;
+                    return OpenLayers.Control.prototype.activate.call(this, arguments);
+                },
+                deactivate: function() {
+                    deactivateCtrl1Cnt++;
+                    return OpenLayers.Control.prototype.deactivate.call(this, arguments);
+                }
+            });
+            ctrl1.deactivate();
+            ctrl2 = new OpenLayers.Control({
+                activate: function() {
+                    activateCtrl2Cnt++;
+                    return OpenLayers.Control.prototype.activate.call(this, arguments);
+                },
+                deactivate: function() {
+                    deactivateCtrl2Cnt++;
+                    return OpenLayers.Control.prototype.deactivate.call(this, arguments);
+                }
+            });
+            ctrl2.activate();
+
+            // the actions
+            action1 = new GeoExt.Action({
+                control: ctrl1,
+                toggleGroup: "ctrl",
+                pressed: false
+            });
+            action2 = new GeoExt.Action({
+                control: ctrl2,
+                toggleGroup: "ctrl",
+                pressed: true
+            });
+            
+            // create buttons from actions and add them to toolbar
+            btn1 = new Ext.Button(action1);
+            tb.add(btn1);
+            btn2 = new Ext.Button(action2);
+            tb.add(btn2);
+
+            // simulate click on btn1
+            // 2 tests
+            // test that ctrl1 gets activated once
+            // test that ctrl2 gets deactivated once
+            activateCtrl1Cnt = 0;
+            deactivateCtrl2Cnt = 0;
+            btn1.onClick(evt);
+            t.eq(activateCtrl1Cnt, 1, "click on btn1 activates ctrl1 once");
+            t.eq(deactivateCtrl2Cnt, 1, "click on btn1 deactivates ctrl2 once");
+
+            // simulate click on btn2
+            // 2 tests
+            // test that ctrl1 gets deactivated once
+            // test that ctrl2 gets activated once
+            deactivateCtrl1Cnt = 0;
+            activateCtrl2Cnt = 0;
+            btn2.onClick(evt);
+            t.eq(deactivateCtrl1Cnt, 1, "click on btn2 deactivates ctrl1 once");
+            t.eq(activateCtrl2Cnt, 1, "click on btn2 activates ctrl2 once");
+
+            // create menu items from actions and them to menu
+            item1 = new Ext.menu.CheckItem(action1);
+            menu.add(item1);
+            item2 = new Ext.menu.CheckItem(action2);
+            menu.add(item2);
+
+            // simulate click on item1
+            // 2 tests
+            // test that ctrl1 gets activated once
+            // test that ctrl2 gets deactivated once
+            activateCtrl1Cnt = 0;
+            deactivateCtrl2Cnt = 0;
+            menuBtn.showMenu();
+            item1.onClick(evt);
+            menuBtn.hideMenu();
+            t.eq(activateCtrl1Cnt, 1, "click on item1 activates ctrl1 once");
+            t.eq(deactivateCtrl2Cnt, 1, "click on item1 deactivates ctrl2 once");
+
+            // simulate click on item2
+            // 2 tests
+            // test that ctrl1 gets deactivated once
+            // test that ctrl2 gets activated once
+            deactivateCtrl1Cnt = 0;
+            activateCtrl2Cnt = 0;
+            menuBtn.showMenu();
+            item2.onClick(evt);
+            menuBtn.hideMenu();
+            t.eq(deactivateCtrl1Cnt, 1, "click on item2 deactivates ctrl1 once");
+            t.eq(activateCtrl2Cnt, 1, "click on item2 activates ctrl2 once");
+        }
+    </script>
+  <body>
+    <div id="toolbar"></div>
+  </body>
+</html>

Modified: sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/LegendPanel.html
===================================================================
--- sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/LegendPanel.html	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/LegendPanel.html	2009-06-02 21:10:46 UTC (rev 966)
@@ -1,155 +1,155 @@
-<!DOCTYPE html>
-<html debug="true">
-  <head>
-    <script type="text/javascript" src="../../../../../openlayers/lib/OpenLayers.js"></script>
-    <script type="text/javascript" src="../../../../../ext/adapter/ext/ext-base.js"></script>
-    <script type="text/javascript" src="../../../../../ext/ext-all.js"></script>
-    <script type="text/javascript" src="../../../../lib/GeoExt.js"></script>
-
-    <script type="text/javascript">
-
-        function createMap() {
-            var map = new OpenLayers.Map({allOverlays: true});
-            var layer = new OpenLayers.Layer.WMS("test", '/ows', {layers: 'a'});
-            map.addLayer(layer);
-            return map;
-        }
-
-        function loadMapPanel() {
-            var map = createMap();
-
-            mapPanel = new GeoExt.MapPanel({
-                // panel options
-                id: "map-panel",
-                title: "GeoExt MapPanel",
-                renderTo: "mappanel",
-                height: 400,
-                width: 600,
-                // map panel-specific options
-                map: map,
-                center: new OpenLayers.LonLat(5, 45),
-                zoom: 4
-            });
-
-            return mapPanel;
-        }
-
-        function test_legendurl(t) {
-            t.plan(1);
-            var mapPanel = loadMapPanel();
-            var lp  = new GeoExt.LegendPanel({
-                renderTo: 'legendpanel'});
-            lp.render();
-
-            var newUrl = "http://www.geoext.org//trac/geoext/chrome/site/img/GeoExt.png";
-            mapPanel.layers.getAt(0).set("legendURL", newUrl);
-
-            var item = lp.getComponent(mapPanel.map.layers[0].id);
-            var url = item.items.items[1].items.items[0].getEl().dom.src;
-            t.eq(url, newUrl, "Update the image with the provided legendURL");
-
-            lp.destroy();
-            mapPanel.destroy();
-        }
-
-        function test_togglevisibility(t) {
-            t.plan(2);
-            var mapPanel = loadMapPanel();
-            var lp  = new GeoExt.LegendPanel({
-                renderTo: 'legendpanel'});
-            lp.render();
-
-            mapPanel.map.layers[0].setVisibility(false);
-            var id = mapPanel.layers.getAt(0).get('layer').id;
-            t.eq(lp.getComponent(id).hidden, true, "Layer has been hidden in legend");
-
-            mapPanel.map.layers[0].setVisibility(true);
-            t.eq(lp.getComponent(id).hidden, false, "Layer has been made visible again in legend");
-
-            lp.destroy();
-            mapPanel.destroy();
-        }
-
-        function test_hide(t) {
-            t.plan(1);
-            var mapPanel = loadMapPanel();
-            var lp  = new GeoExt.LegendPanel({
-                renderTo: 'legendpanel'});
-            lp.render();
-
-            mapPanel.layers.getAt(0).set("hideInLegend", true);
-            var id = mapPanel.layers.getAt(0).get('layer').id;
-            t.eq(lp.getComponent(id).hidden, true, "Layer has been hidden in legend");
-
-            lp.destroy();
-            mapPanel.destroy();
-        }
-
-        function test_dynamic(t) {
-            t.plan(1);
-            var mapPanel = loadMapPanel();
-            var lp  = new GeoExt.LegendPanel({
-                dynamic: false,
-                renderTo: 'legendpanel'});
-            lp.render();
-
-            var layer;
-            layer = new OpenLayers.Layer.WMS("test2", '/ows', {layers: 'b', format: 'image/png', transparent: 'TRUE'});
-            mapPanel.map.addLayer(layer);
-
-            t.eq(lp.items.length, 1, "If dynamic is false, do not add or remove layers from legend");
-
-            lp.destroy();
-            mapPanel.destroy();
-        }
-
-        function test_wms(t) {
-            t.plan(1);
-            var mapPanel = loadMapPanel();
-            var lp  = new GeoExt.LegendPanel({
-                renderTo: 'legendpanel'});
-            lp.render();
-
-            var item = lp.getComponent(mapPanel.map.layers[0].id);
-            var url = item.items.items[1].items.items[0].url;
-            var expectedUrl = "/ows?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetLegendGraphic&STYLES=&EXCEPTIONS=application%2Fvnd.ogc.se_xml&FORMAT=image%2Fgif&LAYER=a";
-            t.eq(url, expectedUrl, "GetLegendGraphic url is generated correctly");
-
-            lp.destroy();
-            mapPanel.destroy();
-        }
-
-        function test_addremove(t) {
-            t.plan(4);
-            var mapPanel = loadMapPanel();
-            var lp  = new GeoExt.LegendPanel({
-                renderTo: 'legendpanel'});
-            lp.render();
-            t.eq(lp.items.length, 1, "Same number of layers in legend panel and in map");
-
-            var item = lp.getComponent(mapPanel.map.layers[0].id);
-
-            var layer;
-            layer = new OpenLayers.Layer.WMS("test2", '/ows', {layers: 'b', format: 'image/png', transparent: 'TRUE'});
-            mapPanel.map.addLayer(layer);
-
-            t.eq(lp.items.length, 2, "New WMS layer has been added");
-
-            layer = new OpenLayers.Layer.WMS("test3", '/ows', {layers: 'c'}, {visibility: false});
-            mapPanel.map.addLayer(layer);
-
-            t.eq(lp.items.length, 3, "A non visible WMS layer will be added but will be invisible");
-
-            mapPanel.map.removeLayer(mapPanel.map.layers[0]);
-            t.eq(lp.items.length, 2, "Removing the WMS layer really removes the legend from the panel");
-
-            lp.destroy();
-            mapPanel.destroy();
-        }
-
-    </script>
-  <body>
-    <div id="legendpanel"></div>
-    <div id="mappanel"></div>
-  </body>
-</html>
+<!DOCTYPE html>
+<html debug="true">
+  <head>
+    <script type="text/javascript" src="../../../../../openlayers/lib/OpenLayers.js"></script>
+    <script type="text/javascript" src="../../../../../ext/adapter/ext/ext-base.js"></script>
+    <script type="text/javascript" src="../../../../../ext/ext-all.js"></script>
+    <script type="text/javascript" src="../../../../lib/GeoExt.js"></script>
+
+    <script type="text/javascript">
+
+        function createMap() {
+            var map = new OpenLayers.Map({allOverlays: true});
+            var layer = new OpenLayers.Layer.WMS("test", '/ows', {layers: 'a'});
+            map.addLayer(layer);
+            return map;
+        }
+
+        function loadMapPanel() {
+            var map = createMap();
+
+            mapPanel = new GeoExt.MapPanel({
+                // panel options
+                id: "map-panel",
+                title: "GeoExt MapPanel",
+                renderTo: "mappanel",
+                height: 400,
+                width: 600,
+                // map panel-specific options
+                map: map,
+                center: new OpenLayers.LonLat(5, 45),
+                zoom: 4
+            });
+
+            return mapPanel;
+        }
+
+        function test_legendurl(t) {
+            t.plan(1);
+            var mapPanel = loadMapPanel();
+            var lp  = new GeoExt.LegendPanel({
+                renderTo: 'legendpanel'});
+            lp.render();
+
+            var newUrl = "http://www.geoext.org//trac/geoext/chrome/site/img/GeoExt.png";
+            mapPanel.layers.getAt(0).set("legendURL", newUrl);
+
+            var item = lp.getComponent(mapPanel.map.layers[0].id);
+            var url = item.items.items[1].items.items[0].getEl().dom.src;
+            t.eq(url, newUrl, "Update the image with the provided legendURL");
+
+            lp.destroy();
+            mapPanel.destroy();
+        }
+
+        function test_togglevisibility(t) {
+            t.plan(2);
+            var mapPanel = loadMapPanel();
+            var lp  = new GeoExt.LegendPanel({
+                renderTo: 'legendpanel'});
+            lp.render();
+
+            mapPanel.map.layers[0].setVisibility(false);
+            var id = mapPanel.layers.getAt(0).get('layer').id;
+            t.eq(lp.getComponent(id).hidden, true, "Layer has been hidden in legend");
+
+            mapPanel.map.layers[0].setVisibility(true);
+            t.eq(lp.getComponent(id).hidden, false, "Layer has been made visible again in legend");
+
+            lp.destroy();
+            mapPanel.destroy();
+        }
+
+        function test_hide(t) {
+            t.plan(1);
+            var mapPanel = loadMapPanel();
+            var lp  = new GeoExt.LegendPanel({
+                renderTo: 'legendpanel'});
+            lp.render();
+
+            mapPanel.layers.getAt(0).set("hideInLegend", true);
+            var id = mapPanel.layers.getAt(0).get('layer').id;
+            t.eq(lp.getComponent(id).hidden, true, "Layer has been hidden in legend");
+
+            lp.destroy();
+            mapPanel.destroy();
+        }
+
+        function test_dynamic(t) {
+            t.plan(1);
+            var mapPanel = loadMapPanel();
+            var lp  = new GeoExt.LegendPanel({
+                dynamic: false,
+                renderTo: 'legendpanel'});
+            lp.render();
+
+            var layer;
+            layer = new OpenLayers.Layer.WMS("test2", '/ows', {layers: 'b', format: 'image/png', transparent: 'TRUE'});
+            mapPanel.map.addLayer(layer);
+
+            t.eq(lp.items.length, 1, "If dynamic is false, do not add or remove layers from legend");
+
+            lp.destroy();
+            mapPanel.destroy();
+        }
+
+        function test_wms(t) {
+            t.plan(1);
+            var mapPanel = loadMapPanel();
+            var lp  = new GeoExt.LegendPanel({
+                renderTo: 'legendpanel'});
+            lp.render();
+
+            var item = lp.getComponent(mapPanel.map.layers[0].id);
+            var url = item.items.items[1].items.items[0].url;
+            var expectedUrl = "/ows?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetLegendGraphic&STYLES=&EXCEPTIONS=application%2Fvnd.ogc.se_xml&FORMAT=image%2Fgif&LAYER=a";
+            t.eq(url, expectedUrl, "GetLegendGraphic url is generated correctly");
+
+            lp.destroy();
+            mapPanel.destroy();
+        }
+
+        function test_addremove(t) {
+            t.plan(4);
+            var mapPanel = loadMapPanel();
+            var lp  = new GeoExt.LegendPanel({
+                renderTo: 'legendpanel'});
+            lp.render();
+            t.eq(lp.items.length, 1, "Same number of layers in legend panel and in map");
+
+            var item = lp.getComponent(mapPanel.map.layers[0].id);
+
+            var layer;
+            layer = new OpenLayers.Layer.WMS("test2", '/ows', {layers: 'b', format: 'image/png', transparent: 'TRUE'});
+            mapPanel.map.addLayer(layer);
+
+            t.eq(lp.items.length, 2, "New WMS layer has been added");
+
+            layer = new OpenLayers.Layer.WMS("test3", '/ows', {layers: 'c'}, {visibility: false});
+            mapPanel.map.addLayer(layer);
+
+            t.eq(lp.items.length, 3, "A non visible WMS layer will be added but will be invisible");
+
+            mapPanel.map.removeLayer(mapPanel.map.layers[0]);
+            t.eq(lp.items.length, 2, "Removing the WMS layer really removes the legend from the panel");
+
+            lp.destroy();
+            mapPanel.destroy();
+        }
+
+    </script>
+  <body>
+    <div id="legendpanel"></div>
+    <div id="mappanel"></div>
+  </body>
+</html>

Modified: sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/MapPanel.html
===================================================================
--- sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/MapPanel.html	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/MapPanel.html	2009-06-02 21:10:46 UTC (rev 966)
@@ -12,13 +12,25 @@
             var map = new OpenLayers.Map();
             var layer = new OpenLayers.Layer("test", {isBaseLayer: true});
             map.addLayer(layer);
+            // add a vector layer, which would fail onmapresize if we render
+            // the map before the panel has a layout.
+            map.addLayer(new OpenLayers.Layer.Vector("vector layer"));
             return map;
         }
 
-        function loadMapPanel() {
+        function test_mappanel(t) {
+            t.plan(4)
+
+            var moveToCnt;
+
             var map = createMap();
+            map.moveTo = function() {
+                moveToCnt++;
+                OpenLayers.Map.prototype.moveTo.apply(this, arguments);
+            };
 
-            mapPanel = new GeoExt.MapPanel({
+            moveToCnt = 0;
+            var mapPanel = new GeoExt.MapPanel({
                 // panel options
                 id: "map-panel",
                 title: "GeoExt MapPanel",
@@ -30,17 +42,15 @@
                 center: new OpenLayers.LonLat(5, 45),
                 zoom: 4
             });
-
-            return mapPanel;
-        }
-
-        function test_mappanel(t) {
-            t.plan(3)
-            
-            loadMapPanel();
+            t.eq(moveToCnt, 1, "map.moveTo called exactly once");
             t.eq(mapPanel.map.getCenter().toString(), "lon=5,lat=45", "Map center set correctly");
             t.eq(mapPanel.map.getZoom(), 4, "Zoom set correctly");
             t.eq(GeoExt.MapPanel.guess().id, mapPanel.id, "MapPanel guessed correctly");
+            
+            // since we created the map, we destroy it
+            map.destroy();
+            
+            mapPanel.destroy();
         }
         
         function test_allOverlays(t) {
@@ -52,11 +62,15 @@
                 map: map
             });
             t.eq(panel.map.allOverlays, false, "allOverlays is not set if map is provided to panel");
+            // since we created the map, we destroy it
+            map.destroy();
             panel.destroy();
             
             panel = new GeoExt.MapPanel({
             });
             t.eq(panel.map.allOverlays, true, "allOverlays set to true if no map is provided to panel");
+            // since we created the map, we destroy it
+            map.destroy();
             panel.destroy();
             
             panel = new GeoExt.MapPanel({
@@ -65,10 +79,30 @@
                 }
             });
             t.eq(panel.map.allOverlays, true, "allOverlays set to true if map config is provided to panel");
+            // since we created the map, we destroy it
+            map.destroy();
             panel.destroy();
             
         }
 
+        function test_zoom(t) {
+            
+            t.plan(1);
+            
+            var panel = new GeoExt.MapPanel({
+                title: "GeoExt MapPanel",
+                renderTo: "mappanel",
+                height: 400,
+                width: 600,
+                layers: [new OpenLayers.Layer()],
+                zoom: 4
+            });
+            
+            t.eq(panel.map.zoom, 4, "zoom correctly set");
+            
+            panel.destroy();
+        }
+
         function test_extent(t) {
             
             t.plan(3);
@@ -77,38 +111,50 @@
             map = createMap();
             map.zoomToExtent = function(extent) {
                 log.extent = extent;
-            }
+            };
             panel = new GeoExt.MapPanel({
                 renderTo: "mappanel",
                 map: map,
+                height: 400,
+                width: 600,
                 extent: [1, 2, 3, 4]
             });            
             t.eq(log.extent.toArray(), [1, 2, 3, 4], "map extent set with array");
             delete log.extent;
+            // since we created the map, we destroy it
+            map.destroy();
             panel.destroy();
             
             map = createMap();
             map.zoomToExtent = function(extent) {
                 log.extent = extent;
-            }
+            };
             panel = new GeoExt.MapPanel({
                 renderTo: "mappanel",
                 map: map,
+                height: 400,
+                width: 600,
                 extent: "1, 2, 3, 4"
             });
             t.eq(log.extent.toArray(), [1, 2, 3, 4], "map extent set with string");
+            // since we created the map, we destroy it
+            map.destroy();
             panel.destroy();
             
             map = createMap();
             map.zoomToExtent = function(extent) {
                 log.extent = extent;
-            }
+            };
             panel = new GeoExt.MapPanel({
                 renderTo: "mappanel",
                 map: map,
+                height: 400,
+                width: 600,
                 extent: new OpenLayers.Bounds(1, 2, 3, 4)
             });
             t.eq(log.extent.toArray(), [1, 2, 3, 4], "map extent set with Bounds");
+            // since we created the map, we destroy it
+            map.destroy();
             panel.destroy();            
             
         }
@@ -121,45 +167,130 @@
             map = createMap();
             map.setCenter = function(center) {
                 log.center = center;
-            }
+            };
             panel = new GeoExt.MapPanel({
                 renderTo: "mappanel",
                 map: map,
+                height: 400,
+                width: 600,
                 center: [1, 2]
             });            
             t.eq(log.center.toString(), "lon=1,lat=2", "map center set with array");
             delete log.center;
+            // since we created the map, we destroy it
+            map.destroy();
             panel.destroy();
 
             map = createMap();
             map.setCenter = function(center) {
                 log.center = center;
-            }
+            };
             panel = new GeoExt.MapPanel({
                 renderTo: "mappanel",
                 map: map,
+                height: 400,
+                width: 600,
                 center: "1, 2"
             });            
             t.eq(log.center.toString(), "lon=1,lat=2", "map center set with string");
             delete log.center;
+            // since we created the map, we destroy it
+            map.destroy();
             panel.destroy();
 
             map = createMap();
             map.setCenter = function(center) {
                 log.center = center;
-            }
+            };
             panel = new GeoExt.MapPanel({
                 renderTo: "mappanel",
                 map: map,
+                height: 400,
+                width: 600,
                 center: new OpenLayers.LonLat(1, 2)
             });            
             t.eq(log.center.toString(), "lon=1,lat=2", "map center set with LonLat");
             delete log.center;
+            // since we created the map, we destroy it
+            map.destroy();
             panel.destroy();
 
         }
+        
+        function test_destroy(t) {
+            
+            /**
+             * If the panel is passed an instance of OpenLayers.Map, we don't
+             * touch it in the destroy sequence, we only remove our reference
+             * to it.  If the panel is passed a map config object, the panel
+             * creates the OpenLayers.Map instance, and the panel destroys the
+             * map in its destroy sequence.
+             */
+            
+            t.plan(3);
+            
+            var panel = new GeoExt.MapPanel({
+                renderTo: "mappanel",
+                layers: [
+                    new OpenLayers.Layer("test")
+                ],
+                zoom: 1
+            });
+            
+            t.ok(panel.map instanceof OpenLayers.Map, "panel creates a map");
+            
+            var called = false;
+            panel.map.destroy = function() {
+                called = true;
+                OpenLayers.Map.prototype.destroy.apply(panel.map, arguments);
+            }
+            try {
+                panel.destroy();
+                t.ok(called, "panel.destroy calls map.destroy");
+            } catch(err) {
+                t.fail("panel.destroy causes problems: " + err);
+            }
+            t.ok(!panel.map, "panel has no reference to a map");
+            
+        }
 
+        function test_layout(t) {
+            t.plan(1);
 
+            var map, panel, layout = 0;
+
+            map = new OpenLayers.Map({
+                render: function() {
+                    OpenLayers.Map.prototype.render.apply(this, arguments);
+                    t.ok(layout, 1,
+                         "the OpenLayers map is rendered once the container " +
+                         "has its final dimensions");
+                },
+                allOverlays: true
+            });
+            
+            panel = new Ext.Panel({
+                layout: "border",
+                renderTo: "mappanel",
+                listeners: {
+                    afterlayout: function() {
+                        layout++;
+                    }
+                },
+                items: [{
+                    region: "center",
+                    xtype: "gx_mappanel",
+                    map: map,
+                    layers: [
+                        new OpenLayers.Layer("")
+                    ]
+                }]
+            });
+
+            panel.destroy();
+        }
+
+
     </script>
   <body>
     <div id="mappanel"></div>

Modified: sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/Popup.html
===================================================================
--- sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/Popup.html	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/Popup.html	2009-06-02 21:10:46 UTC (rev 966)
@@ -9,26 +9,13 @@
 
     <script type="text/javascript">
 
-        function makeFeature() {
-           return new OpenLayers.Feature.Vector(
-              new OpenLayers.Geometry.Point(100,50),
-              {
-                  name : "My Feature"
-              }
-           )
-        }
-       
-        function createMap() {
+        function setupContext() {        
+
             var map = new OpenLayers.Map();
             var layer = new OpenLayers.Layer("test", {isBaseLayer: true});
             map.addLayer(layer);
-            return map;
-        }
 
-        function loadMapPanel() {
-            var map = createMap();
-
-            mapPanel = new GeoExt.MapPanel({
+            var mapPanel = new GeoExt.MapPanel({
                 // panel options
                 id: "map-panel",
                 title: "GeoExt MapPanel",
@@ -41,35 +28,34 @@
                 zoom: 4
             });
 
-            return mapPanel;
-        }
+            var feature = new OpenLayers.Feature.Vector(
+                new OpenLayers.Geometry.Point(100,50),
+                {name: "My Feature"}
+            );
 
-        function setupContext() {        
-            var mapPanel = loadMapPanel();
-            var feature = makeFeature();
-            var map = mapPanel.map;
-
             return {
-               feature : feature,
-               map : map,
-               mapPanel : mapPanel
+                feature: feature,
+                map: map,
+                mapPanel: mapPanel
             };      
         }
+        
+        function tearDown(context) {
+            context.feature.destroy();
+            context.map.destroy();
+            context.mapPanel.destroy();
+        }
 
-        function popup(feature){
-          pop = new GeoExt.Popup({
-            title: 'My Popup',
-            feature: feature,
-            width:200,
-            html: bogusMarkup,
-            collapsible: true
-          });
-
-          return pop;
+        function popup(feature) {
+            return new GeoExt.Popup({
+                title: 'My Popup',
+                feature: feature,
+                width: 200,
+                html: "Lorem ipsum dolor sit amet, consectetuer adipiscing elit.",
+                collapsible: true
+            });
         }
 
-        var bogusMarkup = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit."
-
         function test_addtomappanel(t) {
             t.plan(1);
 
@@ -77,11 +63,11 @@
 
             var pop = popup(context.feature);
 
-            pop.addToMapPanel(context.mapPanel);
+            context.mapPanel.add(pop);
 
             t.ok(context.mapPanel.el.child("div." + pop.popupCls),"Map panel contains popup");
-
-            context.mapPanel.destroy();
+            
+            tearDown(context);
         }
 
         function test_anchorPopup(t) {
@@ -89,9 +75,9 @@
 
             var context = setupContext();
 
-            var pop = popup(context.feature, context.mapPanel);
+            var pop = popup(context.feature);
 
-            pop.addToMapPanel(context.mapPanel);
+            context.mapPanel.add(pop);
 
             pop.on({
                 'move' : function(c,x,y){
@@ -110,6 +96,8 @@
 
             action = "popup expand"
             pop.expand();
+            
+            tearDown(context);
         }
 
 
@@ -120,7 +108,7 @@
 
             var pop = popup(context.feature, context.mapPanel);
 
-            pop.addToMapPanel(context.mapPanel);
+            context.mapPanel.add(pop);
         
             pop.collapse();
 
@@ -152,34 +140,32 @@
             
             action = "popup collapse";
             pop.collapse();
+            
+            tearDown(context);
 
         }
 
-        function test_Popup_beforeDestroy(t){
+        function test_Popup_destroy(t){
 
-            t.plan(0);
+            t.plan(1);
 
             var context = setupContext();
-            var pop = popup(context.feature, context.mapPanel);
-            pop.addToMapPanel(context.mapPanel);
-
+            var pop = popup(context.feature);
+            context.mapPanel.add(pop);
+            
+            var called = false;
             pop.on({
-                'move' : function(c,x,y){
-                    t.ok(false,"Move event fired improperly on " + action); //should happen twice, on call to position()
-                },
-                scope : this
+                move: function() {
+                    called = true;
+                }
             });
+            pop.destroy();
 
-            pop.beforeDestroy();
-
-            var action = "map move";
             context.map.events.triggerEvent("move");
-
-            action = "popup expand"
-            pop.expand();
             
-            action = "popup collapse";
-            pop.collapse();
+            t.ok(!called, "pop is not moved after it is destroyed");
+            
+            tearDown(context);
         }
 
     </script>

Added: sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/ZoomSlider.html
===================================================================
--- sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/ZoomSlider.html	                        (rev 0)
+++ sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/ZoomSlider.html	2009-06-02 21:10:46 UTC (rev 966)
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html debug="true">
+  <head>
+    <script type="text/javascript" src="../../../../../openlayers/lib/OpenLayers.js"></script>
+    <script type="text/javascript" src="../../../../../ext/adapter/ext/ext-base.js"></script>
+    <script type="text/javascript" src="../../../../../ext/ext-all-debug.js"></script>
+    <script type="text/javascript" src="../../../../lib/GeoExt.js"></script>
+
+    <script type="text/javascript">
+       
+        function test_zoomslider(t) {
+            t.plan(7);
+            
+            var map = new OpenLayers.Map({
+                div: "map",
+                allOverlays: true
+            });
+            map.addLayer(new OpenLayers.Layer());
+            map.setCenter(new OpenLayers.LonLat(0, 0), 2);
+            
+            var slider = new GeoExt.ZoomSlider({
+                map: map,
+                renderTo: document.body
+            });
+            
+            // test range of values
+            t.eq(slider.minValue, 0, "slider min is 0");
+            t.eq(slider.maxValue, 15, "slider can go to 15");
+            
+            // test initial value
+            t.eq(slider.getValue(), 2, "slider has correct value after setCenter");
+            
+            // zoom in and test that value is updated
+            map.zoomIn();
+            t.eq(slider.getValue(), 3, "slider has correct value after zoomIn");
+            
+            // test that zoomTo updates slider value
+            map.zoomTo(0);
+            t.eq(slider.getValue(), 0, "slider has correct value after zoomTo");
+            
+            // test that slider can be destroyed
+            try {
+                slider.destroy();
+                t.ok(true, "slider.destroy does not cause problems");
+            } catch(err) {
+                t.fail("slider.destroy causes problems: " + err);
+            }
+            
+            // test that map can be zoomed without trouble after slider destroy
+            try {
+                map.zoomIn();
+                t.ok(true, "map.zoomIn does not cause problems after slider.destroy");
+            } catch(err) {
+                t.fail("map.zoomIn causes problems after slider.destroy: " + err);
+            }
+            
+            map.destroy();
+
+        }
+
+    </script>
+  <body>
+    <div id="map" style="width: 512px; height: 256px;"></div>
+  </body>
+</html>

Modified: sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/form/SearchAction.html
===================================================================
--- sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/form/SearchAction.html	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/form/SearchAction.html	2009-06-02 21:10:46 UTC (rev 966)
@@ -8,139 +8,6 @@
 
     <script type="text/javascript">
 
-    function test_filterFromForm(t) {
-        t.plan(27);
-
-        /*
-         * Set up
-         */
-
-        var form, filter, fields = [];
-
-        fields.push(new Ext.form.TextField({
-            name: "foo0",
-            value: "bar0"
-        }));
-
-        fields.push(new Ext.form.TextField({
-            name: "foo1__eq",
-            value: "bar1"
-        }));
-
-        fields.push(new Ext.form.TextField({
-            name: "foo2__ne",
-            value: "bar2"
-        }));
-
-        fields.push(new Ext.form.TextField({
-            name: "foo3__lt",
-            value: "bar3"
-        }));
-
-        fields.push(new Ext.form.TextField({
-            name: "foo4__le",
-            value: "bar4"
-        }));
-
-        fields.push(new Ext.form.TextField({
-            name: "foo5__gt",
-            value: "bar5"
-        }));
-
-        fields.push(new Ext.form.TextField({
-            name: "foo6__ge",
-            value: "bar6"
-        }));
-
-        fields.push(new Ext.form.TextField({
-            name: "foo7__like",
-            value: "bar7"
-        }));
-
-        form = new Ext.form.FormPanel({
-            renderTo: "form",
-            items: fields
-        });
-
-        /*
-         * Test
-         */
-
-        // 26 tests
-        filter = GeoExt.form.filterFromForm(form);
-
-        t.ok(filter instanceof OpenLayers.Filter.Logical,
-             "GeoExt.form.filterFormForm returns a logical filter");
-
-        t.eq(filter.type, OpenLayers.Filter.Logical.AND,
-             ["GeoExt.form.filterFormForm returns a logical AND filter if",
-              "logicalOp is undefined"].join(" "));
-
-        t.eq(filter.filters[0].type, OpenLayers.Filter.Comparison.EQUAL_TO,
-             "GeoExt.form.filterFormForm creates correct filter type");
-        t.eq(filter.filters[0].property, "foo0",
-             "GeoExt.form.filterFormForm creates correct filter prop");
-        t.eq(filter.filters[0].value, "bar0",
-             "GeoExt.form.filterFormForm creates correct filter value");
-
-        t.eq(filter.filters[1].type, OpenLayers.Filter.Comparison.EQUAL_TO,
-             "GeoExt.form.filterFormForm creates correct filter type (__eq)");
-        t.eq(filter.filters[1].property, "foo1",
-             "GeoExt.form.filterFormForm creates correct filter prop (__eq)");
-        t.eq(filter.filters[1].value, "bar1",
-             "GeoExt.form.filterFormForm creates correct filter value (__eq)");
-
-        t.eq(filter.filters[2].type, OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
-             "GeoExt.form.filterFormForm creates correct filter type (__ne)");
-        t.eq(filter.filters[2].property, "foo2",
-             "GeoExt.form.filterFormForm creates correct filter prop (__ne)");
-        t.eq(filter.filters[2].value, "bar2",
-             "GeoExt.form.filterFormForm creates correct filter value (__ne)");
-
-        t.eq(filter.filters[3].type, OpenLayers.Filter.Comparison.LESS_THAN,
-             "GeoExt.form.filterFormForm creates correct filter type (__lt)");
-        t.eq(filter.filters[3].property, "foo3",
-             "GeoExt.form.filterFormForm creates correct filter prop (__lt)");
-        t.eq(filter.filters[3].value, "bar3",
-             "GeoExt.form.filterFormForm creates correct filter value (__lt)");
-
-        t.eq(filter.filters[4].type, OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO,
-             "GeoExt.form.filterFormForm creates correct filter type (__le)");
-        t.eq(filter.filters[4].property, "foo4",
-             "GeoExt.form.filterFormForm creates correct filter prop (__le)");
-        t.eq(filter.filters[4].value, "bar4",
-             "GeoExt.form.filterFormForm creates correct filter value (__le)");
-
-        t.eq(filter.filters[5].type, OpenLayers.Filter.Comparison.GREATER_THAN,
-             "GeoExt.form.filterFormForm creates correct filter type (__gt)");
-        t.eq(filter.filters[5].property, "foo5",
-             "GeoExt.form.filterFormForm creates correct filter prop (__gt)");
-        t.eq(filter.filters[5].value, "bar5",
-             "GeoExt.form.filterFormForm creates correct filter value (__gt)");
-
-        t.eq(filter.filters[6].type, OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO,
-             "GeoExt.form.filterFormForm creates correct filter type (__ge)");
-        t.eq(filter.filters[6].property, "foo6",
-             "GeoExt.form.filterFormForm creates correct filter prop (__ge)");
-        t.eq(filter.filters[6].value, "bar6",
-             "GeoExt.form.filterFormForm creates correct filter value (__ge)");
-
-        t.eq(filter.filters[7].type, OpenLayers.Filter.Comparison.LIKE,
-             "GeoExt.form.filterFormForm creates correct filter type (__like)");
-        t.eq(filter.filters[7].property, "foo7",
-             "GeoExt.form.filterFormForm creates correct filter prop (__like)");
-        t.eq(filter.filters[7].value, "bar7",
-             "GeoExt.form.filterFormForm creates correct filter value (__like)");
-
-        // 1 test
-        filter = GeoExt.form.filterFromForm(form,
-                                            OpenLayers.Filter.Logical.OR);
-
-        t.eq(filter.type, OpenLayers.Filter.Logical.OR,
-             ["GeoExt.form.filterFormForm returns a logical OR filter if",
-              "logicalOp is OpenLayers.Filter.Logical.OR"].join(" "));
-    }
-
     function test_constructor(t) {
         t.plan(1);
 

Added: sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/form.html
===================================================================
--- sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/form.html	                        (rev 0)
+++ sandbox/opengeo/geoexplorer/tests/lib/GeoExt/widgets/form.html	2009-06-02 21:10:46 UTC (rev 966)
@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html debug="true">
+  <head>
+    <script type="text/javascript" src="../../../../../openlayers/lib/OpenLayers.js"></script>
+    <script type="text/javascript" src="../../../../../ext/adapter/ext/ext-base.js"></script>
+    <script type="text/javascript" src="../../../../../ext/ext-all-debug.js"></script>
+    <script type="text/javascript" src="../../../../lib/GeoExt.js"></script>
+
+    <script type="text/javascript">
+
+    function test_toFilter(t) {
+        t.plan(27);
+
+        /*
+         * Set up
+         */
+
+        var form, filter, fields = [];
+
+        fields.push(new Ext.form.TextField({
+            name: "foo0",
+            value: "bar0"
+        }));
+
+        fields.push(new Ext.form.TextField({
+            name: "foo1__eq",
+            value: "bar1"
+        }));
+
+        fields.push(new Ext.form.TextField({
+            name: "foo2__ne",
+            value: "bar2"
+        }));
+
+        fields.push(new Ext.form.TextField({
+            name: "foo3__lt",
+            value: "bar3"
+        }));
+
+        fields.push(new Ext.form.TextField({
+            name: "foo4__le",
+            value: "bar4"
+        }));
+
+        fields.push(new Ext.form.TextField({
+            name: "foo5__gt",
+            value: "bar5"
+        }));
+
+        fields.push(new Ext.form.TextField({
+            name: "foo6__ge",
+            value: "bar6"
+        }));
+
+        fields.push(new Ext.form.TextField({
+            name: "foo7__like",
+            value: "bar7"
+        }));
+
+        form = new Ext.form.FormPanel({
+            renderTo: "form",
+            items: fields
+        });
+
+        /*
+         * Test
+         */
+
+        // 26 tests
+        filter = GeoExt.form.toFilter(form);
+
+        t.ok(filter instanceof OpenLayers.Filter.Logical,
+             "GeoExt.form.filterFormForm returns a logical filter");
+
+        t.eq(filter.type, OpenLayers.Filter.Logical.AND,
+             ["GeoExt.form.filterFormForm returns a logical AND filter if",
+              "logicalOp is undefined"].join(" "));
+
+        t.eq(filter.filters[0].type, OpenLayers.Filter.Comparison.EQUAL_TO,
+             "GeoExt.form.filterFormForm creates correct filter type");
+        t.eq(filter.filters[0].property, "foo0",
+             "GeoExt.form.filterFormForm creates correct filter prop");
+        t.eq(filter.filters[0].value, "bar0",
+             "GeoExt.form.filterFormForm creates correct filter value");
+
+        t.eq(filter.filters[1].type, OpenLayers.Filter.Comparison.EQUAL_TO,
+             "GeoExt.form.filterFormForm creates correct filter type (__eq)");
+        t.eq(filter.filters[1].property, "foo1",
+             "GeoExt.form.filterFormForm creates correct filter prop (__eq)");
+        t.eq(filter.filters[1].value, "bar1",
+             "GeoExt.form.filterFormForm creates correct filter value (__eq)");
+
+        t.eq(filter.filters[2].type, OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
+             "GeoExt.form.filterFormForm creates correct filter type (__ne)");
+        t.eq(filter.filters[2].property, "foo2",
+             "GeoExt.form.filterFormForm creates correct filter prop (__ne)");
+        t.eq(filter.filters[2].value, "bar2",
+             "GeoExt.form.filterFormForm creates correct filter value (__ne)");
+
+        t.eq(filter.filters[3].type, OpenLayers.Filter.Comparison.LESS_THAN,
+             "GeoExt.form.filterFormForm creates correct filter type (__lt)");
+        t.eq(filter.filters[3].property, "foo3",
+             "GeoExt.form.filterFormForm creates correct filter prop (__lt)");
+        t.eq(filter.filters[3].value, "bar3",
+             "GeoExt.form.filterFormForm creates correct filter value (__lt)");
+
+        t.eq(filter.filters[4].type, OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO,
+             "GeoExt.form.filterFormForm creates correct filter type (__le)");
+        t.eq(filter.filters[4].property, "foo4",
+             "GeoExt.form.filterFormForm creates correct filter prop (__le)");
+        t.eq(filter.filters[4].value, "bar4",
+             "GeoExt.form.filterFormForm creates correct filter value (__le)");
+
+        t.eq(filter.filters[5].type, OpenLayers.Filter.Comparison.GREATER_THAN,
+             "GeoExt.form.filterFormForm creates correct filter type (__gt)");
+        t.eq(filter.filters[5].property, "foo5",
+             "GeoExt.form.filterFormForm creates correct filter prop (__gt)");
+        t.eq(filter.filters[5].value, "bar5",
+             "GeoExt.form.filterFormForm creates correct filter value (__gt)");
+
+        t.eq(filter.filters[6].type, OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO,
+             "GeoExt.form.filterFormForm creates correct filter type (__ge)");
+        t.eq(filter.filters[6].property, "foo6",
+             "GeoExt.form.filterFormForm creates correct filter prop (__ge)");
+        t.eq(filter.filters[6].value, "bar6",
+             "GeoExt.form.filterFormForm creates correct filter value (__ge)");
+
+        t.eq(filter.filters[7].type, OpenLayers.Filter.Comparison.LIKE,
+             "GeoExt.form.filterFormForm creates correct filter type (__like)");
+        t.eq(filter.filters[7].property, "foo7",
+             "GeoExt.form.filterFormForm creates correct filter prop (__like)");
+        t.eq(filter.filters[7].value, "bar7",
+             "GeoExt.form.filterFormForm creates correct filter value (__like)");
+
+        // 1 test
+        filter = GeoExt.form.toFilter(form,
+                                      OpenLayers.Filter.Logical.OR);
+
+        t.eq(filter.type, OpenLayers.Filter.Logical.OR,
+             ["GeoExt.form.filterFormForm returns a logical OR filter if",
+              "logicalOp is OpenLayers.Filter.Logical.OR"].join(" "));
+    }
+    </script>
+  <body>
+    <div id="form"></div>
+  </body>
+</html>

Modified: sandbox/opengeo/geoexplorer/tests/list-tests.html
===================================================================
--- sandbox/opengeo/geoexplorer/tests/list-tests.html	2009-06-02 20:53:06 UTC (rev 965)
+++ sandbox/opengeo/geoexplorer/tests/list-tests.html	2009-06-02 21:10:46 UTC (rev 966)
@@ -6,16 +6,17 @@
   <li>lib/GeoExt/data/LayerReader.html</li>
   <li>lib/GeoExt/data/LayerStore.html</li>
   <li>lib/GeoExt/data/ScaleStore.html</li>
-  <li>lib/GeoExt/data/ScaleStore.html</li>
   <li>lib/GeoExt/data/ProtocolProxy.html</li>
   <li>lib/GeoExt/data/WMSCapabilitiesReader.html</li>
+  <li>lib/GeoExt/widgets/Action.html</li>
   <li>lib/GeoExt/widgets/MapPanel.html</li>
   <li>lib/GeoExt/widgets/Popup.html</li>
-  <li>lib/GeoExt/widgets/tree/LayerContainer.html</li>
+  <li>lib/GeoExt/widgets/form.html</li>
   <li>lib/GeoExt/widgets/form/SearchAction.html</li>
   <li>lib/GeoExt/widgets/form/BasicForm.html</li>
   <li>lib/GeoExt/widgets/form/FormPanel.html</li>
   <li>lib/GeoExt/widgets/tree/LayerNode.html</li>
   <li>lib/GeoExt/widgets/tree/LayerContainer.html</li>
   <li>lib/GeoExt/widgets/LegendPanel.html</li>
+  <li>lib/GeoExt/widgets/ZoomSlider.html</li>
 </ul>



More information about the Commits mailing list