diff options
author | Siva Velusamy <vsiva@google.com> | 2015-07-09 18:08:10 -0700 |
---|---|---|
committer | Deepanshu Gupta <deepanshu@google.com> | 2015-07-13 17:39:17 -0700 |
commit | a14d4f97d06117b46831345e15b651f29ad55331 (patch) | |
tree | 5a23e513fa85481f859106c3c919f0a4cda727d3 /designer | |
parent | 93f10fa0464e004ed7bb220fd5c19f9d8b34a166 (diff) | |
download | idea-a14d4f97d06117b46831345e15b651f29ad55331.tar.gz |
property sheet: Improve keyboard focus handling
- Pressing Enter key will expand/collapse a node if it
has children, start editing the value otherwise.
- an editor.activate() allows editors to do something custom:
e.g. a boolean editor would just change the state
- pressing space will change the property value only if
it is a boolean property
- Pressing the right/left arrows will also behave just like
a tree would
Change-Id: I4c91d77e2ccfbdf4a1228c5ce5d7c4d5698ef577
(cherry picked from commit f476896c9e14ee1cfdf2c4e8a49d4b8fa85c4e6b)
Diffstat (limited to 'designer')
11 files changed, 276 insertions, 17 deletions
diff --git a/designer/src/com/android/tools/idea/uibuilder/property/NlPropertiesPanel.java b/designer/src/com/android/tools/idea/uibuilder/property/NlPropertiesPanel.java index 55ebcc6aea1..8774e7a13dc 100644 --- a/designer/src/com/android/tools/idea/uibuilder/property/NlPropertiesPanel.java +++ b/designer/src/com/android/tools/idea/uibuilder/property/NlPropertiesPanel.java @@ -123,6 +123,9 @@ public class NlPropertiesPanel extends JPanel implements DesignSurfaceListener { if (first != null) { mySelectedComponentLabel.setText(first.getTagName()); } + else { + mySelectedComponentLabel.setText(""); + } myTable.setPaintBusy(false); } }); diff --git a/designer/src/com/android/tools/idea/uibuilder/property/NlProperty.java b/designer/src/com/android/tools/idea/uibuilder/property/NlProperty.java index fc928d61336..39a75b71d3a 100644 --- a/designer/src/com/android/tools/idea/uibuilder/property/NlProperty.java +++ b/designer/src/com/android/tools/idea/uibuilder/property/NlProperty.java @@ -17,6 +17,7 @@ package com.android.tools.idea.uibuilder.property; import com.android.SdkConstants; import com.android.tools.idea.uibuilder.model.NlComponent; +import com.android.tools.idea.uibuilder.property.ptable.PTableCellEditor; import com.android.tools.idea.uibuilder.property.ptable.PTableItem; import com.android.tools.idea.uibuilder.property.editors.NlPropertyEditors; import com.android.tools.idea.uibuilder.property.renderer.NlPropertyRenderers; @@ -120,7 +121,7 @@ public class NlProperty extends PTableItem { } @Override - public TableCellEditor getCellEditor() { + public PTableCellEditor getCellEditor() { return NlPropertyEditors.get(this); } diff --git a/designer/src/com/android/tools/idea/uibuilder/property/editors/NlBooleanEditor.java b/designer/src/com/android/tools/idea/uibuilder/property/editors/NlBooleanEditor.java index 9303970fe13..476e175679e 100644 --- a/designer/src/com/android/tools/idea/uibuilder/property/editors/NlBooleanEditor.java +++ b/designer/src/com/android/tools/idea/uibuilder/property/editors/NlBooleanEditor.java @@ -16,6 +16,7 @@ package com.android.tools.idea.uibuilder.property.editors; import com.android.tools.idea.uibuilder.property.NlProperty; +import com.android.tools.idea.uibuilder.property.ptable.PTableCellEditor; import com.android.tools.idea.uibuilder.property.renderer.NlBooleanRenderer; import com.intellij.openapi.ui.FixedSizeButton; import com.intellij.openapi.util.SystemInfo; @@ -30,7 +31,7 @@ import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -public class NlBooleanEditor extends AbstractTableCellEditor implements ActionListener { +public class NlBooleanEditor extends PTableCellEditor implements ActionListener { private final JPanel myPanel; private final FixedSizeButton myBrowseButton; private final ThreeStateCheckBox myCheckbox; @@ -98,4 +99,15 @@ public class NlBooleanEditor extends AbstractTableCellEditor implements ActionLi } } } + + @Override + public void activate() { + myValue = NlBooleanRenderer.getNextState(myCheckbox.getState()); + stopCellEditing(); + } + + @Override + public boolean isBooleanEditor() { + return true; + } } diff --git a/designer/src/com/android/tools/idea/uibuilder/property/editors/NlEnumEditor.java b/designer/src/com/android/tools/idea/uibuilder/property/editors/NlEnumEditor.java index 1eae24c874e..870046abb7b 100644 --- a/designer/src/com/android/tools/idea/uibuilder/property/editors/NlEnumEditor.java +++ b/designer/src/com/android/tools/idea/uibuilder/property/editors/NlEnumEditor.java @@ -16,6 +16,7 @@ package com.android.tools.idea.uibuilder.property.editors; import com.android.tools.idea.uibuilder.property.NlProperty; +import com.android.tools.idea.uibuilder.property.ptable.PTableCellEditor; import com.intellij.openapi.ui.FixedSizeButton; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.text.StringUtil; @@ -30,8 +31,9 @@ import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; -public class NlEnumEditor extends AbstractTableCellEditor implements ActionListener { +public class NlEnumEditor extends PTableCellEditor implements ActionListener { private static final String UNSET = "<unset>"; private final JPanel myPanel; @@ -96,7 +98,15 @@ public class NlEnumEditor extends AbstractTableCellEditor implements ActionListe } else if (e.getSource() == myCombo) { myValue = myCombo.getModel().getSelectedItem(); - stopCellEditing(); + // stop cell editing only if a value has been picked from the combo box, not for every event from the combo + if ("comboBoxEdited".equals(e.getActionCommand())) { + stopCellEditing(); + } } } + + @Override + public void activate() { + myCombo.showPopup(); + } } diff --git a/designer/src/com/android/tools/idea/uibuilder/property/editors/NlPropertyEditors.java b/designer/src/com/android/tools/idea/uibuilder/property/editors/NlPropertyEditors.java index d93019b01ef..763ff3ecf76 100644 --- a/designer/src/com/android/tools/idea/uibuilder/property/editors/NlPropertyEditors.java +++ b/designer/src/com/android/tools/idea/uibuilder/property/editors/NlPropertyEditors.java @@ -16,6 +16,7 @@ package com.android.tools.idea.uibuilder.property.editors; import com.android.tools.idea.uibuilder.property.NlProperty; +import com.android.tools.idea.uibuilder.property.ptable.PTableCellEditor; import com.intellij.openapi.project.Project; import org.jetbrains.android.dom.attrs.AttributeDefinition; import org.jetbrains.android.dom.attrs.AttributeFormat; @@ -29,7 +30,7 @@ public class NlPropertyEditors { private static NlEnumEditor ourComboEditor; private static NlReferenceEditor ourDefaultEditor; - public static TableCellEditor get(@NotNull NlProperty property) { + public static PTableCellEditor get(@NotNull NlProperty property) { AttributeDefinition definition = property.getDefinition(); if (definition == null) { // TODO: default to text editor @@ -53,7 +54,7 @@ public class NlPropertyEditors { return getDefaultEditor(property.getComponent().getModel().getProject()); } - private static TableCellEditor getBooleanEditor() { + private static PTableCellEditor getBooleanEditor() { if (ourBooleanEditor == null) { ourBooleanEditor = new NlBooleanEditor(); } @@ -61,7 +62,7 @@ public class NlPropertyEditors { return ourBooleanEditor; } - private static TableCellEditor getComboEditor() { + private static PTableCellEditor getComboEditor() { if (ourComboEditor == null) { ourComboEditor = new NlEnumEditor(); } @@ -69,7 +70,7 @@ public class NlPropertyEditors { return ourComboEditor; } - private static TableCellEditor getDefaultEditor(Project project) { + private static PTableCellEditor getDefaultEditor(Project project) { if (ourDefaultEditor == null) { ourDefaultEditor = new NlReferenceEditor(project); } diff --git a/designer/src/com/android/tools/idea/uibuilder/property/editors/NlReferenceEditor.java b/designer/src/com/android/tools/idea/uibuilder/property/editors/NlReferenceEditor.java index 0b6122d5f2f..53e2d0c60a5 100644 --- a/designer/src/com/android/tools/idea/uibuilder/property/editors/NlReferenceEditor.java +++ b/designer/src/com/android/tools/idea/uibuilder/property/editors/NlReferenceEditor.java @@ -18,6 +18,7 @@ package com.android.tools.idea.uibuilder.property.editors; import com.android.SdkConstants; import com.android.resources.ResourceType; import com.android.tools.idea.uibuilder.property.NlProperty; +import com.android.tools.idea.uibuilder.property.ptable.PTableCellEditor; import com.android.tools.idea.uibuilder.property.renderer.NlDefaultRenderer; import com.google.common.collect.Lists; import com.intellij.android.designer.propertyTable.editors.ResourceEditor; @@ -53,7 +54,7 @@ import java.awt.event.*; import java.util.*; import java.util.List; -public class NlReferenceEditor extends AbstractTableCellEditor implements ActionListener { +public class NlReferenceEditor extends PTableCellEditor implements ActionListener { private final JPanel myPanel; private final JBLabel myLabel; private final TextFieldWithAutoCompletion myTextFieldWithAutoCompletion; diff --git a/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTable.java b/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTable.java index bd215844b77..8eb3c15c63a 100644 --- a/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTable.java +++ b/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTable.java @@ -16,21 +16,30 @@ package com.android.tools.idea.uibuilder.property.ptable; import com.android.tools.idea.uibuilder.property.ptable.renderers.PNameRenderer; +import com.intellij.designer.model.Property; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy; import com.intellij.ui.Cell; import com.intellij.ui.TableSpeedSearch; +import com.intellij.ui.TableUtil; import com.intellij.ui.table.JBTable; import com.intellij.util.PairFunction; import com.intellij.util.containers.Convertor; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; +import javax.swing.plaf.TableUI; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableModel; import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.util.*; public class PTable extends JBTable { private final PNameRenderer myNameRenderer = new PNameRenderer(); @@ -109,7 +118,169 @@ public class PTable extends JBTable { return row == myMouseHoverRow && col == myMouseHoverCol; } - // Expand/Collapse group items if necessary + @Override + public void setUI(TableUI ui) { + super.setUI(ui); + + // Setup focus traversal keys such that tab takes focus out of the table + setFocusTraversalKeys( + KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, + KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS)); + setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, KeyboardFocusManager.getCurrentKeyboardFocusManager() + .getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS)); + + // Customize keymaps. See https://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html for info on how this works, but the + // summary is that we set an input map mapping key bindings to a string, and an action map that maps those strings to specific actions. + ActionMap actionMap = getActionMap(); + InputMap focusedInputMap = getInputMap(JComponent.WHEN_FOCUSED); + InputMap ancestorInputMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + + focusedInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "smartEnter"); + ancestorInputMap.remove(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)); + actionMap.put("smartEnter", new MyEnterAction(false)); + + focusedInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "toggleEditor"); + ancestorInputMap.remove(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)); + actionMap.put("toggleEditor", new MyEnterAction(true)); + + ancestorInputMap.remove(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0)); + ancestorInputMap.remove(KeyStroke.getKeyStroke(KeyEvent.VK_KP_RIGHT, 0)); + focusedInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "expandCurrentRight"); + focusedInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_KP_RIGHT, 0), "expandCurrentRight"); + actionMap.put("expandCurrentRight", new MyExpandCurrentAction(true)); + + ancestorInputMap.remove(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)); + ancestorInputMap.remove(KeyStroke.getKeyStroke(KeyEvent.VK_KP_LEFT, 0)); + focusedInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "collapseCurrentLeft"); + focusedInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_KP_LEFT, 0), "collapseCurrentLeft"); + actionMap.put("collapseCurrentLeft", new MyExpandCurrentAction(false)); + } + + private void toggleTreeNode(int row) { + PTableItem item = (PTableItem)myModel.getValueAt(row, 0); + if (item.isExpanded()) { + myModel.collapse(row); + } + else { + myModel.expand(row); + } + } + + private void selectRow(int row) { + getSelectionModel().setSelectionInterval(row, row); + TableUtil.scrollSelectionToVisible(this); + } + + private void quickEdit(int row) { + final PTableCellEditor editor = ((PTableItem)myModel.getValueAt(row, 0)).getCellEditor(); + if (editor == null) { + return; + } + + // only perform edit if we know the editor is capable of a quick toggle action. + // We know that boolean editors switch their state and finish editing right away + if (editor.isBooleanEditor()) { + startEditing(row); + } + } + + private void startEditing(int row) { + final PTableCellEditor editor = ((PTableItem)myModel.getValueAt(row, 0)).getCellEditor(); + if (editor == null) { + return; + } + + editCellAt(row, 1); + + final JComponent preferredComponent = getComponentToFocus(editor); + if (preferredComponent == null) return; + + IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(new Runnable() { + @Override + public void run() { + preferredComponent.requestFocusInWindow(); + editor.activate(); + } + }); + } + + @Nullable + private JComponent getComponentToFocus(PTableCellEditor editor) { + JComponent preferredComponent = editor.getPreferredFocusComponent(); + if (preferredComponent == null) { + preferredComponent = IdeFocusTraversalPolicy.getPreferredFocusedComponent((JComponent)editorComp); + } + if (preferredComponent == null) { + return null; + } + return preferredComponent; + } + + // Expand/Collapse if it is a group property, start editing otherwise + private class MyEnterAction extends AbstractAction { + // don't launch a full editor, just perform a quick toggle + private final boolean myToggleOnly; + + public MyEnterAction(boolean toggleOnly) { + myToggleOnly = toggleOnly; + } + + @Override + public void actionPerformed(ActionEvent e) { + int selectedRow = getSelectedRow(); + if (isEditing() || selectedRow == -1) { + return; + } + + PTableItem item = (PTableItem)myModel.getValueAt(selectedRow, 0); + if (item.hasChildren()) { + toggleTreeNode(selectedRow); + selectRow(selectedRow); + } + else if (myToggleOnly) { + quickEdit(selectedRow); + } + else { + startEditing(selectedRow); + } + } + } + + // Expand/Collapse items on right/left key press + private class MyExpandCurrentAction extends AbstractAction { + private final boolean myExpand; + + public MyExpandCurrentAction(boolean expand) { + myExpand = expand; + } + + @Override + public void actionPerformed(ActionEvent e) { + int selectedRow = getSelectedRow(); + if (isEditing() || selectedRow == -1) { + return; + } + + PTableItem item = (PTableItem)myModel.getValueAt(selectedRow, 0); + if (myExpand) { + if (item.hasChildren() && !item.isExpanded()) { + myModel.expand(selectedRow); + selectRow(selectedRow); + } + } + else { + if (item.isExpanded()) { // if it is a compound node, collapse it + myModel.collapse(selectedRow); + selectRow(selectedRow); + } + else if (item.getParent() != null) { // if it is a child node, move selection to the parent + selectRow(myModel.getParent(selectedRow)); + } + } + } + } + + // Expand/Collapse group items on mouse click private class MouseTableListener extends MouseAdapter { @Override public void mousePressed(MouseEvent e) { @@ -131,12 +302,7 @@ public class PTable extends JBTable { return; } - if (item.isExpanded()) { - myModel.collapse(row); - } - else { - myModel.expand(row); - } + toggleTreeNode(row); } } diff --git a/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableCellEditor.java b/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableCellEditor.java new file mode 100644 index 00000000000..d02b42635f9 --- /dev/null +++ b/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableCellEditor.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2015 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.android.tools.idea.uibuilder.property.ptable; + +import com.intellij.util.ui.AbstractTableCellEditor; + +import javax.swing.*; + +public abstract class PTableCellEditor extends AbstractTableCellEditor { + public JComponent getPreferredFocusComponent() { + return null; + } + + public boolean isBooleanEditor() { + return false; + } + + public void activate() { + } +} diff --git a/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableItem.java b/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableItem.java index 627a3639bde..a053684d013 100644 --- a/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableItem.java +++ b/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableItem.java @@ -65,11 +65,13 @@ public abstract class PTableItem { public void setValue(Object value) { } + @Nullable public String getTooltipText() { return null; } - public TableCellEditor getCellEditor() { + @Nullable + public PTableCellEditor getCellEditor() { return null; } diff --git a/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableModel.java b/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableModel.java index 5cd9f280dcf..8c330ab4655 100644 --- a/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableModel.java +++ b/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableModel.java @@ -77,6 +77,24 @@ public class PTableModel extends AbstractTableModel { fireTableDataChanged(); } + public int getParent(int row) { + if (row >= myItems.size()) { + return row; + } + + PTableItem item = myItems.get(row); + if (item.getParent() == null) { + return row; + } + + PTableItem parent = item.getParent(); + do { + row--; + } + while (row >= 0 && myItems.get(row) != parent); + return row; + } + public void expand(int row) { if (row >= myItems.size()) { return; diff --git a/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlBooleanRenderer.java b/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlBooleanRenderer.java index d0c841c56d8..8bea0238c57 100644 --- a/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlBooleanRenderer.java +++ b/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlBooleanRenderer.java @@ -104,6 +104,18 @@ public class NlBooleanRenderer extends NlAttributeRenderer { } } + public static Boolean getNextState(ThreeStateCheckBox.State state) { + switch (state) { + case DONT_CARE: + return Boolean.TRUE; + case SELECTED: + return Boolean.FALSE; + case NOT_SELECTED: + default: + return null; + } + } + @Override public boolean canRender(@NotNull NlProperty p, @NotNull Set<AttributeFormat> formats) { return formats.contains(AttributeFormat.Boolean); |