You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pivot.apache.org by tv...@apache.org on 2009/04/10 20:35:29 UTC

svn commit: r764000 - in /incubator/pivot/trunk: tutorials/src/pivot/tutorials/ wtk/src/pivot/wtk/ wtk/src/pivot/wtk/skin/ wtk/src/pivot/wtk/skin/terra/

Author: tvolkert
Date: Fri Apr 10 18:35:28 2009
New Revision: 764000

URL: http://svn.apache.org/viewvc?rev=764000&view=rev
Log:
Resolved PIVOT-63 and PIVOT-64 :: Updated Rollup API, and added "useBullet" style to TerraRollupSkin (defaulting to false).  Also refactored common skin code into abstract base class, RollupSkin.

Added:
    incubator/pivot/trunk/wtk/src/pivot/wtk/RollupListener.java
    incubator/pivot/trunk/wtk/src/pivot/wtk/RollupStateListener.java
      - copied, changed from r763964, incubator/pivot/trunk/wtk/src/pivot/wtk/RollupListener.java
    incubator/pivot/trunk/wtk/src/pivot/wtk/skin/RollupSkin.java
Modified:
    incubator/pivot/trunk/tutorials/src/pivot/tutorials/Demo.java
    incubator/pivot/trunk/tutorials/src/pivot/tutorials/demo.wtkx
    incubator/pivot/trunk/tutorials/src/pivot/tutorials/navigation.wtkx
    incubator/pivot/trunk/wtk/src/pivot/wtk/Rollup.java
    incubator/pivot/trunk/wtk/src/pivot/wtk/skin/terra/TerraRollupSkin.java

Modified: incubator/pivot/trunk/tutorials/src/pivot/tutorials/Demo.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/tutorials/src/pivot/tutorials/Demo.java?rev=764000&r1=763999&r2=764000&view=diff
==============================================================================
--- incubator/pivot/trunk/tutorials/src/pivot/tutorials/Demo.java (original)
+++ incubator/pivot/trunk/tutorials/src/pivot/tutorials/Demo.java Fri Apr 10 18:35:28 2009
@@ -56,7 +56,7 @@
 import pivot.wtk.Component;
 import pivot.wtk.Display;
 import pivot.wtk.Rollup;
-import pivot.wtk.RollupListener;
+import pivot.wtk.RollupStateListener;
 import pivot.wtk.Slider;
 import pivot.wtk.SliderValueListener;
 import pivot.wtk.Spinner;
