5353import javax .swing .BoxLayout ;
5454import javax .swing .DefaultListModel ;
5555import javax .swing .ImageIcon ;
56+ import javax .swing .JButton ;
5657import javax .swing .JDialog ;
5758import javax .swing .JLabel ;
5859import javax .swing .JList ;
6162import javax .swing .JSplitPane ;
6263import javax .swing .JTextField ;
6364import javax .swing .ScrollPaneConstants ;
65+ import javax .swing .SwingConstants ;
6466import javax .swing .border .CompoundBorder ;
6567import javax .swing .border .EmptyBorder ;
68+ import javax .swing .border .LineBorder ;
6669import javax .swing .border .MatteBorder ;
6770import javax .swing .event .DocumentEvent ;
6871import javax .swing .event .DocumentListener ;
6972
73+ import net .miginfocom .swing .MigLayout ;
74+
7075import org .scijava .Context ;
7176import org .scijava .Priority ;
7277import 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