diff --git a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPIdenticon.java b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPIdenticon.java
index d0b4d0d6..3bd4d726 100644
--- a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPIdenticon.java
+++ b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPIdenticon.java
@@ -27,6 +27,7 @@ import com.lyndir.lhunath.opal.system.logging.Logger;
import java.nio.*;
import java.nio.charset.Charset;
import java.util.Arrays;
+import java.util.Locale;
/**
@@ -83,6 +84,10 @@ public class MPIdenticon {
return text;
}
+ public String getHTML() {
+ return strf( "%s", color.getCSS(), text );
+ }
+
public Color getColor() {
return color;
}
@@ -94,6 +99,15 @@ public class MPIdenticon {
BLUE,
MAGENTA,
CYAN,
- MONO
+ MONO {
+ @Override
+ public String getCSS() {
+ return "inherit";
+ }
+ };
+
+ public String getCSS() {
+ return name().toLowerCase( Locale.ROOT );
+ }
}
}
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/Res.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/Res.java
index ccfd3399..66aec042 100644
--- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/Res.java
+++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/Res.java
@@ -180,20 +180,20 @@ public abstract class Res {
public static class Fonts {
- public Font emoticonsFont() {
- return emoticonsRegular();
+ public Font emoticonsFont(final float size) {
+ return emoticonsRegular().deriveFont( size );
}
- public Font controlFont() {
- return exoRegular();
+ public Font controlFont(final float size) {
+ return exoRegular().deriveFont( size );
}
- public Font valueFont() {
- return sourceSansProRegular();
+ public Font valueFont(final float size) {
+ return sourceSansProRegular().deriveFont( size );
}
- public Font bigValueFont() {
- return sourceSansProBlack();
+ public Font bigValueFont(final float size) {
+ return sourceSansProBlack().deriveFont( size );
}
public Font emoticonsRegular() {
@@ -268,12 +268,17 @@ public abstract class Res {
public static class Colors {
+ private final Color transparent = new Color( 0, 0, 0, 0 );
private final Color frameBg = Color.decode( "#5A5D6B" );
private final Color controlBg = SystemColor.window;
private final Color controlBorder = Color.decode( "#BFBFBF" );
private final Color highlightFg = SystemColor.controlHighlight;
private final Color errorFg = Color.decode( "#FF3333" );
+ public Color transparent() {
+ return transparent;
+ }
+
public Color frameBg() {
return frameBg;
}
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/CollectionListModel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/CollectionListModel.java
index 5ca2c7e0..127d1b8f 100644
--- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/CollectionListModel.java
+++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/CollectionListModel.java
@@ -4,17 +4,20 @@ import com.google.common.collect.ImmutableList;
import java.util.*;
import javax.annotation.Nullable;
import javax.swing.*;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
/**
* @author lhunath, 2018-07-19
*/
@SuppressWarnings("serial")
-public class CollectionListModel extends AbstractListModel implements ComboBoxModel {
+public class CollectionListModel extends AbstractListModel implements ComboBoxModel, ListSelectionListener {
- private final List model = new LinkedList<>();
+ private final List model = new LinkedList<>();
@Nullable
- private E selectedItem;
+ private E selectedItem;
+ private JList list;
public CollectionListModel() {
}
@@ -77,6 +80,10 @@ public class CollectionListModel extends AbstractListModel implements Comb
if (!Objects.equals( selectedItem, newSelectedItem ) && model.contains( newSelectedItem )) {
selectedItem = (E) newSelectedItem;
fireContentsChanged( this, -1, -1 );
+
+ //noinspection ObjectEquality
+ if ((list != null) && (list.getModel() == this))
+ list.setSelectedValue( selectedItem, true );
}
}
@@ -85,4 +92,21 @@ public class CollectionListModel extends AbstractListModel implements Comb
public synchronized E getSelectedItem() {
return selectedItem;
}
+
+ public synchronized void registerList(final JList 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();
+ }
}
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java
index bd73dec3..f8023f15 100644
--- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java
+++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java
@@ -32,8 +32,10 @@ import javax.swing.border.CompoundBorder;
*/
public abstract class Components {
- private static final float HEADING_TEXT_SIZE = 19f;
- private static final float CONTROL_TEXT_SIZE = 13f;
+ public static final float TEXT_SIZE_HEADING = 19f;
+ 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) {
GradientPanel container = gradientPanel( null, null );
@@ -77,7 +79,7 @@ public abstract class Components {
{
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
- setFont( Res.fonts().valueFont().deriveFont( CONTROL_TEXT_SIZE ) );
+ setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@@ -108,7 +110,7 @@ public abstract class Components {
public static JList list(final ListModel model, final Function valueTransformer) {
return new JList( model ) {
{
- setFont( Res.fonts().valueFont().deriveFont( CONTROL_TEXT_SIZE ) );
+ setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
setBorder( BorderFactory.createEmptyBorder( 4, 0, 4, 0 ) );
setCellRenderer( new DefaultListCellRenderer() {
{
@@ -147,7 +149,7 @@ public abstract class Components {
public static JButton button(final String label) {
return new JButton( label ) {
{
- setFont( Res.fonts().controlFont().deriveFont( CONTROL_TEXT_SIZE ) );
+ setFont( Res.fonts().controlFont( TEXT_SIZE_CONTROL ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@@ -160,7 +162,7 @@ public abstract class Components {
}
public static Component strut() {
- return strut( 8 );
+ return strut( SIZE_PADDING );
}
public static Component strut(final int size) {
@@ -172,6 +174,18 @@ public abstract class Components {
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) {
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) {
return heading( heading, SwingConstants.CENTER );
}
@@ -207,7 +229,7 @@ public abstract class Components {
public static JLabel heading(@Nullable final String heading, final int 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 );
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) {
return label( label, SwingConstants.LEADING );
}
@@ -235,7 +265,7 @@ public abstract class Components {
public static JLabel label(@Nullable final String label, final int horizontalAlignment) {
return new JLabel( label, horizontalAlignment ) {
{
- setFont( Res.fonts().controlFont().deriveFont( CONTROL_TEXT_SIZE ) );
+ setFont( Res.fonts().controlFont( TEXT_SIZE_CONTROL ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@@ -250,7 +280,7 @@ public abstract class Components {
public static JCheckBox checkBox(final String label) {
return new JCheckBox( label ) {
{
- setFont( Res.fonts().controlFont().deriveFont( CONTROL_TEXT_SIZE ) );
+ setFont( Res.fonts().controlFont( TEXT_SIZE_CONTROL ) );
setBackground( null );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
@@ -266,7 +296,7 @@ public abstract class Components {
public static JComboBox comboBox(final ComboBoxModel model, final Function valueTransformer) {
return new JComboBox( model ) {
{
- setFont( Res.fonts().valueFont().deriveFont( CONTROL_TEXT_SIZE ) );
+ setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
setBorder( BorderFactory.createEmptyBorder( 4, 0, 4, 0 ) );
setRenderer( new DefaultListCellRenderer() {
{
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java
index 18b97d1d..4290fbb9 100644
--- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java
+++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java
@@ -28,7 +28,7 @@ public class FilesPanel extends JPanel implements ItemListener {
protected FilesPanel() {
setOpaque( false );
- setBackground( new Color( 0, 0, 0, 0 ) );
+ setBackground( Res.colors().transparent() );
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
// -
@@ -46,9 +46,6 @@ public class FilesPanel extends JPanel implements ItemListener {
// User Selection
add( userField );
userField.addItemListener( this );
-
- // -
- add( Box.createVerticalGlue() );
}
public void reload() {
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/MasterPasswordFrame.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/MasterPasswordFrame.java
index d39183d3..8139fa9d 100644
--- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/MasterPasswordFrame.java
+++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/MasterPasswordFrame.java
@@ -1,9 +1,12 @@
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.util.Components;
import com.lyndir.masterpassword.model.MPUser;
import java.awt.*;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
import javax.annotation.Nullable;
import javax.swing.*;
@@ -11,21 +14,24 @@ import javax.swing.*;
/**
* @author lhunath, 2018-07-14
*/
-@SuppressWarnings("MagicNumber")
-public class MasterPasswordFrame extends JFrame implements FilesPanel.Listener {
+@SuppressWarnings("serial")
+public class MasterPasswordFrame extends JFrame implements FilesPanel.Listener, ComponentListener {
+
+ private static final Logger logger = Logger.get( MasterPasswordFrame.class );
@SuppressWarnings("FieldCanBeLocal")
private final Components.GradientPanel root;
private final FilesPanel filesPanel = new FilesPanel();
private final UserPanel userPanel = new UserPanel();
+ @SuppressWarnings("MagicNumber")
public MasterPasswordFrame() {
super( "Master Password" );
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
setContentPane( root = Components.gradientPanel( Res.colors().frameBg(), new FlowLayout() ) );
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( new JSeparator( SwingConstants.HORIZONTAL ) );
@@ -35,7 +41,8 @@ public class MasterPasswordFrame extends JFrame implements FilesPanel.Listener {
filesPanel.addListener( this );
filesPanel.reload();
- setMinimumSize( new Dimension( 640, 480 ) );
+ addComponentListener(this );
+ setPreferredSize( new Dimension( 640, 480 ) );
pack();
setLocationByPlatform( true );
@@ -46,4 +53,21 @@ public class MasterPasswordFrame extends JFrame implements FilesPanel.Listener {
public void onUserSelected(@Nullable final MPUser> 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) {
+ }
}
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserPanel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserPanel.java
index 2ea6e2a0..86d3f3df 100644
--- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserPanel.java
+++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserPanel.java
@@ -15,7 +15,9 @@ import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.*;
import java.util.Objects;
+import java.util.Random;
import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -34,8 +36,9 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
private MPUser> user;
public UserPanel() {
- super( new BorderLayout( 20, 20 ), null );
- setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
+ super( new BorderLayout( Components.margin(), Components.margin() ), null );
+ setBorder( Components.marginBorder() );
+ setUser( null );
}
public void setUser(@Nullable final MPUser> user) {
@@ -61,6 +64,7 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
}
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 Random random = new Random();
+
@Nonnull
private final MPUser> user;
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) {
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
@@ -110,39 +119,72 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
add( errorLabel );
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
public void actionPerformed(final ActionEvent event) {
- try {
- user.authenticate( masterPasswordField.getPassword() );
- }
- catch (final MPIncorrectMasterPasswordException e) {
- logger.wrn( e, "During user authentication for: %s", user );
- errorLabel.setText( e.getLocalizedMessage() );
- }
- catch (final MPAlgorithmException e) {
- logger.err( e, "During user authentication for: %s", user );
- errorLabel.setText( e.getLocalizedMessage() );
- }
+ updateIdenticon();
+
+ char[] masterPassword = masterPasswordField.getPassword();
+ Res.job( () -> {
+ try {
+ user.authenticate( masterPassword );
+ }
+ catch (final MPIncorrectMasterPasswordException e) {
+ logger.wrn( e, "During user authentication for: %s", user );
+ errorLabel.setText( e.getLocalizedMessage() );
+ }
+ catch (final MPAlgorithmException e) {
+ logger.err( e, "During user authentication for: %s", user );
+ errorLabel.setText( e.getLocalizedMessage() );
+ }
+ } );
}
@Override
public void insertUpdate(final DocumentEvent event) {
- errorLabel.setText( null );
+ update();
}
@Override
public void removeUpdate(final DocumentEvent event) {
- errorLabel.setText( null );
+ update();
}
@Override
public void changedUpdate(final DocumentEvent event) {
+ update();
+ }
+
+ private synchronized void update() {
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,
KeyListener {
+ public static final int SIZE_RESULT = 48;
+
@Nonnull
private final MPUser> user;
- private final JLabel passwordLabel = Components.label( " ", SwingConstants.CENTER );
- private final JLabel passwordField = Components.heading( " ", SwingConstants.CENTER );
- private final JLabel queryLabel = Components.label( " " );
+ private final JLabel passwordLabel = Components.label( SwingConstants.CENTER );
+ private final JLabel passwordField = Components.heading( SwingConstants.CENTER );
+ private final JLabel queryLabel = Components.label();
private final JTextField queryField = Components.textField();
private final CollectionListModel> sitesModel = new CollectionListModel<>();
private final JList> sitesList = Components.list( sitesModel,
@@ -172,7 +216,7 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
add( passwordLabel );
add( passwordField );
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( Components.strut() );
@@ -182,12 +226,12 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
queryField.addActionListener( this );
queryField.addKeyListener( this );
queryField.getDocument().addDocumentListener( this );
+ queryField.requestFocusInWindow();
add( Components.strut() );
add( Components.scrollPane( sitesList ) );
+ sitesModel.registerList( sitesList );
sitesList.addListSelectionListener( this );
add( Box.createGlue() );
-
- Res.ui( false, queryField::requestFocusInWindow );
}
@Override