2
0

Identicon support and UI improvements.

This commit is contained in:
Maarten Billemont 2018-07-23 11:23:26 -04:00
parent 400ebe59db
commit 16cdcda94b
7 changed files with 192 additions and 54 deletions

View File

@ -27,6 +27,7 @@ import com.lyndir.lhunath.opal.system.logging.Logger;
import java.nio.*; import java.nio.*;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Arrays; import java.util.Arrays;
import java.util.Locale;
/** /**
@ -83,6 +84,10 @@ public class MPIdenticon {
return text; return text;
} }
public String getHTML() {
return strf( "<span style='color: %s'>%s</span>", color.getCSS(), text );
}
public Color getColor() { public Color getColor() {
return color; return color;
} }
@ -94,6 +99,15 @@ public class MPIdenticon {
BLUE, BLUE,
MAGENTA, MAGENTA,
CYAN, CYAN,
MONO MONO {
@Override
public String getCSS() {
return "inherit";
}
};
public String getCSS() {
return name().toLowerCase( Locale.ROOT );
}
} }
} }

View File

@ -180,20 +180,20 @@ public abstract class Res {
public static class Fonts { public static class Fonts {
public Font emoticonsFont() { public Font emoticonsFont(final float size) {
return emoticonsRegular(); return emoticonsRegular().deriveFont( size );
} }
public Font controlFont() { public Font controlFont(final float size) {
return exoRegular(); return exoRegular().deriveFont( size );
} }
public Font valueFont() { public Font valueFont(final float size) {
return sourceSansProRegular(); return sourceSansProRegular().deriveFont( size );
} }
public Font bigValueFont() { public Font bigValueFont(final float size) {
return sourceSansProBlack(); return sourceSansProBlack().deriveFont( size );
} }
public Font emoticonsRegular() { public Font emoticonsRegular() {
@ -268,12 +268,17 @@ public abstract class Res {
public static class Colors { public static class Colors {
private final Color transparent = new Color( 0, 0, 0, 0 );
private final Color frameBg = Color.decode( "#5A5D6B" ); private final Color frameBg = Color.decode( "#5A5D6B" );
private final Color controlBg = SystemColor.window; private final Color controlBg = SystemColor.window;
private final Color controlBorder = Color.decode( "#BFBFBF" ); private final Color controlBorder = Color.decode( "#BFBFBF" );
private final Color highlightFg = SystemColor.controlHighlight; private final Color highlightFg = SystemColor.controlHighlight;
private final Color errorFg = Color.decode( "#FF3333" ); private final Color errorFg = Color.decode( "#FF3333" );
public Color transparent() {
return transparent;
}
public Color frameBg() { public Color frameBg() {
return frameBg; return frameBg;
} }

View File

@ -4,17 +4,20 @@ import com.google.common.collect.ImmutableList;
import java.util.*; import java.util.*;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.swing.*; import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
/** /**
* @author lhunath, 2018-07-19 * @author lhunath, 2018-07-19
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class CollectionListModel<E> extends AbstractListModel<E> implements ComboBoxModel<E> { public class CollectionListModel<E> extends AbstractListModel<E> implements ComboBoxModel<E>, ListSelectionListener {
private final List<E> model = new LinkedList<>(); private final List<E> model = new LinkedList<>();
@Nullable @Nullable
private E selectedItem; private E selectedItem;
private JList<E> list;
public CollectionListModel() { public CollectionListModel() {
} }
@ -77,6 +80,10 @@ public class CollectionListModel<E> extends AbstractListModel<E> implements Comb
if (!Objects.equals( selectedItem, newSelectedItem ) && model.contains( newSelectedItem )) { if (!Objects.equals( selectedItem, newSelectedItem ) && model.contains( newSelectedItem )) {
selectedItem = (E) newSelectedItem; selectedItem = (E) newSelectedItem;
fireContentsChanged( this, -1, -1 ); fireContentsChanged( this, -1, -1 );
//noinspection ObjectEquality
if ((list != null) && (list.getModel() == this))
list.setSelectedValue( selectedItem, true );
} }
} }
@ -85,4 +92,21 @@ public class CollectionListModel<E> extends AbstractListModel<E> implements Comb
public synchronized E getSelectedItem() { public synchronized E getSelectedItem() {
return selectedItem; return selectedItem;
} }
public synchronized void registerList(final JList<E> list) {
// TODO: This class should probably implement ListSelectionModel instead.
if (this.list != null)
this.list.removeListSelectionListener( this );
this.list = list;
this.list.addListSelectionListener( this );
this.list.setModel( this );
}
@Override
public synchronized void valueChanged(final ListSelectionEvent event) {
//noinspection ObjectEquality
if ((event.getSource() == list) && (list.getModel() == this))
selectedItem = list.getSelectedValue();
}
} }

View File

@ -32,8 +32,10 @@ import javax.swing.border.CompoundBorder;
*/ */
public abstract class Components { public abstract class Components {
private static final float HEADING_TEXT_SIZE = 19f; public static final float TEXT_SIZE_HEADING = 19f;
private static final float CONTROL_TEXT_SIZE = 13f; public static final float TEXT_SIZE_CONTROL = 13f;
public static final int SIZE_MARGIN = 20;
public static final int SIZE_PADDING = 8;
public static GradientPanel boxPanel(final int axis, final Component... components) { public static GradientPanel boxPanel(final int axis, final Component... components) {
GradientPanel container = gradientPanel( null, null ); GradientPanel container = gradientPanel( null, null );
@ -77,7 +79,7 @@ public abstract class Components {
{ {
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ), setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) ); BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
setFont( Res.fonts().valueFont().deriveFont( CONTROL_TEXT_SIZE ) ); setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT ); setAlignmentY( BOTTOM_ALIGNMENT );
} }
@ -108,7 +110,7 @@ public abstract class Components {
public static <E> JList<E> list(final ListModel<E> model, final Function<E, String> valueTransformer) { public static <E> JList<E> list(final ListModel<E> model, final Function<E, String> valueTransformer) {
return new JList<E>( model ) { return new JList<E>( model ) {
{ {
setFont( Res.fonts().valueFont().deriveFont( CONTROL_TEXT_SIZE ) ); setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
setBorder( BorderFactory.createEmptyBorder( 4, 0, 4, 0 ) ); setBorder( BorderFactory.createEmptyBorder( 4, 0, 4, 0 ) );
setCellRenderer( new DefaultListCellRenderer() { setCellRenderer( new DefaultListCellRenderer() {
{ {
@ -147,7 +149,7 @@ public abstract class Components {
public static JButton button(final String label) { public static JButton button(final String label) {
return new JButton( label ) { return new JButton( label ) {
{ {
setFont( Res.fonts().controlFont().deriveFont( CONTROL_TEXT_SIZE ) ); setFont( Res.fonts().controlFont( TEXT_SIZE_CONTROL ) );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT ); setAlignmentY( BOTTOM_ALIGNMENT );
} }
@ -160,7 +162,7 @@ public abstract class Components {
} }
public static Component strut() { public static Component strut() {
return strut( 8 ); return strut( SIZE_PADDING );
} }
public static Component strut(final int size) { public static Component strut(final int size) {
@ -172,6 +174,18 @@ public abstract class Components {
return rigidArea; return rigidArea;
} }
public static int margin() {
return SIZE_MARGIN;
}
public static Border marginBorder() {
return marginBorder( margin() );
}
public static Border marginBorder(final int size) {
return BorderFactory.createEmptyBorder( size, size, size, size );
}
public static JSpinner spinner(final SpinnerModel model) { public static JSpinner spinner(final SpinnerModel model) {
return new JSpinner( model ) { return new JSpinner( model ) {
{ {
@ -191,6 +205,14 @@ public abstract class Components {
}; };
} }
public static JLabel heading() {
return heading( " " );
}
public static JLabel heading(final int horizontalAlignment) {
return heading( " ", horizontalAlignment );
}
public static JLabel heading(@Nullable final String heading) { public static JLabel heading(@Nullable final String heading) {
return heading( heading, SwingConstants.CENTER ); return heading( heading, SwingConstants.CENTER );
} }
@ -207,7 +229,7 @@ public abstract class Components {
public static JLabel heading(@Nullable final String heading, final int horizontalAlignment) { public static JLabel heading(@Nullable final String heading, final int horizontalAlignment) {
return new JLabel( heading, horizontalAlignment ) { return new JLabel( heading, horizontalAlignment ) {
{ {
setFont( Res.fonts().controlFont().deriveFont( Font.BOLD, HEADING_TEXT_SIZE ) ); setFont( Res.fonts().controlFont( TEXT_SIZE_HEADING ).deriveFont( Font.BOLD ) );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT ); setAlignmentY( BOTTOM_ALIGNMENT );
} }
@ -219,6 +241,14 @@ public abstract class Components {
}; };
} }
public static JLabel label() {
return label( " " );
}
public static JLabel label(final int horizontalAlignment) {
return label( " ", horizontalAlignment );
}
public static JLabel label(@Nullable final String label) { public static JLabel label(@Nullable final String label) {
return label( label, SwingConstants.LEADING ); return label( label, SwingConstants.LEADING );
} }
@ -235,7 +265,7 @@ public abstract class Components {
public static JLabel label(@Nullable final String label, final int horizontalAlignment) { public static JLabel label(@Nullable final String label, final int horizontalAlignment) {
return new JLabel( label, horizontalAlignment ) { return new JLabel( label, horizontalAlignment ) {
{ {
setFont( Res.fonts().controlFont().deriveFont( CONTROL_TEXT_SIZE ) ); setFont( Res.fonts().controlFont( TEXT_SIZE_CONTROL ) );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT ); setAlignmentY( BOTTOM_ALIGNMENT );
} }
@ -250,7 +280,7 @@ public abstract class Components {
public static JCheckBox checkBox(final String label) { public static JCheckBox checkBox(final String label) {
return new JCheckBox( label ) { return new JCheckBox( label ) {
{ {
setFont( Res.fonts().controlFont().deriveFont( CONTROL_TEXT_SIZE ) ); setFont( Res.fonts().controlFont( TEXT_SIZE_CONTROL ) );
setBackground( null ); setBackground( null );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT ); setAlignmentY( BOTTOM_ALIGNMENT );
@ -266,7 +296,7 @@ public abstract class Components {
public static <E> JComboBox<E> comboBox(final ComboBoxModel<E> model, final Function<E, String> valueTransformer) { public static <E> JComboBox<E> comboBox(final ComboBoxModel<E> model, final Function<E, String> valueTransformer) {
return new JComboBox<E>( model ) { return new JComboBox<E>( model ) {
{ {
setFont( Res.fonts().valueFont().deriveFont( CONTROL_TEXT_SIZE ) ); setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
setBorder( BorderFactory.createEmptyBorder( 4, 0, 4, 0 ) ); setBorder( BorderFactory.createEmptyBorder( 4, 0, 4, 0 ) );
setRenderer( new DefaultListCellRenderer() { setRenderer( new DefaultListCellRenderer() {
{ {

View File

@ -28,7 +28,7 @@ public class FilesPanel extends JPanel implements ItemListener {
protected FilesPanel() { protected FilesPanel() {
setOpaque( false ); setOpaque( false );
setBackground( new Color( 0, 0, 0, 0 ) ); setBackground( Res.colors().transparent() );
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) ); setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
// - // -
@ -46,9 +46,6 @@ public class FilesPanel extends JPanel implements ItemListener {
// User Selection // User Selection
add( userField ); add( userField );
userField.addItemListener( this ); userField.addItemListener( this );
// -
add( Box.createVerticalGlue() );
} }
public void reload() { public void reload() {

View File

@ -1,9 +1,12 @@
package com.lyndir.masterpassword.gui.view; package com.lyndir.masterpassword.gui.view;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.gui.Res; import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.util.Components; import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.model.MPUser; import com.lyndir.masterpassword.model.MPUser;
import java.awt.*; import java.awt.*;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.swing.*; import javax.swing.*;
@ -11,21 +14,24 @@ import javax.swing.*;
/** /**
* @author lhunath, 2018-07-14 * @author lhunath, 2018-07-14
*/ */
@SuppressWarnings("MagicNumber") @SuppressWarnings("serial")
public class MasterPasswordFrame extends JFrame implements FilesPanel.Listener { public class MasterPasswordFrame extends JFrame implements FilesPanel.Listener, ComponentListener {
private static final Logger logger = Logger.get( MasterPasswordFrame.class );
@SuppressWarnings("FieldCanBeLocal") @SuppressWarnings("FieldCanBeLocal")
private final Components.GradientPanel root; private final Components.GradientPanel root;
private final FilesPanel filesPanel = new FilesPanel(); private final FilesPanel filesPanel = new FilesPanel();
private final UserPanel userPanel = new UserPanel(); private final UserPanel userPanel = new UserPanel();
@SuppressWarnings("MagicNumber")
public MasterPasswordFrame() { public MasterPasswordFrame() {
super( "Master Password" ); super( "Master Password" );
setDefaultCloseOperation( DISPOSE_ON_CLOSE ); setDefaultCloseOperation( DISPOSE_ON_CLOSE );
setContentPane( root = Components.gradientPanel( Res.colors().frameBg(), new FlowLayout() ) ); setContentPane( root = Components.gradientPanel( Res.colors().frameBg(), new FlowLayout() ) );
root.setLayout( new BoxLayout( root, BoxLayout.PAGE_AXIS ) ); root.setLayout( new BoxLayout( root, BoxLayout.PAGE_AXIS ) );
root.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) ); root.setBorder( Components.marginBorder() );
root.add( filesPanel ); root.add( filesPanel );
root.add( new JSeparator( SwingConstants.HORIZONTAL ) ); root.add( new JSeparator( SwingConstants.HORIZONTAL ) );
@ -35,7 +41,8 @@ public class MasterPasswordFrame extends JFrame implements FilesPanel.Listener {
filesPanel.addListener( this ); filesPanel.addListener( this );
filesPanel.reload(); filesPanel.reload();
setMinimumSize( new Dimension( 640, 480 ) ); addComponentListener(this );
setPreferredSize( new Dimension( 640, 480 ) );
pack(); pack();
setLocationByPlatform( true ); setLocationByPlatform( true );
@ -46,4 +53,21 @@ public class MasterPasswordFrame extends JFrame implements FilesPanel.Listener {
public void onUserSelected(@Nullable final MPUser<?> selectedUser) { public void onUserSelected(@Nullable final MPUser<?> selectedUser) {
userPanel.setUser( selectedUser ); userPanel.setUser( selectedUser );
} }
@Override
public void componentResized(final ComponentEvent e) {
}
@Override
public void componentMoved(final ComponentEvent e) {
}
@Override
public void componentShown(final ComponentEvent e) {
userPanel.transferFocus();
}
@Override
public void componentHidden(final ComponentEvent e) {
}
} }

View File

@ -15,7 +15,9 @@ import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable; import java.awt.datatransfer.Transferable;
import java.awt.event.*; import java.awt.event.*;
import java.util.Objects; import java.util.Objects;
import java.util.Random;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer; import java.util.function.Consumer;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -34,8 +36,9 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
private MPUser<?> user; private MPUser<?> user;
public UserPanel() { public UserPanel() {
super( new BorderLayout( 20, 20 ), null ); super( new BorderLayout( Components.margin(), Components.margin() ), null );
setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) ); setBorder( Components.marginBorder() );
setUser( null );
} }
public void setUser(@Nullable final MPUser<?> user) { public void setUser(@Nullable final MPUser<?> user) {
@ -61,6 +64,7 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
} }
revalidate(); revalidate();
transferFocus();
} ); } );
} }
@ -88,11 +92,16 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
private static final class AuthenticateUserPanel extends JPanel implements ActionListener, DocumentListener { private static final class AuthenticateUserPanel extends JPanel implements ActionListener, DocumentListener {
private static final Random random = new Random();
@Nonnull @Nonnull
private final MPUser<?> user; private final MPUser<?> user;
private final JPasswordField masterPasswordField = Components.passwordField(); private final JPasswordField masterPasswordField = Components.passwordField();
private final JLabel errorLabel = Components.label( null ); private final JLabel errorLabel = Components.label();
private final JLabel identiconLabel = Components.label( SwingConstants.CENTER );
private Future<?> identiconJob;
private AuthenticateUserPanel(@Nonnull final MPUser<?> user) { private AuthenticateUserPanel(@Nonnull final MPUser<?> user) {
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) ); setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
@ -110,39 +119,72 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
add( errorLabel ); add( errorLabel );
errorLabel.setForeground( Res.colors().errorFg() ); errorLabel.setForeground( Res.colors().errorFg() );
add( Box.createGlue() ); add( Components.strut() );
add( identiconLabel );
identiconLabel.setFont( Res.fonts().emoticonsFont( Components.TEXT_SIZE_CONTROL ) );
Res.ui( false, masterPasswordField::requestFocusInWindow ); add( Box.createGlue() );
} }
@Override @Override
public void actionPerformed(final ActionEvent event) { public void actionPerformed(final ActionEvent event) {
try { updateIdenticon();
user.authenticate( masterPasswordField.getPassword() );
} char[] masterPassword = masterPasswordField.getPassword();
catch (final MPIncorrectMasterPasswordException e) { Res.job( () -> {
logger.wrn( e, "During user authentication for: %s", user ); try {
errorLabel.setText( e.getLocalizedMessage() ); user.authenticate( masterPassword );
} }
catch (final MPAlgorithmException e) { catch (final MPIncorrectMasterPasswordException e) {
logger.err( e, "During user authentication for: %s", user ); logger.wrn( e, "During user authentication for: %s", user );
errorLabel.setText( e.getLocalizedMessage() ); errorLabel.setText( e.getLocalizedMessage() );
} }
catch (final MPAlgorithmException e) {
logger.err( e, "During user authentication for: %s", user );
errorLabel.setText( e.getLocalizedMessage() );
}
} );
} }
@Override @Override
public void insertUpdate(final DocumentEvent event) { public void insertUpdate(final DocumentEvent event) {
errorLabel.setText( null ); update();
} }
@Override @Override
public void removeUpdate(final DocumentEvent event) { public void removeUpdate(final DocumentEvent event) {
errorLabel.setText( null ); update();
} }
@Override @Override
public void changedUpdate(final DocumentEvent event) { public void changedUpdate(final DocumentEvent event) {
update();
}
private synchronized void update() {
errorLabel.setText( null ); errorLabel.setText( null );
if (identiconJob != null)
identiconJob.cancel( true );
identiconJob = Res.job( this::updateIdenticon, 100 + random.nextInt( 100 ), TimeUnit.MILLISECONDS );
}
private void updateIdenticon() {
char[] masterPassword = masterPasswordField.getPassword();
MPIdenticon identicon = ((masterPassword != null) && (masterPassword.length > 0))?
new MPIdenticon( user.getFullName(), masterPassword ): null;
Res.ui( () -> {
if (identicon != null) {
identiconLabel.setForeground(
Res.colors().fromIdenticonColor( identicon.getColor(), Res.Colors.BackgroundMode.LIGHT ) );
identiconLabel.setText( identicon.getText() );
} else {
identiconLabel.setForeground( null );
identiconLabel.setText( " " );
}
} );
} }
} }
@ -150,11 +192,13 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
private static final class AuthenticatedUserPanel extends JPanel implements ActionListener, DocumentListener, ListSelectionListener, private static final class AuthenticatedUserPanel extends JPanel implements ActionListener, DocumentListener, ListSelectionListener,
KeyListener { KeyListener {
public static final int SIZE_RESULT = 48;
@Nonnull @Nonnull
private final MPUser<?> user; private final MPUser<?> user;
private final JLabel passwordLabel = Components.label( " ", SwingConstants.CENTER ); private final JLabel passwordLabel = Components.label( SwingConstants.CENTER );
private final JLabel passwordField = Components.heading( " ", SwingConstants.CENTER ); private final JLabel passwordField = Components.heading( SwingConstants.CENTER );
private final JLabel queryLabel = Components.label( " " ); private final JLabel queryLabel = Components.label();
private final JTextField queryField = Components.textField(); private final JTextField queryField = Components.textField();
private final CollectionListModel<MPSite<?>> sitesModel = new CollectionListModel<>(); private final CollectionListModel<MPSite<?>> sitesModel = new CollectionListModel<>();
private final JList<MPSite<?>> sitesList = Components.list( sitesModel, private final JList<MPSite<?>> sitesList = Components.list( sitesModel,
@ -172,7 +216,7 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
add( passwordLabel ); add( passwordLabel );
add( passwordField ); add( passwordField );
passwordField.setForeground( Res.colors().highlightFg() ); passwordField.setForeground( Res.colors().highlightFg() );
passwordField.setFont( Res.fonts().bigValueFont().deriveFont( Font.BOLD, 48 ) ); passwordField.setFont( Res.fonts().bigValueFont( SIZE_RESULT ) );
add( Box.createGlue() ); add( Box.createGlue() );
add( Components.strut() ); add( Components.strut() );
@ -182,12 +226,12 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
queryField.addActionListener( this ); queryField.addActionListener( this );
queryField.addKeyListener( this ); queryField.addKeyListener( this );
queryField.getDocument().addDocumentListener( this ); queryField.getDocument().addDocumentListener( this );
queryField.requestFocusInWindow();
add( Components.strut() ); add( Components.strut() );
add( Components.scrollPane( sitesList ) ); add( Components.scrollPane( sitesList ) );
sitesModel.registerList( sitesList );
sitesList.addListSelectionListener( this ); sitesList.addListSelectionListener( this );
add( Box.createGlue() ); add( Box.createGlue() );
Res.ui( false, queryField::requestFocusInWindow );
} }
@Override @Override