From 596ace51ea5bf42771662ab14ccd85c65c9b3244 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Wed, 18 Jul 2018 12:27:19 -0400 Subject: [PATCH] WIP - new Java UI. --- .../com/lyndir/masterpassword/gui/GUI.java | 21 +- .../com/lyndir/masterpassword/gui/Res.java | 72 +++-- .../masterpassword/gui/util/Components.java | 47 ++- .../gui/view/AuthenticationPanel.java | 84 ------ .../masterpassword/gui/view/FilesPanel.java | 106 +++++++ .../view/IncognitoAuthenticationPanel.java | 127 -------- .../gui/view/MasterPasswordFrame.java | 47 +++ .../gui/view/ModelAuthenticationPanel.java | 248 ---------------- .../gui/view/PasswordFrame.java | 279 ------------------ .../masterpassword/gui/view/UnlockFrame.java | 216 -------------- .../masterpassword/gui/view/UserPanel.java | 208 +++++++++++++ 11 files changed, 443 insertions(+), 1012 deletions(-) delete mode 100644 platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/AuthenticationPanel.java create mode 100644 platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java delete mode 100644 platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/IncognitoAuthenticationPanel.java create mode 100644 platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/MasterPasswordFrame.java delete mode 100644 platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/ModelAuthenticationPanel.java delete mode 100755 platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/PasswordFrame.java delete mode 100644 platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UnlockFrame.java create mode 100644 platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserPanel.java diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/GUI.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/GUI.java index 133c714a..da703b2f 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/GUI.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/GUI.java @@ -24,8 +24,7 @@ import com.google.common.base.Charsets; import com.google.common.io.ByteSource; import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.util.TypeUtils; -import com.lyndir.masterpassword.gui.view.PasswordFrame; -import com.lyndir.masterpassword.gui.view.UnlockFrame; +import com.lyndir.masterpassword.gui.view.MasterPasswordFrame; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; @@ -39,13 +38,12 @@ import javax.swing.*; * * @author mbillemo */ -public class GUI implements UnlockFrame.SignInCallback { +public class GUI { @SuppressWarnings("UnusedDeclaration") private static final Logger logger = Logger.get( GUI.class ); - private final UnlockFrame unlockFrame = new UnlockFrame( this ); - private PasswordFrame passwordFrame; + private final MasterPasswordFrame frame = new MasterPasswordFrame(); public static void main(final String... args) { Thread.setDefaultUncaughtExceptionHandler( @@ -114,17 +112,8 @@ public class GUI implements UnlockFrame.SignInCallback { } protected void open() { - SwingUtilities.invokeLater( () -> { - if (passwordFrame == null) - unlockFrame.setVisible( true ); - else - passwordFrame.setVisible( true ); + Res.ui( () -> { + frame.setVisible( true ); } ); } - - @Override - public void signedIn(final PasswordFrame passwordFrame) { - this.passwordFrame = passwordFrame; - open(); - } } diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/Res.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/Res.java index 16917c46..415a56ac 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/Res.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/Res.java @@ -23,19 +23,15 @@ import static com.lyndir.lhunath.opal.system.util.StringUtils.*; import com.google.common.collect.Maps; import com.google.common.io.Resources; -import com.google.common.util.concurrent.JdkFutureAdapters; -import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.*; import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.masterpassword.MPIdenticon; import java.awt.*; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; import java.awt.image.ImageObserver; import java.io.IOException; import java.lang.ref.SoftReference; import java.net.URL; import java.util.Map; -import java.util.WeakHashMap; import java.util.concurrent.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -49,19 +45,20 @@ import org.jetbrains.annotations.NonNls; @SuppressWarnings({ "HardcodedFileSeparator", "MethodReturnAlwaysConstant", "SpellCheckingInspection" }) public abstract class Res { - private static final int AVATAR_COUNT = 19; - private static final Map jobExecutorByWindow = new WeakHashMap<>(); - private static final Executor immediateUiExecutor = new SwingExecutorService( true ); - private static final Executor laterUiExecutor = new SwingExecutorService( false ); - private static final Logger logger = Logger.get( Res.class ); - private static final Colors colors = new Colors(); + private static final int AVATAR_COUNT = 19; + private static final ListeningScheduledExecutorService jobExecutor = MoreExecutors.listeningDecorator( + Executors.newSingleThreadScheduledExecutor() ); + private static final Executor immediateUiExecutor = new SwingExecutorService( true ); + private static final Executor laterUiExecutor = new SwingExecutorService( false ); + private static final Logger logger = Logger.get( Res.class ); + private static final Colors colors = new Colors(); - public static Future job(final Window host, final Runnable job) { - return job( host, job, 0, TimeUnit.MILLISECONDS ); + public static Future job(final Runnable job) { + return job( job, 0, TimeUnit.MILLISECONDS ); } - public static Future job(final Window host, final Runnable job, final long delay, final TimeUnit timeUnit) { - return jobExecutor( host ).schedule( () -> { + public static Future job(final Runnable job, final long delay, final TimeUnit timeUnit) { + return jobExecutor.schedule( () -> { try { job.run(); } @@ -71,38 +68,30 @@ public abstract class Res { }, delay, timeUnit ); } - public static ListenableFuture job(final Window host, final Callable job) { - return job( host, job, 0, TimeUnit.MILLISECONDS ); + public static ListenableFuture job(final Callable job) { + return job( job, 0, TimeUnit.MILLISECONDS ); } - public static ListenableFuture job(final Window host, final Callable job, final long delay, final TimeUnit timeUnit) { - ScheduledExecutorService executor = jobExecutor( host ); - return JdkFutureAdapters.listenInPoolThread( executor.schedule( job::call, delay, timeUnit ), executor ); + public static ListenableFuture job(final Callable job, final long delay, final TimeUnit timeUnit) { + return jobExecutor.schedule( job, delay, timeUnit ); + } + + 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) { 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() { 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 controlBg = Color.decode( "#ECECEC" ); private final Color controlBorder = Color.decode( "#BFBFBF" ); + private final Color errorFg = Color.decode( "#FF3333" ); public Color frameBg() { return frameBg; @@ -286,6 +276,10 @@ public abstract class Res { return controlBorder; } + public Color errorFg() { + return errorFg; + } + public Color fromIdenticonColor(final MPIdenticon.Color identiconColor, final BackgroundMode backgroundMode) { switch (identiconColor) { case RED: diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java index 4853299d..1fa3c9cd 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java @@ -31,6 +31,7 @@ import javax.swing.border.CompoundBorder; */ public abstract class Components { + private static final float HEADING_TEXT_SIZE = 18f; private static final float CONTROL_TEXT_SIZE = 12f; public static GradientPanel boxLayout(final int axis, final Component... components) { @@ -118,8 +119,12 @@ public abstract class Components { }; } - public static Component stud() { - Dimension studDimension = new Dimension( 8, 8 ); + public static Component strut() { + 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 ); rigidArea.setAlignmentX( Component.LEFT_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) { return label( label, SwingConstants.LEADING ); } @@ -218,7 +251,15 @@ public abstract class Components { @Nullable 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 ); this.gradientColor = gradientColor; setBackground( null ); diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/AuthenticationPanel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/AuthenticationPanel.java deleted file mode 100644 index 11018d79..00000000 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/AuthenticationPanel.java +++ /dev/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 . -//============================================================================== - -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> 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 getButtons() { - return ImmutableList.of(); - } - - public abstract void reset(); - - public abstract PasswordFrame newPasswordFrame(); -} diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java new file mode 100644 index 00000000..d5c260d8 --- /dev/null +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java @@ -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 listeners = new CopyOnWriteArraySet<>(); + + private final JLabel avatarLabel = new JLabel(); + private final JComboBox 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); + } +} diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/IncognitoAuthenticationPanel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/IncognitoAuthenticationPanel.java deleted file mode 100644 index b88be32f..00000000 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/IncognitoAuthenticationPanel.java +++ /dev/null @@ -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 . -//============================================================================== - -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 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 newPasswordFrame() { - return new PasswordFrame( 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 ); - } -} diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/MasterPasswordFrame.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/MasterPasswordFrame.java new file mode 100644 index 00000000..913d54bd --- /dev/null +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/MasterPasswordFrame.java @@ -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 ); + } +} diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/ModelAuthenticationPanel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/ModelAuthenticationPanel.java deleted file mode 100644 index 7d1bc9ed..00000000 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/ModelAuthenticationPanel.java +++ /dev/null @@ -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 . -//============================================================================== - -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 implements ItemListener, ActionListener, DocumentListener { - - @SuppressWarnings("UnusedDeclaration") - private static final Logger logger = Logger.get( ModelAuthenticationPanel.class ); - private static final long serialVersionUID = 1L; - - private final JComboBox 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) 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 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 newPasswordFrame() { - return new PasswordFrame( 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 ); - } -} diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/PasswordFrame.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/PasswordFrame.java deleted file mode 100755 index 3bc357a2..00000000 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/PasswordFrame.java +++ /dev/null @@ -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 . -//============================================================================== - -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, 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 siteVersionField; - private final JSpinner siteCounterField; - private final UnsignedIntegerModel siteCounterModel; - private final JComboBox 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( 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 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 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 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( 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 ); - } -} diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UnlockFrame.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UnlockFrame.java deleted file mode 100644 index c8c75998..00000000 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UnlockFrame.java +++ /dev/null @@ -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 . -//============================================================================== - -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> 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> 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); - } -} diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserPanel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserPanel.java new file mode 100644 index 00000000..b8866fe7 --- /dev/null +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserPanel.java @@ -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 + + } + } +}