]> source.dussan.org Git - vaadin-framework.git/commitdiff
Changes in Table:
authorJohn Alhroos <john.ahlroos@itmill.com>
Tue, 4 May 2010 13:02:44 +0000 (13:02 +0000)
committerJohn Alhroos <john.ahlroos@itmill.com>
Tue, 4 May 2010 13:02:44 +0000 (13:02 +0000)
- Changed keyboard selection mechanism
- Added keyboard control with Home,End,Page Up and Page Down
- Added focus outline

svn changeset:13023/svn branch:6.4

WebContent/VAADIN/themes/base/styles.css
WebContent/VAADIN/themes/base/table/table.css
WebContent/VAADIN/themes/reindeer/styles.css
WebContent/VAADIN/themes/runo/styles.css
src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java
tests/src/com/vaadin/tests/components/table/KeyControl.java
tests/src/com/vaadin/tests/components/table/TableContextMenuOnField.java [new file with mode: 0644]

index 393f4dc1412a22c7546c586856d1090cc9652313..6a7f0255c31f21301969b368064d84ef5a943b58 100644 (file)
@@ -1,5 +1,5 @@
-.v-theme-version:after {content:"6_4_0_dev-20100420";}
-.v-theme-version-6_4_0_dev-20100420 {display: none;}
+.v-theme-version:after {content:"6_4_0_dev-20100504";}
+.v-theme-version-6_4_0_dev-20100504 {display: none;}
 /* Automatically compiled css file from subdirectories. */
 
 .v-absolutelayout-wrapper {
@@ -1423,6 +1423,9 @@ div.v-progressindicator-indeterminate-disabled {
 .v-ie7 .v-table {
        overflow: visible;
 }
+.v-table-body.focused{
+       border-color: #388ddd;
+}
 .v-table-header-wrap {
        overflow: hidden;
        border: 1px solid #aaa;
@@ -1452,10 +1455,9 @@ div.v-progressindicator-indeterminate-disabled {
        border-top: none;
        background: #efefef;
 }
-.v-table-footer table,
-.v-table-table {
+.v-table-footer table{
        border-spacing: 0;
-       border-collapse: separate;
+       border-collapse: collapse;
        margin: 0;
        padding: 0;
        border: 0;
@@ -1575,6 +1577,10 @@ div.v-progressindicator-indeterminate-disabled {
        display: block;
        text-align: center;
 }
+.v-table-body:focus,
+.v-table-body-wrapper:focus{
+       outline: none;
+}
 /* row in column selector */
 .v-on {
        
index 421aad5488e6b2ab746784ab4849c6dfd54200fc..7fdcd5b98a5adce97a20eb9447783e291668eb5e 100644 (file)
@@ -29,6 +29,9 @@
 .v-ie7 .v-table {
        overflow: visible;
 }
+.v-table-body.focused{
+       border-color: #388ddd;
+}
 .v-table-header-wrap {
        overflow: hidden;
        border: 1px solid #aaa;
index 20c7bb00b0d9f74fdf61ea1271ea276ccd143bde..42f18208cb38618dc904a344aa888e9d375c8476 100644 (file)
@@ -1,5 +1,5 @@
-.v-theme-version:after {content:"6_4_0_dev-20100420";}
-.v-theme-version-6_4_0_dev-20100420 {display: none;}
+.v-theme-version:after {content:"6_4_0_dev-20100504";}
+.v-theme-version-6_4_0_dev-20100504 {display: none;}
 /* Automatically compiled css file from subdirectories. */
 
 .v-absolutelayout-wrapper {
@@ -1423,6 +1423,9 @@ div.v-progressindicator-indeterminate-disabled {
 .v-ie7 .v-table {
        overflow: visible;
 }
+.v-table-body.focused{
+       border-color: #388ddd;
+}
 .v-table-header-wrap {
        overflow: hidden;
        border: 1px solid #aaa;
@@ -1452,10 +1455,9 @@ div.v-progressindicator-indeterminate-disabled {
        border-top: none;
        background: #efefef;
 }
-.v-table-footer table,
-.v-table-table {
+.v-table-footer table{
        border-spacing: 0;
-       border-collapse: separate;
+       border-collapse: collapse;
        margin: 0;
        padding: 0;
        border: 0;
@@ -1575,6 +1577,10 @@ div.v-progressindicator-indeterminate-disabled {
        display: block;
        text-align: center;
 }
+.v-table-body:focus,
+.v-table-body-wrapper:focus{
+       outline: none;
+}
 /* row in column selector */
 .v-on {
        
@@ -4240,6 +4246,17 @@ td.v-datefield-calendarpanel-nextyear {
 .v-table .v-selected .v-table-cell-content {
        border-right-color: #466c90;
 }
+.v-table-body:focus{
+       outline: none;
+}
+.v-table .v-table-focus .v-table-cell-content{
+       border-top: 1px dotted black;
+       border-bottom: 1px dotted black;        
+}
+.v-table .v-table-focus .v-table-cell-content .v-table-cell-wrapper{
+       padding-top:2px;
+       padding-bottom:2px;     
+}
 .v-table-column-selector {
        width: 16px;
        height: 20px;
index 3c6e578d37ed21dea6385b3222b3c2683a2b01ab..ccaa635e66f11b98ac07c5dae1c19240b041cc2d 100644 (file)
@@ -1,5 +1,5 @@
-.v-theme-version:after {content:"6_4_0_dev-20100429";}
-.v-theme-version-6_4_0_dev-20100429 {display: none;}
+.v-theme-version:after {content:"6_4_0_dev-20100504";}
+.v-theme-version-6_4_0_dev-20100504 {display: none;}
 /* Automatically compiled css file from subdirectories. */
 
 .v-absolutelayout-wrapper {
@@ -1423,6 +1423,9 @@ div.v-progressindicator-indeterminate-disabled {
 .v-ie7 .v-table {
        overflow: visible;
 }
+.v-table-body.focused{
+       border-color: #388ddd;
+}
 .v-table-header-wrap {
        overflow: hidden;
        border: 1px solid #aaa;
index 17b822a204577059817cccd1674812b5605ad32a..b7feba223a277fb8bdaf0b0336d578b181d4f171 100644 (file)
@@ -145,12 +145,33 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
      * are selecting a range which is over several pages long
      */
     private int lastSelectedRowKey = -1;
-    
+
+    /*
+     * These are used when jumping between pages when pressing Home and End
+     */
+    private boolean selectLastItemInNextRender = false;
+    private boolean selectFirstItemInNextRender = false;
+    private boolean focusFirstItemInNextRender = false;
+    private boolean focusLastItemInNextRender = false;
+
     /*
      * The currently focused row
      */
     private VScrollTableRow focusedRow;
 
+    /*
+     * Flag for notifying when the selection has changed and should be sent to
+     * the server
+     */
+    private boolean selectionChanged = false;
+
+    /*
+     * The speed (in pixels) which the scrolling scrolls vertically/horizontally
+     */
+    private int scrollingVelocity = 10;
+
+    private Timer scrollingVelocityTimer = null;;
+
     /**
      * Represents a select range of rows
      */
@@ -187,6 +208,18 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
         public String toString() {
             return startRowKey + "-" + endRowKey;
         }
+
+        public boolean inRange(int key) {
+            return key >= startRowKey && key <= endRowKey;
+        }
+
+        public int getStartKey() {
+            return startRowKey;
+        }
+
+        public int getEndKey() {
+            return endRowKey;
+        }
     };
 
     private final HashSet<SelectionRange> selectedRowRanges = new HashSet<SelectionRange>();
@@ -246,15 +279,13 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
 
     private int multiselectmode;
 
-    
-
     public VScrollTable() {
         bodyContainerFocus.setStyleName(CLASSNAME + "-body-wrapper");
 
         /*
-         * Firefox handler auto-repeat works correctly only if we use a key
-         * press handler, other browsers handle it correctly when using a key
-         * down handler
+         * Firefox auto-repeat works correctly only if we use a key press
+         * handler, other browsers handle it correctly when using a key down
+         * handler
          */
         if (BrowserInfo.get().isGecko()) {
             bodyContainerFocus.addKeyPressHandler(this);
@@ -275,24 +306,64 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
         add(tFoot);
 
         rowRequestHandler = new RowRequestHandler();
+
+        /*
+         * We need to use the sinkEvents method to catch the keyUp events so we
+         * can cache a single shift. KeyUpHandler cannot do this.
+         */
+        sinkEvents(Event.ONKEYUP);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user
+     * .client.Event)
+     */
+    @Override
+    public void onBrowserEvent(Event event) {
+        if (event.getTypeInt() == Event.ONKEYUP) {
+            if (event.getKeyCode() == KeyCodes.KEY_SHIFT) {
+                sendSelectedRows();
+            } else if ((event.getKeyCode() == getNavigationUpKey()
+                    || event.getKeyCode() == getNavigationDownKey()
+                    || event.getKeyCode() == getNavigationPageUpKey() || event
+                    .getKeyCode() == getNavigationPageDownKey())
+                    && !event.getShiftKey()) {
+                sendSelectedRows();
+            }
+
+            scrollingVelocityTimer.cancel();
+            scrollingVelocityTimer = null;
+            scrollingVelocity = 10;
+        }
+    }
+
+    /**
+     * Moves the focus one step down
+     * 
+     * @return Returns true if succeeded
+     */
+    private boolean moveFocusDown() {
+        return moveFocusDown(0);
     }
 
     /**
-     * Moves the selection head one row downloads
+     * Moves the focus down by 1+offset rows
      * 
      * @return Returns true if succeeded, else false if the selection could not
      *         be move downwards
      */
-    private boolean moveSelectionDown() {
+    private boolean moveFocusDown(int offset) {
         if (selectMode > VScrollTable.SELECT_MODE_NONE) {
-            if (focusedRow == null) {
-                setRowFocus((VScrollTableRow) scrollBody.iterator().next());
-                return true;
+            if (focusedRow == null && scrollBody.iterator().hasNext()) {
+                return setRowFocus((VScrollTableRow) scrollBody.iterator()
+                        .next());
             } else {
-                VScrollTableRow next = getNextRow(focusedRow);
+                VScrollTableRow next = getNextRow(focusedRow, offset);
                 if (next != null) {
-                    setRowFocus(next);
-                    return true;
+                    return setRowFocus(next);
                 }
             }
         }
@@ -301,22 +372,30 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
     }
 
     /**
-     * Moves the selection head one row upwards
+     * Moves the selection one step up
+     * 
+     * @return Returns true if succeeded
+     */
+    private boolean moveFocusUp() {
+        return moveFocusUp(0);
+    }
+
+    /**
+     * Moves the focus row upwards
      * 
      * @return Returns true if succeeded, else false if the selection could not
      *         be move upwards
      * 
      */
-    private boolean moveSelectionUp() {
+    private boolean moveFocusUp(int offset) {
         if (selectMode > VScrollTable.SELECT_MODE_NONE) {
-            if (focusedRow == null) {
-                setRowFocus((VScrollTableRow) scrollBody.iterator().next());
-                return true;
+            if (focusedRow == null && scrollBody.iterator().hasNext()) {
+                return setRowFocus((VScrollTableRow) scrollBody.iterator()
+                        .next());
             } else {
-                VScrollTableRow prev = getPreviousRow(focusedRow);
+                VScrollTableRow prev = getPreviousRow(focusedRow, offset);
                 if (prev != null) {
-                    setRowFocus(prev);
-                    return true;
+                    return setRowFocus(prev);
                 }
             }
         }
@@ -335,21 +414,80 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
      */
     private void selectFocusedRow(boolean ctrlSelect, boolean shiftSelect) {
         if (focusedRow != null) {
-            if (ctrlSelect && !shiftSelect && selectMode == SELECT_MODE_MULTI) {
-                focusedRow.toggleSelection(ctrlSelect);
-            } else if (!ctrlSelect && shiftSelect
-                    && selectMode == SELECT_MODE_MULTI) {
-                focusedRow.toggleShiftSelection(true);
-            } else if (ctrlSelect && shiftSelect
-                    && selectMode == SELECT_MODE_MULTI) {
-                focusedRow.toggleShiftSelection(false);
-            } else {
+            // Arrows moves the selection and clears previous selections
+            if (selectMode > SELECT_MODE_NONE && !ctrlSelect && !shiftSelect) {
                 deselectAll();
-                focusedRow.toggleSelection(true);
+                focusedRow.toggleSelection(!ctrlSelect);
+            }
+
+            // Ctrl+arrows moves selection head
+            else if (selectMode > SELECT_MODE_NONE && ctrlSelect
+                    && !shiftSelect) {
+                // No selection, only selection head is moved
+            }
+
+            // Shift+arrows selection selects a range
+            else if (selectMode == SELECT_MODE_MULTI && !ctrlSelect
+                    && shiftSelect) {
+                focusedRow.toggleShiftSelection(shiftSelect);
             }
         }
     }
 
+    /**
+     * Sends the selection to the server
+     */
+    private void sendSelectedRows(){
+        // Don't send anything if selection has not changed
+        if (!selectionChanged) {
+            return;
+        }
+
+        // Reset selection changed flag
+        selectionChanged = false;
+
+        // Note: changing the immediateness of this
+        // might
+        // require changes to "clickEvent" immediateness
+        // also.
+        if (multiselectmode == MULTISELECT_MODE_DEFAULT) {
+            // Convert ranges to a set of strings
+            Set<String> ranges = new HashSet<String>();
+            for (SelectionRange range : selectedRowRanges) {
+                ranges.add(range.toString());
+            }
+
+            /*
+             * Clear ranges since they are transformed
+             * on the server side to row selections
+             */
+            if (immediate) {
+                selectedRowRanges.clear();
+            }
+
+            // Send the selected row ranges
+            client
+                    .updateVariable(
+                            paintableId,
+                            "selectedRanges",
+                            ranges
+                                    .toArray(new String[selectedRowRanges
+                                            .size()]),
+                            false);
+        }
+
+        // Send the selected rows
+        client
+                .updateVariable(
+                        paintableId,
+                        "selected",
+                        selectedRowKeys
+                                .toArray(new String[selectedRowKeys
+                                        .size()]),
+                        immediate);
+
+    }
+
     /**
      * Get the key that moves the selection head upwards. By default it is the
      * up arrow key but by overriding this you can change the key to whatever
@@ -405,6 +543,57 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
         return 32;
     }
 
+    /**
+     * Get the key the moves the selection one page up in the table. By default
+     * this is the Page Up key but by overriding this you can change the key to
+     * whatever you want.
+     * 
+     * @return
+     */
+    protected int getNavigationPageUpKey() {
+        return KeyCodes.KEY_PAGEUP;
+    }
+
+    /**
+     * Get the key the moves the selection one page down in the table. By
+     * default this is the Page Down key but by overriding this you can change
+     * the key to whatever you want.
+     * 
+     * @return
+     */
+    protected int getNavigationPageDownKey() {
+        return KeyCodes.KEY_PAGEDOWN;
+    }
+
+    /**
+     * Get the key the moves the selection to the beginning of the table. By
+     * default this is the Home key but by overriding this you can change the
+     * key to whatever you want.
+     * 
+     * @return
+     */
+    protected int getNavigationStartKey() {
+        return KeyCodes.KEY_HOME;
+    }
+
+    /**
+     * Get the key the moves the selection to the end of the table. By default
+     * this is the End key but by overriding this you can change the key to
+     * whatever you want.
+     * 
+     * @return
+     */
+    protected int getNavigationEndKey() {
+        return KeyCodes.KEY_END;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.vaadin.terminal.gwt.client.Paintable#updateFromUIDL(com.vaadin.terminal
+     * .gwt.client.UIDL, com.vaadin.terminal.gwt.client.ApplicationConnection)
+     */
     @SuppressWarnings("unchecked")
     public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
         rendering = true;
@@ -590,10 +779,62 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
 
         hideScrollPositionAnnotation();
         purgeUnregistryBag();
+
+        // This is called when the Home button has been pressed and the pages
+        // changes
+        if (selectFirstItemInNextRender) {
+            selectFirstRenderedRow(false);
+            selectFirstItemInNextRender = false;
+        }
+
+        if (focusFirstItemInNextRender) {
+            selectFirstRenderedRow(true);
+            focusFirstItemInNextRender = false;
+        }
+
+        // This is called when the End button has been pressed and the pages
+        // changes
+        if (selectLastItemInNextRender) {
+            selectLastRenderedRow(false);
+            selectLastItemInNextRender = false;
+        }
+
+        if (focusLastItemInNextRender) {
+            selectLastRenderedRow(true);
+            focusLastItemInNextRender = false;
+        }
+
         rendering = false;
         headerChangedDuringUpdate = false;
     }
 
+    private void selectLastRenderedRow(boolean focusOnly) {
+        VScrollTableRow row = null;
+        Iterator<Widget> it = scrollBody.iterator();
+        while (it.hasNext()) {
+            row = (VScrollTableRow) it.next();
+        }
+        if (row != null) {
+            setRowFocus(row);
+            if (!focusOnly) {
+                deselectAll();
+                selectFocusedRow(false, false);
+                sendSelectedRows();
+            }
+        }
+
+    }
+
+    private void selectFirstRenderedRow(boolean focusOnly) {
+        setRowFocus((VScrollTableRow) scrollBody.iterator().next());
+        if (!focusOnly) {
+            deselectAll();
+            selectFocusedRow(false, false);
+            sendSelectedRows();
+        }
+
+    }
+
     private void setCacheRate(double d) {
         if (cache_rate != d) {
             cache_rate = d;
@@ -812,25 +1053,27 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
         }
         return null;
     }
-    
+
     /**
      * Returns the next row to the given row
      * 
      * @param row
      *            The row to calculate from
+     * 
      * @return The next row or null if no row exists
      */
-    private VScrollTableRow getNextRow(VScrollTableRow row){
+    private VScrollTableRow getNextRow(VScrollTableRow row, int offset) {
         final Iterator<Widget> it = scrollBody.iterator();
         VScrollTableRow r = null;
         while (it.hasNext()) {
             r = (VScrollTableRow) it.next();
             if(r == row){
-                if (it.hasNext()) {
-                    return (VScrollTableRow) it.next();
-                } else {
-                    break;
+                r = null;
+                while (offset >= 0 && it.hasNext()) {
+                    r = (VScrollTableRow) it.next();
+                    offset--;
                 }
+                return r;
             }            
         }
         
@@ -844,18 +1087,22 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
      *            The row to calculate from
      * @return The previous row or null if no row exists
      */
-    private VScrollTableRow getPreviousRow(VScrollTableRow row) {
+    private VScrollTableRow getPreviousRow(VScrollTableRow row, int offset) {
         final Iterator<Widget> it = scrollBody.iterator();
+        final Iterator<Widget> offsetIt = scrollBody.iterator();
         VScrollTableRow r = null;
         VScrollTableRow prev = null;
         while (it.hasNext()) {
             r = (VScrollTableRow) it.next();
+            if (offset < 0) {
+                prev = (VScrollTableRow) offsetIt.next();
+            }
             if (r == row) {
                 return prev;
-            } else {
-                prev = r;
             }
+            offset--;
         }
+
         return null;
     }
 
@@ -3449,35 +3696,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
                                             .setPropertyJSO("onselectstart",
                                                     null);
                                 }
-
-                                // Note: changing the immediateness of this
-                                // might
-                                // require changes to "clickEvent" immediateness
-                                // also.
-                                if (multiselectmode == MULTISELECT_MODE_DEFAULT) {
-                                    Set<String> ranges = new HashSet<String>();
-                                    for (SelectionRange range : selectedRowRanges) {
-                                        ranges.add(range.toString());
-                                    }
-                                    client
-                                            .updateVariable(
-                                                    paintableId,
-                                                    "selectedRanges",
-                                                    ranges
-                                                            .toArray(new String[selectedRowRanges
-                                                                    .size()]),
-                                                    false);
-                                }
-
-                                // Send the selected rows
-                                client
-                                        .updateVariable(
-                                                paintableId,
-                                                "selected",
-                                                selectedRowKeys
-                                                        .toArray(new String[selectedRowKeys
-                                                                .size()]),
-                                                immediate);
+                                sendSelectedRows();
                             }
                             break;
                         case Event.ONCONTEXTMENU:
@@ -3641,6 +3860,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
              */
             public void toggleSelection(boolean ctrlSelect) {
                 selected = !selected;
+                selectionChanged = true;
                 if (selected) {
                     if (ctrlSelect) {
                         lastSelectedRowKey = rowKey;
@@ -3650,6 +3870,40 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
                 } else {
                     removeStyleName("v-selected");
                     selectedRowKeys.remove(String.valueOf(rowKey));
+                    removeKeyFromSelectedRange(rowKey);
+                }
+            }
+
+            /**
+             * Removes a key from a range if the key is found in a selected
+             * range
+             * 
+             * @param key
+             *            The key to remove
+             */
+            private void removeKeyFromSelectedRange(int key){
+                for(SelectionRange range : selectedRowRanges){
+                    if (range.inRange(key)) {
+                        int start = range.getStartKey();
+                        int end = range.getEndKey();
+
+                        if (start < key && end > key) {
+                            selectedRowRanges.add(new SelectionRange(start,
+                                    key - 1));
+                            selectedRowRanges.add(new SelectionRange(key + 1,
+                                    end));
+                        } else if (start == key && start < end) {
+                            selectedRowRanges.add(new SelectionRange(start + 1,
+                                    end));
+                        } else if (end == key && start < end) {
+                            selectedRowRanges.add(new SelectionRange(start,
+                                    end - 1));
+                        }
+
+                        selectedRowRanges.remove(range);
+
+                        break;
+                    }
                 }
             }
 
@@ -3690,19 +3944,31 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
                 }
 
                 // Select the range (not including this row)
-                for (int r = startKey; r <= endKey; r++) {
-                    if (r != rowKey) {
-                        VScrollTableRow row = getRenderedRowByKey(String
-                                .valueOf(r));
-                        if (row != null && !row.isSelected()) {
-                            row.toggleSelection(false);
-                            selectedRowKeys.add(String.valueOf(r));
-                        }
-                    }
+                VScrollTableRow startRow = getRenderedRowByKey(String
+                        .valueOf(startKey));
+                VScrollTableRow endRow = getRenderedRowByKey(String
+                        .valueOf(endKey));
+
+                // If start row is null then we have a multipage selection from
+                // above
+                if (startRow == null) {
+                    startRow = (VScrollTableRow) scrollBody.iterator().next();
                 }
 
-                // Toggle clicked rows selection
-                toggleSelection(false);
+                Iterator<Widget> rows = scrollBody.iterator();
+                boolean startSelection = false;
+                while (rows.hasNext()) {
+                    VScrollTableRow row = (VScrollTableRow) rows.next();
+                    if (row == startRow || startSelection) {
+                        startSelection = true;
+                        row.toggleSelection(false);
+                        selectedRowKeys.add(row.getKey());
+                    }
+
+                    if (row == endRow && row != null) {
+                        startSelection = false;
+                    }
+                }
 
                 // Add range
                 selectedRowRanges.add(new SelectionRange(startKey, endKey));
@@ -4376,11 +4642,12 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
      * 
      * @param row
      *            The row to where the selection head should move
+     * @return Returns true if focus was moved successfully, else false
      */
-    private void setRowFocus(VScrollTableRow row) {
+    private boolean setRowFocus(VScrollTableRow row) {
 
         if (selectMode == SELECT_MODE_NONE) {
-            return;
+            return false;
         }
 
         // Remove previous selection
@@ -4389,8 +4656,16 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
         }
 
         if (row != null) {
-            // Apply focus style to new selection
+
+            // Trying to set focus on already focused row
+            if (row == focusedRow) {
+                return false;
+            }
+
+            // Set new focused row
             focusedRow = row;
+
+            // Apply focus style to new selection
             focusedRow.addStyleName(CLASSNAME_SELECTION_FOCUS);
 
             // Scroll up or down if needed
@@ -4407,7 +4682,13 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
                         .getScrollPosition()
                         - focusedRow.getOffsetHeight());
             }
+
+            return true;
+        } else {
+            focusedRow = null;
         }
+
+        return false;
     }
 
     /**
@@ -4417,64 +4698,149 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
      *            The keyboard event received
      */
     private void handleNavigation(Event event) {
+        if (event.getKeyCode() == KeyCodes.KEY_TAB) {
+            // Do not handle tab key
+            return;
+        }
 
         // Down navigation
         if (selectMode == SELECT_MODE_NONE
                 && event.getKeyCode() == getNavigationDownKey()) {
-            bodyContainer
-                    .setScrollPosition(bodyContainer.getScrollPosition() + 5);
-            event.preventDefault();
+            bodyContainer.setScrollPosition(bodyContainer.getScrollPosition()
+                    + scrollingVelocity);
         } else if (event.getKeyCode() == getNavigationDownKey()) {
-                if (moveSelectionDown()) {
-                    if (event.getShiftKey()) {
-                        selectFocusedRow(false, true);
-                    }
-                    event.preventDefault();
+            if (selectMode == SELECT_MODE_MULTI && moveFocusDown()) {
+                selectFocusedRow(event.getCtrlKey() || event.getMetaKey(),
+                        event.getShiftKey());
+            } else if (selectMode == SELECT_MODE_SINGLE && !event.getShiftKey()
+                    && moveFocusDown()) {
+                selectFocusedRow(event.getCtrlKey() || event.getMetaKey(),
+                        event.getShiftKey());
             }
         }
 
         // Up navigation
         if (selectMode == SELECT_MODE_NONE
                 && event.getKeyCode() == getNavigationUpKey()) {
-            bodyContainer
-                    .setScrollPosition(bodyContainer.getScrollPosition() - 5);
-            event.preventDefault();
+            bodyContainer.setScrollPosition(bodyContainer.getScrollPosition()
+                    - scrollingVelocity);
         } else if (event.getKeyCode() == getNavigationUpKey()) {
-                if (moveSelectionUp()) {
-                    if (event.getShiftKey()) {
-                        selectFocusedRow(false, true);
-                    }
-                    event.preventDefault();
+            if (selectMode == SELECT_MODE_MULTI && moveFocusUp()) {
+                selectFocusedRow(event.getCtrlKey() || event.getMetaKey(),
+                        event.getShiftKey());
+            } else if (selectMode == SELECT_MODE_SINGLE && !event.getShiftKey()
+                    && moveFocusUp()) {
+                selectFocusedRow(event.getCtrlKey() || event.getMetaKey(),
+                        event.getShiftKey());
             }
+        }
 
-            // Left navigation
-        } else if (event.getKeyCode() == getNavigationLeftKey()) {
+        // Left navigation
+        if (event.getKeyCode() == getNavigationLeftKey()) {
             bodyContainer.setHorizontalScrollPosition(bodyContainer
-                    .getHorizontalScrollPosition() - 5);
-            event.preventDefault();
+                    .getHorizontalScrollPosition()
+                    - scrollingVelocity);
 
             // Right navigation
         } else if (event.getKeyCode() == getNavigationRightKey()) {
             bodyContainer.setHorizontalScrollPosition(bodyContainer
-                    .getHorizontalScrollPosition() + 5);
-            event.preventDefault();
+                    .getHorizontalScrollPosition()
+                    + scrollingVelocity);
         }
 
         // Select navigation
         if (selectMode > SELECT_MODE_NONE
-                && event.getKeyCode() == getNavigationSelectKey()) {         
-                selectFocusedRow(event.getCtrlKey() || event.getMetaKey(),
-                        event.getShiftKey());
+                && event.getKeyCode() == getNavigationSelectKey()) {
+            if (selectMode == SELECT_MODE_SINGLE) {
+                boolean wasSelected = focusedRow.isSelected();
+                deselectAll();
+                lastSelectedRowKey = -1;
+                if (!wasSelected) {
+                    focusedRow.toggleSelection(true);
+                }
 
-                event.preventDefault();
+            } else {
+                focusedRow.toggleSelection(true);
+            }
+
+            sendSelectedRows();
+        }
+
+        // Page Down navigation
+        if (event.getKeyCode() == getNavigationPageDownKey()) {
+            int rowHeight = (int) scrollBody.getRowHeight();
+            int offset = pageLength * rowHeight - rowHeight;
+            bodyContainer.setScrollPosition(bodyContainer.getScrollPosition()
+                    + offset);
+            if (selectMode > SELECT_MODE_NONE) {
+                if (!moveFocusDown(pageLength - 2)) {
+                    final int lastRendered = scrollBody.getLastRendered();
+                    if (lastRendered == totalRows - 1) {
+                        selectLastRenderedRow(false);
+                    } else {
+                        selectLastItemInNextRender = true;
+                    }
+                } else {
+                    selectFocusedRow(false, false);
+                    sendSelectedRows();
+                }
+            }
+        }
 
-                // Send the selected rows
-                client
-                        .updateVariable(paintableId, "selected",
-                                selectedRowKeys
-                                        .toArray(new String[selectedRowKeys
-                                                .size()]), immediate);
+        // Page Up navigation
+        if (event.getKeyCode() == getNavigationPageUpKey()) {
+            int rowHeight = (int) scrollBody.getRowHeight();
+            int offset = pageLength * rowHeight - rowHeight;
+            bodyContainer.setScrollPosition(bodyContainer.getScrollPosition()
+                    - offset);
+            if (selectMode > SELECT_MODE_NONE) {
+                if (!moveFocusUp(pageLength - 2)) {
+                    final int firstRendered = scrollBody.getFirstRendered();
+                    if (firstRendered == 0) {
+                        selectFirstRenderedRow(false);
+                    } else {
+                        selectFirstItemInNextRender = true;
+                    }
+                } else {
+                    selectFocusedRow(false, false);
+                    sendSelectedRows();
+                }
+            }
+        }
+
+        // Goto start navigation
+        if (event.getKeyCode() == getNavigationStartKey()) {
+            if (selectMode > SELECT_MODE_NONE) {
+                final int firstRendered = scrollBody.getFirstRendered();
+                boolean focusOnly = event.getCtrlKey() || event.getMetaKey();
+                if (firstRendered == 0) {
+                    selectFirstRenderedRow(focusOnly);
+                } else if (focusOnly) {
+                    focusFirstItemInNextRender = true;
+                } else {
+                    selectFirstItemInNextRender = true;
+                }
+            }
+            bodyContainer.setScrollPosition(0);
+        }
+
+        // Goto end navigation
+        if (event.getKeyCode() == getNavigationEndKey()) {
+            if (selectMode > SELECT_MODE_NONE) {
+                final int lastRendered = scrollBody.getLastRendered();
+                boolean focusOnly = event.getCtrlKey() || event.getMetaKey();
+                if (lastRendered == totalRows - 1) {
+                    selectLastRenderedRow(focusOnly);
+                } else if (focusOnly) {
+                    focusLastItemInNextRender = true;
+                } else {
+                    selectLastItemInNextRender = true;
+                }
             }
+            bodyContainer.setScrollPosition(scrollBody.getOffsetHeight());
+        }
+
+        event.preventDefault();
     }
 
     /*
@@ -4486,6 +4852,17 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
      */
     public void onKeyPress(KeyPressEvent event) {
         handleNavigation((Event) event.getNativeEvent().cast());
+
+        // Start the velocityTimer
+        if (scrollingVelocityTimer == null) {
+            scrollingVelocityTimer = new Timer() {
+                @Override
+                public void run() {
+                    scrollingVelocity++;
+                }
+            };
+            scrollingVelocityTimer.scheduleRepeating(100);
+        }
     }
 
     /*
@@ -4497,6 +4874,17 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
      */
     public void onKeyDown(KeyDownEvent event) {
         handleNavigation((Event) event.getNativeEvent().cast());
+
+        // Start the velocityTimer
+        if (scrollingVelocityTimer == null) {
+            scrollingVelocityTimer = new Timer() {
+                @Override
+                public void run() {
+                    scrollingVelocity++;
+                }
+            };
+            scrollingVelocityTimer.scheduleRepeating(100);
+        }
     }
 
     /*
@@ -4507,10 +4895,9 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
      * .dom.client.FocusEvent)
      */
     public void onFocus(FocusEvent event) {
-        // Move focus from wrapper to container in FF, ignored in other browsers
-        if (BrowserInfo.get().isFF3()) {
-            bodyContainer.getElement().focus();
-        }
+        bodyContainer.addStyleName("focused");
+
+        // Focus a row if no row is in focus
         if (focusedRow == null) {
             setRowFocus((VScrollTableRow) scrollBody.iterator().next());
         }
@@ -4524,7 +4911,12 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler,
      * .dom.client.BlurEvent)
      */
     public void onBlur(BlurEvent event) {
+        bodyContainer.removeStyleName("focused");
+
+        // Unfocus any row
         setRowFocus(null);
     }
 
+
+
 }
index 19a5a0eca60370cf2f0485d33890d8b75664e7b0..a0a13e48580456f4f73ff75d34ff004f6ca3814e 100644 (file)
@@ -64,7 +64,8 @@ public class KeyControl extends TestBase {
 
         table2.addListener(new Table.ValueChangeListener() {
             public void valueChange(ValueChangeEvent event) {
-                String value = table2.getValue().toString();
+                String value = table2.getValue() == null ? "No selected items"
+                        : table2.getValue().toString();
                 selected2.setValue(value);
             }
         });
@@ -87,7 +88,8 @@ public class KeyControl extends TestBase {
         table3.addListener(new Table.ValueChangeListener() {
             public void valueChange(ValueChangeEvent event) {
                 Set<String> value = (Set<String>) table3.getValue();
-                selected3.setValue(value);
+                selected3.setValue(value.size() == 0 ? "No selected items"
+                        : value);
             }
         });
 
diff --git a/tests/src/com/vaadin/tests/components/table/TableContextMenuOnField.java b/tests/src/com/vaadin/tests/components/table/TableContextMenuOnField.java
new file mode 100644 (file)
index 0000000..4c6f658
--- /dev/null
@@ -0,0 +1,93 @@
+package com.vaadin.tests.components.table;
+
+import com.vaadin.event.Action;
+import com.vaadin.event.LayoutEvents.LayoutClickEvent;
+import com.vaadin.event.LayoutEvents.LayoutClickListener;
+import com.vaadin.tests.components.TestBase;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.Link;
+import com.vaadin.ui.Table;
+import com.vaadin.ui.TextField;
+import com.vaadin.ui.VerticalLayout;
+
+public class TableContextMenuOnField extends TestBase {
+
+    private static final Action ACTION_MYACTION = new Action("Action!!");
+
+    @Override
+    protected void setup() {
+        Table table = new Table();
+        table.setSelectable(true);
+        table.setMultiSelect(true);
+
+        table.addActionHandler(new Action.Handler() {
+            public void handleAction(Action action, Object sender, Object target) {
+                // TODO Auto-generated method stub
+
+            }
+
+            public Action[] getActions(Object target, Object sender) {
+                return new Action[] { ACTION_MYACTION };
+            }
+        });
+
+        table.addGeneratedColumn("layout", new Table.ColumnGenerator() {
+
+            public Component generateCell(Table source, Object itemId,
+                    Object columnId) {
+
+                VerticalLayout layout = new VerticalLayout();
+                layout.addComponent(new TextField());
+
+                layout.addListener(new LayoutClickListener() {
+
+                    public void layoutClick(LayoutClickEvent event) {
+                        getMainWindow().showNotification("HELLO");
+
+                    }
+                });
+
+                return layout;
+            }
+        });
+
+        table.addGeneratedColumn("textfield", new Table.ColumnGenerator() {
+            public Component generateCell(Table source, Object itemId,
+                    Object columnId) {
+                return new TextField();
+            }
+        });
+
+        table.addGeneratedColumn("link", new Table.ColumnGenerator() {
+            public Component generateCell(Table source, Object itemId,
+                    Object columnId) {
+                return new Link("Link", null);
+            }
+        });
+
+        table.addGeneratedColumn("button", new Table.ColumnGenerator() {
+            public Component generateCell(Table source, Object itemId,
+                    Object columnId) {
+                return new Button("Button");
+            }
+        });
+
+        table.addItem();
+        table.addItem();
+        table.addItem();
+        addComponent(table);
+    }
+
+    @Override
+    protected String getDescription() {
+        return "Right clicking on an item without a context menu should bring"
+                + "up the Tables context menu";
+    }
+
+    @Override
+    protected Integer getTicketNumber() {
+        return 4264;
+    }
+
+}