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
protected byte[] getKey() {
Preconditions.checkState( isValid() );
return Preconditions.checkNotNull( masterKey );
}

View File

@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 <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 ) );
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 <M> JComboBox<M> comboBox(final ComboBoxModel<M> model) {
return new JComboBox<M>( 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