Skip to content

Commit eea89d9

Browse files
committed
Improve QuickSearch behavior in OutlineView, TreeTable, and ListView.
Details: * For OutlineView and TreeTable, avoid changing the selection when pressing Escape. (TreeView already has the desired behavior.) * Add F3 and Ctrl/Command+G as alternative keystrokes for next-match (and Shift variants for previous-match). * Have QuickSearch select all when invoking Ctrl+F or alternative shortcuts while the search bar is already open. * Make the ListView's quick search box look more modern, and consistent with that of OutlineView/TreeView. Use the magnifying glass icon instead of the text 'Search', and use a flat border style. * Add more alternative find-next/previous keystrokes for ListView, like we did in QuickSearch (which is used by TreeView and OutlineView but not ListView). On Ctrl+F or F3, open the search bar, or select-all the existing text if it is already open. * In OutlineView and TreeTable, avoid backtracking to the first hit if backspace is pressed on a still-matching selection.
1 parent 4bc8045 commit eea89d9

6 files changed

Lines changed: 114 additions & 26 deletions

File tree

platform/o.n.swing.laf.flatlaf/src/org/netbeans/swing/laf/flatlaf/FlatLFCustoms.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ public Object[] createApplicationSpecificKeysAndValues() {
9191
EDITOR_TOOLBAR_BORDER, new CompoundBorder(DPISafeBorder.matte(0, 0, 1, 0, editorContentBorderColor), BorderFactory.createEmptyBorder(1, 0, 1, 0)),
9292
"NbExplorerView.quicksearch.border.instance",
9393
new CompoundBorder(DPISafeBorder.matte(1, 0, 0, 0, editorContentBorderColor), BorderFactory.createEmptyBorder(2, 6, 2, 2)),
94+
"NbExplorerView.quicksearch.listview.border.instance",
95+
new CompoundBorder(DPISafeBorder.matte(1, 1, 1, 1, editorContentBorderColor), BorderFactory.createEmptyBorder(2, 6, 2, 2)),
9496
EDITOR_TAB_CONTENT_BORDER, DPISafeBorder.matte(0, 1, 1, 1, editorContentBorderColor),
9597
VIEW_TAB_CONTENT_BORDER, DPISafeBorder.matte(0, 1, 1, 1, UIManager.getColor("TabbedContainer.view.contentBorderColor")), // NOI18N
9698

platform/openide.awt/src/org/openide/awt/QuickSearch.java

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ public class QuickSearch {
5252
@StaticResource
5353
private static final String ICON_FIND_WITH_MENU = "org/openide/awt/resources/quicksearch/findMenu.png"; // NOI18N
5454
private static final Object CLIENT_PROPERTY_KEY = new Object();
55-
55+
private static final KeyStroke NEXT_ALTERNATIVE_KEY_STROKE = Utilities.stringToKey("D-G");
56+
private static final KeyStroke PREVIOUS_ALTERNATIVE_KEY_STROKE = Utilities.stringToKey("DS-G");
57+
5658
private final JComponent component;
5759
private final Object constraints;
5860
private final Callback callback;
@@ -288,10 +290,12 @@ private void fireQuickSearchUpdate(String searchText) {
288290
}
289291

290292
private void fireShowNextSelection(boolean forward) {
291-
if (asynchronous) {
292-
rp.post(new LazyFire(QS_FIRE.NEXT, forward));
293-
} else {
294-
callback.showNextSelection(forward);
293+
if (hasSearchText) {
294+
if (asynchronous) {
295+
rp.post(new LazyFire(QS_FIRE.NEXT, forward));
296+
} else {
297+
callback.showNextSelection(forward);
298+
}
295299
}
296300
}
297301

@@ -354,6 +358,23 @@ public void keyTyped(KeyEvent e) {
354358
if(isAlwaysShown()) {
355359
displaySearchField();
356360
}
361+
component.getActionMap().put("openQuickSearch", new AbstractAction() {
362+
@Override
363+
public void actionPerformed(ActionEvent evt) {
364+
if (searchPanel != null && isEnabled()) {
365+
searchTextField.selectAll();
366+
return;
367+
}
368+
if (searchPanel != null || !isEnabled()) {
369+
return;
370+
}
371+
searchTextField.setText("");
372+
displaySearchField();
373+
}
374+
});
375+
InputMap im = component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
376+
im.put(Utilities.stringToKey("D-F"), "openQuickSearch");
377+
im.put(Utilities.stringToKey("F3"), "openQuickSearch");
357378
}
358379

359380
private void displaySearchField() {
@@ -678,7 +699,7 @@ public void run() {
678699
}
679700
);
680701
}
681-
702+
682703
@Override
683704
public boolean isManagingFocus() {
684705
return true;
@@ -707,9 +728,14 @@ public void processKeyEvent(KeyEvent ke) {
707728
} else {
708729
if (!hasSearchText) {
709730
int keyCode = ke.getKeyCode();
731+
KeyStroke ks = KeyStroke.getKeyStrokeForEvent(ke);
710732
if (keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_UP ||
711733
keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_RIGHT ||
712-
keyCode == KeyEvent.VK_TAB || keyCode == KeyEvent.VK_F3) {
734+
keyCode == KeyEvent.VK_TAB || keyCode == KeyEvent.VK_F3 ||
735+
keyCode == KeyEvent.VK_ENTER && ke.getModifiersEx() == KeyEvent.SHIFT_DOWN_MASK ||
736+
NEXT_ALTERNATIVE_KEY_STROKE.equals(ks) ||
737+
PREVIOUS_ALTERNATIVE_KEY_STROKE.equals(ks))
738+
{
713739
// Ignore movement events when search text was not set
714740
return ;
715741
}
@@ -748,6 +774,7 @@ public void removeUpdate(DocumentEvent e) {
748774
@Override
749775
public void keyPressed(KeyEvent e) {
750776
int keyCode = e.getKeyCode();
777+
KeyStroke ks = KeyStroke.getKeyStrokeForEvent(e);
751778

752779
if (keyCode == KeyEvent.VK_ESCAPE) {
753780
removeSearchField();
@@ -763,12 +790,22 @@ public void keyPressed(KeyEvent e) {
763790
callback.quickSearchCanceled();
764791
hasSearchText = false;
765792
e.consume();
766-
} else if (keyCode == KeyEvent.VK_UP || (keyCode == KeyEvent.VK_F3 && e.isShiftDown())) {
793+
} else if (keyCode == KeyEvent.VK_UP ||
794+
keyCode == KeyEvent.VK_F3 && e.getModifiersEx() == KeyEvent.SHIFT_DOWN_MASK ||
795+
keyCode == KeyEvent.VK_ENTER && e.getModifiersEx() == KeyEvent.SHIFT_DOWN_MASK ||
796+
PREVIOUS_ALTERNATIVE_KEY_STROKE.equals(ks))
797+
{
767798
fireShowNextSelection(false);
768799
// Stop processing the event here. Otherwise it's dispatched
769800
// to the tree too (which scrolls)
770801
e.consume();
771-
} else if (keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_F3) {
802+
} else if (keyCode == KeyEvent.VK_DOWN ||
803+
keyCode == KeyEvent.VK_F3 && e.getModifiersEx() == 0 ||
804+
/* We can't use ENTER to go to the next match, as this keystroke is used to
805+
invoke the row's default action. */
806+
/* keyCode == KeyEvent.VK_ENTER && e.getModifiersEx() == 0 || */
807+
NEXT_ALTERNATIVE_KEY_STROKE.equals(ks))
808+
{
772809
fireShowNextSelection(true);
773810
// Stop processing the event here. Otherwise it's dispatched
774811
// to the tree too (which scrolls)

platform/openide.explorer/nbproject/project.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@
2525
<data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
2626
<code-name-base>org.openide.explorer</code-name-base>
2727
<module-dependencies>
28+
<dependency>
29+
<code-name-base>org.netbeans.api.annotations.common</code-name-base>
30+
<build-prerequisite/>
31+
<compile-dependency/>
32+
<run-dependency>
33+
<release-version>1</release-version>
34+
<specification-version>1.13</specification-version>
35+
</run-dependency>
36+
</dependency>
2837
<dependency>
2938
<code-name-base>org.netbeans.swing.outline</code-name-base>
3039
<build-prerequisite/>

platform/openide.explorer/src/org/openide/explorer/view/ListView.java

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
import javax.swing.AbstractAction;
4646
import javax.swing.Action;
4747
import javax.swing.BorderFactory;
48-
import javax.swing.BoxLayout;
4948
import javax.swing.JComponent;
5049
import javax.swing.JLabel;
5150
import javax.swing.JList;
@@ -58,6 +57,8 @@
5857
import javax.swing.ListSelectionModel;
5958
import javax.swing.SwingUtilities;
6059
import javax.swing.ToolTipManager;
60+
import javax.swing.UIManager;
61+
import javax.swing.border.Border;
6162
import javax.swing.event.DocumentEvent;
6263
import javax.swing.event.DocumentListener;
6364
import javax.swing.event.ListDataEvent;
@@ -73,10 +74,11 @@
7374
import org.openide.nodes.Node.Property;
7475
import org.openide.nodes.NodeOp;
7576
import org.openide.util.Mutex;
76-
import org.openide.util.NbBundle;
7777
import org.openide.util.Utilities;
7878
import org.openide.util.WeakListeners;
7979
import org.openide.util.actions.CallbackSystemAction;
80+
import org.netbeans.api.annotations.common.StaticResource;
81+
import org.openide.util.ImageUtilities;
8082

8183
/** Explorer view to display items in a list.
8284
* <p>
@@ -112,6 +114,13 @@
112114
* @author Ian Formanek, Jan Jancura, Jaroslav Tulach
113115
*/
114116
public class ListView extends JScrollPane implements Externalizable {
117+
@StaticResource
118+
private static final String ICON_FIND = "org/openide/explorer/view/find.png"; // NOI18N
119+
private static final KeyStroke NEXT_ALTERNATIVE_KEY_STROKE = Utilities.stringToKey("D-G");
120+
private static final KeyStroke PREVIOUS_ALTERNATIVE_KEY_STROKE = Utilities.stringToKey("DS-G");
121+
private static final KeyStroke OPEN_SEARCH_KEY_STROKE = Utilities.stringToKey("D-F");
122+
private static final KeyStroke OPEN_SEARCH_ALTERNATIVE_KEY_STROKE = Utilities.stringToKey("F3");
123+
115124
/** generated Serialized Version UID */
116125
static final long serialVersionUID = -7540940974042262975L;
117126

@@ -1035,6 +1044,13 @@ private void setupSearch() {
10351044
public void keyPressed(KeyEvent e) {
10361045
int modifiers = e.getModifiers();
10371046
int keyCode = e.getKeyCode();
1047+
KeyStroke ks = KeyStroke.getKeyStrokeForEvent(e);
1048+
1049+
if (OPEN_SEARCH_KEY_STROKE.equals(ks) || OPEN_SEARCH_ALTERNATIVE_KEY_STROKE.equals(ks)) {
1050+
searchTextField.setText("");
1051+
displaySearchField();
1052+
return;
1053+
}
10381054

10391055
if (((modifiers > 0) && (modifiers != KeyEvent.SHIFT_MASK)) || e.isActionKey()) {
10401056
return;
@@ -1108,12 +1124,17 @@ private void prepareSearchPanel() {
11081124
if (searchpanel == null) {
11091125
searchpanel = new JPanel();
11101126

1111-
JLabel lbl = new JLabel(NbBundle.getMessage(TreeView.class, "LBL_QUICKSEARCH")); //NOI18N
1127+
JLabel lbl = new JLabel(ImageUtilities.loadImageIcon(ICON_FIND, false)); //NOI18N
11121128
searchpanel.setLayout(new BorderLayout(5, 0));
11131129
searchpanel.add(lbl, BorderLayout.WEST);
11141130
searchpanel.add(searchTextField, BorderLayout.CENTER);
11151131
lbl.setLabelFor(searchTextField);
1116-
searchpanel.setBorder(BorderFactory.createRaisedBevelBorder());
1132+
Border customBorder = UIManager.getBorder("NbExplorerView.quicksearch.listview.border.instance");
1133+
if (customBorder != null) {
1134+
searchpanel.setBorder(customBorder);
1135+
} else {
1136+
searchpanel.setBorder(BorderFactory.createRaisedBevelBorder());
1137+
}
11171138
}
11181139
}
11191140

@@ -1216,19 +1237,30 @@ public void removeUpdate(DocumentEvent e) {
12161237

12171238
@Override
12181239
public void keyPressed(KeyEvent e) {
1240+
// Recognize the same keystrokes as in org.openide.awt.QuickSearch.
12191241
int keyCode = e.getKeyCode();
1242+
KeyStroke ks = KeyStroke.getKeyStrokeForEvent(e);
12201243

12211244
if (keyCode == KeyEvent.VK_ESCAPE) {
12221245
removeSearchField();
12231246
NbList.this.requestFocus();
1224-
} else if (keyCode == KeyEvent.VK_UP) {
1247+
} else if (OPEN_SEARCH_KEY_STROKE.equals(ks) || OPEN_SEARCH_ALTERNATIVE_KEY_STROKE.equals(ks)) {
1248+
searchTextField.selectAll();
1249+
} else if (keyCode == KeyEvent.VK_UP ||
1250+
keyCode == KeyEvent.VK_F3 && e.getModifiersEx() == KeyEvent.SHIFT_DOWN_MASK ||
1251+
keyCode == KeyEvent.VK_ENTER && e.getModifiersEx() == KeyEvent.SHIFT_DOWN_MASK ||
1252+
PREVIOUS_ALTERNATIVE_KEY_STROKE.equals(ks))
1253+
{
12251254
currentSelectionIndex--;
12261255
displaySearchResult();
12271256

12281257
// Stop processing the event here. Otherwise it's dispatched
12291258
// to the tree too (which scrolls)
12301259
e.consume();
1231-
} else if (keyCode == KeyEvent.VK_DOWN) {
1260+
} else if (keyCode == KeyEvent.VK_DOWN ||
1261+
keyCode == KeyEvent.VK_F3 && e.getModifiersEx() == 0 ||
1262+
NEXT_ALTERNATIVE_KEY_STROKE.equals(ks))
1263+
{
12321264
currentSelectionIndex++;
12331265
displaySearchResult();
12341266

platform/openide.explorer/src/org/openide/explorer/view/TableQuickSearchSupport.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,10 @@ public void quickSearchUpdate(String searchText) {
8787
quickSearchInitialColumn = 0;
8888
}
8989
}
90-
quickSearchLastRow = quickSearchInitialRow;
91-
quickSearchLastColumn = quickSearchInitialColumn;
90+
if (quickSearchLastRow < 0 || quickSearchLastColumn < 0) {
91+
quickSearchLastRow = quickSearchInitialRow;
92+
quickSearchLastColumn = quickSearchInitialColumn;
93+
}
9294
doSearch(searchText, true);
9395
}
9496

@@ -152,14 +154,18 @@ public void quickSearchConfirmed() {
152154
quickSearchInitialColumn = -1;
153155
}
154156

157+
private static final boolean RESTORE_PRIOR_SELECTION_AFTER_QUICK_SEARCH = false;
158+
155159
@Override
156160
public void quickSearchCanceled() {
157161
// Check whether the cancel was explicit or implicit.
158162
// Implicit cancel has undefined focus owner
159163
// TODO: After switch to JDK 8, we can e.g. add a static method to Callback providing the info.
160-
Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
161-
if (focusOwner != null) {
162-
displaySearchResult(quickSearchInitialRow, quickSearchInitialColumn);
164+
if (RESTORE_PRIOR_SELECTION_AFTER_QUICK_SEARCH) {
165+
Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
166+
if (focusOwner != null) {
167+
displaySearchResult(quickSearchInitialRow, quickSearchInitialColumn);
168+
}
163169
}
164170
quickSearchInitialRow = -1;
165171
quickSearchInitialColumn = -1;

platform/openide.explorer/src/org/openide/explorer/view/TreeView.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1957,12 +1957,14 @@ public void quickSearchUpdate(String searchText) {
19571957

19581958
@Override
19591959
public void showNextSelection(boolean forward) {
1960-
if (forward) {
1961-
currentSelectionIndex++;
1962-
} else {
1963-
currentSelectionIndex--;
1960+
if (lastSearchText != null) {
1961+
if (forward) {
1962+
currentSelectionIndex++;
1963+
} else {
1964+
currentSelectionIndex--;
1965+
}
1966+
displaySearchResult();
19641967
}
1965-
displaySearchResult();
19661968
}
19671969

19681970
@Override
@@ -2096,7 +2098,7 @@ private void displaySearchResult() {
20962098
setSelectionPath(path);
20972099
scrollPathToVisible(path);
20982100
} else {
2099-
if (lastSearchText.isEmpty() && origSelectionPaths != null) {
2101+
if ((lastSearchText == null || lastSearchText.isEmpty()) && origSelectionPaths != null) {
21002102
setSelectionPaths(origSelectionPaths);
21012103
scrollPathToVisible(origSelectionPaths[0]);
21022104
} else {

0 commit comments

Comments
 (0)