Support compound key combinations in GlobalKey

Compound keys can now be formed by embedding a KeyCommandSet inside
of a CompoundKeyCommand.  The set is only enabled for a short time
(250 ms) after the activation key on the CompoundKeyCommand is hit
by the user.  If the timeout expires, the set is disabled.  If a
key is selected, the set is disabled.

Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/src/main/java/com/google/gwtexpui/globalkey/client/CompoundKeyCommand.java b/src/main/java/com/google/gwtexpui/globalkey/client/CompoundKeyCommand.java
new file mode 100644
index 0000000..304d56e
--- /dev/null
+++ b/src/main/java/com/google/gwtexpui/globalkey/client/CompoundKeyCommand.java
@@ -0,0 +1,40 @@
+// Copyright (C) 2009 The Android Open Source Project
+//
+// 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.gwtexpui.globalkey.client;
+
+import com.google.gwt.event.dom.client.KeyPressEvent;
+
+public final class CompoundKeyCommand extends KeyCommand {
+  final KeyCommandSet set;
+
+  public CompoundKeyCommand(int mask, char key, String help, KeyCommandSet s) {
+    super(mask, key, help);
+    set = s;
+  }
+
+  public CompoundKeyCommand(int mask, int key, String help, KeyCommandSet s) {
+    super(mask, key, help);
+    set = s;
+  }
+
+  public KeyCommandSet getSet() {
+    return set;
+  }
+
+  @Override
+  public void onKeyPress(final KeyPressEvent event) {
+    GlobalKey.temporaryWithTimeout(set);
+  }
+}
diff --git a/src/main/java/com/google/gwtexpui/globalkey/client/GlobalKey.java b/src/main/java/com/google/gwtexpui/globalkey/client/GlobalKey.java
index 014a673..b99e63f 100644
--- a/src/main/java/com/google/gwtexpui/globalkey/client/GlobalKey.java
+++ b/src/main/java/com/google/gwtexpui/globalkey/client/GlobalKey.java
@@ -20,6 +20,7 @@
 import com.google.gwt.event.logical.shared.CloseEvent;
 import com.google.gwt.event.logical.shared.CloseHandler;
 import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Timer;
 import com.google.gwt.user.client.ui.PopupPanel;
 import com.google.gwt.user.client.ui.Widget;
 
@@ -35,16 +36,29 @@
   private static State global;
   static State active;
   private static CloseHandler<PopupPanel> restoreGlobal;
+  private static Timer restoreTimer;
 
   private static void initEvents() {
     if (active == null) {
       DocWidget.get().addKeyPressHandler(new KeyPressHandler() {
         @Override
         public void onKeyPress(final KeyPressEvent event) {
-          active.all.onKeyPress(event);
+          final KeyCommandSet s = active.live;
+          if (s != active.all) {
+            active.live = active.all;
+            restoreTimer.cancel();
+          }
+          s.onKeyPress(event);
         }
       });
 
+      restoreTimer = new Timer() {
+        @Override
+        public void run() {
+          active.live = active.all;
+        }
+      };
+
       global = new State(null);
       active = global;
     }
@@ -61,6 +75,11 @@
     }
   }
 
+  static void temporaryWithTimeout(final KeyCommandSet s) {
+    active.live = s;
+    restoreTimer.schedule(250);
+  }
+
   public static void dialog(final PopupPanel panel) {
     initEvents();
     initDialog();
@@ -127,6 +146,7 @@
     final Widget root;
     final KeyCommandSet app;
     final KeyCommandSet all;
+    KeyCommandSet live;
 
     State(final Widget r) {
       root = r;
@@ -136,6 +156,8 @@
 
       all = new KeyCommandSet();
       all.add(app);
+
+      live = all;
     }
 
     void add(final KeyCommand k) {
diff --git a/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommandSet.java b/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommandSet.java
index a788639..d190050 100644
--- a/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommandSet.java
+++ b/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommandSet.java
@@ -91,8 +91,11 @@
       }
     }
     for (final Iterator<KeyCommand> i = map.values().iterator(); i.hasNext();) {
-      if (!filter.include(i.next())) {
+      final KeyCommand kc = i.next();
+      if (!filter.include(kc)) {
         i.remove();
+      } else if (kc instanceof CompoundKeyCommand) {
+        ((CompoundKeyCommand) kc).set.filter(filter);
       }
     }
   }
diff --git a/src/main/java/com/google/gwtexpui/globalkey/client/KeyConstants.java b/src/main/java/com/google/gwtexpui/globalkey/client/KeyConstants.java
index 3b02f33..802e349 100644
--- a/src/main/java/com/google/gwtexpui/globalkey/client/KeyConstants.java
+++ b/src/main/java/com/google/gwtexpui/globalkey/client/KeyConstants.java
@@ -24,6 +24,7 @@
   String keyboardShortcuts();
   String closeButton();
   String orOtherKey();
+  String thenOtherKey();
 
   String keyCtrl();
   String keyAlt();
diff --git a/src/main/java/com/google/gwtexpui/globalkey/client/KeyConstants.properties b/src/main/java/com/google/gwtexpui/globalkey/client/KeyConstants.properties
index 7714ac8..e21daf5 100644
--- a/src/main/java/com/google/gwtexpui/globalkey/client/KeyConstants.properties
+++ b/src/main/java/com/google/gwtexpui/globalkey/client/KeyConstants.properties
@@ -5,6 +5,7 @@
 keyboardShortcuts = Keyboard Shortcuts
 closeButton = Close
 orOtherKey = or
+thenOtherKey = then
 
 keyCtrl = Ctrl
 keyAlt = Alt
