Skip to content

Commit d41f4f7

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 d41f4f7

File tree

6 files changed

+115
-26
lines changed

6 files changed

+115
-26
lines changed

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: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.netbeans.api.annotations.common.StaticResource;
3737
import org.openide.util.ImageUtilities;
3838
import org.openide.util.RequestProcessor;
39+
import org.openide.util.Utilities;
3940

4041
/**
4142
* Quick search infrastructure for an arbitrary component.
@@ -52,7 +53,9 @@ public class QuickSearch {
5253
@StaticResource
5354
private static final String ICON_FIND_WITH_MENU = "org/openide/awt/resources/quicksearch/findMenu.png"; // NOI18N
5455
private static final Object CLIENT_PROPERTY_KEY = new Object();
55-
56+
private static final KeyStroke NEXT_ALTERNATIVE_KEY_STROKE = Utilities.stringToKey("D-G");
57+
private static final KeyStroke PREVIOUS_ALTERNATIVE_KEY_STROKE = Utilities.stringToKey("DS-G");
58+
5659
private final JComponent component;
5760
private final Object constraints;
5861
private final Callback callback;
@@ -288,10 +291,12 @@ private void fireQuickSearchUpdate(String searchText) {
288291
}
289292

290293
private void fireShowNextSelection(boolean forward) {
291-
if (asynchronous) {
292-
rp.post(new LazyFire(QS_FIRE.NEXT, forward));
293-
} else {
294-
callback.showNextSelection(forward);
294+
if (hasSearchText) {
295+
if (asynchronous) {
296+
rp.post(new LazyFire(QS_FIRE.NEXT, forward));
297+
} else {
298+
callback.showNextSelection(forward);
299+
}
295300
}
296301
}
297302

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

359381
private void displaySearchField() {
@@ -678,7 +700,7 @@ public void run() {
678700
}
679701
);
680702
}
681-
703+
682704
@Override
683705
public boolean isManagingFocus() {
684706
return true;
@@ -707,9 +729,14 @@ public void processKeyEvent(KeyEvent ke) {
707729
} else {
708730
if (!hasSearchText) {
709731
int keyCode = ke.getKeyCode();
732+
KeyStroke ks = KeyStroke.getKeyStrokeForEvent(ke);
710733
if (keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_UP ||
711734
keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_RIGHT ||
712-
keyCode == KeyEvent.VK_TAB || keyCode == KeyEvent.VK_F3) {
735+
keyCode == KeyEvent.VK_TAB || keyCode == KeyEvent.VK_F3 ||
736+
keyCode == KeyEvent.VK_ENTER && ke.getModifiersEx() == KeyEvent.SHIFT_DOWN_MASK ||
737+
NEXT_ALTERNATIVE_KEY_STROKE.equals(ks) ||
738+
PREVIOUS_ALTERNATIVE_KEY_STROKE.equals(ks))
739+
{
713740
// Ignore movement events when search text was not set
714741
return ;
715742
}
@@ -748,6 +775,7 @@ public void removeUpdate(DocumentEvent e) {
748775
@Override
749776
public void keyPressed(KeyEvent e) {
750777
int keyCode = e.getKeyCode();
778+
KeyStroke ks = KeyStroke.getKeyStrokeForEvent(e);
751779

752780
if (keyCode == KeyEvent.VK_ESCAPE) {
753781
removeSearchField();
@@ -763,12 +791,22 @@ public void keyPressed(KeyEvent e) {
763791
callback.quickSearchCanceled();
764792
hasSearchText = false;
765793
e.consume();
766-
} else if (keyCode == KeyEvent.VK_UP || (keyCode == KeyEvent.VK_F3 && e.isShiftDown())) {
794+
} else if (keyCode == KeyEvent.VK_UP ||
795+
keyCode == KeyEvent.VK_F3 && e.getModifiersEx() == KeyEvent.SHIFT_DOWN_MASK ||
796+
keyCode == KeyEvent.VK_ENTER && e.getModifiersEx() == KeyEvent.SHIFT_DOWN_MASK ||
797+
PREVIOUS_ALTERNATIVE_KEY_STROKE.equals(ks))
798+
{
767799
fireShowNextSelection(false);
768800
// Stop processing the event here. Otherwise it's dispatched
769801
// to the tree too (which scrolls)
770802
e.consume();
771-
} else if (keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_F3) {
803+
} else if (keyCode == KeyEvent.VK_DOWN ||
804+
keyCode == KeyEvent.VK_F3 && e.getModifiersEx() == 0 ||
805+
/* We can't use ENTER to go to the next match, as this keystroke is used to
806+
invoke the row's default action. */
807+
/* keyCode == KeyEvent.VK_ENTER && e.getModifiersEx() == 0 || */
808+
NEXT_ALTERNATIVE_KEY_STROKE.equals(ks))
809+
{
772810
fireShowNextSelection(true);
773811
// Stop processing the event here. Otherwise it's dispatched
774812
// 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)