WIP - new Java UI.
This commit is contained in:
parent
80b5fcd785
commit
596ace51ea
@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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:
|
||||||
|
@ -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 );
|
||||||
|
@ -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();
|
|
||||||
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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 );
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
}
|
@ -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 );
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 );
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user