Modified bug museum to allow bugs to be sliced and diced more easily by 
   a) maintaining  separate bug lists and customizable Museum entry points
   b) adding a SuggestBox search ability
   c) mandating bug summaries.
   d) fixed css loading problem
   e) added ability to check DOM after widgets are created
Desk check by:jlabanca

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2518 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/common/AbstractIssue.java b/reference/code-museum/src/com/google/gwt/museum/client/common/AbstractIssue.java
new file mode 100644
index 0000000..fd002ce
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/client/common/AbstractIssue.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.museum.client.common;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.LinkElement;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * An abstract issue that can be used in the code museum. Each
+ * {@link AbstractIssue} should address a single issue. If at all possible, that
+ * issue should be obvious from the initial ui state.
+ */
+public abstract class AbstractIssue implements EntryPoint {
+  /**
+   * Headline for this issue.
+   */
+  private String headline;
+
+  /**
+   * Creates the css associated with this issue.
+   * 
+   * @return link with css
+   */
+  public LinkElement createCSS() {
+    String cssName;
+    if (hasCSS()) {
+      // Fetch the associated style sheet using an HTTP request
+      cssName = getClass().getName();
+      cssName = cssName.substring(cssName.lastIndexOf(".") + 1);
+    } else {
+      cssName = "Default";
+    }
+
+    LinkElement issueLinkElement = Document.get().createLinkElement();
+    issueLinkElement.setRel("stylesheet");
+    issueLinkElement.setType("text/css");
+    issueLinkElement.setHref("issues/" + cssName + ".css");
+    return issueLinkElement;
+  }
+
+  /**
+   * <p>
+   * Create a widget that illustrates the issue. Each issue should include a
+   * detailed description of the expected results and the observed results
+   * before the issue was fixed.
+   * </p>
+   * <p>
+   * Note that createIssue will may be called multiple times if the user
+   * refreshes the issue. If you save state within the instance, you must clear
+   * it out and reset the issue when createIssue is called again.
+   * </p>
+   * 
+   * @return a widget that can reproduce the issue
+   */
+  public abstract Widget createIssue();
+
+  /**
+   * Returns the "<i>classname</i>: summary".
+   * 
+   * @return a short summary of the issue, including the class name
+   */
+  public final String getHeadline() {
+    if (headline == null) {
+      String className = getClass().getName();
+      headline = className.substring(className.lastIndexOf(".") + 1) + ": "
+          + getSummary();
+    }
+    return headline;
+  }
+
+  /**
+   * Return a detailed description of what the user should expect to see. The
+   * description will be added above the example. You can also include
+   * instructions to reproduce the issue.
+   * 
+   * @return instructions explaining what the user should see
+   */
+  public abstract String getInstructions();
+
+  /**
+   * Gets the summary for this test. All tests should include a summary so users
+   * can scan through them quickly.
+   */
+  public abstract String getSummary();
+
+  /**
+   * Does the test have css?
+   * 
+   * @return true to load a CSS file of the same name, placed in the issues
+   *         directory
+   */
+  public abstract boolean hasCSS();
+
+  /**
+   * Called immediately after the widget is attached.
+   */
+  public void onAttached() {
+    // By default do nothing.
+  }
+
+  public void onModuleLoad() {
+    if (hasCSS()) {
+      Utility.getHeadElement().appendChild(createCSS());
+    }
+
+    Window.setTitle(getHeadline());
+    RootPanel.get().add(new HTML(getInstructions()));
+    RootPanel.get().add(createIssue());
+    onAttached();
+  }
+
+}
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/common/Utility.java b/reference/code-museum/src/com/google/gwt/museum/client/common/Utility.java
new file mode 100644
index 0000000..c4d4093
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/client/common/Utility.java
@@ -0,0 +1,33 @@
+/*

+ * Copyright 2008 Google Inc.

+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not

+ * use this file except in compliance with the License. You may obtain a copy of

+ * the License at

+ * 

+ * http://www.apache.org/licenses/LICENSE-2.0

+ * 

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the

+ * License for the specific language governing permissions and limitations under

+ * the License.

+ */

+

+package com.google.gwt.museum.client.common;

+

+import com.google.gwt.dom.client.HeadElement;

+

+/**

+ * Utility helper methods.

+ */

+public class Utility {

+  /**

+   * Convenience method for getting the document's head element.

+   * 

+   * @return the document's head element

+   */

+  public static native HeadElement getHeadElement() /*-{

+    return $doc.getElementsByTagName("head")[0];

+  }-*/;

+}

