2
0

WIP - new Java UI.

This commit is contained in:
Maarten Billemont 2018-07-18 12:27:19 -04:00
parent 80b5fcd785
commit 596ace51ea
11 changed files with 443 additions and 1012 deletions

View File

@ -24,8 +24,7 @@ import com.google.common.base.Charsets;
import com.google.common.io.ByteSource; import com.google.common.io.ByteSource;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.TypeUtils; import com.lyndir.lhunath.opal.system.util.TypeUtils;
import com.lyndir.masterpassword.gui.view.PasswordFrame; import com.lyndir.masterpassword.gui.view.MasterPasswordFrame;
import com.lyndir.masterpassword.gui.view.UnlockFrame;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@ -39,13 +38,12 @@ import javax.swing.*;
* *
* @author mbillemo * @author mbillemo
*/ */
public class GUI implements UnlockFrame.SignInCallback { public class GUI {
@SuppressWarnings("UnusedDeclaration") @SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( GUI.class ); private static final Logger logger = Logger.get( GUI.class );
private final UnlockFrame unlockFrame = new UnlockFrame( this ); private final MasterPasswordFrame frame = new MasterPasswordFrame();
private PasswordFrame<?, ?> passwordFrame;
public static void main(final String... args) { public static void main(final String... args) {
Thread.setDefaultUncaughtExceptionHandler( Thread.setDefaultUncaughtExceptionHandler(
@ -114,17 +112,8 @@ public class GUI implements UnlockFrame.SignInCallback {
} }
protected void open() { protected void open() {
SwingUtilities.invokeLater( () -> { Res.ui( () -> {
if (passwordFrame == null) frame.setVisible( true );
unlockFrame.setVisible( true );
else
passwordFrame.setVisible( true );
} ); } );
} }
@Override
public void signedIn(final PasswordFrame<?, ?> passwordFrame) {
this.passwordFrame = passwordFrame;
open();
}
} }

View File

@ -23,19 +23,15 @@ import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.io.Resources; import com.google.common.io.Resources;
import com.google.common.util.concurrent.JdkFutureAdapters; import com.google.common.util.concurrent.*;
import com.google.common.util.concurrent.ListenableFuture;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.MPIdenticon; import com.lyndir.masterpassword.MPIdenticon;
import java.awt.*; import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.ImageObserver; import java.awt.image.ImageObserver;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.SoftReference; import java.lang.ref.SoftReference;
import java.net.URL; import java.net.URL;
import java.util.Map; import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -50,18 +46,19 @@ import org.jetbrains.annotations.NonNls;
public abstract class Res { public abstract class Res {
private static final int AVATAR_COUNT = 19; private static final int AVATAR_COUNT = 19;
private static final Map<Window, ScheduledExecutorService> jobExecutorByWindow = new WeakHashMap<>(); private static final ListeningScheduledExecutorService jobExecutor = MoreExecutors.listeningDecorator(
Executors.newSingleThreadScheduledExecutor() );
private static final Executor immediateUiExecutor = new SwingExecutorService( true ); private static final Executor immediateUiExecutor = new SwingExecutorService( true );
private static final Executor laterUiExecutor = new SwingExecutorService( false ); private static final Executor laterUiExecutor = new SwingExecutorService( false );
private static final Logger logger = Logger.get( Res.class ); private static final Logger logger = Logger.get( Res.class );
private static final Colors colors = new Colors(); private static final Colors colors = new Colors();
public static Future<?> job(final Window host, final Runnable job) { public static Future<?> job(final Runnable job) {
return job( host, job, 0, TimeUnit.MILLISECONDS ); return job( job, 0, TimeUnit.MILLISECONDS );
} }
public static Future<?> job(final Window host, final Runnable job, final long delay, final TimeUnit timeUnit) { public static Future<?> job(final Runnable job, final long delay, final TimeUnit timeUnit) {
return jobExecutor( host ).schedule( () -> { return jobExecutor.schedule( () -> {
try { try {
job.run(); job.run();
} }
@ -71,38 +68,30 @@ public abstract class Res {
}, delay, timeUnit ); }, delay, timeUnit );
} }
public static <V> ListenableFuture<V> job(final Window host, final Callable<V> job) { public static <V> ListenableFuture<V> job(final Callable<V> job) {
return job( host, job, 0, TimeUnit.MILLISECONDS ); return job( job, 0, TimeUnit.MILLISECONDS );
} }
public static <V> ListenableFuture<V> job(final Window host, final Callable<V> job, final long delay, final TimeUnit timeUnit) { public static <V> ListenableFuture<V> job(final Callable<V> job, final long delay, final TimeUnit timeUnit) {
ScheduledExecutorService executor = jobExecutor( host ); return jobExecutor.schedule( job, delay, timeUnit );
return JdkFutureAdapters.listenInPoolThread( executor.schedule( job::call, delay, timeUnit ), executor ); }
public static void ui(final Runnable job) {
ui( true, job );
}
public static void ui(final boolean immediate, final Runnable job) {
uiExecutor( immediate ).execute( job );
}
public static Executor uiExecutor() {
return uiExecutor( true );
} }
public static Executor uiExecutor(final boolean immediate) { public static Executor uiExecutor(final boolean immediate) {
return immediate? immediateUiExecutor: laterUiExecutor; return immediate? immediateUiExecutor: laterUiExecutor;
} }
public static ScheduledExecutorService jobExecutor(final Window host) {
ScheduledExecutorService executor = jobExecutorByWindow.get( host );
if (executor == null) {
jobExecutorByWindow.put( host, executor = Executors.newSingleThreadScheduledExecutor() );
host.addWindowListener( new WindowAdapter() {
@Override
public void windowClosed(final WindowEvent e) {
ExecutorService executor = jobExecutorByWindow.remove( host );
if (executor != null)
executor.shutdownNow();
}
} );
}
return executor;
}
public static Icon iconAdd() { public static Icon iconAdd() {
return new RetinaIcon( Resources.getResource( "media/icon_add@2x.png" ) ); return new RetinaIcon( Resources.getResource( "media/icon_add@2x.png" ) );
} }
@ -273,6 +262,7 @@ public abstract class Res {
private final Color frameBg = Color.decode( "#5A5D6B" ); private final Color frameBg = Color.decode( "#5A5D6B" );
private final Color controlBg = Color.decode( "#ECECEC" ); private final Color controlBg = Color.decode( "#ECECEC" );
private final Color controlBorder = Color.decode( "#BFBFBF" ); private final Color controlBorder = Color.decode( "#BFBFBF" );
private final Color errorFg = Color.decode( "#FF3333" );
public Color frameBg() { public Color frameBg() {
return frameBg; return frameBg;
@ -286,6 +276,10 @@ public abstract class Res {
return controlBorder; return controlBorder;
} }
public Color errorFg() {
return errorFg;
}
public Color fromIdenticonColor(final MPIdenticon.Color identiconColor, final BackgroundMode backgroundMode) { public Color fromIdenticonColor(final MPIdenticon.Color identiconColor, final BackgroundMode backgroundMode) {
switch (identiconColor) { switch (identiconColor) {
case RED: case RED:

View File

@ -31,6 +31,7 @@ import javax.swing.border.CompoundBorder;
*/ */
public abstract class Components { public abstract class Components {
private static final float HEADING_TEXT_SIZE = 18f;
private static final float CONTROL_TEXT_SIZE = 12f; private static final float CONTROL_TEXT_SIZE = 12f;
public static GradientPanel boxLayout(final int axis, final Component... components) { public static GradientPanel boxLayout(final int axis, final Component... components) {
@ -118,8 +119,12 @@ public abstract class Components {
}; };
} }
public static Component stud() { public static Component strut() {
Dimension studDimension = new Dimension( 8, 8 ); return strut( 8 );
}
public static Component strut(final int size) {
Dimension studDimension = new Dimension( size, size );
Box.Filler rigidArea = new Box.Filler( studDimension, studDimension, studDimension ); Box.Filler rigidArea = new Box.Filler( studDimension, studDimension, studDimension );
rigidArea.setAlignmentX( Component.LEFT_ALIGNMENT ); rigidArea.setAlignmentX( Component.LEFT_ALIGNMENT );
rigidArea.setAlignmentY( Component.BOTTOM_ALIGNMENT ); rigidArea.setAlignmentY( Component.BOTTOM_ALIGNMENT );
@ -146,6 +151,34 @@ public abstract class Components {
}; };
} }
public static JLabel heading(@Nullable final String heading) {
return heading( heading, SwingConstants.CENTER );
}
/**
* @param horizontalAlignment One of the following constants
* defined in {@code SwingConstants}:
* {@code LEFT},
* {@code CENTER},
* {@code RIGHT},
* {@code LEADING} or
* {@code TRAILING}.
*/
public static JLabel heading(@Nullable final String heading, final int horizontalAlignment) {
return new JLabel( heading, horizontalAlignment ) {
{
setFont( Res.controlFont().deriveFont( Font.BOLD, HEADING_TEXT_SIZE ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
};
}
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 );
} }
@ -218,7 +251,15 @@ public abstract class Components {
@Nullable @Nullable
private GradientPaint paint; private GradientPaint paint;
protected GradientPanel(@Nullable final LayoutManager layout, @Nullable final Color gradientColor) { public GradientPanel() {
this( null, null );
}
public GradientPanel(@Nullable final Color gradientColor) {
this( null, gradientColor );
}
public GradientPanel(@Nullable final LayoutManager layout, @Nullable final Color gradientColor) {
super( layout ); super( layout );
this.gradientColor = gradientColor; this.gradientColor = gradientColor;
setBackground( null ); setBackground( null );

View File

@ -1,84 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.gui.view;
import com.google.common.collect.ImmutableList;
import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.model.MPUser;
import java.awt.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.*;
/**
* @author lhunath, 2014-06-11
*/
public abstract class AuthenticationPanel<U extends MPUser<?>> extends Components.GradientPanel {
protected final UnlockFrame unlockFrame;
protected final JLabel avatarLabel;
protected AuthenticationPanel(final UnlockFrame unlockFrame) {
super( null, null );
this.unlockFrame = unlockFrame;
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
// Avatar
add( Box.createVerticalGlue() );
add( avatarLabel = new JLabel( Res.avatar( 0 ) ) {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
}
} );
add( Box.createVerticalGlue() );
avatarLabel.setToolTipText( "The avatar for your user. Click to change it." );
}
protected void updateUser(final boolean repack) {
unlockFrame.updateUser( getSelectedUser() );
validate();
if (repack)
unlockFrame.repack();
}
@Nullable
protected abstract U getSelectedUser();
@Nonnull
public abstract char[] getMasterPassword();
@Nullable
public Component getFocusComponent() {
return null;
}
public Iterable<? extends JButton> getButtons() {
return ImmutableList.of();
}
public abstract void reset();
public abstract PasswordFrame<?, ?> newPasswordFrame();
}

View File

@ -0,0 +1,106 @@
package com.lyndir.masterpassword.gui.view;
import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.model.MPUser;
import com.lyndir.masterpassword.model.impl.MPFileUser;
import com.lyndir.masterpassword.model.impl.MPFileUserManager;
import java.awt.*;
import java.awt.event.*;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.plaf.metal.MetalComboBoxEditor;
/**
* @author lhunath, 2018-07-14
*/
public class FilesPanel extends JPanel implements ActionListener {
private final Set<Listener> listeners = new CopyOnWriteArraySet<>();
private final JLabel avatarLabel = new JLabel();
private final JComboBox<MPFileUser> userField = Components.comboBox();
protected FilesPanel() {
setOpaque( false );
setBackground( new Color( 0, 0, 0, 0 ) );
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
// -
add( Box.createVerticalGlue() );
// Avatar
add( avatarLabel );
avatarLabel.setHorizontalAlignment( SwingConstants.CENTER );
avatarLabel.setMaximumSize( new Dimension( Integer.MAX_VALUE, 0 ) );
avatarLabel.setToolTipText( "The avatar for your user. Click to change it." );
// -
add( Components.strut( 20 ) );
// User Selection
add( userField );
userField.setFont( Res.valueFont().deriveFont( userField.getFont().getSize2D() ) );
userField.addActionListener( this );
userField.setRenderer( new DefaultListCellRenderer() {
@Override
@SuppressWarnings("unchecked")
public Component getListCellRendererComponent(final JList<?> list, final Object value, final int index,
final boolean isSelected, final boolean cellHasFocus) {
String userValue = (value == null)? null: ((MPFileUser) value).getFullName();
return super.getListCellRendererComponent( list, userValue, index, isSelected, cellHasFocus );
}
} );
userField.setEditor( new MetalComboBoxEditor() {
@Override
protected JTextField createEditorComponent() {
JTextField editorComponents = Components.textField();
editorComponents.setForeground( Color.red );
return editorComponents;
}
} );
// -
add( Box.createVerticalGlue() );
}
public void reload() {
MPFileUserManager.get().reload();
userField.setModel( new DefaultComboBoxModel<>( MPFileUserManager.get().getFiles().toArray( new MPFileUser[0] ) ) );
updateFile();
}
@Override
public void actionPerformed(final ActionEvent e) {
updateFile();
}
@Nullable
private MPFileUser getSelectedUser() {
int selectedIndex = userField.getSelectedIndex();
if (selectedIndex < 0)
return null;
return userField.getModel().getElementAt( selectedIndex );
}
private void updateFile() {
MPFileUser selectedFile = getSelectedUser();
avatarLabel.setIcon( Res.avatar( (selectedFile == null)? 0: selectedFile.getAvatar() ) );
for (final Listener listener : listeners)
listener.onUserSelected( selectedFile );
}
public boolean addListener(final Listener listener) {
return listeners.add( listener );
}
public interface Listener {
void onUserSelected(@Nullable MPUser<?> selectedUser);
}
}

View File

@ -1,127 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.gui.view;
import com.google.common.base.Preconditions;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.masterpassword.MPAlgorithm;
import com.lyndir.masterpassword.MPResultType;
import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.model.MPIncognitoSite;
import com.lyndir.masterpassword.gui.model.MPIncognitoUser;
import com.lyndir.masterpassword.gui.util.Components;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/**
* @author lhunath, 2014-06-11
*/
@SuppressWarnings({ "serial", "MagicNumber" })
public class IncognitoAuthenticationPanel extends AuthenticationPanel<MPIncognitoUser> implements DocumentListener, ActionListener {
private final JTextField fullNameField;
private final JPasswordField masterPasswordField;
public IncognitoAuthenticationPanel(final UnlockFrame unlockFrame) {
// Full Name
super( unlockFrame );
add( Components.stud() );
JLabel fullNameLabel = Components.label( "Full Name:" );
add( fullNameLabel );
fullNameField = Components.textField();
fullNameField.setFont( Res.valueFont().deriveFont( 12f ) );
fullNameField.getDocument().addDocumentListener( this );
fullNameField.addActionListener( this );
add( fullNameField );
add( Components.stud() );
// Master Password
JLabel masterPasswordLabel = Components.label( "Master Password:" );
add( masterPasswordLabel );
masterPasswordField = Components.passwordField();
masterPasswordField.addActionListener( this );
masterPasswordField.getDocument().addDocumentListener( this );
add( masterPasswordField );
}
@Override
public Component getFocusComponent() {
return fullNameField;
}
@Override
public void reset() {
masterPasswordField.setText( "" );
}
@Override
public PasswordFrame<MPIncognitoUser, ?> newPasswordFrame() {
return new PasswordFrame<MPIncognitoUser, MPIncognitoSite>( Preconditions.checkNotNull( getSelectedUser() ) ) {
@Override
protected MPIncognitoSite createSite(final MPIncognitoUser user, final String siteName, final UnsignedInteger siteCounter,
final MPResultType resultType, final MPAlgorithm algorithm) {
return new MPIncognitoSite( user, siteName, algorithm, siteCounter, resultType, null );
}
};
}
@Nullable
@Override
protected MPIncognitoUser getSelectedUser() {
return new MPIncognitoUser( fullNameField.getText() );
}
@Nonnull
@Override
public char[] getMasterPassword() {
return masterPasswordField.getPassword();
}
@Override
public void insertUpdate(final DocumentEvent e) {
updateUser( false );
}
@Override
public void removeUpdate(final DocumentEvent e) {
updateUser( false );
}
@Override
public void changedUpdate(final DocumentEvent e) {
updateUser( false );
}
@Override
public void actionPerformed(final ActionEvent e) {
updateUser( false );
unlockFrame.trySignIn( fullNameField, masterPasswordField );
}
}

View File

@ -0,0 +1,47 @@
package com.lyndir.masterpassword.gui.view;
import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.model.MPUser;
import java.awt.*;
import javax.annotation.Nullable;
import javax.swing.*;
/**
* @author lhunath, 2018-07-14
*/
@SuppressWarnings("MagicNumber")
public class MasterPasswordFrame extends JFrame implements FilesPanel.Listener {
@SuppressWarnings("FieldCanBeLocal")
private final Components.GradientPanel root;
private final FilesPanel filesPanel = new FilesPanel();
private final UserPanel userPanel = new UserPanel();
public MasterPasswordFrame() {
super( "Master Password" );
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
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( filesPanel );
root.add( Components.borderPanel( userPanel, BorderFactory.createRaisedBevelBorder(), Res.colors().controlBg() ) );
filesPanel.addListener( this );
filesPanel.reload();
setMinimumSize( new Dimension( 640, 480 ) );
pack();
setLocationByPlatform( true );
setLocationRelativeTo( null );
}
@Override
public void onUserSelected(@Nullable final MPUser<?> selectedUser) {
userPanel.setUser( selectedUser );
}
}

View File

@ -1,248 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.gui.view;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.MPAlgorithm;
import com.lyndir.masterpassword.MPResultType;
import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.model.MPUser;
import com.lyndir.masterpassword.model.impl.*;
import java.awt.*;
import java.awt.event.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.plaf.metal.MetalComboBoxEditor;
/**
* @author lhunath, 2014-06-11
*/
public class ModelAuthenticationPanel extends AuthenticationPanel<MPFileUser> implements ItemListener, ActionListener, DocumentListener {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( ModelAuthenticationPanel.class );
private static final long serialVersionUID = 1L;
private final JComboBox<MPFileUser> userField;
private final JLabel masterPasswordLabel;
private final JPasswordField masterPasswordField;
public ModelAuthenticationPanel(final UnlockFrame unlockFrame) {
super( unlockFrame );
add( Components.stud() );
// Avatar
avatarLabel.addMouseListener( new MouseAdapter() {
@Override
public void mouseClicked(final MouseEvent e) {
MPFileUser selectedUser = getSelectedUser();
if (selectedUser != null) {
selectedUser.setAvatar( selectedUser.getAvatar() + 1 );
updateUser( false );
}
}
} );
// User
JLabel userLabel = Components.label( "User:" );
add( userLabel );
userField = Components.comboBox( readConfigUsers() );
userField.setFont( Res.valueFont().deriveFont( userField.getFont().getSize2D() ) );
userField.addItemListener( this );
userField.addActionListener( this );
userField.setRenderer( new DefaultListCellRenderer() {
private static final long serialVersionUID = 1L;
@Override
@SuppressWarnings("unchecked")
public Component getListCellRendererComponent(final JList<?> list, final Object value, final int index,
final boolean isSelected, final boolean cellHasFocus) {
String userValue = ((MPUser<MPFileSite>) value).getFullName();
return super.getListCellRendererComponent( list, userValue, index, isSelected, cellHasFocus );
}
} );
userField.setEditor( new MetalComboBoxEditor() {
@Override
protected JTextField createEditorComponent() {
JTextField editorComponents = Components.textField();
editorComponents.setForeground( Color.red );
return editorComponents;
}
} );
add( userField );
add( Components.stud() );
// Master Password
masterPasswordLabel = Components.label( "Master Password:" );
add( masterPasswordLabel );
masterPasswordField = Components.passwordField();
masterPasswordField.addActionListener( this );
masterPasswordField.getDocument().addDocumentListener( this );
add( masterPasswordField );
}
@Override
public Component getFocusComponent() {
return masterPasswordField.isVisible()? masterPasswordField: null;
}
@Override
protected void updateUser(boolean repack) {
MPFileUser selectedUser = getSelectedUser();
if (selectedUser != null) {
avatarLabel.setIcon( Res.avatar( selectedUser.getAvatar() ) );
boolean showPasswordField = !selectedUser.isMasterKeyAvailable(); // TODO: is this the same as keySaved()?
if (masterPasswordField.isVisible() != showPasswordField) {
masterPasswordLabel.setVisible( showPasswordField );
masterPasswordField.setVisible( showPasswordField );
repack = true;
}
}
super.updateUser( repack );
}
@Nullable
@Override
protected MPFileUser getSelectedUser() {
int selectedIndex = userField.getSelectedIndex();
if (selectedIndex < 0)
return null;
return userField.getModel().getElementAt( selectedIndex );
}
@Nonnull
@Override
public char[] getMasterPassword() {
return masterPasswordField.getPassword();
}
@Override
public Iterable<? extends JButton> getButtons() {
return ImmutableList.of( new JButton( Res.iconAdd() ) {
{
addActionListener( new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
String fullName = JOptionPane.showInputDialog( ModelAuthenticationPanel.this, //
"Enter your full name, ensuring it is correctly spelled and capitalized:",
"New User", JOptionPane.QUESTION_MESSAGE );
MPFileUserManager.get().addUser( new MPFileUser( fullName ) );
userField.setModel( new DefaultComboBoxModel<>( readConfigUsers() ) );
updateUser( true );
}
} );
setToolTipText( "Add a new user to the list." );
}
}, new JButton( Res.iconDelete() ) {
{
addActionListener( new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
MPFileUser deleteUser = getSelectedUser();
if (deleteUser == null)
return;
if (JOptionPane.showConfirmDialog( ModelAuthenticationPanel.this, //
strf( "Are you sure you want to delete the user and sites remembered for:%n%s.",
deleteUser.getFullName() ), //
"Delete User", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE )
== JOptionPane.CANCEL_OPTION)
return;
MPFileUserManager.get().deleteUser( deleteUser );
userField.setModel( new DefaultComboBoxModel<>( readConfigUsers() ) );
updateUser( true );
}
} );
setToolTipText( "Delete the selected user." );
}
}, new JButton( Res.iconQuestion() ) {
{
addActionListener( e -> JOptionPane.showMessageDialog(
ModelAuthenticationPanel.this, //
strf( "Reads users and sites from the directory at:%n%s",
MPFileUserManager.get().getPath().getAbsolutePath() ), //
"Help", JOptionPane.INFORMATION_MESSAGE ) );
setToolTipText( "More information." );
}
} );
}
@Override
public void reset() {
masterPasswordField.setText( "" );
}
@Override
public PasswordFrame<MPFileUser, MPFileSite> newPasswordFrame() {
return new PasswordFrame<MPFileUser, MPFileSite>( Preconditions.checkNotNull( getSelectedUser() ) ) {
@Override
protected MPFileSite createSite(final MPFileUser user, final String siteName, final UnsignedInteger siteCounter,
final MPResultType resultType,
final MPAlgorithm algorithm) {
return new MPFileSite( user, siteName, algorithm, siteCounter, resultType );
}
};
}
private static MPFileUser[] readConfigUsers() {
return MPFileUserManager.get().getUsers().toArray( new MPFileUser[0] );
}
@Override
public void itemStateChanged(final ItemEvent e) {
updateUser( false );
}
@Override
public void actionPerformed(final ActionEvent e) {
updateUser( false );
unlockFrame.trySignIn( userField );
}
@Override
public void insertUpdate(final DocumentEvent e) {
updateUser( false );
}
@Override
public void removeUpdate(final DocumentEvent e) {
updateUser( false );
}
@Override
public void changedUpdate(final DocumentEvent e) {
updateUser( false );
}
}

View File

@ -1,279 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.gui.view;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.collect.Iterables;
import com.google.common.primitives.UnsignedInteger;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.*;
import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.util.*;
import com.lyndir.masterpassword.model.MPSite;
import com.lyndir.masterpassword.model.MPUser;
import com.lyndir.masterpassword.model.impl.MPFileSite;
import com.lyndir.masterpassword.model.impl.MPFileUser;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.WindowEvent;
import java.util.Collection;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/**
* @author lhunath, 2014-06-08
*/
public abstract class PasswordFrame<U extends MPUser<S>, S extends MPSite<?>> extends JFrame implements DocumentListener {
private static final Logger logger = Logger.get( PasswordFrame.class );
@SuppressWarnings("FieldCanBeLocal")
private final Components.GradientPanel root;
private final JTextField siteNameField;
private final JButton siteActionButton;
private final JComboBox<MPAlgorithm.Version> siteVersionField;
private final JSpinner siteCounterField;
private final UnsignedIntegerModel siteCounterModel;
private final JComboBox<MPResultType> resultTypeField;
private final JPasswordField passwordField;
private final JLabel tipLabel;
private final JCheckBox maskPasswordField;
private final char passwordEchoChar;
private final Font passwordEchoFont;
private final U user;
@Nullable
private S currentSite;
private boolean updatingUI;
@SuppressWarnings("MagicNumber")
protected PasswordFrame(final U user) {
super( "Master Password" );
this.user = user;
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
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 ) );
// Site
JPanel sitePanel = Components.boxLayout( BoxLayout.PAGE_AXIS );
sitePanel.setOpaque( true );
sitePanel.setBackground( Res.colors().controlBg() );
sitePanel.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
root.add( Components.borderPanel( sitePanel, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) );
// User
sitePanel.add( Components.label( strf( "Generating passwords for: %s", user.getFullName() ), SwingConstants.CENTER ) );
sitePanel.add( Components.stud() );
// Site Name
sitePanel.add( Components.label( "Site Name:" ) );
JComponent siteControls = Components.boxLayout( BoxLayout.LINE_AXIS, //
siteNameField = Components.textField(), Components.stud(),
siteActionButton = Components.button( "Add Site" ) );
siteNameField.getDocument().addDocumentListener( this );
siteNameField.addActionListener(
e -> Futures.addCallback( updatePassword( true ), new FailableCallback<String>( logger ) {
@Override
public void onSuccess(@Nullable final String sitePassword) {
if (sitePassword == null)
return;
if (currentSite instanceof MPFileSite)
((MPFileSite) currentSite).use();
Transferable clipboardContents = new StringSelection( sitePassword );
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
dispatchEvent( new WindowEvent( PasswordFrame.this, WindowEvent.WINDOW_CLOSING ) );
}
}, Res.uiExecutor( false ) ) );
siteActionButton.addActionListener(
e -> {
if (currentSite == null)
return;
if (currentSite instanceof MPFileSite)
this.user.deleteSite( currentSite );
else
this.user.addSite( currentSite );
siteNameField.requestFocus();
updatePassword( true );
} );
sitePanel.add( siteControls );
sitePanel.add( Components.stud() );
// Site Type & Counter
siteCounterModel = new UnsignedIntegerModel( UnsignedInteger.ONE, UnsignedInteger.ONE );
MPResultType[] types = Iterables.toArray( MPResultType.forClass( MPResultTypeClass.Template ), MPResultType.class );
JComponent siteSettings = Components.boxLayout( BoxLayout.LINE_AXIS, //
resultTypeField = Components.comboBox( types ), //
Components.stud(), //
siteVersionField = Components.comboBox( MPAlgorithm.Version.values() ), //
Components.stud(), //
siteCounterField = Components.spinner( siteCounterModel ) );
sitePanel.add( siteSettings );
resultTypeField.setFont( Res.valueFont().deriveFont( resultTypeField.getFont().getSize2D() ) );
resultTypeField.setSelectedItem( user.getAlgorithm().mpw_default_result_type() );
resultTypeField.addItemListener( e -> updatePassword( true ) );
siteVersionField.setFont( Res.valueFont().deriveFont( siteVersionField.getFont().getSize2D() ) );
siteVersionField.setAlignmentX( RIGHT_ALIGNMENT );
siteVersionField.setSelectedItem( user.getAlgorithm() );
siteVersionField.addItemListener( e -> updatePassword( true ) );
siteCounterField.setFont( Res.valueFont().deriveFont( siteCounterField.getFont().getSize2D() ) );
siteCounterField.setAlignmentX( RIGHT_ALIGNMENT );
siteCounterField.addChangeListener( e -> updatePassword( true ) );
// Mask
maskPasswordField = Components.checkBox( "Hide Password" );
maskPasswordField.setAlignmentX( Component.CENTER_ALIGNMENT );
maskPasswordField.setSelected( true );
maskPasswordField.addItemListener( e -> updateMask() );
// Password
passwordField = Components.passwordField();
passwordField.setAlignmentX( Component.CENTER_ALIGNMENT );
passwordField.setHorizontalAlignment( SwingConstants.CENTER );
passwordField.putClientProperty( "JPasswordField.cutCopyAllowed", true );
passwordField.setEditable( false );
passwordField.setBackground( null );
passwordField.setBorder( null );
passwordEchoChar = passwordField.getEchoChar();
passwordEchoFont = passwordField.getFont().deriveFont( 40f );
updateMask();
// Tip
tipLabel = Components.label( " ", SwingConstants.CENTER );
tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT );
JPanel passwordContainer = Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, Box.createGlue(), passwordField,
Box.createGlue(), tipLabel );
passwordContainer.setOpaque( true );
passwordContainer.setBackground( Color.white );
passwordContainer.setBorder( BorderFactory.createEmptyBorder( 8, 8, 8, 8 ) );
root.add( Box.createVerticalStrut( 8 ) );
root.add( Components.borderPanel( passwordContainer, BorderFactory.createLoweredSoftBevelBorder(), Res.colors().frameBg() ),
BorderLayout.SOUTH );
pack();
setMinimumSize( new Dimension( Math.max( 600, getPreferredSize().width ), Math.max( 300, getPreferredSize().height ) ) );
pack();
setLocationByPlatform( true );
setLocationRelativeTo( null );
}
@SuppressWarnings("MagicNumber")
private void updateMask() {
passwordField.setEchoChar( maskPasswordField.isSelected()? passwordEchoChar: (char) 0 );
passwordField.setFont( maskPasswordField.isSelected()? passwordEchoFont: Res.bigValueFont().deriveFont( 40f ) );
}
@Nonnull
private ListenableFuture<String> updatePassword(final boolean allowNameCompletion) {
String siteNameQuery = siteNameField.getText();
if (updatingUI)
return Futures.immediateCancelledFuture();
if ((siteNameQuery == null) || siteNameQuery.isEmpty() || !user.isMasterKeyAvailable()) {
siteActionButton.setVisible( false );
tipLabel.setText( null );
passwordField.setText( null );
return Futures.immediateCancelledFuture();
}
MPResultType resultType = resultTypeField.getModel().getElementAt( resultTypeField.getSelectedIndex() );
MPAlgorithm siteAlgorithm = siteVersionField.getItemAt( siteVersionField.getSelectedIndex() ).getAlgorithm();
UnsignedInteger siteCounter = siteCounterModel.getNumber();
Collection<S> siteResults = user.findSites( siteNameQuery );
if (!allowNameCompletion)
siteResults = siteResults.stream().filter(
siteResult -> (siteResult != null) && siteNameQuery.equals( siteResult.getName() ) ).collect( Collectors.toList() );
S site = ifNotNullElse( Iterables.getFirst( siteResults, null ),
createSite( user, siteNameQuery, siteCounter, resultType, siteAlgorithm ) );
if ((currentSite != null) && currentSite.getName().equals( site.getName() )) {
site.setResultType( resultType );
site.setAlgorithm( siteAlgorithm );
site.setCounter( siteCounter );
}
ListenableFuture<String> passwordFuture = Res.job( this, () ->
site.getResult( MPKeyPurpose.Authentication, null, null ) );
SwingUtilities.invokeLater( () -> {
updatingUI = true;
currentSite = site;
siteActionButton.setVisible( user instanceof MPFileUser );
if (currentSite instanceof MPFileSite)
siteActionButton.setText( "Delete Site" );
else
siteActionButton.setText( "Add Site" );
resultTypeField.setSelectedItem( currentSite.getResultType() );
siteVersionField.setSelectedItem( currentSite.getAlgorithm().version() );
siteCounterField.setValue( currentSite.getCounter() );
siteNameField.setText( currentSite.getName() );
if (siteNameField.getText().startsWith( siteNameQuery ))
siteNameField.select( siteNameQuery.length(), siteNameField.getText().length() );
passwordField.setText( null );
tipLabel.setText( "Getting password..." );
Futures.addCallback( passwordFuture, new FailableCallback<String>( logger ) {
@Override
public void onSuccess(@Nullable final String sitePassword) {
if (sitePassword != null)
tipLabel.setText( "Press [Enter] to copy the password. Then paste it into the password field." );
passwordField.setText( sitePassword );
updatingUI = false;
}
}, Res.uiExecutor( true ) );
} );
return passwordFuture;
}
protected abstract S createSite(U user, String siteName, UnsignedInteger siteCounter, MPResultType resultType, MPAlgorithm algorithm);
@Override
public void insertUpdate(final DocumentEvent e) {
updatePassword( true );
}
@Override
public void removeUpdate(final DocumentEvent e) {
updatePassword( false );
}
@Override
public void changedUpdate(final DocumentEvent e) {
updatePassword( true );
}
}

View File

@ -1,216 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.gui.view;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.lyndir.masterpassword.MPAlgorithmException;
import com.lyndir.masterpassword.MPIdenticon;
import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.model.*;
import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import javax.swing.*;
/**
* @author lhunath, 2014-06-08
*/
@SuppressWarnings({ "MagicNumber", "serial" })
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;
private Future<?> identiconFuture;
private boolean incognito;
@Nullable
private MPUser<? extends MPSite<?>> user;
public UnlockFrame(final SignInCallback signInCallback) {
super( "Unlock Master Password" );
this.signInCallback = signInCallback;
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
addWindowFocusListener( new WindowAdapter() {
@Override
public void windowGainedFocus(final WindowEvent e) {
root.setGradientColor( Res.colors().frameBg() );
}
@Override
public void windowLostFocus(final WindowEvent e) {
root.setGradientColor( Color.RED );
}
} );
// Sign In
JPanel signInBox = Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = Components.button( "Sign In" ),
Box.createGlue() );
signInBox.setBackground( null );
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(
strf( "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) {
trySignIn();
}
} );
createAuthenticationPanel();
setLocationByPlatform( true );
setLocationRelativeTo( null );
}
protected void repack() {
pack();
setMinimumSize( new Dimension( Math.max( 300, getPreferredSize().width ), Math.max( 300, getPreferredSize().height ) ) );
pack();
}
private void createAuthenticationPanel() {
authenticationContainer.removeAll();
if (incognito) {
authenticationPanel = new IncognitoAuthenticationPanel( this );
} else {
authenticationPanel = new ModelAuthenticationPanel( this );
}
authenticationPanel.updateUser( false );
authenticationContainer.add( authenticationPanel );
authenticationContainer.add( Components.stud() );
JCheckBox incognitoCheckBox = Components.checkBox( "Incognito" );
incognitoCheckBox.setToolTipText( "Log in without saving any information." );
incognitoCheckBox.setSelected( incognito );
incognitoCheckBox.addItemListener( e -> {
incognito = incognitoCheckBox.isSelected();
SwingUtilities.invokeLater( this::createAuthenticationPanel );
} );
JComponent toolsPanel = Components.boxLayout( BoxLayout.LINE_AXIS, incognitoCheckBox, Box.createGlue() );
authenticationContainer.add( toolsPanel );
for (final 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 );
}
checkSignIn();
validate();
repack();
SwingUtilities.invokeLater( () -> ifNotNullElse( authenticationPanel.getFocusComponent(), signInButton ).requestFocusInWindow() );
}
void updateUser(@Nullable final MPUser<? extends MPSite<?>> user) {
this.user = user;
checkSignIn();
}
boolean checkSignIn() {
if (identiconFuture != null)
identiconFuture.cancel( false );
identiconFuture = Res.job( this, () -> SwingUtilities.invokeLater( () -> {
String fullName = (user == null)? "": user.getFullName();
char[] masterPassword = authenticationPanel.getMasterPassword();
if (fullName.isEmpty() || (masterPassword.length == 0)) {
identiconLabel.setText( " " );
return;
}
MPIdenticon identicon = new MPIdenticon( fullName, masterPassword );
identiconLabel.setText( identicon.getText() );
identiconLabel.setForeground(
Res.colors().fromIdenticonColor( identicon.getColor(), Res.Colors.BackgroundMode.DARK ) );
} ), 300, TimeUnit.MILLISECONDS );
String fullName = (user == null)? "": user.getFullName();
char[] masterPassword = authenticationPanel.getMasterPassword();
boolean enabled = !fullName.isEmpty() && (masterPassword.length > 0);
signInButton.setEnabled( enabled );
return enabled;
}
void trySignIn(final JComponent... signInComponents) {
if ((user == null) || !checkSignIn())
return;
for (final JComponent signInComponent : signInComponents)
signInComponent.setEnabled( false );
signInButton.setEnabled( false );
signInButton.setText( "Signing In..." );
Res.job( this, () -> {
try {
user.authenticate( authenticationPanel.getMasterPassword() );
SwingUtilities.invokeLater( () -> {
signInCallback.signedIn( authenticationPanel.newPasswordFrame() );
dispose();
} );
}
catch (final MPIncorrectMasterPasswordException | MPAlgorithmException e) {
SwingUtilities.invokeLater( () -> {
JOptionPane.showMessageDialog( null, e.getLocalizedMessage(), "Sign In Failed", JOptionPane.ERROR_MESSAGE );
authenticationPanel.reset();
signInButton.setText( "Sign In" );
for (final JComponent signInComponent : signInComponents)
signInComponent.setEnabled( true );
checkSignIn();
} );
}
} );
}
@FunctionalInterface
public interface SignInCallback {
void signedIn(PasswordFrame<?, ?> passwordFrame);
}
}

View File

@ -0,0 +1,208 @@
package com.lyndir.masterpassword.gui.view;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.*;
import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
import com.lyndir.masterpassword.model.MPUser;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/**
* @author lhunath, 2018-07-14
*/
public class UserPanel extends Components.GradientPanel implements MPUser.Listener {
private static final Logger logger = Logger.get( UserPanel.class );
@Nullable
private MPUser<?> user;
public UserPanel() {
super( new BorderLayout( 20, 20 ), null );
setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
}
public void setUser(@Nullable final MPUser<?> user) {
if ((this.user != null) && !Objects.equals( this.user, user ))
this.user.removeListener( this );
this.user = user;
if (this.user != null)
this.user.addListener( this );
Res.ui( () -> {
removeAll();
if (this.user == null)
add( new NoUserPanel(), BorderLayout.CENTER );
else {
if (!this.user.isMasterKeyAvailable())
add( new AuthenticateUserPanel( this.user ), BorderLayout.CENTER );
else
add( new AuthenticatedUserPanel( this.user ), BorderLayout.CENTER );
}
revalidate();
} );
}
@Override
public void onUserUpdated(final MPUser<?> user) {
setUser( user );
}
@Override
public void onUserAuthenticated(final MPUser<?> user) {
setUser( user );
}
private static final class NoUserPanel extends JPanel {
private NoUserPanel() {
super( new BorderLayout() );
add( Components.heading( "Select a user to proceed." ), BorderLayout.CENTER );
}
}
private static final class AuthenticateUserPanel extends JPanel implements ActionListener, DocumentListener {
@Nonnull
private final MPUser<?> user;
private final JPasswordField masterPasswordField = Components.passwordField();
private final JLabel errorLabel = Components.label( null );
private AuthenticateUserPanel(@Nonnull final MPUser<?> user) {
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
this.user = user;
add( Box.createGlue() );
add( Components.heading( user.getFullName(), SwingConstants.CENTER ) );
add( Components.strut() );
add( Components.label( "Master Password:" ) );
add( masterPasswordField );
masterPasswordField.addActionListener( this );
masterPasswordField.getDocument().addDocumentListener( this );
add( errorLabel );
errorLabel.setForeground( Res.colors().errorFg() );
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() );
}
}
@Override
public void insertUpdate(final DocumentEvent e) {
errorLabel.setText( null );
}
@Override
public void removeUpdate(final DocumentEvent e) {
errorLabel.setText( null );
}
@Override
public void changedUpdate(final DocumentEvent e) {
errorLabel.setText( null );
}
}
private static final class AuthenticatedUserPanel extends JPanel implements ActionListener, DocumentListener {
@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( " ", SwingConstants.CENTER );
private final JTextField queryField = Components.textField();
private AuthenticatedUserPanel(@Nonnull final MPUser<?> user) {
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
this.user = user;
add( passwordLabel );
add( passwordField );
add( Box.createGlue() );
add( queryLabel );
queryLabel.setText( strf( "%s's password for:", user.getFullName() ) );
add( queryField );
queryField.addActionListener( this );
queryField.getDocument().addDocumentListener( this );
add( Box.createGlue() );
}
@Override
public void actionPerformed(final ActionEvent event) {
String siteName = queryField.getText();
Res.job( () -> {
try {
String result = user.getMasterKey().siteResult(
siteName, user.getAlgorithm(), UnsignedInteger.ONE,
MPKeyPurpose.Authentication, null, MPResultType.GeneratedLong, null );
Res.ui( () -> {
passwordLabel.setText( strf( "Your password for %s:", siteName ) );
passwordField.setText( result );
} );
}
catch (final MPKeyUnavailableException | MPAlgorithmException e) {
logger.err( e, "While resolving password for: %s", siteName );
}
} );
}
@Override
public void insertUpdate(final DocumentEvent e) {
// TODO
}
@Override
public void removeUpdate(final DocumentEvent e) {
// TODO
}
@Override
public void changedUpdate(final DocumentEvent e) {
// TODO
}
}
}