2
0

Java identicon support.

This commit is contained in:
Maarten Billemont 2015-03-29 20:30:57 -04:00
parent ea9d8cc275
commit 70f7fa1345
9 changed files with 200 additions and 39 deletions

View File

@ -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 );
}
}
}

View File

@ -65,6 +65,7 @@ public abstract class MasterKey {
@Nonnull @Nonnull
protected byte[] getKey() { protected byte[] getKey() {
Preconditions.checkState( isValid() );
return Preconditions.checkNotNull( masterKey ); return Preconditions.checkNotNull( masterKey );
} }

View File

@ -1,9 +1,13 @@
package com.lyndir.masterpassword.gui; package com.lyndir.masterpassword.gui;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.lyndir.masterpassword.MPIdenticon;
import com.lyndir.masterpassword.gui.util.Components; import com.lyndir.masterpassword.gui.util.Components;
import java.awt.*; import java.awt.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.*; import javax.swing.*;
import org.jetbrains.annotations.NotNull;
/** /**
@ -29,6 +33,8 @@ public abstract class AuthenticationPanel extends Components.GradientPanel {
} }
} ); } );
add( Box.createVerticalGlue() ); add( Box.createVerticalGlue() );
avatarLabel.setToolTipText( "The avatar for your user. Click to change it." );
} }
protected void updateUser(boolean repack) { protected void updateUser(boolean repack) {
@ -39,8 +45,11 @@ public abstract class AuthenticationPanel extends Components.GradientPanel {
unlockFrame.repack(); unlockFrame.repack();
} }
@Nullable
protected abstract User getSelectedUser(); protected abstract User getSelectedUser();
@NotNull
@Nonnull
public abstract char[] getMasterPassword(); public abstract char[] getMasterPassword();
public Component getFocusComponent() { public Component getFocusComponent() {

View File

@ -7,6 +7,7 @@ import java.awt.event.ActionListener;
import javax.swing.*; import javax.swing.*;
import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener; 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() ); return new IncognitoUser( fullNameField.getText() );
} }
@NotNull
@Override @Override
public char[] getMasterPassword() { public char[] getMasterPassword() {
return masterPasswordField.getPassword(); return masterPasswordField.getPassword();

View File

@ -14,6 +14,7 @@ import javax.swing.*;
import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener; import javax.swing.event.DocumentListener;
import javax.swing.plaf.metal.MetalComboBoxEditor; 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 ); return userField.getModel().getElementAt( selectedIndex );
} }
@NotNull
@Override @Override
public char[] getMasterPassword() { public char[] getMasterPassword() {
return masterPasswordField.getPassword(); return masterPasswordField.getPassword();
@ -124,6 +126,7 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
updateUser( true ); updateUser( true );
} }
} ); } );
setToolTipText( "Add a new user to the list." );
} }
}, new JButton( Res.iconQuestion() ) { }, new JButton( Res.iconQuestion() ) {
{ {
@ -131,10 +134,11 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
@Override @Override
public void actionPerformed(final ActionEvent e) { public void actionPerformed(final ActionEvent e) {
JOptionPane.showMessageDialog( ModelAuthenticationPanel.this, // 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 ); "Help", JOptionPane.INFORMATION_MESSAGE );
} }
} ); } );
setToolTipText( "More information." );
} }
} ); } );
} }

View File

@ -53,7 +53,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
add( Components.borderPanel( sitePanel, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ), BorderLayout.CENTER ); add( Components.borderPanel( sitePanel, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ), BorderLayout.CENTER );
// User // 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() ); sitePanel.add( Components.stud() );
// Site Name // Site Name
@ -161,7 +161,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
updateMask(); updateMask();
// Tip // Tip
tipLabel = Components.label( " ", JLabel.CENTER ); tipLabel = Components.label( " ", SwingConstants.CENTER );
tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT ); tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT );
JPanel passwordContainer = Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, passwordField, tipLabel ); JPanel passwordContainer = Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, passwordField, tipLabel );
passwordContainer.setOpaque( true ); passwordContainer.setOpaque( true );

View File

@ -97,6 +97,10 @@ public abstract class Res {
return 19; return 19;
} }
public static Font emoticonsFont() {
return emoticonsRegular();
}
public static Font controlFont() { public static Font controlFont() {
return arimoRegular(); return arimoRegular();
} }
@ -109,6 +113,10 @@ public abstract class Res {
return sourceSansProBlack(); return sourceSansProBlack();
} }
public static Font emoticonsRegular() {
return font( "fonts/Emoticons-Regular.otf" );
}
public static Font sourceCodeProRegular() { public static Font sourceCodeProRegular() {
return font( "fonts/SourceCodePro-Regular.otf" ); return font( "fonts/SourceCodePro-Regular.otf" );
} }

View File

@ -2,10 +2,12 @@ package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*; 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.gui.util.Components;
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException; import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import javax.annotation.Nullable;
import javax.swing.*; import javax.swing.*;
@ -16,6 +18,7 @@ public class UnlockFrame extends JFrame {
private final SignInCallback signInCallback; private final SignInCallback signInCallback;
private final Components.GradientPanel root; private final Components.GradientPanel root;
private final JLabel identiconLabel;
private final JButton signInButton; private final JButton signInButton;
private final JPanel authenticationContainer; private final JPanel authenticationContainer;
private AuthenticationPanel authenticationPanel; private AuthenticationPanel authenticationPanel;
@ -27,33 +30,40 @@ public class UnlockFrame extends JFrame {
super( "Unlock Master Password" ); super( "Unlock Master Password" );
this.signInCallback = signInCallback; this.signInCallback = signInCallback;
setDefaultCloseOperation(DISPOSE_ON_CLOSE); setDefaultCloseOperation( DISPOSE_ON_CLOSE );
setContentPane(root = Components.gradientPanel(new BorderLayout(20, 20), Res.colors().frameBg())); addWindowFocusListener( new WindowAdapter() {
root.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
addWindowFocusListener(new WindowAdapter() {
@Override @Override
public void windowGainedFocus(WindowEvent e) { public void windowGainedFocus(WindowEvent e) {
root.setGradientColor(Res.colors().frameBg()); root.setGradientColor( Res.colors().frameBg() );
} }
@Override @Override
public void windowLostFocus(WindowEvent e) { 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 // Sign In
JPanel signInBox = Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = Components.button( "Sign In" ), JPanel signInBox = Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = Components.button( "Sign In" ),
Box.createGlue() ); Box.createGlue() );
signInBox.setBackground( null ); 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() { signInButton.addActionListener( new AbstractAction() {
@Override @Override
public void actionPerformed(final ActionEvent e) { public void actionPerformed(final ActionEvent e) {
@ -86,6 +96,7 @@ public class UnlockFrame extends JFrame {
authenticationContainer.add( Components.stud() ); authenticationContainer.add( Components.stud() );
final JCheckBox incognitoCheckBox = Components.checkBox( "Incognito" ); final JCheckBox incognitoCheckBox = Components.checkBox( "Incognito" );
incognitoCheckBox.setToolTipText( "Log in without saving any information." );
incognitoCheckBox.setSelected( incognito ); incognitoCheckBox.setSelected( incognito );
incognitoCheckBox.addItemListener( new ItemListener() { incognitoCheckBox.addItemListener( new ItemListener() {
@Override @Override
@ -104,9 +115,10 @@ public class UnlockFrame extends JFrame {
authenticationContainer.add( toolsPanel ); authenticationContainer.add( toolsPanel );
for (JButton button : authenticationPanel.getButtons()) { for (JButton button : authenticationPanel.getButtons()) {
toolsPanel.add( button ); toolsPanel.add( button );
button.setBorder( BorderFactory.createEmptyBorder() );
button.setMargin( new Insets( 0, 0, 0, 0 ) ); button.setMargin( new Insets( 0, 0, 0, 0 ) );
button.setAlignmentX(RIGHT_ALIGNMENT); button.setAlignmentX( RIGHT_ALIGNMENT );
button.setContentAreaFilled(false); button.setContentAreaFilled( false );
} }
checkSignIn(); checkSignIn();
@ -121,13 +133,24 @@ public class UnlockFrame extends JFrame {
} ); } );
} }
void updateUser(User user) { void updateUser(@Nullable User user) {
this.user = user; this.user = user;
checkSignIn(); checkSignIn();
} }
boolean 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 ); signInButton.setEnabled( enabled );
return enabled; return enabled;
@ -170,7 +193,6 @@ public class UnlockFrame extends JFrame {
} }
} ); } );
} }
} }
} ); } );
} }

View File

@ -70,10 +70,10 @@ public abstract class Components {
public static JPasswordField passwordField() { public static JPasswordField passwordField() {
return new JPasswordField() { return new JPasswordField() {
{ {
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 ) ) );
setAlignmentX(LEFT_ALIGNMENT); setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY(BOTTOM_ALIGNMENT); setAlignmentY( BOTTOM_ALIGNMENT );
} }
@Override @Override
@ -116,7 +116,7 @@ public abstract class Components {
((DefaultEditor) getEditor()).getTextField().setBorder( editorBorder ); ((DefaultEditor) getEditor()).getTextField().setBorder( editorBorder );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT ); setAlignmentY( BOTTOM_ALIGNMENT );
setBorder(null); setBorder( null );
} }
@Override @Override
@ -126,12 +126,21 @@ public abstract class Components {
}; };
} }
public static JLabel label(final String label) { public static JLabel label(@Nullable String label) {
return label( label, JLabel.LEADING ); 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 <code>SwingConstants</code>:
* <code>LEFT</code>,
* <code>CENTER</code>,
* <code>RIGHT</code>,
* <code>LEADING</code> or
* <code>TRAILING</code>.
*/
public static JLabel label(@Nullable final String label, final int horizontalAlignment) {
return new JLabel( label, horizontalAlignment ) {
{ {
setFont( Res.controlFont().deriveFont( 12f ) ); setFont( Res.controlFont().deriveFont( 12f ) );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
@ -148,8 +157,8 @@ 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.controlFont().deriveFont(12f)); setFont( Res.controlFont().deriveFont( 12f ) );
setBackground(null); setBackground( null );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT ); setAlignmentY( BOTTOM_ALIGNMENT );
} }
@ -164,14 +173,14 @@ public abstract class Components {
public static <M> JComboBox<M> comboBox(final ComboBoxModel<M> model) { public static <M> JComboBox<M> comboBox(final ComboBoxModel<M> model) {
return new JComboBox<M>( model ) { return new JComboBox<M>( model ) {
{ {
// CompoundBorder editorBorder = BorderFactory.createCompoundBorder( // CompoundBorder editorBorder = BorderFactory.createCompoundBorder(
// BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ), // BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
// BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ); // BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) );
// ((JComponent) ((BasicComboBoxEditor) getEditor()).getEditorComponent()).setBorder(editorBorder); // ((JComponent) ((BasicComboBoxEditor) getEditor()).getEditorComponent()).setBorder(editorBorder);
setFont( Res.controlFont().deriveFont( 12f ) ); setFont( Res.controlFont().deriveFont( 12f ) );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT ); setAlignmentY( BOTTOM_ALIGNMENT );
// setBorder(null); // setBorder(null);
} }
@Override @Override