Skip to content

Commit ce0898c

Browse files
committed
SwingSearchBar: better selection, populate details
1 parent ca0b877 commit ce0898c

File tree

1 file changed

+87
-9
lines changed

1 file changed

+87
-9
lines changed

src/main/java/org/scijava/ui/swing/search/SwingSearchBar.java

Lines changed: 87 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import javax.swing.BoxLayout;
5454
import javax.swing.DefaultListModel;
5555
import javax.swing.ImageIcon;
56+
import javax.swing.JButton;
5657
import javax.swing.JDialog;
5758
import javax.swing.JLabel;
5859
import javax.swing.JList;
@@ -61,12 +62,16 @@
6162
import javax.swing.JSplitPane;
6263
import javax.swing.JTextField;
6364
import javax.swing.ScrollPaneConstants;
65+
import javax.swing.SwingConstants;
6466
import javax.swing.border.CompoundBorder;
6567
import javax.swing.border.EmptyBorder;
68+
import javax.swing.border.LineBorder;
6669
import javax.swing.border.MatteBorder;
6770
import javax.swing.event.DocumentEvent;
6871
import javax.swing.event.DocumentListener;
6972

73+
import net.miginfocom.swing.MigLayout;
74+
7075
import org.scijava.Context;
7176
import org.scijava.Priority;
7277
import org.scijava.plugin.Parameter;
@@ -161,7 +166,7 @@ public void changedUpdate(final DocumentEvent e) {
161166

162167
@Override
163168
public void focusGained(final FocusEvent e) {
164-
setText("");
169+
if (DEFAULT_MESSAGE.equals(getText())) setText("");
165170
}
166171

167172
@Override
@@ -174,7 +179,10 @@ public void focusLost(final FocusEvent e) {
174179

175180
/** Called externally to bring the search bar into focus. */
176181
public void activate() {
177-
requestFocus();
182+
threadService.queue(() -> {
183+
setText("");
184+
requestFocus();
185+
});
178186
}
179187

180188
// -- Utility methods --
@@ -270,6 +278,7 @@ public SwingSearchPanel() {
270278
event -> threadService.queue(() -> update(event)));
271279

272280
allResults = new HashMap<>();
281+
273282
resultsList = new JList<>();
274283
resultsList.setCellRenderer((list, value, index, isSelected,
275284
cellHasFocus) -> {
@@ -291,18 +300,70 @@ public SwingSearchPanel() {
291300
item.setBackground(isSelected ? SELECTED_COLOR : list.getBackground());
292301
return item;
293302
});
303+
resultsList.setBorder(new EmptyBorder(0, 0, PAD, 0));
304+
final JScrollPane resultsPane = new JScrollPane(resultsList);
305+
resultsPane.setHorizontalScrollBarPolicy(
306+
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
307+
308+
final JPanel detailsPane = new JPanel();
309+
final JLabel detailsTitle = new JLabel();
310+
detailsTitle.setHorizontalAlignment(SwingConstants.CENTER);
311+
final JPanel detailsProps = new JPanel();
312+
detailsProps.setLayout(new MigLayout("fillx,wrap 2", "[50%|50%]"));
313+
final JPanel detailsButtons = new JPanel();
314+
detailsButtons.setLayout(new BoxLayout(detailsButtons, BoxLayout.X_AXIS));
315+
detailsTitle.setAlignmentX(0.5f);
316+
detailsProps.setAlignmentX(0.5f);
317+
detailsButtons.setAlignmentX(0.5f);
318+
319+
detailsPane.setLayout(new BoxLayout(detailsPane, BoxLayout.Y_AXIS));
320+
detailsPane.add(detailsTitle);
321+
detailsPane.add(detailsProps);
322+
detailsPane.add(detailsButtons);
323+
// DEBUG
324+
detailsTitle.setBorder(new LineBorder(Color.red, 1));
325+
detailsProps.setBorder(new LineBorder(Color.red, 1));
326+
detailsButtons.setBorder(new LineBorder(Color.red, 1));
327+
328+
resultsList.addListSelectionListener(lse -> {
329+
if (lse.getValueIsAdjusting()) return;
330+
final SearchResult result = resultsList.getSelectedValue();
331+
if (result == null) {
332+
// clear details pane
333+
detailsTitle.setText("");
334+
detailsProps.removeAll();
335+
detailsButtons.removeAll();
336+
}
337+
else {
338+
// populate details pane
339+
detailsTitle.setText("<html><h2>" + result.name() + "</h2>");
340+
detailsProps.removeAll();
341+
result.properties().forEach((k, v) -> {
342+
final JLabel kLabel = new JLabel(
343+
"<html><strong style=\"color: gray\">" + k + "</strong>");
344+
kLabel.setHorizontalAlignment(SwingConstants.RIGHT);
345+
detailsProps.add(kLabel);
346+
detailsProps.add(new JLabel(v));
347+
});
348+
detailsButtons.removeAll();
349+
final List<SearchAction> actions = searchService.actions(result);
350+
actions.forEach(action -> {
351+
final JButton button = new JButton(action.toString());
352+
button.addActionListener(ae -> {
353+
action.run();
354+
reset();
355+
});
356+
detailsButtons.add(button);
357+
});
358+
}
359+
});
294360

361+
setLayout(new BorderLayout());
295362
setPreferredSize(new Dimension(800, 300));
296363

297364
final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
298-
resultsList.setBorder(new EmptyBorder(0, 0, PAD, 0));
299-
final JScrollPane resultsPane = new JScrollPane(resultsList);
300-
resultsPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
301-
final JPanel detailsPane = new JPanel();
302365
splitPane.setLeftComponent(resultsPane);
303366
splitPane.setRightComponent(detailsPane);
304-
305-
setLayout(new BorderLayout());
306367
add(splitPane, BorderLayout.CENTER);
307368
}
308369

@@ -312,6 +373,7 @@ public void search(final String text) {
312373

313374
// -- Helper methods --
314375

376+
/** Called whenever a new batch of search results comes in. */
315377
private void update(final SearchEvent event) {
316378
assertDispatchThread();
317379
allResults.put(event.searcher().getClass(), event);
@@ -349,18 +411,22 @@ private void execute() {
349411
assertDispatchThread();
350412
final SearchResult result = resultsList.getSelectedValue();
351413
if (result == null) return;
352-
List<SearchAction> actions = searchService.actions(result);
414+
final List<SearchAction> actions = searchService.actions(result);
353415
if (actions.isEmpty()) return;
354416
threadService.run(() -> actions.get(0).run());
355417
}
356418

357419
private void rebuild() {
358420
assertDispatchThread();
421+
422+
final SearchResult previous = resultsList.getSelectedValue();
423+
359424
// Gets list of Searchers, sorted by priority.
360425
final List<Searcher> searchers = allResults.values().stream().map(
361426
event -> event.searcher()).collect(Collectors.toList());
362427
sort(searchers, Searcher.class);
363428

429+
// Build the new list model.
364430
DefaultListModel<SearchResult> listModel = new DefaultListModel<>();
365431
for (final Searcher searcher : searchers) {
366432
// Add section header.
@@ -372,6 +438,10 @@ private void rebuild() {
372438
}
373439
}
374440
resultsList.setModel(listModel);
441+
442+
// TODO: Improve retainment of previous selection.
443+
if (previous == null) resultsList.setSelectedIndex(firstResultIndex());
444+
else resultsList.setSelectedValue(previous, true);
375445
}
376446

377447
private List<SearchResult> results(final Searcher searcher) {
@@ -408,6 +478,14 @@ private SearchResult result(final int index) {
408478
return resultsList.getModel().getElementAt(index);
409479
}
410480

481+
private int firstResultIndex() {
482+
assertDispatchThread();
483+
for (int i = 0; i < resultsList.getModel().getSize(); i++) {
484+
if (!isHeader(result(i))) return i;
485+
}
486+
return -1;
487+
}
488+
411489
private void select(final int i) {
412490
assertDispatchThread();
413491
resultsList.setSelectedIndex(i);

0 commit comments

Comments
 (0)