@@ -82,11 +82,11 @@
 import pivot.wtkx.WTKXSerializer;
 
 public class Demo implements Application {
-    private abstract class RollupHandler implements RollupListener {
+    private abstract class RollupStateHandler implements RollupStateListener {
         private String resourceName;
         private Component component = null;
 
-        public RollupHandler(String resourceName) {
+        public RollupStateHandler(String resourceName) {
             this.resourceName = resourceName;
         }
 
@@ -96,11 +96,7 @@
 
                 try {
                     component = (Component)wtkxSerializer.readObject(resourceName);
-
-                    // TODO This is a hack; it shows knowledge that rollup adds
-                    // an internal component to handle expand/collapse
-                    rollup.remove(2, 1);
-                    rollup.add(component);
+                    rollup.setContent(component);
                     initialize(wtkxSerializer);
                 } catch(SerializationException exception) {
                     throw new RuntimeException(exception);
@@ -155,7 +151,7 @@
         Component content = (Component)wtkxSerializer.readObject("pivot/tutorials/demo.wtkx");
 
         final Rollup buttonsRollup = (Rollup)wtkxSerializer.getObjectByName("buttonsRollup");
-        buttonsRollup.getRollupListeners().add(new RollupHandler("pivot/tutorials/buttons.wtkx") {
+        buttonsRollup.getRollupStateListeners().add(new RollupStateHandler("pivot/tutorials/buttons.wtkx") {
             @Override
             protected void initialize(WTKXSerializer wtkxSerializer) {
                 // No-op
@@ -163,7 +159,7 @@
         });
 
         Rollup listsRollup = (Rollup)wtkxSerializer.getObjectByName("listsRollup");
-        listsRollup.getRollupListeners().add(new RollupHandler("pivot/tutorials/lists.wtkx") {
+        listsRollup.getRollupStateListeners().add(new RollupStateHandler("pivot/tutorials/lists.wtkx") {
             @Override
             protected void initialize(WTKXSerializer wtkxSerializer) {
                 ListView editableListView = (ListView)wtkxSerializer.getObjectByName("editableListView");
@@ -188,7 +184,7 @@
         });
 
         Rollup textRollup = (Rollup)wtkxSerializer.getObjectByName("textRollup");
-        textRollup.getRollupListeners().add(new RollupHandler("pivot/tutorials/text.wtkx") {
+        textRollup.getRollupStateListeners().add(new RollupStateHandler("pivot/tutorials/text.wtkx") {
             @Override
             protected void initialize(WTKXSerializer wtkxSerializer) {
                 PlainTextSerializer plainTextSerializer = new PlainTextSerializer("UTF-8");
@@ -224,7 +220,7 @@
         });
 
         Rollup calendarsRollup = (Rollup)wtkxSerializer.getObjectByName("calendarsRollup");
-        calendarsRollup.getRollupListeners().add(new RollupHandler("pivot/tutorials/calendars.wtkx") {
+        calendarsRollup.getRollupStateListeners().add(new RollupStateHandler("pivot/tutorials/calendars.wtkx") {
             @Override
             protected void initialize(WTKXSerializer wtkxSerializer) {
                 // No-op
@@ -232,7 +228,7 @@
         });
 
         Rollup navigationRollup = (Rollup)wtkxSerializer.getObjectByName("navigationRollup");
-        navigationRollup.getRollupListeners().add(new RollupHandler("pivot/tutorials/navigation.wtkx") {
+        navigationRollup.getRollupStateListeners().add(new RollupStateHandler("pivot/tutorials/navigation.wtkx") {
             @Override
             protected void initialize(WTKXSerializer wtkxSerializer) {
                 // No-op
@@ -240,7 +236,7 @@
         });
 
         Rollup splittersRollup = (Rollup)wtkxSerializer.getObjectByName("splittersRollup");
-        splittersRollup.getRollupListeners().add(new RollupHandler("pivot/tutorials/splitters.wtkx") {
+        splittersRollup.getRollupStateListeners().add(new RollupStateHandler("pivot/tutorials/splitters.wtkx") {
             @Override
             protected void initialize(WTKXSerializer wtkxSerializer) {
                 // No-op
@@ -276,7 +272,7 @@
         };
 
         Rollup menusRollup = (Rollup)wtkxSerializer.getObjectByName("menusRollup");
-        menusRollup.getRollupListeners().add(new RollupHandler("pivot/tutorials/menus.wtkx") {
+        menusRollup.getRollupStateListeners().add(new RollupStateHandler("pivot/tutorials/menus.wtkx") {
             @Override
             protected void initialize(WTKXSerializer wtkxSerializer) throws SerializationException, IOException {
                 menuImageView = (ImageView)wtkxSerializer.getObjectByName("imageView");
@@ -303,7 +299,7 @@
         });
 
         Rollup metersRollup = (Rollup)wtkxSerializer.getObjectByName("metersRollup");
-        metersRollup.getRollupListeners().add(new RollupHandler("pivot/tutorials/meters.wtkx") {
+        metersRollup.getRollupStateListeners().add(new RollupStateHandler("pivot/tutorials/meters.wtkx") {
             @Override
             protected void initialize(WTKXSerializer wtkxSerializer) {
                 // No-op
@@ -311,7 +307,7 @@
         });
 
         Rollup spinnersRollup = (Rollup)wtkxSerializer.getObjectByName("spinnersRollup");
-        spinnersRollup.getRollupListeners().add(new RollupHandler("pivot/tutorials/spinners.wtkx") {
+        spinnersRollup.getRollupStateListeners().add(new RollupStateHandler("pivot/tutorials/spinners.wtkx") {
             @Override
             protected void initialize(WTKXSerializer wtkxSerializer) {
                 Spinner numericSpinner = (Spinner)wtkxSerializer.getObjectByName("numericSpinner");
@@ -345,7 +341,7 @@
         });
 
         Rollup tablesRollup = (Rollup)wtkxSerializer.getObjectByName("tablesRollup");
-        tablesRollup.getRollupListeners().add(new RollupHandler("pivot/tutorials/tables.wtkx") {
+        tablesRollup.getRollupStateListeners().add(new RollupStateHandler("pivot/tutorials/tables.wtkx") {
             @Override
             protected void initialize(WTKXSerializer wtkxSerializer) {
                 sortableTableView = (TableView)wtkxSerializer.getObjectByName("sortableTableView");
@@ -356,7 +352,7 @@
         });
 
         Rollup treesRollup = (Rollup)wtkxSerializer.getObjectByName("treesRollup");
-        treesRollup.getRollupListeners().add(new RollupHandler("pivot/tutorials/trees.wtkx") {
+        treesRollup.getRollupStateListeners().add(new RollupStateHandler("pivot/tutorials/trees.wtkx") {
             @Override
             protected void initialize(WTKXSerializer wtkxSerializer) {
                 TreeView editableTreeView = (TreeView)wtkxSerializer.getObjectByName("editableTreeView");
@@ -366,7 +362,7 @@
         });
 
         Rollup dragDropRollup = (Rollup)wtkxSerializer.getObjectByName("dragDropRollup");
-        dragDropRollup.getRollupListeners().add(new RollupHandler("pivot/tutorials/dragdrop.wtkx") {
+        dragDropRollup.getRollupStateListeners().add(new RollupStateHandler("pivot/tutorials/dragdrop.wtkx") {
             @Override
             protected void initialize(WTKXSerializer wtkxSerializer) {
                 DragSource imageDragSource = new DragSource() {
@@ -486,7 +482,7 @@
         });
 
         Rollup alertsRollup = (Rollup)wtkxSerializer.getObjectByName("alertsRollup");
-        alertsRollup.getRollupListeners().add(new RollupHandler("pivot/tutorials/alerts.wtkx") {
+        alertsRollup.getRollupStateListeners().add(new RollupStateHandler("pivot/tutorials/alerts.wtkx") {
             @Override
             protected void initialize(WTKXSerializer wtkxSerializer) {
                 alertButton = (PushButton)wtkxSerializer.getObjectByName("alertButton");

Modified: incubator/pivot/trunk/tutorials/src/pivot/tutorials/demo.wtkx
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/tutorials/src/pivot/tutorials/demo.wtkx?rev=764000&r1=763999&r2=764000&view=diff
==============================================================================
--- incubator/pivot/trunk/tutorials/src/pivot/tutorials/demo.wtkx (original)
+++ incubator/pivot/trunk/tutorials/src/pivot/tutorials/demo.wtkx Fri Apr 10 18:35:28 2009
@@ -29,56 +29,69 @@
                     <view>
                         <FlowPane orientation="vertical" styles="{padding:6}">
                             <Rollup wtkx:id="buttonsRollup">
-                                <Label text="Buttons" styles="{fontBold:true, fontSize:12, color:13}"/>
-                                <Label/>
+                                <heading>
+                                    <Label text="Buttons" styles="{fontBold:true, fontSize:12, color:13}"/>
+                                </heading>
                             </Rollup>
                             <Rollup wtkx:id="listsRollup">
-                                <Label text="Lists" styles="{fontBold:true, fontSize:12, color:13}"/>
-                                <Label/>
+                                <heading>
+                                    <Label text="Lists" styles="{fontBold:true, fontSize:12, color:13}"/>
+                                </heading>
                             </Rollup>
                             <Rollup wtkx:id="textRollup">
-                                <Label text="Text" styles="{fontBold:true, fontSize:12, color:13}"/>
-                                <Label/>
+                                <heading>
+                                    <Label text="Text" styles="{fontBold:true, fontSize:12, color:13}"/>
+                                </heading>
                             </Rollup>
                             <Rollup wtkx:id="calendarsRollup">
-                                <Label text="Calendars" styles="{fontBold:true, fontSize:12, color:13}"/>
-                                <Label/>
+                                <heading>
+                                    <Label text="Calendars" styles="{fontBold:true, fontSize:12, color:13}"/>
+                                </heading>
                             </Rollup>
                             <Rollup wtkx:id="navigationRollup">
-                                <Label text="Navigation" styles="{fontBold:true, fontSize:12, color:13}"/>
-                                <Label/>
+                                <heading>
+                                    <Label text="Navigation" styles="{fontBold:true, fontSize:12, color:13}"/>
+                                </heading>
                             </Rollup>
                             <Rollup wtkx:id="splittersRollup">
-                                <Label text="Splitters" styles="{fontBold:true, fontSize:12, color:13}"/>
-                                <Label/>
+                                <heading>
+                                    <Label text="Splitters" styles="{fontBold:true, fontSize:12, color:13}"/>
+                                </heading>
                             </Rollup>
                             <Rollup wtkx:id="menusRollup">
-                                <Label text="Menus" styles="{fontBold:true, fontSize:12, color:13}"/>
-                                <Label/>
+                                <heading>
+                                    <Label text="Menus" styles="{fontBold:true, fontSize:12, color:13}"/>
+                                </heading>
                             </Rollup>
                             <Rollup wtkx:id="metersRollup">
-                                <Label text="Meters" styles="{fontBold:true, fontSize:12, color:13}"/>
-                                <Label/>
+                                <heading>
+                                    <Label text="Meters" styles="{fontBold:true, fontSize:12, color:13}"/>
+                                </heading>
                             </Rollup>
                             <Rollup wtkx:id="spinnersRollup">
-                                <Label text="Spinners &amp; Sliders" styles="{fontBold:true, fontSize:12, color:13}"/>
-                                <Label/>
+                                <heading>
+                                    <Label text="Spinners &amp; Sliders" styles="{fontBold:true, fontSize:12, color:13}"/>
+                                </heading>
                             </Rollup>
                             <Rollup wtkx:id="tablesRollup">
-                                <Label text="Tables" styles="{fontBold:true, fontSize:12, color:13}"/>
-                                <Label/>
+                                <heading>
+                                    <Label text="Tables" styles="{fontBold:true, fontSize:12, color:13}"/>
+                                </heading>
                             </Rollup>
                             <Rollup wtkx:id="treesRollup">
-                                <Label text="Trees" styles="{fontBold:true, fontSize:12, color:13}"/>
-                                <Label/>
+                                <heading>
+                                    <Label text="Trees" styles="{fontBold:true, fontSize:12, color:13}"/>
+                                </heading>
                             </Rollup>
                             <Rollup wtkx:id="dragDropRollup">
-                                <Label text="Drag &amp; Drop" styles="{fontBold:true, fontSize:12, color:13}"/>
-                                <Label/>
+                                <heading>
+                                    <Label text="Drag &amp; Drop" styles="{fontBold:true, fontSize:12, color:13}"/>
+                                </heading>
                             </Rollup>
                             <Rollup wtkx:id="alertsRollup">
-                                <Label text="Alerts &amp; Prompts" styles="{fontBold:true, fontSize:12, color:13}"/>
-                                <Label/>
+                                <heading>
+                                    <Label text="Alerts &amp; Prompts" styles="{fontBold:true, fontSize:12, color:13}"/>
+                                </heading>
                             </Rollup>
                         </FlowPane>
                     </view>

Modified: incubator/pivot/trunk/tutorials/src/pivot/tutorials/navigation.wtkx
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/tutorials/src/pivot/tutorials/navigation.wtkx?rev=764000&r1=763999&r2=764000&view=diff
==============================================================================
--- incubator/pivot/trunk/tutorials/src/pivot/tutorials/navigation.wtkx (original)
+++ incubator/pivot/trunk/tutorials/src/pivot/tutorials/navigation.wtkx Fri Apr 10 18:35:28 2009
@@ -158,55 +158,73 @@
             <FlowPane orientation="vertical" styles="{padding:{top:2, left:4, bottom:4, right:4}, spacing:6}">
                 <Label text="Rollups" styles="{fontBold:true}"/>
                 <Rollup>
-                    <Label text="Colors"/>
-                    <Label text="Red" styles="{color:'#ff0000'}"/>
-                    <Label text="Orange" styles="{color:'#ffa500'}"/>
-                    <Label text="Yellow" styles="{color:'#ffff00'}"/>
-                    <Label text="Green" styles="{color:'#00ff00'}"/>
-                    <Label text="Blue" styles="{color:'#0000ff'}"/>
-                    <Label text="Purple" styles="{color:'#800080'}"/>
+                    <heading>
+                        <Label text="Colors"/>
+                    </heading>
+                    <content>
+                        <FlowPane orientation="vertical">
+                            <Label text="Red" styles="{color:'#ff0000'}"/>
+                            <Label text="Orange" styles="{color:'#ffa500'}"/>
+                            <Label text="Yellow" styles="{color:'#ffff00'}"/>
+                            <Label text="Green" styles="{color:'#00ff00'}"/>
+                            <Label text="Blue" styles="{color:'#0000ff'}"/>
+                            <Label text="Purple" styles="{color:'#800080'}"/>
+                        </FlowPane>
+                    </content>
                 </Rollup>
                 <Rollup expanded="true">
-                    <Label text="Shapes"/>
-                    <Checkbox buttonData="Circle"/>
-                    <Checkbox buttonData="Ellipse" selected="true"/>
-                    <Checkbox buttonData="Square" selected="true"/>
-                    <Checkbox buttonData="Rectangle"/>
-                    <Checkbox buttonData="Hexagon"/>
-                    <Checkbox buttonData="Octagon" selected="true"/>
+                    <heading>
+                        <Label text="Shapes"/>
+                    </heading>
+                    <content>
+                        <FlowPane orientation="vertical">
+                            <Checkbox buttonData="Circle"/>
+                            <Checkbox buttonData="Ellipse" selected="true"/>
+                            <Checkbox buttonData="Square" selected="true"/>
+                            <Checkbox buttonData="Rectangle"/>
+                            <Checkbox buttonData="Hexagon"/>
+                            <Checkbox buttonData="Octagon" selected="true"/>
+                        </FlowPane>
+                    </content>
                 </Rollup>
                 <Rollup>
-                    <Label text="Images"/>
-                    <RadioButton group="images" selected="true">
-                        <buttonData>
-                            <content:ButtonData text="Anchor" icon="@anchor.png"/>
-                        </buttonData>
-                    </RadioButton>
-                    <RadioButton group="images" selected="true">
-                        <buttonData>
-                            <content:ButtonData text="Bell" icon="@bell.png"/>
-                        </buttonData>
-                    </RadioButton>
-                    <RadioButton group="images" selected="true">
-                        <buttonData>
-                            <content:ButtonData text="Clock" icon="@clock.png"/>
-                        </buttonData>
-                    </RadioButton>
-                    <RadioButton group="images" selected="true">
-                        <buttonData>
-                            <content:ButtonData text="Cup" icon="@cup.png"/>
-                        </buttonData>
-                    </RadioButton>
-                    <RadioButton group="images" selected="true">
-                        <buttonData>
-                            <content:ButtonData text="House" icon="@house.png"/>
-                        </buttonData>
-                    </RadioButton>
-                    <RadioButton group="images" selected="true">
-                        <buttonData>
-                            <content:ButtonData text="Star" icon="@star.png"/>
-                        </buttonData>
-                    </RadioButton>
+                    <heading>
+                        <Label text="Images"/>
+                    </heading>
+                    <content>
+                        <FlowPane orientation="vertical">
+                            <RadioButton group="images" selected="true">
+                                <buttonData>
+                                    <content:ButtonData text="Anchor" icon="@anchor.png"/>
+                                </buttonData>
+                            </RadioButton>
+                            <RadioButton group="images" selected="true">
+                                <buttonData>
+                                    <content:ButtonData text="Bell" icon="@bell.png"/>
+                                </buttonData>
+                            </RadioButton>
+                            <RadioButton group="images" selected="true">
+                                <buttonData>
+                                    <content:ButtonData text="Clock" icon="@clock.png"/>
+                                </buttonData>
+                            </RadioButton>
+                            <RadioButton group="images" selected="true">
+                                <buttonData>
+                                    <content:ButtonData text="Cup" icon="@cup.png"/>
+                                </buttonData>
+                            </RadioButton>
+                            <RadioButton group="images" selected="true">
+                                <buttonData>
+                                    <content:ButtonData text="House" icon="@house.png"/>
+                                </buttonData>
+                            </RadioButton>
+                            <RadioButton group="images" selected="true">
+                                <buttonData>
+                                    <content:ButtonData text="Star" icon="@star.png"/>
+                                </buttonData>
+                            </RadioButton>
+                        </FlowPane>
+                    </content>
                 </Rollup>
             </FlowPane>
         </content>

Modified: incubator/pivot/trunk/wtk/src/pivot/wtk/Rollup.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/pivot/wtk/Rollup.java?rev=764000&r1=763999&r2=764000&view=diff
==============================================================================
--- incubator/pivot/trunk/wtk/src/pivot/wtk/Rollup.java (original)
+++ incubator/pivot/trunk/wtk/src/pivot/wtk/Rollup.java Fri Apr 10 18:35:28 2009
@@ -16,25 +16,40 @@
  */
 package pivot.wtk;
 
+import pivot.collections.Sequence;
 import pivot.util.ListenerList;
 import pivot.util.Vote;
 
 /**
  * Container that can be expanded or collapsed to respectively show or hide its
- * children. When expanded, the rollup's children are displayed vertically like
- * a vertical flow pane. When collapsed, only the rollup's first child is
- * displayed.
+ * content. A rollup has a heading component that is always visible, and when
+ * the user expands the rollup, its content component will be shown beneath the
+ * heading.
  *
- * @author gbrown
  * @author tvolkert
  */
-public class Rollup extends pivot.wtk.Container {
+public class Rollup extends Container {
     private static class RollupListenerList extends ListenerList<RollupListener>
         implements RollupListener {
+        public void headingChanged(Rollup rollup, Component previousHeading) {
+            for (RollupListener listener : this) {
+                listener.headingChanged(rollup, previousHeading);
+            }
+        }
+
+        public void contentChanged(Rollup rollup, Component previousContent) {
+            for (RollupListener listener : this) {
+                listener.contentChanged(rollup, previousContent);
+            }
+        }
+    }
+
+    private static class RollupStateListenerList extends ListenerList<RollupStateListener>
+        implements RollupStateListener {
         public Vote previewExpandedChange(Rollup rollup) {
             Vote vote = Vote.APPROVE;
 
-            for (RollupListener listener : this) {
+            for (RollupStateListener listener : this) {
                 vote = vote.tally(listener.previewExpandedChange(rollup));
             }
 
@@ -42,20 +57,25 @@
         }
 
         public void expandedChangeVetoed(Rollup rollup, Vote reason) {
-            for (RollupListener listener : this) {
+            for (RollupStateListener listener : this) {
                 listener.expandedChangeVetoed(rollup, reason);
             }
         }
 
         public void expandedChanged(Rollup rollup) {
-            for (RollupListener listener : this) {
+            for (RollupStateListener listener : this) {
                 listener.expandedChanged(rollup);
             }
         }
     }
 
     private boolean expanded = true;
+
+    private Component heading = null;
+    private Component content = null;
+
     private RollupListenerList rollupListeners = new RollupListenerList();
+    private RollupStateListenerList rollupStateListeners = new RollupStateListenerList();
 
     public Rollup() {
         this(false, null);
@@ -65,17 +85,17 @@
         this(expanded, null);
     }
 
-    public Rollup(Component firstChild) {
-        this(false, firstChild);
+    public Rollup(Component content) {
+        this(false, content);
     }
 
-    public Rollup(boolean expanded, Component firstChild) {
+    public Rollup(boolean expanded, Component content) {
         this.expanded = expanded;
 
         installSkin(Rollup.class);
 
-        if (firstChild != null) {
-            add(firstChild);
+        if (content != null) {
+            setContent(content);
         }
     }
 
@@ -85,18 +105,88 @@
 
     public void setExpanded(boolean expanded) {
         if (expanded != this.expanded) {
-            Vote vote = rollupListeners.previewExpandedChange(this);
+            Vote vote = rollupStateListeners.previewExpandedChange(this);
 
             if (vote == Vote.APPROVE) {
                 this.expanded = expanded;
-                rollupListeners.expandedChanged(this);
+                rollupStateListeners.expandedChanged(this);
             } else {
-                rollupListeners.expandedChangeVetoed(this, vote);
+                rollupStateListeners.expandedChangeVetoed(this, vote);
             }
         }
     }
 
+    public Component getHeading() {
+        return heading;
+    }
+
+    public void setHeading(Component heading) {
+       Component previousHeading = this.heading;
+
+        if (heading != previousHeading) {
+            // Remove any previous heading component
+            this.heading = null;
+
+            if (previousHeading != null) {
+                remove(previousHeading);
+            }
+
+            // Set the new heading component
+            if (heading != null) {
+                add(heading);
+            }
+
+            this.heading = heading;
+
+            rollupListeners.headingChanged(this, previousHeading);
+        }
+    }
+
+    public Component getContent() {
+        return content;
+    }
+
+    public void setContent(Component content) {
+       Component previousContent = this.content;
+
+        if (content != previousContent) {
+            // Remove any previous content component
+            this.content = null;
+
+            if (previousContent != null) {
+                remove(previousContent);
+            }
+
+            // Set the new content component
+            if (content != null) {
+                add(content);
+            }
+
+            this.content = content;
+
+            rollupListeners.contentChanged(this, previousContent);
+        }
+    }
+
+    @Override
+    public Sequence<Component> remove(int index, int count) {
+        for (int i = index, n = index + count; i < n; i++) {
+            Component component = get(i);
+            if (component == heading
+                || component == content) {
+                throw new UnsupportedOperationException();
+            }
+        }
+
+        // Call the base method to remove the components
+        return super.remove(index, count);
+    }
+
     public ListenerList<RollupListener> getRollupListeners() {
         return rollupListeners;
     }
+
+    public ListenerList<RollupStateListener> getRollupStateListeners() {
+        return rollupStateListeners;
+    }
 }

Added: incubator/pivot/trunk/wtk/src/pivot/wtk/RollupListener.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/pivot/wtk/RollupListener.java?rev=764000&view=auto
==============================================================================
--- incubator/pivot/trunk/wtk/src/pivot/wtk/RollupListener.java (added)
+++ incubator/pivot/trunk/wtk/src/pivot/wtk/RollupListener.java Fri Apr 10 18:35:28 2009
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package pivot.wtk;
+
+import pivot.util.Vote;
+
+/**
+ * Defines event listener methods that pertain to rollups. Developers
+ * register for such events by adding themselves to a rollup's list of "rollup
+ * listeners" (see {@link Rollup#getRollupListeners()}).
+ *
+ * @author tvolkert
+ */
+public interface RollupListener {
+    /**
+     * Called when a rollup's heading component changed.
+     *
+     * @param rollup
+     * @param previousHeading
+     */
+    public void headingChanged(Rollup rollup, Component previousHeading);
+
+    /**
+     * Called when a rollup's content component changed.
+     *
+     * @param rollup
+     * @param previousContent
+     */
+    public void contentChanged(Rollup rollup, Component previousContent);
+}

Copied: incubator/pivot/trunk/wtk/src/pivot/wtk/RollupStateListener.java (from r763964, incubator/pivot/trunk/wtk/src/pivot/wtk/RollupListener.java)
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/pivot/wtk/RollupStateListener.java?p2=incubator/pivot/trunk/wtk/src/pivot/wtk/RollupStateListener.java&p1=incubator/pivot/trunk/wtk/src/pivot/wtk/RollupListener.java&r1=763964&r2=764000&rev=764000&view=diff
==============================================================================
--- incubator/pivot/trunk/wtk/src/pivot/wtk/RollupListener.java (original)
+++ incubator/pivot/trunk/wtk/src/pivot/wtk/RollupStateListener.java Fri Apr 10 18:35:28 2009
@@ -19,13 +19,13 @@
 import pivot.util.Vote;
 
 /**
- * Defines event listener methods that pertain to rollups. Developers register
- * for such events by adding themselves to a rollup's list of "rollup
- * listeners" (see {@link Rollup#getRollupListeners()}).
+ * Defines event listener methods that pertain to rollup state. Developers
+ * register for such events by adding themselves to a rollup's list of "rollup
+ * state listeners" (see {@link Rollup#getRollupStateListeners()}).
  *
  * @author tvolkert
  */
-public interface RollupListener {
+public interface RollupStateListener {
     /**
      * Called to preview a rollup expansion event.
      *

Added: incubator/pivot/trunk/wtk/src/pivot/wtk/skin/RollupSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/pivot/wtk/skin/RollupSkin.java?rev=764000&view=auto
==============================================================================
--- incubator/pivot/trunk/wtk/src/pivot/wtk/skin/RollupSkin.java (added)
+++ incubator/pivot/trunk/wtk/src/pivot/wtk/skin/RollupSkin.java Fri Apr 10 18:35:28 2009
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package pivot.wtk.skin;
+
+import pivot.util.Vote;
+import pivot.wtk.Component;
+import pivot.wtk.Rollup;
+import pivot.wtk.RollupListener;
+import pivot.wtk.RollupStateListener;
+
+/**
+ * Abstract base class for rollup skins.
+ *
+ * @author tvolkert
+ */
+public abstract class RollupSkin extends ContainerSkin
+    implements RollupListener, RollupStateListener {
+    @Override
+    public void install(Component component) {
+        super.install(component);
+
+        Rollup rollup = (Rollup)component;
+        rollup.getRollupListeners().add(this);
+        rollup.getRollupStateListeners().add(this);
+    }
+
+    @Override
+    public void uninstall() {
+        Rollup rollup = (Rollup)getComponent();
+        rollup.getRollupListeners().remove(this);
+        rollup.getRollupStateListeners().remove(this);
+
+        super.uninstall();
+    }
+
+    // RollupListener methods
+
+    public void headingChanged(Rollup rollup, Component previousHeading) {
+        // No-op
+    }
+
+    public void contentChanged(Rollup rollup, Component previousContent) {
+        // No-op
+    }
+
+    // RollupStateListener methods
+
+    public Vote previewExpandedChange(Rollup rollup) {
+        // No-op
+        return Vote.APPROVE;
+    }
+
+    public void expandedChangeVetoed(Rollup rollup, Vote reason) {
+        // No-op
+    }
+
+    public void expandedChanged(Rollup rollup) {
+        // No-op
+    }
+}

Modified: incubator/pivot/trunk/wtk/src/pivot/wtk/skin/terra/TerraRollupSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/pivot/wtk/skin/terra/TerraRollupSkin.java?rev=764000&r1=763999&r2=764000&view=diff
==============================================================================
--- incubator/pivot/trunk/wtk/src/pivot/wtk/skin/terra/TerraRollupSkin.java (original)
+++ incubator/pivot/trunk/wtk/src/pivot/wtk/skin/terra/TerraRollupSkin.java Fri Apr 10 18:35:28 2009
@@ -25,7 +25,6 @@
 import pivot.collections.Sequence;
 import pivot.util.Vote;
 import pivot.wtk.Button;
-import pivot.wtk.ButtonPressListener;
 import pivot.wtk.Component;
 import pivot.wtk.ComponentMouseButtonListener;
 import pivot.wtk.Container;
@@ -34,26 +33,26 @@
 import pivot.wtk.Mouse;
 import pivot.wtk.PushButton;
 import pivot.wtk.Rollup;
-import pivot.wtk.RollupListener;
 import pivot.wtk.Theme;
 import pivot.wtk.effects.Transition;
 import pivot.wtk.effects.TransitionListener;
 import pivot.wtk.effects.easing.Easing;
 import pivot.wtk.effects.easing.Quadratic;
 import pivot.wtk.media.Image;
-import pivot.wtk.skin.ButtonSkin;
-import pivot.wtk.skin.ContainerSkin;
+import pivot.wtk.skin.ComponentSkin;
+import pivot.wtk.skin.RollupSkin;
 
 /**
- * Rollup skin.
- * <p>
- * TODO Optimize this class by performing preferred size calculation in one
- * pass.
+ * Terra theme's rollup skin.
  *
  * @author tvolkert
  */
-public class TerraRollupSkin extends ContainerSkin
-    implements RollupListener, ButtonPressListener {
+public class TerraRollupSkin extends RollupSkin {
+    /**
+     * Provides expand/collapse animation.
+     *
+     * @author tvolkert
+     */
     private class ExpansionTransition extends Transition {
         private int height1;
         private int height2;
@@ -108,124 +107,93 @@
                     height = (int)easing.easeOut(elapsedTime, height1, height2 - height1, duration);
                 }
 
-                Rollup rollup = (Rollup)getComponent();
-                rollup.setPreferredHeight(height);
+                getComponent().setPreferredHeight(height);
             }
         }
     }
 
-    protected class RollupButton extends PushButton {
+    /**
+     * Component that allows the user to expand and collapse the Rollup.
+     *
+     * @author tvolkert
+     */
+    protected class RollupButton extends Component {
         public RollupButton() {
-            super(null);
             setSkin(new RollupButtonSkin());
         }
     }
 
-    protected class RollupButtonSkin extends ButtonSkin {
+    /**
+     * Skin for the rollup button.
+     *
+     * @author tvolkert
+     */
+    protected class RollupButtonSkin extends ComponentSkin {
         @Override
         public boolean isFocusable() {
             return false;
         }
 
         public int getPreferredWidth(int height) {
-            RollupButton rollupButton = (RollupButton)getComponent();
-            Button.DataRenderer dataRenderer = rollupButton.getDataRenderer();
-            dataRenderer.render(rollupButton.getButtonData(), rollupButton, false);
-            return dataRenderer.getPreferredWidth(height);
+            return 7;
         }
 
         public int getPreferredHeight(int width) {
-            RollupButton rollupButton = (RollupButton)getComponent();
-            Button.DataRenderer dataRenderer = rollupButton.getDataRenderer();
-            dataRenderer.render(rollupButton.getButtonData(), rollupButton, false);
-            return dataRenderer.getPreferredHeight(width);
+            return 7;
         }
 
         public Dimensions getPreferredSize() {
-            RollupButton rollupButton = (RollupButton)getComponent();
-            Button.DataRenderer dataRenderer = rollupButton.getDataRenderer();
-            dataRenderer.render(rollupButton.getButtonData(), rollupButton, false);
-            Dimensions contentSize = dataRenderer.getPreferredSize();
-            return new Dimensions(contentSize.width, contentSize.height);
-        }
-
-        public void paint(Graphics2D graphics) {
-            RollupButton rollupButton = (RollupButton)getComponent();
-
-            // Paint the content
-            Button.DataRenderer dataRenderer = rollupButton.getDataRenderer();
-            dataRenderer.render(rollupButton.getButtonData(), rollupButton, false);
-
-            Dimensions contentSize = dataRenderer.getPreferredSize();
-            dataRenderer.setSize(contentSize.width, contentSize.height);
-            dataRenderer.paint(graphics);
-        }
-
-        @Override
-        public boolean mouseClick(Component component, Mouse.Button button, int x, int y, int count) {
-            PushButton pushButton = (PushButton)getComponent();
-            pushButton.press();
-            return true;
+            return new Dimensions(7, 7);
         }
-    }
 
-    protected abstract class ButtonImage extends Image {
-        public int getWidth() {
-            return 7;
+        public void layout() {
+            // No-op
         }
 
-        public int getHeight() {
-            return 7;
-        }
-    }
-
-    protected class ExpandImage extends ButtonImage {
         public void paint(Graphics2D graphics) {
-            graphics.setStroke(new BasicStroke(0));
-            graphics.setPaint(buttonColor);
-
-            graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
-                    RenderingHints.VALUE_ANTIALIAS_ON);
+            Rollup rollup = (Rollup)TerraRollupSkin.this.getComponent();
 
-            int[] xPoints = {0, 6, 0};
-            int[] yPoints = {0, 3, 6};
-            graphics.fillPolygon(xPoints, yPoints, 3);
-            graphics.drawPolygon(xPoints, yPoints, 3);
-        }
-    }
-
-    protected class CollapseImage extends ButtonImage {
-        public void paint(Graphics2D graphics) {
             graphics.setStroke(new BasicStroke(0));
             graphics.setPaint(buttonColor);
-
             graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
-                    RenderingHints.VALUE_ANTIALIAS_ON);
+                RenderingHints.VALUE_ANTIALIAS_ON);
 
-            int[] xPoints = {0, 3, 6};
-            int[] yPoints = {0, 6, 0};
-            graphics.fillPolygon(xPoints, yPoints, 3);
-            graphics.drawPolygon(xPoints, yPoints, 3);
+            if (rollup.getContent() == null && useBullet) {
+                // Paint the bullet
+                RoundRectangle2D.Double shape = new RoundRectangle2D.Double(1, 1, 4, 4, 2, 2);
+                graphics.draw(shape);
+                graphics.fill(shape);
+            } else if (rollup.isExpanded()) {
+                // Paint the collapse image
+                int[] xPoints = {0, 3, 6};
+                int[] yPoints = {0, 6, 0};
+                graphics.fillPolygon(xPoints, yPoints, 3);
+                graphics.drawPolygon(xPoints, yPoints, 3);
+            } else {
+                // Paint the expand image
+                int[] xPoints = {0, 6, 0};
+                int[] yPoints = {0, 3, 6};
+                graphics.fillPolygon(xPoints, yPoints, 3);
+                graphics.drawPolygon(xPoints, yPoints, 3);
+            }
         }
-    }
-
-    protected class BulletImage extends ButtonImage {
-        public void paint(Graphics2D graphics) {
-            graphics.setStroke(new BasicStroke(0));
-            graphics.setPaint(buttonColor);
-
-            RoundRectangle2D.Double shape = new RoundRectangle2D.Double(1, 1, 4, 4, 2, 2);
-
-            graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
-                RenderingHints.VALUE_ANTIALIAS_ON);
 
-            graphics.draw(shape);
-            graphics.fill(shape);
+        @Override
+        public boolean mouseClick(Component component, Mouse.Button button, int x, int y, int count) {
+            Rollup rollup = (Rollup)TerraRollupSkin.this.getComponent();
+            rollup.setExpanded(!rollup.isExpanded());
+            return true;
         }
     }
 
-    private class ToggleComponentMouseHandler
-        implements ComponentMouseButtonListener {
+    /**
+     * Responsible for expanding and collapsing the rollup when the user clicks
+     * on the heading component. This only applies if <tt>headingToggles</tt>
+     * is <tt>true</tt>.
+     *
+     * @author tvolkert
+     */
+    private class HeadingMouseButtonHandler implements ComponentMouseButtonListener {
         public boolean mouseDown(Component component, Mouse.Button button, int x, int y) {
             return false;
         }
@@ -235,39 +203,48 @@
         }
 
         public boolean mouseClick(Component component, Mouse.Button button, int x, int y, int count) {
-            Rollup rollup = (Rollup)getComponent();
-            rollup.setExpanded(!rollup.isExpanded());
-            return true;
+            boolean consumed = false;
+
+            if (headingToggles) {
+                Rollup rollup = (Rollup)getComponent();
+                rollup.setExpanded(!rollup.isExpanded());
+                consumed = true;
+            }
+
+            return consumed;
         }
     }
 
+    // Skin components
     private RollupButton rollupButton = null;
-    private Component toggleComponent = null;
-    private ToggleComponentMouseHandler toggleComponentMouseHandler =
-        new ToggleComponentMouseHandler();
-    private ExpandImage expandImage = new ExpandImage();
-    private CollapseImage collapseImage = new CollapseImage();
-    private BulletImage bulletImage = new BulletImage();
 
+    // Internal handlers
+    private HeadingMouseButtonHandler headingMouseButtonHandler = new HeadingMouseButtonHandler();
+
+    // Animation support
     private ExpansionTransition expandTransition = null;
     private ExpansionTransition collapseTransition = null;
 
+    // Styles
     private Color buttonColor;
     private int spacing;
     private int buffer;
     private boolean justify;
-    private boolean firstChildToggles;
+    private boolean headingToggles;
+    private boolean useBullet;
 
     private static final int EXPANSION_DURATION = 250;
     private static final int EXPANSION_RATE = 30;
 
     public TerraRollupSkin() {
         TerraTheme theme = (TerraTheme)Theme.getTheme();
+
         buttonColor = theme.getColor(9);
         spacing = 4;
         buffer = 4;
         justify = false;
-        firstChildToggles = true;
+        headingToggles = true;
+        useBullet = false;
     }
 
     @Override
@@ -275,30 +252,30 @@
         super.install(component);
 
         Rollup rollup = (Rollup)component;
-        rollup.getRollupListeners().add(this);
-
-        updateToggleComponent();
 
+        // Add the rollup button
         rollupButton = new RollupButton();
-        updateRollupButton();
         rollup.add(rollupButton);
-        rollupButton.getButtonPressListeners().add(this);
+
+        // Initialize state
+        headingChanged(rollup, null);
+        contentChanged(rollup, null);
     }
 
     @Override
     public void uninstall() {
         Rollup rollup = (Rollup)getComponent();
-        rollup.getRollupListeners().remove(this);
 
-        rollupButton.getButtonPressListeners().remove(this);
+        // Uninitialize state
+        Component heading = rollup.getHeading();
+        if (heading != null) {
+            heading.getComponentMouseButtonListeners().remove(headingMouseButtonHandler);
+        }
+
+        // Remove the rollup button
         rollup.remove(rollupButton);
         rollupButton = null;
 
-        if (toggleComponent != null) {
-            toggleComponent.getComponentMouseButtonListeners().remove(toggleComponentMouseHandler);
-            toggleComponent = null;
-        }
-
         super.uninstall();
     }
 
@@ -306,28 +283,17 @@
     public int getPreferredWidth(int height) {
         Rollup rollup = (Rollup)getComponent();
 
-        int preferredWidth = 0;
+        Component heading = rollup.getHeading();
+        Component content = rollup.getContent();
 
-        // Preferred width is the max of our childrens' preferred widths, plus
-        // the button width, buffer, and padding. If we're collapsed, we only
-        // look at the first child.
-        for (int i = 0, n = rollup.getLength(); i < n; i++) {
-            Component component = rollup.get(i);
-
-            if (component == rollupButton) {
-                // Ignore "private" component
-                continue;
-            }
+        int preferredWidth = 0;
 
-            if (component.isDisplayable()) {
-                int componentPreferredWidth = component.getPreferredWidth(-1);
-                preferredWidth = Math.max(preferredWidth, componentPreferredWidth);
-            }
+        if (heading != null) {
+            preferredWidth = heading.getPreferredWidth(-1);
+        }
 
-            if (!rollup.isExpanded()) {
-                // If we're collapsed, we only look at the first child.
-                break;
-            }
+        if (rollup.isExpanded() && content != null) {
+            preferredWidth = Math.max(preferredWidth, content.getPreferredWidth(-1));
         }
 
         preferredWidth += rollupButton.getPreferredWidth(-1) + buffer;
@@ -341,106 +307,99 @@
         return getPreferredHeight(width, rollup.isExpanded());
     }
 
+    /**
+     * Gets the preferred height of the rollup assuming the specified expansion
+     * state. We use this to transition from one expansion state to another
+     * because it allows us to have foreknowledge of the height value to which
+     * we are transitioning.
+     *
+     * @param width
+     * The width constraint.
+     *
+     * @param expanded
+     * The supposed expansion state.
+     */
     private int getPreferredHeight(int width, boolean expanded) {
         Rollup rollup = (Rollup)getComponent();
 
-        // Preferred height is the sum of our childrens' preferred heights,
-        // plus spacing and padding.
-        Dimensions rollupButtonPreferredSize = rollupButton.getPreferredSize();
-
-        if (justify
-            && width != -1) {
-            width = Math.max(width - rollupButtonPreferredSize.width - buffer, 0);
-        } else {
-            width = -1;
-        }
+        Component heading = rollup.getHeading();
+        Component content = rollup.getContent();
 
         int preferredHeight = 0;
 
-        int displayableComponentCount = 0;
-        for (int i = 0, n = rollup.getLength(); i < n; i++) {
-            Component component = rollup.get(i);
-
-            if (component == rollupButton) {
-                // Ignore "private" component
-                continue;
-            }
-
-            if (component.isDisplayable()) {
-                preferredHeight += component.getPreferredHeight(width);
-                displayableComponentCount++;
-            }
+        // Calculate our internal width constraint
+        if (justify && width >= 0) {
+            width = Math.max(width - rollupButton.getPreferredWidth(-1) - buffer, 0);
+        } else {
+            width = -1;
+        }
 
-            if (!expanded) {
-                // If we're collapsed, we only look at the first child.
-                break;
-            }
+        if (heading != null) {
+            preferredHeight += heading.getPreferredHeight(width);
         }
 
-        if (displayableComponentCount > 0) {
-            preferredHeight += (displayableComponentCount - 1) * spacing;
+        if (expanded && content != null) {
+            preferredHeight += spacing + content.getPreferredHeight(width);
         }
 
-        preferredHeight = Math.max(preferredHeight,
-            rollupButtonPreferredSize.height);
+        preferredHeight = Math.max(preferredHeight, rollupButton.getPreferredHeight(-1));
 
         return preferredHeight;
     }
 
     public void layout() {
         Rollup rollup = (Rollup)getComponent();
+
+        Component heading = rollup.getHeading();
+        Component content = rollup.getContent();
+
         Dimensions rollupButtonSize = rollupButton.getPreferredSize();
-        rollupButton.setSize(rollupButtonSize);
+        rollupButton.setSize(rollupButtonSize.width, rollupButtonSize.height);
 
         int x = rollupButtonSize.width + buffer;
         int y = 0;
         int justifiedWidth = Math.max(getWidth() - rollupButtonSize.width - buffer, 0);
 
-        Component firstComponent = null;
-
-        for (int i = 0, n = rollup.getLength(); i < n; i++) {
-            Component component = rollup.get(i);
-
-            if (component == rollupButton) {
-                // Ignore "private" component
-                continue;
+        if (heading != null) {
+            int headingWidth, headingHeight;
+            if (justify) {
+                headingWidth = justifiedWidth;
+                headingHeight = heading.getPreferredHeight(headingWidth);
+            } else {
+                Dimensions headingPreferredSize = heading.getPreferredSize();
+                headingWidth = headingPreferredSize.width;
+                headingHeight = headingPreferredSize.height;
             }
 
-            if (firstComponent == null) {
-                firstComponent = component;
-            }
+            heading.setVisible(true);
+            heading.setLocation(x, y);
+            heading.setSize(headingWidth, headingHeight);
 
-            if ((component == firstComponent
-                || rollup.isExpanded())
-                && component.isDisplayable()) {
-                // We lay this child out and make sure it's painted.
-                component.setVisible(true);
+            y += headingHeight + spacing;
+        }
 
-                int componentWidth, componentHeight;
+        if (content != null) {
+            if (rollup.isExpanded()) {
+                int contentWidth, contentHeight;
                 if (justify) {
-                    componentWidth = justifiedWidth;
-                    componentHeight = component.getPreferredHeight(componentWidth);
+                    contentWidth = justifiedWidth;
+                    contentHeight = content.getPreferredHeight(contentWidth);
                 } else {
-                    Dimensions componentPreferredSize = component.getPreferredSize();
-                    componentWidth = componentPreferredSize.width;
-                    componentHeight = componentPreferredSize.height;
+                    Dimensions contentPreferredSize = content.getPreferredSize();
+                    contentWidth = contentPreferredSize.width;
+                    contentHeight = contentPreferredSize.height;
                 }
 
-                component.setLocation(x, y);
-                component.setSize(componentWidth, componentHeight);
-
-                y += componentHeight + spacing;
+                content.setVisible(true);
+                content.setLocation(x, y);
+                content.setSize(contentWidth, contentHeight);
             } else {
-                // We make sure this child doesn't get painted.  There's also
-                // no need to lay the child out.
-                component.setVisible(false);
+                content.setVisible(false);
             }
         }
 
-        int rollupButtonY = (firstComponent == null) ?
-            0 : (firstComponent.getHeight() - rollupButtonSize.height) / 2 + 1;
-
-        rollupButton.setLocation(0, rollupButtonY);
+        y = (heading == null ? 0 : (heading.getHeight() - rollupButtonSize.height) / 2 + 1);
+        rollupButton.setLocation(0, y);
     }
 
     public Color getButtonColor() {
@@ -448,8 +407,12 @@
     }
 
     public void setButtonColor(Color buttonColor) {
+        if (buttonColor == null) {
+            throw new IllegalArgumentException("buttonColor is null.");
+        }
+
         this.buttonColor = buttonColor;
-        repaintComponent();
+        rollupButton.repaint();
     }
 
     public final void setButtonColor(String buttonColor) {
@@ -466,7 +429,11 @@
 
     public void setSpacing(int spacing) {
         this.spacing = spacing;
-        invalidateComponent();
+
+        Rollup rollup = (Rollup)getComponent();
+        if (rollup.isExpanded()) {
+            invalidateComponent();
+        }
     }
 
     public int getBuffer() {
@@ -487,89 +454,62 @@
         invalidateComponent();
     }
 
-    public boolean getFirstChildToggles() {
-        return firstChildToggles;
+    public boolean getHeadingToggles() {
+        return headingToggles;
     }
 
-    public void setFirstChildToggles(boolean firstChildToggles) {
-        this.firstChildToggles = firstChildToggles;
-        updateToggleComponent();
+    public void setHeadingToggles(boolean headingToggles) {
+        this.headingToggles = headingToggles;
     }
 
-    private void updateRollupButton() {
-        Rollup rollup = (Rollup)getComponent();
+    public boolean getUseBullet() {
+        return useBullet;
+    }
 
-        Image buttonData = null;
-        Cursor cursor = Cursor.HAND;
+    public void setUseBullet(boolean useBullet) {
+        this.useBullet = useBullet;
 
-        // Make sure to account for rollupButton
-        if (rollup.getLength() == 2) {
-            buttonData = bulletImage;
-            cursor = Cursor.DEFAULT;
-        } else if (rollup.isExpanded()) {
-            buttonData = collapseImage;
-        } else {
-            buttonData = expandImage;
+        Rollup rollup = (Rollup)getComponent();
+        if (rollup.getContent() == null) {
+            rollupButton.repaint();
         }
-
-        rollupButton.setButtonData(buttonData);
-        rollupButton.setCursor(cursor);
     }
 
-    private void updateToggleComponent() {
-        Rollup rollup = (Rollup)getComponent();
-        Component previousToggleComponent = toggleComponent;
-
-        toggleComponent = null;
-        if (firstChildToggles) {
-            for (int i = 0, n = rollup.getLength(); i < n; i++) {
-                Component child = rollup.get(i);
-                if (child != rollupButton) {
-                    toggleComponent = child;
-                    break;
-                }
-            }
-        }
+    // RollupListener methods
 
-        if (toggleComponent != null
-            && rollup.getLength() > 2) {
-            // TODO Record original cursor
-            toggleComponent.setCursor(Cursor.HAND);
+    @Override
+    public void headingChanged(Rollup rollup, Component previousHeading) {
+        if (previousHeading != null) {
+            previousHeading.getComponentMouseButtonListeners().remove(headingMouseButtonHandler);
         }
 
-        if (toggleComponent != previousToggleComponent) {
-            if (previousToggleComponent != null) {
-                // TODO Restore original cursor
-                previousToggleComponent.setCursor(Cursor.DEFAULT);
+        Component heading = rollup.getHeading();
 
-                previousToggleComponent.getComponentMouseButtonListeners().remove(toggleComponentMouseHandler);
-            }
-
-            if (toggleComponent != null) {
-                toggleComponent.getComponentMouseButtonListeners().add(toggleComponentMouseHandler);
-            }
+        if (heading != null) {
+            heading.getComponentMouseButtonListeners().add(headingMouseButtonHandler);
         }
-    }
 
-    // Container events
-    @Override
-    public void componentInserted(Container container, int index) {
-        super.componentInserted(container, index);
-
-        updateRollupButton();
-        updateToggleComponent();
+        invalidateComponent();
     }
 
     @Override
-    public void componentsRemoved(Container container, int index, Sequence<Component> components) {
-        super.componentsRemoved(container, index, components);
+    public void contentChanged(Rollup rollup, Component previousContent) {
+        if (rollup.getContent() == null && useBullet) {
+            rollupButton.setCursor(Cursor.DEFAULT);
+        } else {
+            rollupButton.setCursor(Cursor.HAND);
+        }
+
+        rollupButton.repaint();
 
-        updateRollupButton();
-        updateToggleComponent();
+        if (rollup.isExpanded()) {
+            invalidateComponent();
+        }
     }
 
-    // RollupListener methods
+    // RollupStateListener methods
 
+    @Override
     public Vote previewExpandedChange(final Rollup rollup) {
         Vote vote = Vote.APPROVE;
 
@@ -615,6 +555,7 @@
         return vote;
     }
 
+    @Override
     public void expandedChangeVetoed(Rollup rollup, Vote reason) {
         if (reason == Vote.DENY
             && collapseTransition != null) {
@@ -623,8 +564,8 @@
         }
     }
 
+    @Override
     public void expandedChanged(Rollup rollup) {
-        updateRollupButton();
         invalidateComponent();
 
         if (rollup.getDisplay() != null) {
@@ -643,11 +584,4 @@
             }
         }
     }
-
-    // ButtonPressListener methods
-
-    public void buttonPressed(Button button) {
-        Rollup rollup = (Rollup)getComponent();
-        rollup.setExpanded(!rollup.isExpanded());
-    }
 }