Mail sample app to work on selection concepts


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7809 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/ButtonCell.java b/bikeshed/src/com/google/gwt/bikeshed/cells/client/ButtonCell.java
index d318e2f..129966b 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/ButtonCell.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/cells/client/ButtonCell.java
@@ -22,6 +22,11 @@
  * A {@link Cell} used to render a button.
  */
 public class ButtonCell extends Cell<String, Void> {
+  
+  @Override
+  public boolean consumesEvents() {
+    return true;
+  }
 
   @Override
   public Void onBrowserEvent(Element parent, String value, Void viewData,
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/Cell.java b/bikeshed/src/com/google/gwt/bikeshed/cells/client/Cell.java
index 44ed5fe..8d4d795 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/Cell.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/cells/client/Cell.java
@@ -27,6 +27,14 @@
 public abstract class Cell<C, V> {
 
   /**
+   * Returns true if the cell is interested in browser events. The default
+   * implementation returns false.
+   */
+  public boolean consumesEvents() {
+    return false;
+  }
+
+  /**
    * Handle a browser event that took place within the cell. The default
    * implementation returns null.
    * 
@@ -43,8 +51,8 @@
   }
 
   /**
-   * Render a cell as HTML into a StringBuilder, suitable for passing
-   * to setInnerHTML on a container element.
+   * Render a cell as HTML into a StringBuilder, suitable for passing to
+   * {@link Element#setInnerHTML} on a container element.
    * 
    * @param value the cell value to be rendered
    * @param viewData view data associated with the cell
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/CheckboxCell.java b/bikeshed/src/com/google/gwt/bikeshed/cells/client/CheckboxCell.java
index f0accb1..0a157e0 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/CheckboxCell.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/cells/client/CheckboxCell.java
@@ -23,6 +23,11 @@
  * A {@link Cell} used to render a checkbox.
  */
 public class CheckboxCell extends Cell<Boolean, Void> {
+  
+  @Override
+  public boolean consumesEvents() {
+    return true;
+  }
 
   @Override
   public Void onBrowserEvent(Element parent, Boolean value, Void viewData,
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/TextInputCell.java b/bikeshed/src/com/google/gwt/bikeshed/cells/client/TextInputCell.java
index 4a7e5bd..6846084 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/TextInputCell.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/cells/client/TextInputCell.java
@@ -23,6 +23,11 @@
  * A {@link Cell} used to render a text input.
  */
 public class TextInputCell extends Cell<String, Void> {
+  
+  @Override
+  public boolean consumesEvents() {
+    return true;
+  }
 
   @Override
   public Void onBrowserEvent(Element parent, String value, Void viewData,
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/PagingTableListView.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/PagingTableListView.java
index 3a189aa..915e404 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/PagingTableListView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/PagingTableListView.java
@@ -19,6 +19,7 @@
 import com.google.gwt.bikeshed.list.shared.ListHandler;
 import com.google.gwt.bikeshed.list.shared.ListModel;
 import com.google.gwt.bikeshed.list.shared.ListRegistration;
+import com.google.gwt.bikeshed.list.shared.SelectionModel;
 import com.google.gwt.bikeshed.list.shared.SizeChangeEvent;
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
@@ -45,21 +46,23 @@
 public class PagingTableListView<T> extends Widget {
 
   protected int curPage;
-  private int pageSize;
-  private int numPages;
-  private ListRegistration listReg;
-  private int totalSize;
   private List<Column<T, ?, ?>> columns = new ArrayList<Column<T, ?, ?>>();
   private ArrayList<T> data = new ArrayList<T>();
-
-  private List<Header<?>> headers = new ArrayList<Header<?>>();
   private List<Header<?>> footers = new ArrayList<Header<?>>();
+  private List<Header<?>> headers = new ArrayList<Header<?>>();
+  private ListRegistration listReg;
+  private int numPages;
+
+  private int pageSize;
+  private SelectionModel<T> selectionModel;
 
   private TableElement table;
-  private TableSectionElement thead;
-  private TableSectionElement tfoot;
-  private TableSectionElement tbody;
 
+  private TableSectionElement tbody;
+  private TableSectionElement tfoot;
+  private TableSectionElement thead;
+  private int totalSize;
+  
   public PagingTableListView(ListModel<T> listModel, final int pageSize) {
     this.pageSize = pageSize;
     setElement(table = Document.get().createTableElement());
@@ -149,6 +152,7 @@
       int row = tr.getSectionRowIndex();
       T value = data.get(row);
       Column<T, ?, ?> column = columns.get(col);
+
       column.onBrowserEvent(cell, curPage * pageSize + row, value, event);
     }
   }
@@ -156,6 +160,11 @@
   public void previousPage() {
     setPage(curPage - 1);
   }
+  
+  public void refresh() {
+    listReg.setRangeOfInterest(curPage * pageSize, pageSize);
+    updateRowVisibility();
+  }
 
   /**
    * Set the current visible page.
@@ -192,6 +201,10 @@
     setPage(curPage);
   }
 
+  public void setSelectionModel(SelectionModel<T> selectionModel) {
+    this.selectionModel = selectionModel;
+  }
+
   protected void render(int start, int length, List<T> values) {
     int numCols = columns.size();
     int pageStart = curPage * pageSize;
@@ -200,6 +213,12 @@
     for (int r = start; r < start + length; ++r) {
       TableRowElement row = rows.getItem(r - pageStart);
       T q = values.get(r - start);
+      if (selectionModel != null && selectionModel.isSelected(q)) {
+        row.setClassName("pagingTableListView selected");
+      } else {
+        row.setClassName("pagingTableListView " +
+            (((r - pageStart) & 0x1) == 0 ? "evenRow" : "oddRow"));
+      }
 
       data.set(r - pageStart, q);
       for (int c = 0; c < numCols; ++c) {
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/shared/SelectionModel.java b/bikeshed/src/com/google/gwt/bikeshed/list/shared/SelectionModel.java
new file mode 100644
index 0000000..2dfece3
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/shared/SelectionModel.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.bikeshed.list.shared;
+
+/**
+ * A model for selection within a list.
+ * 
+ * @param <T> the data type of records in the list
+ */
+public interface SelectionModel<T> {
+
+  boolean isSelected(T object);
+
+  void setSelected(T object, boolean selected);
+}
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/mail/MailSample.gwt.xml b/bikeshed/src/com/google/gwt/sample/bikeshed/mail/MailSample.gwt.xml
new file mode 100644
index 0000000..19da671
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/mail/MailSample.gwt.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 0.0.999//EN" "http://google-web-toolkit.googlecode.com/svn/tags/0.0.999/distro-source/core/src/gwt-module.dtd">
+<module rename-to='mail'>
+  <!-- Inherit the core Web Toolkit stuff.                        -->
+  <inherits name='com.google.gwt.user.User'/>
+  <inherits name='com.google.gwt.bikeshed.list.List'/>
+  <inherits name='com.google.gwt.bikeshed.tree.Tree'/>
+
+  <!-- Inherit the default GWT style sheet.  You can change       -->
+  <!-- the theme of your GWT application by uncommenting          -->
+  <!-- any one of the following lines.                            -->
+  <inherits name='com.google.gwt.user.theme.standard.Standard'/>
+  <!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
+  <!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/>     -->
+
+  <!-- Other module inherits                                      -->
+
+  <!-- Specify the app entry point class.                         -->
+  <entry-point class='com.google.gwt.sample.bikeshed.mail.client.MailSample'/>
+
+  <!-- Specify the paths for translatable code                    -->
+  <source path='client'/>
+  <source path='shared'/>
+
+</module>
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/mail/client/MailSample.java b/bikeshed/src/com/google/gwt/sample/bikeshed/mail/client/MailSample.java
new file mode 100644
index 0000000..cde957f
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/mail/client/MailSample.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.bikeshed.mail.client;
+
+import com.google.gwt.bikeshed.cells.client.CheckboxCell;
+import com.google.gwt.bikeshed.cells.client.FieldUpdater;
+import com.google.gwt.bikeshed.cells.client.TextCell;
+import com.google.gwt.bikeshed.list.client.Column;
+import com.google.gwt.bikeshed.list.client.Header;
+import com.google.gwt.bikeshed.list.client.PagingTableListView;
+import com.google.gwt.bikeshed.list.shared.ListListModel;
+import com.google.gwt.bikeshed.list.shared.SelectionModel;
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.user.client.ui.RootPanel;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * A demo of selection features.
+ */
+public class MailSample implements EntryPoint {
+
+  class Message {
+    int id;
+    boolean isRead;
+    boolean isSelected;
+    String sender;
+    String subject;
+
+    public Message(int id, String sender, String subject) {
+      super();
+      this.id = id;
+      this.sender = sender;
+      this.subject = subject;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (!(obj instanceof Message)) {
+        return false;
+      }
+      return id == ((Message) obj).id;
+    }
+
+    public int getId() {
+      return id;
+    }
+
+    public String getSender() {
+      return sender;
+    }
+
+    public String getSubject() {
+      return subject;
+    }
+
+    @Override
+    public int hashCode() {
+      return id;
+    }
+
+    public boolean isRead() {
+      return isRead;
+    }
+
+    public boolean isSelected() {
+      return isSelected;
+    }
+
+    @Override
+    public String toString() {
+      return "Message [id=" + id + ", isSelected=" + isSelected + ", sender="
+          + sender + ", subject=" + subject + ", read=" + isRead + "]";
+    }
+  }
+
+  class MailSelectionModel implements SelectionModel<Message> {
+    private Set<Integer> plusExceptions = new HashSet<Integer>();
+    private Set<Integer> minusExceptions = new HashSet<Integer>();
+
+    private boolean isDefaultSelected(Message object) {
+      return object.isRead();
+    }
+
+    public boolean isSelected(Message object) {
+      return (isDefaultSelected(object) || plusExceptions.contains(object.id))
+          && !minusExceptions.contains(object.id);
+    }
+
+    public void setSelected(Message object, boolean selected) {
+      if (!selected) {
+        minusExceptions.add(object.id);
+        plusExceptions.remove(object.id);
+      } else {
+        minusExceptions.remove(object.id);
+        plusExceptions.add(object.id);
+      }
+    }
+  }
+
+  private static final String[] senders = {
+      "test@example.com", "spam1@spam.com", "gwt@google.com"};
+
+  private static final String[] subjects = {
+      "GWT rocks", "What's a widget?", "Money in Nigeria"};
+
+  public void onModuleLoad() {
+    TextCell textCell = new TextCell();
+
+    ListListModel<Message> listModel = new ListListModel<Message>();
+    List<Message> messages = listModel.getList();
+    Random rand = new Random();
+    for (int i = 0; i < 1000; i++) {
+      Message message = new Message(i, senders[rand.nextInt(senders.length)],
+          subjects[rand.nextInt(subjects.length)]);
+      message.isRead = rand.nextBoolean();
+      messages.add(message);
+    }
+
+    final SelectionModel<Message> selectionModel = new MailSelectionModel();
+    
+    final PagingTableListView<Message> table =
+      new PagingTableListView<Message>(listModel, 10);
+
+    Column<Message, Boolean, Void> selectedColumn = new Column<Message, Boolean, Void>(
+        new CheckboxCell()) {
+      @Override
+      public Boolean getValue(Message object) {
+        return selectionModel.isSelected(object);
+      }
+    };
+    selectedColumn.setFieldUpdater(new FieldUpdater<Message, Boolean, Void>() {
+      public void update(int index, Message object, Boolean value, Void viewData) {
+        selectionModel.setSelected(object, value);
+        table.refresh(); // TODO - remove
+      }
+    });
+    Header<String> selectedHeader = new Header<String>(textCell);
+    selectedHeader.setValue("Selected");
+    table.addColumn(selectedColumn, selectedHeader);
+
+    Column<Message, String, Void> isReadColumn =
+      new Column<Message, String, Void>(textCell) {
+      @Override
+      public String getValue(Message object) {
+        return object.isRead ? "read" : "unread";
+      }
+    };
+    Header<String> isReadHeader = new Header<String>(textCell);
+    isReadHeader.setValue("Read");
+    table.addColumn(isReadColumn, isReadHeader);
+
+    Column<Message, String, Void> senderColumn = new Column<Message, String, Void>(
+        new TextCell()) {
+      @Override
+      public String getValue(Message object) {
+        return object.getSender();
+      }
+    };
+    Header<String> senderHeader = new Header<String>(textCell);
+    senderHeader.setValue("Sender");
+    table.addColumn(senderColumn, senderHeader);
+
+    Column<Message, String, Void> subjectColumn = new Column<Message, String, Void>(
+        textCell) {
+      @Override
+      public String getValue(Message object) {
+        return object.getSubject();
+      }
+    };
+    Header<String> subjectHeader = new Header<String>(textCell);
+    subjectHeader.setValue("Subject");
+    table.addColumn(subjectColumn, subjectHeader);
+
+    table.setSelectionModel(selectionModel);
+
+    RootPanel.get().add(table);
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/validation/client/ValidatableInputCell.java b/bikeshed/src/com/google/gwt/sample/bikeshed/validation/client/ValidatableInputCell.java
index 344cfb1..99e5f77 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/validation/client/ValidatableInputCell.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/validation/client/ValidatableInputCell.java
@@ -29,6 +29,11 @@
 public class ValidatableInputCell extends Cell<String, ValidatableField<String>> {
 
   @Override
+  public boolean consumesEvents() {
+    return true;
+  }
+
+  @Override
   public ValidatableField<String> onBrowserEvent(Element parent, String value, 
       ValidatableField<String> viewData, NativeEvent event,
       ValueUpdater<String, ValidatableField<String>> valueUpdater) {
diff --git a/bikeshed/war/Mail.css b/bikeshed/war/Mail.css
new file mode 100644
index 0000000..7856cdc
--- /dev/null
+++ b/bikeshed/war/Mail.css
@@ -0,0 +1,109 @@
+body {
+  background: url(bg.png) repeat-x;
+  background-color: rgb(216, 220, 224);
+  color: black;
+  margin: 8px;
+  margin-top: 3px;
+}
+
+body, table {
+  font-family: Helvetica Neue Light, Helvetica, Arial, sans-serif; */
+  font-weight: light;
+  font-size: small;
+}
+
+tr.selected {
+  background-color: rgb(56, 117, 215);
+}
+
+tr.evenRow {
+  background-color: rgb(255, 255, 255);
+}
+
+tr.oddRow {
+  background-color: rgb(220, 220, 220);
+}
+
+div.gwt-sstree-column {
+  overflow-y: scroll;
+  overflow-x: auto;
+  position: relative;
+}
+
+div.gwt-sstree {
+}
+
+div.gwt-sstree-selectedItem {
+  background-color: rgb(56, 117, 215);
+}
+
+div.gwt-sstree-evenRow {
+  background-color: rgb(255, 255, 255);
+}
+
+div.gwt-sstree-oddRow {
+  background-color: rgb(220, 220, 220);
+}
+
+/** Example rules used by the template application (remove for your app) */
+h1 {
+  font-size: 2em;
+  font-weight: bold;
+  color: #777777;
+  margin: 40px 0px 70px;
+  text-align: center;
+}
+
+.sendButton {
+  display: block;
+  font-size: 16pt;
+}
+
+/** Most GWT widgets already have a style name defined */
+.gwt-DialogBox {
+  width: 400px;
+}
+
+.dialogVPanel {
+  margin: 5px;
+}
+
+.serverResponseLabelError {
+  color: red;
+}
+
+.playerScoreBox {
+  border: 1px solid #777;
+  padding:3px;
+  background: #eee;
+  margin-bottom: 5px;
+}
+
+/** Set ids using widget.getElement().setId("idOfElement") */
+#closeButton {
+  margin: 15px 6px 6px;
+}
+
+.gwt-DialogBox {
+  border: 8px solid white;
+  border-right: 11px solid white;
+  border-bottom: 11px solid white;
+  -webkit-border-image: url(blueborder.png) 8 11 11 8 round round;
+  -moz-border-image: url(blueborder.png) 8 11 11 8 round round;
+}
+
+.gwt-DialogBox .Caption {
+  font-weight: light;
+  font-size: 12pt;
+  text-align: center;
+  margin-bottom: 0.5em;
+  border-bottom: 1px solid #ccc;
+}
+
+.gwt-SplitLayoutPanel-HDragger {
+  background: transparent url(hsplitter-grip.png) center center no-repeat;
+}
+
+.gwt-SplitLayoutPanel-VDragger {
+  background: transparent url(vsplitter-grip.png) center center no-repeat;
+}
diff --git a/bikeshed/war/Mail.html b/bikeshed/war/Mail.html
new file mode 100644
index 0000000..6f3cf17
--- /dev/null
+++ b/bikeshed/war/Mail.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<html>
+  <head>
+    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+    <link type="text/css" rel="stylesheet" href="Mail.css">
+    <title>Mail Sample</title>
+    <script type="text/javascript" language="javascript" src="mail/mail.nocache.js"></script>
+  </head>
+
+  <body>
+    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
+    <noscript>
+      <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
+        Your web browser must have JavaScript enabled
+        in order for this application to display correctly.
+      </div>
+    </noscript>
+
+    <h1>Mail Sample</h1>
+  </body>
+</html>