diff --git a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPIdenticon.java b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPIdenticon.java
new file mode 100644
index 00000000..b1b799e1
--- /dev/null
+++ b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPIdenticon.java
@@ -0,0 +1,106 @@
+package com.lyndir.masterpassword;
+
+import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
+
+import com.google.common.collect.ImmutableMap;
+import com.lyndir.lhunath.opal.system.MessageAuthenticationDigests;
+import com.lyndir.lhunath.opal.system.logging.Logger;
+import java.awt.*;
+import java.nio.*;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Map;
+
+
+/**
+ * @author lhunath, 15-03-29
+ */
+public class MPIdenticon {
+
+ @SuppressWarnings("UnusedDeclaration")
+ private static final Logger logger = Logger.get( MPIdenticon.class );
+
+ private static final Charset charset = StandardCharsets.UTF_8;
+ private static final Color[] colors = new Color[]{
+ Color.RED, Color.GREEN, Color.YELLOW, Color.BLUE, Color.MAGENTA, Color.CYAN, Color.MONO };
+ private static final char[] leftArm = new char[]{ '╔', '╚', '╰', '═' };
+ private static final char[] rightArm = new char[]{ '╗', '╝', '╯', '═' };
+ private static final char[] body = new char[]{ '█', '░', '▒', '▓', '☺', '☻' };
+ private static final char[] accessory = new char[]{
+ '◈', '◎', '◐', '◑', '◒', '◓', '☀', '☁', '☂', '☃', '☄', '★', '☆', '☎', '☏', '⎈', '⌂', '☘', '☢', '☣', '☕', '⌚', '⌛', '⏰', '⚡',
+ '⛄', '⛅', '☔', '♔', '♕', '♖', '♗', '♘', '♙', '♚', '♛', '♜', '♝', '♞', '♟', '♨', '♩', '♪', '♫', '⚐', '⚑', '⚔', '⚖', '⚙', '⚠',
+ '⌘', '⏎', '✄', '✆', '✈', '✉', '✌' };
+
+ private final String fullName;
+ private final Color color;
+ private final String text;
+
+ public MPIdenticon(String fullName, String masterPassword) {
+ this( fullName, masterPassword.toCharArray() );
+ }
+
+ public MPIdenticon(String fullName, char[] masterPassword) {
+ this.fullName = fullName;
+
+ byte[] masterPasswordBytes = charset.encode( CharBuffer.wrap( masterPassword ) ).array();
+ ByteBuffer identiconSeedBytes = ByteBuffer.wrap(
+ MessageAuthenticationDigests.HmacSHA256.of( masterPasswordBytes, fullName.getBytes( charset ) ) );
+ Arrays.fill( masterPasswordBytes, (byte) 0 );
+
+ IntBuffer identiconSeedBuffer = IntBuffer.allocate( identiconSeedBytes.capacity() );
+ while (identiconSeedBytes.hasRemaining())
+ identiconSeedBuffer.put( identiconSeedBytes.get() & 0xFF );
+ int[] identiconSeed = identiconSeedBuffer.array();
+
+ color = colors[identiconSeed[4] % colors.length];
+ text = strf( "%c%c%c%c", leftArm[identiconSeed[0] % leftArm.length], body[identiconSeed[1] % body.length],
+ rightArm[identiconSeed[2] % rightArm.length], accessory[identiconSeed[3] % accessory.length] );
+ }
+
+ public String getFullName() {
+ return fullName;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public Color getColor() {
+ return color;
+ }
+
+ public enum BackgroundMode {
+ DARK, LIGHT
+ }
+
+
+ public enum Color {
+ RED( "#dc322f", "#dc322f" ),
+ GREEN( "#859900", "#859900" ),
+ YELLOW( "#b58900", "#b58900" ),
+ BLUE( "#268bd2", "#268bd2" ),
+ MAGENTA( "#d33682", "#d33682" ),
+ CYAN( "#2aa198", "#2aa198" ),
+ MONO( "#93a1a1", "#586e75" );
+
+ private final String rgbDark;
+ private final String rgbLight;
+
+ Color(final String rgbDark, final String rgbLight) {
+ this.rgbDark = rgbDark;
+ this.rgbLight = rgbLight;
+ }
+
+ public java.awt.Color getAWTColor(BackgroundMode backgroundMode) {
+ switch (backgroundMode) {
+ case DARK:
+ return new java.awt.Color( Integer.decode( rgbDark ) );
+ case LIGHT:
+ return new java.awt.Color( Integer.decode( rgbLight ) );
+ }
+
+ throw new UnsupportedOperationException( "Unsupported background mode: " + backgroundMode );
+ }
+ }
+}
diff --git a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKey.java b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKey.java
index 84390db9..6e152180 100644
--- a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKey.java
+++ b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKey.java
@@ -65,6 +65,7 @@ public abstract class MasterKey {
@Nonnull
protected byte[] getKey() {
+ Preconditions.checkState( isValid() );
return Preconditions.checkNotNull( masterKey );
}
diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/AuthenticationPanel.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/AuthenticationPanel.java
index fd7a15fc..055cddf1 100644
--- a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/AuthenticationPanel.java
+++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/AuthenticationPanel.java
@@ -1,9 +1,13 @@
package com.lyndir.masterpassword.gui;
import com.google.common.collect.ImmutableList;
+import com.lyndir.masterpassword.MPIdenticon;
import com.lyndir.masterpassword.gui.util.Components;
import java.awt.*;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import javax.swing.*;
+import org.jetbrains.annotations.NotNull;
/**
@@ -29,6 +33,8 @@ public abstract class AuthenticationPanel extends Components.GradientPanel {
}
} );
add( Box.createVerticalGlue() );
+
+ avatarLabel.setToolTipText( "The avatar for your user. Click to change it." );
}
protected void updateUser(boolean repack) {
@@ -39,8 +45,11 @@ public abstract class AuthenticationPanel extends Components.GradientPanel {
unlockFrame.repack();
}
+ @Nullable
protected abstract User getSelectedUser();
+ @NotNull
+ @Nonnull
public abstract char[] getMasterPassword();
public Component getFocusComponent() {
diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/IncognitoAuthenticationPanel.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/IncognitoAuthenticationPanel.java
index 54633a22..8c114bc4 100644
--- a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/IncognitoAuthenticationPanel.java
+++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/IncognitoAuthenticationPanel.java
@@ -7,6 +7,7 @@ import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
+import org.jetbrains.annotations.NotNull;
/**
@@ -58,6 +59,7 @@ public class IncognitoAuthenticationPanel extends AuthenticationPanel implements
return new IncognitoUser( fullNameField.getText() );
}
+ @NotNull
@Override
public char[] getMasterPassword() {
return masterPasswordField.getPassword();
diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/ModelAuthenticationPanel.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/ModelAuthenticationPanel.java
index b78aac31..0f37d704 100644
--- a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/ModelAuthenticationPanel.java
+++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/ModelAuthenticationPanel.java
@@ -14,6 +14,7 @@ import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.plaf.metal.MetalComboBoxEditor;
+import org.jetbrains.annotations.NotNull;
/**
@@ -104,6 +105,7 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
return userField.getModel().getElementAt( selectedIndex );
}
+ @NotNull
@Override
public char[] getMasterPassword() {
return masterPasswordField.getPassword();
@@ -124,6 +126,7 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
updateUser( true );
}
} );
+ setToolTipText( "Add a new user to the list." );
}
}, new JButton( Res.iconQuestion() ) {
{
@@ -131,10 +134,11 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
@Override
public void actionPerformed(final ActionEvent e) {
JOptionPane.showMessageDialog( ModelAuthenticationPanel.this, //
- "Reads users and sites from the directory at ~/.mpw.", //
+ "Reads users and sites from the directory at ~/.mpw.d.", //
"Help", JOptionPane.INFORMATION_MESSAGE );
}
} );
+ setToolTipText( "More information." );
}
} );
}
diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/PasswordFrame.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/PasswordFrame.java
index c6fd071e..54ea2583 100644
--- a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/PasswordFrame.java
+++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/PasswordFrame.java
@@ -53,7 +53,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
add( Components.borderPanel( sitePanel, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ), BorderLayout.CENTER );
// User
- sitePanel.add( Components.label( strf( "Generating passwords for: %s", user.getFullName() ), JLabel.CENTER ) );
+ sitePanel.add( Components.label( strf( "Generating passwords for: %s", user.getFullName() ), SwingConstants.CENTER ) );
sitePanel.add( Components.stud() );
// Site Name
@@ -161,7 +161,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
updateMask();
// Tip
- tipLabel = Components.label( " ", JLabel.CENTER );
+ tipLabel = Components.label( " ", SwingConstants.CENTER );
tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT );
JPanel passwordContainer = Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, passwordField, tipLabel );
passwordContainer.setOpaque( true );
diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/Res.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/Res.java
index 74fb5c84..a76b1a9f 100644
--- a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/Res.java
+++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/Res.java
@@ -97,6 +97,10 @@ public abstract class Res {
return 19;
}
+ public static Font emoticonsFont() {
+ return emoticonsRegular();
+ }
+
public static Font controlFont() {
return arimoRegular();
}
@@ -109,6 +113,10 @@ public abstract class Res {
return sourceSansProBlack();
}
+ public static Font emoticonsRegular() {
+ return font( "fonts/Emoticons-Regular.otf" );
+ }
+
public static Font sourceCodeProRegular() {
return font( "fonts/SourceCodePro-Regular.otf" );
}
diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/UnlockFrame.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/UnlockFrame.java
index 5df63855..a3f3c115 100644
--- a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/UnlockFrame.java
+++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/UnlockFrame.java
@@ -2,10 +2,12 @@ package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
+import com.lyndir.masterpassword.MPIdenticon;
import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
import java.awt.*;
import java.awt.event.*;
+import javax.annotation.Nullable;
import javax.swing.*;
@@ -16,6 +18,7 @@ public class UnlockFrame extends JFrame {
private final SignInCallback signInCallback;
private final Components.GradientPanel root;
+ private final JLabel identiconLabel;
private final JButton signInButton;
private final JPanel authenticationContainer;
private AuthenticationPanel authenticationPanel;
@@ -27,33 +30,40 @@ public class UnlockFrame extends JFrame {
super( "Unlock Master Password" );
this.signInCallback = signInCallback;
- setDefaultCloseOperation(DISPOSE_ON_CLOSE);
- setContentPane(root = Components.gradientPanel(new BorderLayout(20, 20), Res.colors().frameBg()));
- root.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
-
- addWindowFocusListener(new WindowAdapter() {
+ setDefaultCloseOperation( DISPOSE_ON_CLOSE );
+ addWindowFocusListener( new WindowAdapter() {
@Override
public void windowGainedFocus(WindowEvent e) {
- root.setGradientColor(Res.colors().frameBg());
+ root.setGradientColor( Res.colors().frameBg() );
}
@Override
public void windowLostFocus(WindowEvent e) {
- root.setGradientColor(Color.RED);
+ root.setGradientColor( Color.RED );
}
- });
-
- authenticationContainer = Components.boxLayout( BoxLayout.PAGE_AXIS );
- authenticationContainer.setOpaque( true );
- authenticationContainer.setBackground( Res.colors().controlBg() );
- authenticationContainer.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
- add( Components.borderPanel( authenticationContainer, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) );
+ } );
// Sign In
JPanel signInBox = Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = Components.button( "Sign In" ),
Box.createGlue() );
signInBox.setBackground( null );
- root.add( signInBox, BorderLayout.SOUTH );
+
+ setContentPane( root = Components.gradientPanel( new FlowLayout(), Res.colors().frameBg() ) );
+ root.setLayout( new BoxLayout( root, BoxLayout.PAGE_AXIS ) );
+ root.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
+ root.add( Components.borderPanel( authenticationContainer = Components.boxLayout( BoxLayout.PAGE_AXIS ),
+ BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) );
+ root.add( Box.createVerticalStrut( 8 ) );
+ root.add( identiconLabel = Components.label( " ", SwingConstants.CENTER ) );
+ root.add( Box.createVerticalStrut( 8 ) );
+ root.add( signInBox );
+
+ authenticationContainer.setOpaque( true );
+ authenticationContainer.setBackground( Res.colors().controlBg() );
+ authenticationContainer.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
+ identiconLabel.setFont( Res.emoticonsFont().deriveFont( 14.f ) );
+ identiconLabel.setToolTipText(
+ "A representation of your identity across all Master Password apps.\nIt should always be the same." );
signInButton.addActionListener( new AbstractAction() {
@Override
public void actionPerformed(final ActionEvent e) {
@@ -86,6 +96,7 @@ public class UnlockFrame extends JFrame {
authenticationContainer.add( Components.stud() );
final JCheckBox incognitoCheckBox = Components.checkBox( "Incognito" );
+ incognitoCheckBox.setToolTipText( "Log in without saving any information." );
incognitoCheckBox.setSelected( incognito );
incognitoCheckBox.addItemListener( new ItemListener() {
@Override
@@ -104,9 +115,10 @@ public class UnlockFrame extends JFrame {
authenticationContainer.add( toolsPanel );
for (JButton button : authenticationPanel.getButtons()) {
toolsPanel.add( button );
+ button.setBorder( BorderFactory.createEmptyBorder() );
button.setMargin( new Insets( 0, 0, 0, 0 ) );
- button.setAlignmentX(RIGHT_ALIGNMENT);
- button.setContentAreaFilled(false);
+ button.setAlignmentX( RIGHT_ALIGNMENT );
+ button.setContentAreaFilled( false );
}
checkSignIn();
@@ -121,13 +133,24 @@ public class UnlockFrame extends JFrame {
} );
}
- void updateUser(User user) {
+ void updateUser(@Nullable User user) {
this.user = user;
checkSignIn();
}
boolean checkSignIn() {
- boolean enabled = user != null && !user.getFullName().isEmpty() && authenticationPanel.getMasterPassword().length > 0;
+ String fullName = user == null? "": user.getFullName();
+ char[] masterPassword = authenticationPanel.getMasterPassword();
+ boolean enabled = !fullName.isEmpty() && masterPassword.length > 0;
+
+ if (fullName.isEmpty() || masterPassword.length == 0)
+ identiconLabel.setText( " " );
+ else {
+ MPIdenticon identicon = new MPIdenticon( fullName, masterPassword );
+ identiconLabel.setText( identicon.getText() );
+ identiconLabel.setForeground( identicon.getColor().getAWTColor( MPIdenticon.BackgroundMode.DARK ) );
+ }
+
signInButton.setEnabled( enabled );
return enabled;
@@ -170,7 +193,6 @@ public class UnlockFrame extends JFrame {
}
} );
}
-
}
} );
}
diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java
index 1874349e..1110a354 100644
--- a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java
+++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java
@@ -70,10 +70,10 @@ public abstract class Components {
public static JPasswordField passwordField() {
return new JPasswordField() {
{
- setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Res.colors().controlBorder(), 1, true),
- BorderFactory.createEmptyBorder(4, 4, 4, 4)));
- setAlignmentX(LEFT_ALIGNMENT);
- setAlignmentY(BOTTOM_ALIGNMENT);
+ setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
+ BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
+ setAlignmentX( LEFT_ALIGNMENT );
+ setAlignmentY( BOTTOM_ALIGNMENT );
}
@Override
@@ -116,7 +116,7 @@ public abstract class Components {
((DefaultEditor) getEditor()).getTextField().setBorder( editorBorder );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
- setBorder(null);
+ setBorder( null );
}
@Override
@@ -126,12 +126,21 @@ public abstract class Components {
};
}
- public static JLabel label(final String label) {
- return label( label, JLabel.LEADING );
+ public static JLabel label(@Nullable String label) {
+ return label( label, SwingConstants.LEADING );
}
- public static JLabel label(final String label, final int alignment) {
- return new JLabel( label, alignment ) {
+ /**
+ * @param horizontalAlignment One of the following constants
+ * defined in SwingConstants
:
+ * LEFT
,
+ * CENTER
,
+ * RIGHT
,
+ * LEADING
or
+ * TRAILING
.
+ */
+ public static JLabel label(@Nullable final String label, final int horizontalAlignment) {
+ return new JLabel( label, horizontalAlignment ) {
{
setFont( Res.controlFont().deriveFont( 12f ) );
setAlignmentX( LEFT_ALIGNMENT );
@@ -148,8 +157,8 @@ public abstract class Components {
public static JCheckBox checkBox(final String label) {
return new JCheckBox( label ) {
{
- setFont(Res.controlFont().deriveFont(12f));
- setBackground(null);
+ setFont( Res.controlFont().deriveFont( 12f ) );
+ setBackground( null );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@@ -164,14 +173,14 @@ public abstract class Components {
public static JComboBox comboBox(final ComboBoxModel model) {
return new JComboBox( model ) {
{
-// CompoundBorder editorBorder = BorderFactory.createCompoundBorder(
-// BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
-// BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) );
-// ((JComponent) ((BasicComboBoxEditor) getEditor()).getEditorComponent()).setBorder(editorBorder);
+ // CompoundBorder editorBorder = BorderFactory.createCompoundBorder(
+ // BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
+ // BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) );
+ // ((JComponent) ((BasicComboBoxEditor) getEditor()).getEditorComponent()).setBorder(editorBorder);
setFont( Res.controlFont().deriveFont( 12f ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
-// setBorder(null);
+ // setBorder(null);
}
@Override