diff --git a/src/main/java/com/google/gwtexpui/globalkey/client/KeyHelpPopup.java b/src/main/java/com/google/gwtexpui/globalkey/client/KeyHelpPopup.java
index f68c5e7..6e2c276 100644
--- a/src/main/java/com/google/gwtexpui/globalkey/client/KeyHelpPopup.java
+++ b/src/main/java/com/google/gwtexpui/globalkey/client/KeyHelpPopup.java
@@ -101,7 +101,7 @@
     while (setitr.hasNext()) {
       final KeyCommandSet set = setitr.next();
       int row = end[column];
-      row = populate(lists, row, column, set);
+      row = formatGroup(lists, row, column, set);
       end[column] = row;
       if (column == 0) {
         column = 4;
@@ -111,8 +111,82 @@
     }
   }
 
-  private int populate(final Grid lists, int row, final int col,
+  private int formatGroup(final Grid lists, int row, final int col,
       final KeyCommandSet set) {
+    if (set.isEmpty()) {
+      return row;
+    }
+
+    if (lists.getRowCount() < row + 1) {
+      lists.resizeRows(row + 1);
+    }
+    lists.setText(row, col + 2, set.getName());
+    lists.getCellFormatter().addStyleName(row, col + 2, S + "-GroupTitle");
+    row++;
+
+    return formatKeys(lists, row, col, set, null);
+  }
+
+  private int formatKeys(final Grid lists, int row, final int col,
+      final KeyCommandSet set, final SafeHtml prefix) {
+    final CellFormatter fmt = lists.getCellFormatter();
+    final int initialRow = row;
+    final List<KeyCommand> keys = sort(set);
+    if (lists.getRowCount() < row + keys.size()) {
+      lists.resizeRows(row + keys.size());
+    }
+    FORMAT_KEYS: for (int i = 0; i < keys.size(); i++) {
+      final KeyCommand k = keys.get(i);
+
+      if (k instanceof CompoundKeyCommand) {
+        final SafeHtmlBuilder b = new SafeHtmlBuilder();
+        b.append(k.describeKeyStroke());
+        row = formatKeys(lists, row, col, ((CompoundKeyCommand) k).getSet(), b);
+        continue;
+      }
+
+      for (int prior = 0, r = initialRow; prior < i; prior++) {
+        if (KeyCommand.same(keys.get(prior), k)) {
+          final SafeHtmlBuilder b = new SafeHtmlBuilder();
+          b.append(SafeHtml.get(lists, r, col + 0));
+          b.append(" ");
+          b.append(Util.C.orOtherKey());
+          b.append(" ");
+          if (prefix != null) {
+            b.append(prefix);
+            b.append(" ");
+            b.append(Util.C.thenOtherKey());
+            b.append(" ");
+          }
+          b.append(k.describeKeyStroke());
+          SafeHtml.set(lists, r, col + 0, b);
+          continue FORMAT_KEYS;
+        }
+      }
+
+      if (prefix != null) {
+        final SafeHtmlBuilder b = new SafeHtmlBuilder();
+        b.append(prefix);
+        b.append(" ");
+        b.append(Util.C.thenOtherKey());
+        b.append(" ");
+        b.append(k.describeKeyStroke());
+        SafeHtml.set(lists, row, col + 0, b);
+      } else {
+        SafeHtml.set(lists, row, col + 0, k.describeKeyStroke());
+      }
+      lists.setText(row, col + 1, ":");
+      lists.setText(row, col + 2, k.getHelpText());
+
+      fmt.addStyleName(row, col + 0, S + "-TableKeyStroke");
+      fmt.addStyleName(row, col + 1, S + "-TableSeparator");
+      row++;
+    }
+
+    return row;
+  }
+
+  private List<KeyCommand> sort(final KeyCommandSet set) {
     final List<KeyCommand> keys = new ArrayList<KeyCommand>(set.getKeys());
     Collections.sort(keys, new Comparator<KeyCommand>() {
       @Override
@@ -125,45 +199,6 @@
         return 0;
       }
     });
-    if (keys.isEmpty()) {
-      return row;
-    }
-
-    if (lists.getRowCount() < row + 1 + keys.size()) {
-      lists.resizeRows(row + 1 + keys.size());
-    }
-
-    final CellFormatter fmt = lists.getCellFormatter();
-    lists.setText(row, col + 2, set.getName());
-    fmt.addStyleName(row, col + 2, S + "-GroupTitle");
-    row++;
-
-    final int initialRow = row;
-    FORMAT_KEYS: for (int i = 0; i < keys.size(); i++) {
-      final KeyCommand k = keys.get(i);
-
-      for (int prior = 0, r = initialRow; prior < i; prior++) {
-        if (KeyCommand.same(keys.get(prior), k)) {
-          final SafeHtmlBuilder b = new SafeHtmlBuilder();
-          b.append(SafeHtml.get(lists, r, col + 0));
-          b.append(" ");
-          b.append(Util.C.orOtherKey());
-          b.append(" ");
-          b.append(k.describeKeyStroke());
-          SafeHtml.set(lists, r, col + 0, b);
-          continue FORMAT_KEYS;
-        }
-      }
-
-      SafeHtml.set(lists, row, col + 0, k.describeKeyStroke());
-      lists.setText(row, col + 1, ":");
-      lists.setText(row, col + 2, k.getHelpText());
-
-      fmt.addStyleName(row, col + 0, S + "-TableKeyStroke");
-      fmt.addStyleName(row, col + 1, S + "-TableSeparator");
-      row++;
-    }
-
-    return row;
+    return keys;
   }
 }