diff --git a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/DefaultMuseum.java b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/DefaultMuseum.java
new file mode 100644
index 0000000..6484eab
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/DefaultMuseum.java
@@ -0,0 +1,32 @@
+/*

+ * Copyright 2008 Google Inc.

+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not

+ * use this file except in compliance with the License. You may obtain a copy of

+ * the License at

+ * 

+ * http://www.apache.org/licenses/LICENSE-2.0

+ * 

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the

+ * License for the specific language governing permissions and limitations under

+ * the License.

+ */

+

+package com.google.gwt.museum.client.defaultmuseum;

+

+import com.google.gwt.core.client.EntryPoint;

+import com.google.gwt.museum.client.viewer.Museum;

+

+/**

+ * Default bug museum. Contains a list of all GWT issues reported in the system

+ * to date.

+ */

+public class DefaultMuseum extends Museum implements EntryPoint {

+  public DefaultMuseum() {

+    addIssue(new Issue2290());

+    addIssue(new Issue2307());

+    addIssue(new Issue2321());

+  }

+}

diff --git a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue2290.java b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue2290.java
new file mode 100644
index 0000000..8a0e016
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue2290.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.museum.client.defaultmuseum;
+
+import com.google.gwt.museum.client.common.AbstractIssue;
+import com.google.gwt.user.client.ui.Tree;
+import com.google.gwt.user.client.ui.TreeItem;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * <h1> gwt-TreeItem refers to the wrong element in TreeItem </h1>
+ * 
+ * <p>
+ * gwt-TreeItem used to refer to the span that directly wrapped the text in a
+ * TreeItem. Now it refers to the table element that holds the expand/collapse
+ * image and the text. gwt-TreeItem-selected is still added to the span, so
+ * there is an inconsistency here.
+ * </p>
+ */
+public class Issue2290 extends AbstractIssue {
+
+  @Override
+  public Widget createIssue() {
+    Tree tree = new Tree();
+    TreeItem root = tree.addItem("Root Item");
+    root.addItem("Item1");
+    root.addItem("Item2");
+    root.addItem("Item3");
+    root.addItem("Item4");
+
+    root.setState(true);
+    tree.setSelectedItem(root);
+
+    return tree;
+  }
+
+  @Override
+  public String getInstructions() {
+    return "The background of the Root Item, when selected, should be "
+        + "completely red, with no visible green.";
+  }
+
+  @Override
+  public String getSummary() {
+    return "Tree background test";
+  }
+
+  @Override
+  public boolean hasCSS() {
+    return true;
+  }
+}
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue2307.java b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue2307.java
new file mode 100644
index 0000000..10cb529
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue2307.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.museum.client.defaultmuseum;
+
+import com.google.gwt.museum.client.common.AbstractIssue;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.CaptionPanel;
+import com.google.gwt.user.client.ui.ClickListener;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * <h1>Normalize design of CaptionPanel</h1>
+ * 
+ * <p>
+ * Methods should be set/getCaptionHTML, set/getCaptionText,
+ * set/getContentWidget. Write lots of unit tests.
+ * </p>
+ */
+public class Issue2307 extends AbstractIssue {
+
+  private CaptionPanel captionPanel;
+
+  /**
+   * A set of options used to set the caption and content in the caption panel.
+   */
+  private class ControlPanel extends Composite {
+    private final Grid grid = new Grid(3, 2);
+
+    public ControlPanel() {
+      initWidget(grid);
+
+      // Add option to set the text
+      final TextBox textBox = new TextBox();
+      textBox.setText("<b>CaptionPanel</b>");
+      grid.setWidget(0, 1, textBox);
+      grid.setWidget(0, 0, new Button("setCaptionText", new ClickListener() {
+        public void onClick(Widget sender) {
+          captionPanel.setCaptionText(textBox.getText());
+        }
+      }));
+
+      // Add option to set the html
+      final TextBox htmlBox = new TextBox();
+      htmlBox.setText("<b>CaptionPanel</b>");
+      grid.setWidget(1, 1, htmlBox);
+      grid.setWidget(1, 0, new Button("setCaptionHTML", new ClickListener() {
+        public void onClick(Widget sender) {
+          captionPanel.setCaptionHTML(htmlBox.getText());
+        }
+      }));
+
+      // Add option to set the content
+      final TextBox contentBox = new TextBox();
+      contentBox.setText("<b><i>I am a Button</i></b>");
+      grid.setWidget(2, 1, contentBox);
+      grid.setWidget(2, 0, new Button("setContentWidget", new ClickListener() {
+        public void onClick(Widget sender) {
+          captionPanel.setContentWidget(new Button(contentBox.getText()));
+        }
+      }));
+    }
+  }
+
+  @Override
+  public Widget createIssue() {
+    captionPanel = new CaptionPanel("CaptionPanel");
+    VerticalPanel p = new VerticalPanel();
+    p.setSpacing(6);
+    p.add(captionPanel);
+    p.add(new ControlPanel());
+    return p;
+  }
+
+  @Override
+  public String getInstructions() {
+    return "Verify the usage of various CaptionPanel methods.";
+  }
+
+  @Override
+  public String getSummary() {
+    return "CaptionPanel tests";
+  }
+
+  @Override
+  public boolean hasCSS() {
+    return false;
+  }
+}
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue2321.java b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue2321.java
new file mode 100644
index 0000000..6e86956
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/client/defaultmuseum/Issue2321.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.museum.client.defaultmuseum;
+
+import com.google.gwt.museum.client.common.AbstractIssue;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.ClickListener;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.DeckPanel;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * <h1>DeckPanel children getOffsetWidth/Height() return 0 after r2060</h1>
+ * 
+ * <p>
+ * Child widgets of DeckPanel used to be able to call getOffsetWidth/Height() in
+ * onLoad() to obtain the widget's offset dimensions. These methods now return 0
+ * (zero) in onLoad(), and are only correct later, e.g. in a deferred command.
+ * </p>
+ */
+public class Issue2321 extends AbstractIssue {
+  /**
+   * A set of options used to set the caption and content in the caption panel.
+   */
+  private class ControlPanel extends Composite {
+    private final Grid grid = new Grid(3, 2);
+
+    public ControlPanel() {
+      initWidget(grid);
+
+      // Add option to detach the deck panel
+      Button addWidgetButton = new Button("Add widget", new ClickListener() {
+        public void onClick(Widget sender) {
+          addWidgetToDeckPanel();
+        }
+      });
+      grid.setWidget(0, 0, addWidgetButton);
+
+      // Add option to retrieve the dimensions of the content
+      Button updateDimButton = new Button("Get Current Dimensions",
+          new ClickListener() {
+            public void onClick(Widget sender) {
+              updateContentDimensions();
+            }
+          });
+      grid.setWidget(0, 1, updateDimButton);
+
+      // Add labels for the content height and width
+      grid.setHTML(1, 0, "Content Height:");
+      grid.setHTML(2, 0, "Content Width:");
+    }
+
+    /**
+     * Add another widget to the deck panel.
+     */
+    public void addWidgetToDeckPanel() {
+      int numWidgets = deck.getWidgetCount();
+      HTML content = new HTML("Content " + numWidgets) {
+        @Override
+        protected void onLoad() {
+          updateContentDimensions();
+        }
+      };
+
+      content.setStylePrimaryName("deckPanel-content");
+      deck.add(content);
+      deck.showWidget(numWidgets);
+    }
+
+    /**
+     * Retrieve the size of the content widgets.
+     */
+    public void updateContentDimensions() {
+      Widget content = deck.getWidget(deck.getWidgetCount() - 1);
+      grid.setHTML(1, 1, content.getOffsetHeight() + "");
+      grid.setHTML(2, 1, content.getOffsetWidth() + "");
+    }
+  }
+
+  /**
+   * The options panel to control this test.
+   */
+  private ControlPanel controlPanel = new ControlPanel();
+
+  /**
+   * The {@link DeckPanel} to be tested.
+   */
+  private DeckPanel deck;
+
+  /**
+   * The main container that holds the control panel and deck panel.
+   */
+  private VerticalPanel vPanel;
+
+  @Override
+  public Widget createIssue() {
+    // Create the deck panel and grab the size of the contents on load
+    deck = new DeckPanel() {
+      @Override
+      protected void onLoad() {
+        controlPanel.addWidgetToDeckPanel();
+      }
+    };
+    deck.setStylePrimaryName("deckPanel");
+
+    // Combine the control panel and DeckPanel
+    vPanel = new VerticalPanel();
+    vPanel.add(controlPanel);
+    vPanel.add(deck);
+    return vPanel;
+  }
+
+  @Override
+  public String getInstructions() {
+    return "The height and width of the content should be greater than 0.";
+  }
+
+  @Override
+  public String getSummary() {
+    return "DeckPanel should return content dimensions onLoad";
+  }
+
+  @Override
+  public boolean hasCSS() {
+    return true;
+  }
+}
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/viewer/Museum.java b/reference/code-museum/src/com/google/gwt/museum/client/viewer/Museum.java
new file mode 100644
index 0000000..fa258de
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/client/viewer/Museum.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.museum.client.viewer;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.LinkElement;
+import com.google.gwt.museum.client.common.AbstractIssue;
+import com.google.gwt.museum.client.common.Utility;
+import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.IncrementalCommand;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.AbstractImagePrototype;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.ChangeListener;
+import com.google.gwt.user.client.ui.ClickListener;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HasVerticalAlignment;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.ImageBundle;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.MultiWordSuggestOracle;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.SuggestBox;
+import com.google.gwt.user.client.ui.SuggestOracle;
+import com.google.gwt.user.client.ui.SuggestionEvent;
+import com.google.gwt.user.client.ui.SuggestionHandler;
+import com.google.gwt.user.client.ui.Widget;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+/**
+ * A repository for demonstrating bugs we once faced.
+ */
+public class Museum implements EntryPoint {
+  /**
+   * Images used in the museum.
+   */
+  public static interface MuseumImages extends ImageBundle {
+    AbstractImagePrototype nextButton();
+
+    AbstractImagePrototype prevButton();
+  }
+
+  /**
+   * Class used to implement a callback when a css file is loaded. We add a
+   * width style of 10px to isLoaded classes, then test to see if the style is
+   * active.
+   */
+  class Poller implements IncrementalCommand {
+    private HTML test;
+    private AbstractIssue issue;
+    private SimplePanel monitor = new SimplePanel();
+
+    public Poller() {
+      RootPanel.get().add(monitor);
+    }
+
+    public boolean execute() {
+      if (test.getOffsetWidth() == 10) {
+        issueContainer.setWidget(issue.createIssue());
+        issue.onAttached();
+        return false;
+      } else {
+        return true;
+      }
+    }
+
+    public void startPolling(AbstractIssue issue) {
+      test = new HTML();
+      test.setStyleName("isLoaded");
+      monitor.setWidget(test);
+      this.issue = issue;
+      DeferredCommand.addCommand(this);
+    }
+  }
+
+  /**
+   * The images used in this application.
+   */
+  public static final MuseumImages IMAGES = GWT.create(MuseumImages.class);
+
+  /**
+   * A reference for all issues.
+   */
+  private final ArrayList<AbstractIssue> issues = new ArrayList<AbstractIssue>();
+
+  /**
+   * Add an issue to the list of issues.
+   * 
+   * @param issue the issue to add
+   */
+
+  private final Poller poller = new Poller();
+
+  /**
+   * The panel that contains the current example.
+   */
+  private final SimplePanel issueContainer = new SimplePanel();
+
+  /**
+   * A container to hold the CSS that will be applied to the issue.
+   */
+  private LinkElement issueLinkElement = null;
+
+  /**
+   * A description of the current issue.
+   */
+  private final HTML issueDescription = new HTML();
+
+  /**
+   * The list of all issues.
+   */
+  private final ListBox issueList = new ListBox();
+
+  /**
+   * Add an issue to the museum. Should be called in the inherited constructor.
+   * 
+   * @param issue the issue to be added
+   */
+  public void addIssue(AbstractIssue issue) {
+    issues.add(issue);
+  }
+
+  public void onModuleLoad() {
+
+    // Add the options and issue containers to the page
+    RootPanel.get().add(createOptionsPanel());
+    RootPanel.get().add(issueDescription);
+    issueDescription.setStylePrimaryName("museum-issueDescription");
+    RootPanel.get().add(issueContainer);
+    issueContainer.setStylePrimaryName("museum-issueContainer");
+
+    // Default to the first issue
+    refreshIssue();
+  }
+
+  /**
+   * Create a suggest box with all current suggestions in it.
+   */
+  private SuggestBox createIssueFinder() {
+    class IssueSuggestion extends MultiWordSuggestOracle.MultiWordSuggestion {
+      private AbstractIssue issue;
+
+      public IssueSuggestion(AbstractIssue issue) {
+        super("", issue.getHeadline());
+        this.issue = issue;
+      }
+
+      public AbstractIssue getIssue() {
+        return issue;
+      }
+    }
+
+    SuggestOracle oracle = new SuggestOracle() {
+
+      @Override
+      public void requestSuggestions(Request request, Callback callback) {
+        String ofInterest = ("(.* )*" + request.getQuery() + ".*").toLowerCase();
+        ArrayList<Suggestion> suggestions = new ArrayList<Suggestion>();
+        HashSet s = new HashSet();
+        for (AbstractIssue issue : issues) {
+          if (issue.getHeadline().toLowerCase().matches(ofInterest)) {
+            s.add(issue);
+            suggestions.add(new IssueSuggestion(issue));
+          }
+        }
+
+        for (AbstractIssue issue : issues) {
+          if (!s.contains(issue) && issue.getInstructions().matches(ofInterest)) {
+            suggestions.add(new IssueSuggestion(issue));
+          }
+        }
+        callback.onSuggestionsReady(request, new Response(suggestions));
+      }
+
+    };
+
+    SuggestBox box = new SuggestBox(oracle);
+    box.addEventHandler(new SuggestionHandler() {
+      public void onSuggestionSelected(SuggestionEvent event) {
+        AbstractIssue issue = ((IssueSuggestion) event.getSelectedSuggestion()).getIssue();
+        int index = issues.indexOf(issue);
+        issueList.setSelectedIndex(index);
+        refreshIssue();
+      }
+
+    });
+    box.setAnimationEnabled(false);
+    return box;
+  }
+
+  /**
+   * Create the options panel.
+   * 
+   * @return the options panel
+   */
+  private Widget createOptionsPanel() {
+    // Populate a list box containing all issues
+    for (AbstractIssue issue : issues) {
+      issueList.addItem(issue.getHeadline());
+    }
+    issueList.addChangeListener(new ChangeListener() {
+      public void onChange(Widget sender) {
+        refreshIssue();
+      }
+    });
+
+    // Create a button to refresh the current issue
+    Button refreshIssueButton = new Button("Refresh", new ClickListener() {
+      public void onClick(Widget sender) {
+        refreshIssue();
+      }
+    });
+
+    // Create a suggest box to search for issues
+    SuggestBox suggestBox = createIssueFinder();
+
+    // Create a button to move to the previous issue
+    Image prevButton = IMAGES.prevButton().createImage();
+    prevButton.setStyleName("prevButton");
+    prevButton.addClickListener(new ClickListener() {
+      public void onClick(Widget sender) {
+        int selectedIndex = issueList.getSelectedIndex();
+        if (selectedIndex > 0) {
+          issueList.setSelectedIndex(selectedIndex - 1);
+          refreshIssue();
+        } else {
+          Window.alert("You are already on the first issue");
+        }
+      }
+    });
+
+    // Create a button to move to the next issue
+    Image nextButton = IMAGES.nextButton().createImage();
+    nextButton.setStyleName("nextButton");
+    nextButton.addClickListener(new ClickListener() {
+      public void onClick(Widget sender) {
+        int selectedIndex = issueList.getSelectedIndex();
+        if (selectedIndex < issueList.getItemCount() - 1) {
+          issueList.setSelectedIndex(selectedIndex + 1);
+          refreshIssue();
+        } else {
+          Window.alert("You are already on the last issue");
+        }
+      }
+    });
+
+    // Combine the list box and text into a panel
+    HorizontalPanel hPanel = new HorizontalPanel();
+    hPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
+    hPanel.add(new HTML("Select an issue: "));
+    hPanel.add(issueList);
+    hPanel.add(prevButton);
+    hPanel.add(nextButton);
+    hPanel.add(refreshIssueButton);
+    hPanel.add(suggestBox);
+    SimplePanel wrapper = new SimplePanel();
+    wrapper.setStyleName("museum-optionsPanel");
+    wrapper.setWidget(hPanel);
+    return wrapper;
+  }
+
+  /**
+   * Refresh the current issue in the issue list.
+   */
+  private void refreshIssue() {
+    setIssue(issues.get(issueList.getSelectedIndex()));
+  }
+
+  /**
+   * Set the current issue in the issue container.
+   * 
+   * @param issue the issue to set
+   */
+  private void setIssue(AbstractIssue issue) {
+    if (issueLinkElement != null) {
+      Utility.getHeadElement().removeChild(issueLinkElement);
+      issueLinkElement = null;
+    }
+    // Fetch the associated style sheet using an HTTP request
+    issueLinkElement = issue.createCSS();
+    Utility.getHeadElement().appendChild(issueLinkElement);
+    issueDescription.setHTML(issue.getInstructions());
+    poller.startPolling(issue);
+  }
+}
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/viewer/nextButton.png b/reference/code-museum/src/com/google/gwt/museum/client/viewer/nextButton.png
new file mode 100644
index 0000000..5a9cdf0
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/client/viewer/nextButton.png
Binary files differ
diff --git a/reference/code-museum/src/com/google/gwt/museum/client/viewer/prevButton.png b/reference/code-museum/src/com/google/gwt/museum/client/viewer/prevButton.png
new file mode 100644
index 0000000..289b0d4
--- /dev/null
+++ b/reference/code-museum/src/com/google/gwt/museum/client/viewer/prevButton.png
Binary files differ