Re-organize the project into a better hierarchy.
@@ -0,0 +1,64 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.lyndir.masterpassword.MPIdenticon;
|
||||
import com.lyndir.masterpassword.gui.util.Components;
|
||||
import java.awt.*;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-11
|
||||
*/
|
||||
public abstract class AuthenticationPanel extends Components.GradientPanel {
|
||||
|
||||
protected final UnlockFrame unlockFrame;
|
||||
protected final JLabel avatarLabel;
|
||||
|
||||
public 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(boolean repack) {
|
||||
unlockFrame.updateUser( getSelectedUser() );
|
||||
validate();
|
||||
|
||||
if (repack)
|
||||
unlockFrame.repack();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected abstract User getSelectedUser();
|
||||
|
||||
@NotNull
|
||||
@Nonnull
|
||||
public abstract char[] getMasterPassword();
|
||||
|
||||
public Component getFocusComponent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Iterable<? extends JButton> getButtons() {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
public abstract void reset();
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||
import com.lyndir.masterpassword.MPConstant;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-08-31
|
||||
*/
|
||||
public class Config {
|
||||
|
||||
private static final Config instance = new Config();
|
||||
|
||||
public static Config get() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public boolean checkForUpdates() {
|
||||
return ConversionUtils.toBoolean( System.getenv( MPConstant.env_checkUpdates ) ).or( true );
|
||||
}
|
||||
}
|
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2008, Maarten Billemont
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.io.*;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.TypeUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
import java.util.jar.*;
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
/**
|
||||
* <p> <i>Jun 10, 2008</i> </p>
|
||||
*
|
||||
* @author mbillemo
|
||||
*/
|
||||
public class GUI implements UnlockFrame.SignInCallback {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( GUI.class );
|
||||
|
||||
private final UnlockFrame unlockFrame = new UnlockFrame( this );
|
||||
private PasswordFrame passwordFrame;
|
||||
|
||||
public static void main(final String[] args)
|
||||
throws IOException {
|
||||
|
||||
if (Config.get().checkForUpdates())
|
||||
checkUpdate();
|
||||
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
}
|
||||
catch (UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) {
|
||||
}
|
||||
|
||||
TypeUtils.<GUI>newInstance( "com.lyndir.masterpassword.gui.platform.mac.AppleGUI" ).or( new GUI() ).open();
|
||||
}
|
||||
|
||||
private static void checkUpdate() {
|
||||
try {
|
||||
Enumeration<URL> manifestURLs = Thread.currentThread().getContextClassLoader().getResources( JarFile.MANIFEST_NAME );
|
||||
while (manifestURLs.hasMoreElements()) {
|
||||
InputStream manifestStream = manifestURLs.nextElement().openStream();
|
||||
Attributes attributes = new Manifest( manifestStream ).getMainAttributes();
|
||||
if (!GUI.class.getCanonicalName().equals( attributes.getValue( Attributes.Name.MAIN_CLASS ) ))
|
||||
continue;
|
||||
|
||||
String manifestRevision = attributes.getValue( Attributes.Name.IMPLEMENTATION_VERSION );
|
||||
String upstreamRevisionURL = "http://masterpasswordapp.com/masterpassword-gui.jar.rev";
|
||||
CharSource upstream = Resources.asCharSource( URI.create( upstreamRevisionURL ).toURL(), Charsets.UTF_8 );
|
||||
String upstreamRevision = upstream.readFirstLine();
|
||||
logger.inf( "Local Revision: <%s>", manifestRevision );
|
||||
logger.inf( "Upstream Revision: <%s>", upstreamRevision );
|
||||
if (manifestRevision != null && !manifestRevision.equalsIgnoreCase( upstreamRevision )) {
|
||||
logger.wrn( "You are not running the current official version. Please update from:\n"
|
||||
+ "http://masterpasswordapp.com/masterpassword-gui.jar" );
|
||||
JOptionPane.showMessageDialog( null, "A new version of Master Password is available.\n"
|
||||
+ "Please download the latest version from http://masterpasswordapp.com",
|
||||
"Update Available", JOptionPane.WARNING_MESSAGE );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.wrn( e, "Couldn't check for version update." );
|
||||
}
|
||||
}
|
||||
|
||||
protected void open() {
|
||||
SwingUtilities.invokeLater( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (passwordFrame == null)
|
||||
unlockFrame.setVisible( true );
|
||||
else
|
||||
passwordFrame.setVisible( true );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void signedIn(final User user) {
|
||||
passwordFrame = newPasswordFrame( user );
|
||||
open();
|
||||
}
|
||||
|
||||
protected PasswordFrame newPasswordFrame(final User user) {
|
||||
return new PasswordFrame( user );
|
||||
}
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import com.lyndir.masterpassword.gui.util.Components;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-11
|
||||
*/
|
||||
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
|
||||
protected User getSelectedUser() {
|
||||
return new IncognitoUser( fullNameField.getText() );
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@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,59 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.masterpassword.MPSiteType;
|
||||
import com.lyndir.masterpassword.MasterKey;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-16
|
||||
*/
|
||||
public class IncognitoSite extends Site {
|
||||
|
||||
private String siteName;
|
||||
private MPSiteType siteType;
|
||||
private UnsignedInteger siteCounter;
|
||||
private MasterKey.Version algorithmVersion;
|
||||
|
||||
public IncognitoSite(final String siteName, final MPSiteType siteType, final UnsignedInteger siteCounter,
|
||||
final MasterKey.Version algorithmVersion) {
|
||||
this.siteName = siteName;
|
||||
this.siteType = siteType;
|
||||
this.siteCounter = siteCounter;
|
||||
this.algorithmVersion = algorithmVersion;
|
||||
}
|
||||
|
||||
public String getSiteName() {
|
||||
return siteName;
|
||||
}
|
||||
|
||||
public void setSiteName(final String siteName) {
|
||||
this.siteName = siteName;
|
||||
}
|
||||
|
||||
public MPSiteType getSiteType() {
|
||||
return siteType;
|
||||
}
|
||||
|
||||
public void setSiteType(final MPSiteType siteType) {
|
||||
this.siteType = siteType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MasterKey.Version getAlgorithmVersion() {
|
||||
return algorithmVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlgorithmVersion(final MasterKey.Version algorithmVersion) {
|
||||
this.algorithmVersion = algorithmVersion;
|
||||
}
|
||||
|
||||
public UnsignedInteger getSiteCounter() {
|
||||
return siteCounter;
|
||||
}
|
||||
|
||||
public void setSiteCounter(final UnsignedInteger siteCounter) {
|
||||
this.siteCounter = siteCounter;
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-08
|
||||
*/
|
||||
public class IncognitoUser extends User {
|
||||
|
||||
private final String fullName;
|
||||
private char[] masterPassword;
|
||||
|
||||
public IncognitoUser(final String fullName) {
|
||||
this.fullName = fullName;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected char[] getMasterPassword() {
|
||||
return masterPassword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(final char[] masterPassword)
|
||||
throws IncorrectMasterPasswordException {
|
||||
this.masterPassword = masterPassword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Site> findSitesByName(final String siteName) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSite(final Site site) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSite(Site site) {
|
||||
}
|
||||
}
|
@@ -0,0 +1,211 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.*;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.masterpassword.model.MPUser;
|
||||
import com.lyndir.masterpassword.model.MPUserFileManager;
|
||||
import com.lyndir.masterpassword.gui.util.Components;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.plaf.metal.MetalComboBoxEditor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
/**
|
||||
* @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 final JComboBox<ModelUser> 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) {
|
||||
ModelUser 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( 12f ) );
|
||||
userField.addItemListener( this );
|
||||
userField.addActionListener( this );
|
||||
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) {
|
||||
ModelUser selectedUser = getSelectedUser();
|
||||
if (selectedUser != null) {
|
||||
avatarLabel.setIcon( Res.avatar( selectedUser.getAvatar() ) );
|
||||
boolean showPasswordField = !selectedUser.keySaved();
|
||||
if (masterPasswordField.isVisible() != showPasswordField) {
|
||||
masterPasswordLabel.setVisible( showPasswordField );
|
||||
masterPasswordField.setVisible( showPasswordField );
|
||||
repack = true;
|
||||
}
|
||||
}
|
||||
|
||||
super.updateUser( repack );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ModelUser getSelectedUser() {
|
||||
int selectedIndex = userField.getSelectedIndex();
|
||||
if (selectedIndex < 0)
|
||||
return null;
|
||||
|
||||
return userField.getModel().getElementAt( selectedIndex );
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@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 );
|
||||
MPUserFileManager.get().addUser( new MPUser( 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) {
|
||||
ModelUser 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;
|
||||
|
||||
MPUserFileManager.get().deleteUser( deleteUser.getModel() );
|
||||
userField.setModel( new DefaultComboBoxModel<>( readConfigUsers() ) );
|
||||
updateUser( true );
|
||||
}
|
||||
} );
|
||||
setToolTipText( "Delete the selected user." );
|
||||
}
|
||||
}, new JButton( Res.iconQuestion() ) {
|
||||
{
|
||||
addActionListener( new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
JOptionPane.showMessageDialog( ModelAuthenticationPanel.this, //
|
||||
strf( "Reads users and sites from the directory at:\n%s",
|
||||
MPUserFileManager.get().getPath().getAbsolutePath() ), //
|
||||
"Help", JOptionPane.INFORMATION_MESSAGE );
|
||||
}
|
||||
} );
|
||||
setToolTipText( "More information." );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
masterPasswordField.setText( "" );
|
||||
}
|
||||
|
||||
private ModelUser[] readConfigUsers() {
|
||||
return FluentIterable.from( MPUserFileManager.get().getUsers() ).transform( new Function<MPUser, ModelUser>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public ModelUser apply(@Nullable final MPUser model) {
|
||||
return new ModelUser( Preconditions.checkNotNull( model ) );
|
||||
}
|
||||
} ).toArray( ModelUser.class );
|
||||
}
|
||||
|
||||
@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 );
|
||||
}
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.masterpassword.MPSiteType;
|
||||
import com.lyndir.masterpassword.MasterKey;
|
||||
import com.lyndir.masterpassword.model.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-16
|
||||
*/
|
||||
public class ModelSite extends Site {
|
||||
|
||||
private final MPSite model;
|
||||
|
||||
public ModelSite(final MPSiteResult result) {
|
||||
this.model = result.getSite();
|
||||
}
|
||||
|
||||
public MPSite getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public String getSiteName() {
|
||||
return model.getSiteName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSiteName(final String siteName) {
|
||||
model.setSiteName( siteName );
|
||||
MPUserFileManager.get().save();
|
||||
}
|
||||
|
||||
public MPSiteType getSiteType() {
|
||||
return model.getSiteType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSiteType(final MPSiteType siteType) {
|
||||
if (siteType != getSiteType()) {
|
||||
model.setSiteType( siteType );
|
||||
MPUserFileManager.get().save();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MasterKey.Version getAlgorithmVersion() {
|
||||
return model.getAlgorithmVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlgorithmVersion(final MasterKey.Version algorithmVersion) {
|
||||
if (algorithmVersion != getAlgorithmVersion()) {
|
||||
model.setAlgorithmVersion( algorithmVersion );
|
||||
MPUserFileManager.get().save();
|
||||
}
|
||||
}
|
||||
|
||||
public UnsignedInteger getSiteCounter() {
|
||||
return model.getSiteCounter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSiteCounter(final UnsignedInteger siteCounter) {
|
||||
if (siteCounter.equals( getSiteCounter() )) {
|
||||
model.setSiteCounter( siteCounter );
|
||||
MPUserFileManager.get().save();
|
||||
}
|
||||
}
|
||||
|
||||
public void use() {
|
||||
model.updateLastUsed();
|
||||
MPUserFileManager.get().save();
|
||||
}
|
||||
}
|
@@ -0,0 +1,97 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.lyndir.masterpassword.model.*;
|
||||
import java.util.Arrays;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-08
|
||||
*/
|
||||
public class ModelUser extends User {
|
||||
|
||||
private final MPUser model;
|
||||
|
||||
@Nullable
|
||||
private char[] masterPassword;
|
||||
|
||||
public ModelUser(MPUser model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public MPUser getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullName() {
|
||||
return model.getFullName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected char[] getMasterPassword() {
|
||||
return masterPassword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvatar() {
|
||||
return model.getAvatar();
|
||||
}
|
||||
|
||||
public void setAvatar(final int avatar) {
|
||||
model.setAvatar(avatar % Res.avatars());
|
||||
MPUserFileManager.get().save();
|
||||
}
|
||||
|
||||
public void authenticate(final char[] masterPassword)
|
||||
throws IncorrectMasterPasswordException {
|
||||
putKey( model.authenticate( masterPassword ) );
|
||||
this.masterPassword = masterPassword;
|
||||
MPUserFileManager.get().save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
super.reset();
|
||||
|
||||
if (masterPassword != null) {
|
||||
Arrays.fill( masterPassword, (char) 0 );
|
||||
masterPassword = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Site> findSitesByName(final String query) {
|
||||
return FluentIterable.from( model.findSitesByName( query ) ).transform( new Function<MPSiteResult, Site>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public Site apply(@Nullable final MPSiteResult site) {
|
||||
return new ModelSite( Preconditions.checkNotNull( site ) );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSite(final Site site) {
|
||||
model.addSite( new MPSite( model, site.getSiteName(), site.getSiteType(), site.getSiteCounter() ) );
|
||||
model.updateLastUsed();
|
||||
MPUserFileManager.get().save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSite(Site site) {
|
||||
if (site instanceof ModelSite) {
|
||||
model.deleteSite(((ModelSite) site).getModel());
|
||||
MPUserFileManager.get().save();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean keySaved() {
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,293 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.google.common.util.concurrent.*;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.gui.util.Components;
|
||||
import com.lyndir.masterpassword.gui.util.UnsignedIntegerModel;
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.awt.event.*;
|
||||
import java.util.concurrent.Callable;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-08
|
||||
*/
|
||||
public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
|
||||
private final User user;
|
||||
private final Components.GradientPanel root;
|
||||
private final JTextField siteNameField;
|
||||
private final JButton siteActionButton;
|
||||
private final JComboBox<MPSiteType> siteTypeField;
|
||||
private final JComboBox<MasterKey.Version> siteVersionField;
|
||||
private final JSpinner siteCounterField;
|
||||
private final UnsignedIntegerModel siteCounterModel;
|
||||
private final JPasswordField passwordField;
|
||||
private final JLabel tipLabel;
|
||||
private final JCheckBox maskPasswordField;
|
||||
private final char passwordEchoChar;
|
||||
private final Font passwordEchoFont;
|
||||
|
||||
@Nullable
|
||||
private Site currentSite;
|
||||
private boolean updatingUI;
|
||||
|
||||
public PasswordFrame(User user)
|
||||
throws HeadlessException {
|
||||
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( new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
Futures.addCallback( updatePassword( true ), new FutureCallback<String>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable final String sitePassword) {
|
||||
StringSelection clipboardContents = new StringSelection( sitePassword );
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
|
||||
|
||||
SwingUtilities.invokeLater( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
passwordField.setText( null );
|
||||
siteNameField.setText( null );
|
||||
|
||||
dispatchEvent( new WindowEvent( PasswordFrame.this, WindowEvent.WINDOW_CLOSING ) );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
}
|
||||
} );
|
||||
}
|
||||
} );
|
||||
siteActionButton.addActionListener( new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
if (currentSite == null)
|
||||
return;
|
||||
else if (currentSite instanceof ModelSite)
|
||||
PasswordFrame.this.user.deleteSite( currentSite );
|
||||
else
|
||||
PasswordFrame.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 );
|
||||
MPSiteType[] types = Iterables.toArray( MPSiteType.forClass( MPSiteTypeClass.Generated ), MPSiteType.class );
|
||||
JComponent siteSettings = Components.boxLayout( BoxLayout.LINE_AXIS, //
|
||||
siteTypeField = Components.comboBox( types ), //
|
||||
Components.stud(), //
|
||||
siteVersionField = Components.comboBox( MasterKey.Version.values() ), //
|
||||
Components.stud(), //
|
||||
siteCounterField = Components.spinner( siteCounterModel ) );
|
||||
sitePanel.add( siteSettings );
|
||||
siteTypeField.setFont( Res.valueFont().deriveFont( 12f ) );
|
||||
siteTypeField.setSelectedItem( MPSiteType.GeneratedLong );
|
||||
siteTypeField.addItemListener( new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(final ItemEvent e) {
|
||||
updatePassword( true );
|
||||
}
|
||||
} );
|
||||
|
||||
siteVersionField.setFont( Res.valueFont().deriveFont( 12f ) );
|
||||
siteVersionField.setAlignmentX( RIGHT_ALIGNMENT );
|
||||
siteVersionField.setSelectedItem( MasterKey.Version.CURRENT );
|
||||
siteVersionField.addItemListener( new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(final ItemEvent e) {
|
||||
updatePassword( true );
|
||||
}
|
||||
} );
|
||||
|
||||
siteCounterField.setFont( Res.valueFont().deriveFont( 12f ) );
|
||||
siteCounterField.setAlignmentX( RIGHT_ALIGNMENT );
|
||||
siteCounterField.addChangeListener( new ChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(final ChangeEvent e) {
|
||||
updatePassword( true );
|
||||
}
|
||||
} );
|
||||
|
||||
// Mask
|
||||
maskPasswordField = Components.checkBox( "Hide Password" );
|
||||
maskPasswordField.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||
maskPasswordField.setSelected( true );
|
||||
maskPasswordField.addItemListener( new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
updateMask();
|
||||
}
|
||||
} );
|
||||
|
||||
// Password
|
||||
passwordField = Components.passwordField();
|
||||
passwordField.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||
passwordField.setHorizontalAlignment( JTextField.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 );
|
||||
}
|
||||
|
||||
private void updateMask() {
|
||||
passwordField.setEchoChar( maskPasswordField.isSelected()? passwordEchoChar: (char) 0 );
|
||||
passwordField.setFont( maskPasswordField.isSelected()? passwordEchoFont: Res.bigValueFont().deriveFont( 40f ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private ListenableFuture<String> updatePassword(boolean allowNameCompletion) {
|
||||
|
||||
final String siteNameQuery = siteNameField.getText();
|
||||
if (updatingUI)
|
||||
return Futures.immediateCancelledFuture();
|
||||
if (siteNameQuery == null || siteNameQuery.isEmpty() || !user.isKeyAvailable()) {
|
||||
siteActionButton.setVisible( false );
|
||||
tipLabel.setText( null );
|
||||
passwordField.setText( null );
|
||||
return Futures.immediateCancelledFuture();
|
||||
}
|
||||
|
||||
final MPSiteType siteType = siteTypeField.getModel().getElementAt( siteTypeField.getSelectedIndex() );
|
||||
final MasterKey.Version siteVersion = siteVersionField.getItemAt( siteVersionField.getSelectedIndex() );
|
||||
final UnsignedInteger siteCounter = siteCounterModel.getNumber();
|
||||
|
||||
Iterable<Site> siteResults = user.findSitesByName( siteNameQuery );
|
||||
if (!allowNameCompletion)
|
||||
siteResults = FluentIterable.from( siteResults ).filter( new Predicate<Site>() {
|
||||
@Override
|
||||
public boolean apply(@Nullable Site siteResult) {
|
||||
return siteResult != null && siteNameQuery.equals( siteResult.getSiteName() );
|
||||
}
|
||||
} );
|
||||
final Site site = ifNotNullElse( Iterables.getFirst( siteResults, null ),
|
||||
new IncognitoSite( siteNameQuery, siteType, siteCounter, siteVersion ) );
|
||||
if (currentSite != null && currentSite.getSiteName().equals( site.getSiteName() )) {
|
||||
site.setSiteType( siteType );
|
||||
site.setAlgorithmVersion( siteVersion );
|
||||
site.setSiteCounter( siteCounter );
|
||||
}
|
||||
|
||||
ListenableFuture<String> passwordFuture = Res.execute( this, new Callable<String>() {
|
||||
@Override
|
||||
public String call()
|
||||
throws Exception {
|
||||
return user.getKey( site.getAlgorithmVersion() )
|
||||
.encode( site.getSiteName(), site.getSiteType(), site.getSiteCounter(), MPSiteVariant.Password, null );
|
||||
}
|
||||
} );
|
||||
Futures.addCallback( passwordFuture, new FutureCallback<String>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable final String sitePassword) {
|
||||
SwingUtilities.invokeLater( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updatingUI = true;
|
||||
currentSite = site;
|
||||
siteActionButton.setVisible( user instanceof ModelUser );
|
||||
if (currentSite instanceof ModelSite)
|
||||
siteActionButton.setText( "Delete Site" );
|
||||
else
|
||||
siteActionButton.setText( "Add Site" );
|
||||
siteTypeField.setSelectedItem( currentSite.getSiteType() );
|
||||
siteVersionField.setSelectedItem( currentSite.getAlgorithmVersion() );
|
||||
siteCounterField.setValue( currentSite.getSiteCounter() );
|
||||
siteNameField.setText( currentSite.getSiteName() );
|
||||
if (siteNameField.getText().startsWith( siteNameQuery ))
|
||||
siteNameField.select( siteNameQuery.length(), siteNameField.getText().length() );
|
||||
|
||||
passwordField.setText( sitePassword );
|
||||
tipLabel.setText( "Press [Enter] to copy the password. Then paste it into the password field." );
|
||||
updatingUI = false;
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
}
|
||||
} );
|
||||
|
||||
return passwordFuture;
|
||||
}
|
||||
|
||||
@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 );
|
||||
}
|
||||
}
|
@@ -0,0 +1,338 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
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.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;
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-11
|
||||
*/
|
||||
public abstract class Res {
|
||||
|
||||
private static final WeakHashMap<Window, ExecutorService> executorByWindow = new WeakHashMap<>();
|
||||
private static final Logger logger = Logger.get( Res.class );
|
||||
private static final Colors colors = new Colors();
|
||||
|
||||
public static Future<?> execute(final Window host, final Runnable job) {
|
||||
return getExecutor( host ).submit( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
job.run();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
logger.err( t, "Unexpected: %s", t.getLocalizedMessage() );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
public static <V> ListenableFuture<V> execute(final Window host, final Callable<V> job) {
|
||||
ExecutorService executor = getExecutor( host );
|
||||
return JdkFutureAdapters.listenInPoolThread( executor.submit( new Callable<V>() {
|
||||
@Override
|
||||
public V call()
|
||||
throws Exception {
|
||||
try {
|
||||
return job.call();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
logger.err( t, "Unexpected: %s", t.getLocalizedMessage() );
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
} ), executor );
|
||||
}
|
||||
|
||||
private static ExecutorService getExecutor(final Window host) {
|
||||
ExecutorService executor = executorByWindow.get( host );
|
||||
|
||||
if (executor == null) {
|
||||
executorByWindow.put( host, executor = Executors.newSingleThreadExecutor() );
|
||||
|
||||
host.addWindowListener( new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosed(final WindowEvent e) {
|
||||
ExecutorService executor = executorByWindow.remove( host );
|
||||
if (executor != null)
|
||||
executor.shutdownNow();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
return executor;
|
||||
}
|
||||
|
||||
public static Icon iconAdd() {
|
||||
return new RetinaIcon( Resources.getResource( "media/icon_add@2x.png" ) );
|
||||
}
|
||||
|
||||
public static Icon iconDelete() {
|
||||
return new RetinaIcon( Resources.getResource( "media/icon_delete@2x.png" ) );
|
||||
}
|
||||
|
||||
public static Icon iconQuestion() {
|
||||
return new RetinaIcon( Resources.getResource( "media/icon_question@2x.png" ) );
|
||||
}
|
||||
|
||||
public static Icon avatar(final int index) {
|
||||
return new RetinaIcon( Resources.getResource( strf( "media/avatar-%d@2x.png", index % avatars() ) ) );
|
||||
}
|
||||
|
||||
public static int avatars() {
|
||||
return 19;
|
||||
}
|
||||
|
||||
public static Font emoticonsFont() {
|
||||
return emoticonsRegular();
|
||||
}
|
||||
|
||||
public static Font controlFont() {
|
||||
return arimoRegular();
|
||||
}
|
||||
|
||||
public static Font valueFont() {
|
||||
return sourceSansProRegular();
|
||||
}
|
||||
|
||||
public static Font bigValueFont() {
|
||||
return sourceSansProBlack();
|
||||
}
|
||||
|
||||
public static Font emoticonsRegular() {
|
||||
return font( "fonts/Emoticons-Regular.otf" );
|
||||
}
|
||||
|
||||
public static Font sourceCodeProRegular() {
|
||||
return font( "fonts/SourceCodePro-Regular.otf" );
|
||||
}
|
||||
|
||||
public static Font sourceCodeProBlack() {
|
||||
return font( "fonts/SourceCodePro-Bold.otf" );
|
||||
}
|
||||
|
||||
public static Font sourceSansProRegular() {
|
||||
return font( "fonts/SourceSansPro-Regular.otf" );
|
||||
}
|
||||
|
||||
public static Font sourceSansProBlack() {
|
||||
return font( "fonts/SourceSansPro-Bold.otf" );
|
||||
}
|
||||
|
||||
public static Font exoBold() {
|
||||
return font( "fonts/Exo2.0-Bold.otf" );
|
||||
}
|
||||
|
||||
public static Font exoExtraBold() {
|
||||
return font( "fonts/Exo2.0-ExtraBold.otf" );
|
||||
}
|
||||
|
||||
public static Font exoRegular() {
|
||||
return font( "fonts/Exo2.0-Regular.otf" );
|
||||
}
|
||||
|
||||
public static Font exoThin() {
|
||||
return font( "fonts/Exo2.0-Thin.otf" );
|
||||
}
|
||||
|
||||
public static Font arimoBold() {
|
||||
return font( "fonts/Arimo-Bold.ttf" );
|
||||
}
|
||||
|
||||
public static Font arimoBoldItalic() {
|
||||
return font( "fonts/Arimo-BoldItalic.ttf" );
|
||||
}
|
||||
|
||||
public static Font arimoItalic() {
|
||||
return font( "fonts/Arimo-Italic.ttf" );
|
||||
}
|
||||
|
||||
public static Font arimoRegular() {
|
||||
return font( "fonts/Arimo-Regular.ttf" );
|
||||
}
|
||||
|
||||
private static Font font(String fontResourceName) {
|
||||
Map<String, SoftReference<Font>> fontsByResourceName = Maps.newHashMap();
|
||||
SoftReference<Font> fontRef = fontsByResourceName.get( fontResourceName );
|
||||
Font font = fontRef == null? null: fontRef.get();
|
||||
if (font == null)
|
||||
try {
|
||||
fontsByResourceName.put( fontResourceName, new SoftReference<>(
|
||||
font = Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( fontResourceName ).openStream() ) ) );
|
||||
}
|
||||
catch (FontFormatException | IOException e) {
|
||||
throw Throwables.propagate( e );
|
||||
}
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
public static Colors colors() {
|
||||
return colors;
|
||||
}
|
||||
|
||||
private static final class RetinaIcon extends ImageIcon {
|
||||
|
||||
private static final Pattern scalePattern = Pattern.compile( ".*@(\\d+)x.[^.]+$" );
|
||||
|
||||
private final float scale;
|
||||
|
||||
public RetinaIcon(final URL url) {
|
||||
super( url );
|
||||
|
||||
Matcher scaleMatcher = scalePattern.matcher( url.getPath() );
|
||||
if (scaleMatcher.matches())
|
||||
scale = Float.parseFloat( scaleMatcher.group( 1 ) );
|
||||
else
|
||||
scale = 1;
|
||||
}
|
||||
|
||||
//private static URL retinaURL(final URL url) {
|
||||
// try {
|
||||
// final boolean[] isRetina = new boolean[1];
|
||||
// new apple.awt.CImage.HiDPIScaledImage(1,1, BufferedImage.TYPE_INT_ARGB) {
|
||||
// @Override
|
||||
// public void drawIntoImage(BufferedImage image, float v) {
|
||||
// isRetina[0] = v > 1;
|
||||
// }
|
||||
// };
|
||||
// return isRetina[0];
|
||||
// } catch (Throwable e) {
|
||||
// e.printStackTrace();
|
||||
// return url;
|
||||
// }
|
||||
//}
|
||||
|
||||
@Override
|
||||
public int getIconWidth() {
|
||||
return (int) (super.getIconWidth() / scale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconHeight() {
|
||||
return (int) (super.getIconHeight() / scale);
|
||||
}
|
||||
|
||||
public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
|
||||
ImageObserver observer = ifNotNullElse( getImageObserver(), c );
|
||||
|
||||
Image image = getImage();
|
||||
int width = image.getWidth( observer );
|
||||
int height = image.getHeight( observer );
|
||||
final Graphics2D g2d = (Graphics2D) g.create( x, y, width, height );
|
||||
|
||||
g2d.scale( 1 / scale, 1 / scale );
|
||||
g2d.drawImage( image, 0, 0, observer );
|
||||
g2d.scale( 1, 1 );
|
||||
g2d.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Colors {
|
||||
|
||||
private final Color frameBg = Color.decode( "#5A5D6B" );
|
||||
private final Color controlBg = Color.decode( "#ECECEC" );
|
||||
private final Color controlBorder = Color.decode( "#BFBFBF" );
|
||||
|
||||
public Color frameBg() {
|
||||
return frameBg;
|
||||
}
|
||||
|
||||
public Color controlBg() {
|
||||
return controlBg;
|
||||
}
|
||||
|
||||
public Color controlBorder() {
|
||||
return controlBorder;
|
||||
}
|
||||
|
||||
public Color fromIdenticonColor(MPIdenticon.Color identiconColor, BackgroundMode backgroundMode) {
|
||||
switch (identiconColor) {
|
||||
case RED:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#dc322f" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#dc322f" );
|
||||
}
|
||||
break;
|
||||
case GREEN:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#859900" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#859900" );
|
||||
}
|
||||
break;
|
||||
case YELLOW:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#b58900" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#b58900" );
|
||||
}
|
||||
break;
|
||||
case BLUE:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#268bd2" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#268bd2" );
|
||||
}
|
||||
break;
|
||||
case MAGENTA:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#d33682" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#d33682" );
|
||||
}
|
||||
break;
|
||||
case CYAN:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#2aa198" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#2aa198" );
|
||||
}
|
||||
break;
|
||||
case MONO:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#93a1a1" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#586e75" );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException( strf( "Color: %s or mode: %s not supported: ", identiconColor, backgroundMode ) );
|
||||
}
|
||||
|
||||
public enum BackgroundMode {
|
||||
DARK, LIGHT
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.masterpassword.MPSiteType;
|
||||
import com.lyndir.masterpassword.MasterKey;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-16
|
||||
*/
|
||||
public abstract class Site {
|
||||
|
||||
public abstract String getSiteName();
|
||||
|
||||
public abstract void setSiteName(final String siteName);
|
||||
|
||||
public abstract MPSiteType getSiteType();
|
||||
|
||||
public abstract void setSiteType(final MPSiteType siteType);
|
||||
|
||||
public abstract MasterKey.Version getAlgorithmVersion();
|
||||
|
||||
public abstract void setAlgorithmVersion(final MasterKey.Version algorithmVersion);
|
||||
|
||||
public abstract UnsignedInteger getSiteCounter();
|
||||
|
||||
public abstract void setSiteCounter(final UnsignedInteger siteCounter);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return strf( "{%s: %s}", getClass().getSimpleName(), getSiteName() );
|
||||
}
|
||||
}
|
@@ -0,0 +1,204 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
|
||||
import com.lyndir.masterpassword.MPIdenticon;
|
||||
import com.lyndir.masterpassword.gui.util.Components;
|
||||
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-08
|
||||
*/
|
||||
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 boolean incognito;
|
||||
public User user;
|
||||
|
||||
public UnlockFrame(final SignInCallback signInCallback)
|
||||
throws HeadlessException {
|
||||
super( "Unlock Master Password" );
|
||||
this.signInCallback = signInCallback;
|
||||
|
||||
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
|
||||
addWindowFocusListener( new WindowAdapter() {
|
||||
@Override
|
||||
public void windowGainedFocus(WindowEvent e) {
|
||||
root.setGradientColor( Res.colors().frameBg() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowLostFocus(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(
|
||||
"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() );
|
||||
|
||||
final JCheckBox incognitoCheckBox = Components.checkBox( "Incognito" );
|
||||
incognitoCheckBox.setToolTipText( "Log in without saving any information." );
|
||||
incognitoCheckBox.setSelected( incognito );
|
||||
incognitoCheckBox.addItemListener( new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(final ItemEvent e) {
|
||||
incognito = incognitoCheckBox.isSelected();
|
||||
SwingUtilities.invokeLater( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
createAuthenticationPanel();
|
||||
}
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
JComponent toolsPanel = Components.boxLayout( BoxLayout.LINE_AXIS, incognitoCheckBox, Box.createGlue() );
|
||||
authenticationContainer.add( toolsPanel );
|
||||
for (JButton button : authenticationPanel.getButtons()) {
|
||||
toolsPanel.add( button );
|
||||
button.setBorder( BorderFactory.createEmptyBorder() );
|
||||
button.setMargin( new Insets( 0, 0, 0, 0 ) );
|
||||
button.setAlignmentX( RIGHT_ALIGNMENT );
|
||||
button.setContentAreaFilled( false );
|
||||
}
|
||||
|
||||
checkSignIn();
|
||||
validate();
|
||||
repack();
|
||||
|
||||
SwingUtilities.invokeLater( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ifNotNullElse( authenticationPanel.getFocusComponent(), signInButton ).requestFocusInWindow();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
void updateUser(@Nullable User user) {
|
||||
this.user = user;
|
||||
checkSignIn();
|
||||
}
|
||||
|
||||
boolean checkSignIn() {
|
||||
String fullName = user == null? "": user.getFullName();
|
||||
char[] masterPassword = authenticationPanel.getMasterPassword();
|
||||
boolean enabled = !fullName.isEmpty() && masterPassword.length > 0;
|
||||
|
||||
if (fullName.isEmpty() || masterPassword.length == 0)
|
||||
identiconLabel.setText( " " );
|
||||
else {
|
||||
MPIdenticon identicon = new MPIdenticon( fullName, masterPassword );
|
||||
identiconLabel.setText( identicon.getText() );
|
||||
identiconLabel.setForeground( Res.colors().fromIdenticonColor( identicon.getColor(), Res.Colors.BackgroundMode.DARK ) );
|
||||
}
|
||||
|
||||
signInButton.setEnabled( enabled );
|
||||
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void trySignIn(final JComponent... signInComponents) {
|
||||
if (!checkSignIn())
|
||||
return;
|
||||
|
||||
for (JComponent signInComponent : signInComponents)
|
||||
signInComponent.setEnabled( false );
|
||||
|
||||
signInButton.setEnabled( false );
|
||||
signInButton.setText( "Signing In..." );
|
||||
|
||||
Res.execute( this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
user.authenticate( authenticationPanel.getMasterPassword() );
|
||||
|
||||
SwingUtilities.invokeLater( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
signInCallback.signedIn( user );
|
||||
dispose();
|
||||
}
|
||||
} );
|
||||
}
|
||||
catch (final IncorrectMasterPasswordException e) {
|
||||
SwingUtilities.invokeLater( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
JOptionPane.showMessageDialog( null, e.getLocalizedMessage(), "Sign In Failed", JOptionPane.ERROR_MESSAGE );
|
||||
authenticationPanel.reset();
|
||||
signInButton.setText( "Sign In" );
|
||||
for (JComponent signInComponent : signInComponents)
|
||||
signInComponent.setEnabled( true );
|
||||
checkSignIn();
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
interface SignInCallback {
|
||||
|
||||
void signedIn(User user);
|
||||
}
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.lyndir.masterpassword.MasterKey;
|
||||
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Objects;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-08
|
||||
*/
|
||||
public abstract class User {
|
||||
|
||||
@Nonnull
|
||||
private final EnumMap<MasterKey.Version, MasterKey> keyByVersion = Maps.newEnumMap( MasterKey.Version.class );
|
||||
|
||||
public abstract String getFullName();
|
||||
|
||||
@Nullable
|
||||
protected abstract char[] getMasterPassword();
|
||||
|
||||
public abstract void authenticate(final char[] masterPassword)
|
||||
throws IncorrectMasterPasswordException;
|
||||
|
||||
public int getAvatar() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public boolean isKeyAvailable() {
|
||||
return getMasterPassword() != null;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public MasterKey getKey(MasterKey.Version algorithmVersion) {
|
||||
char[] masterPassword = Preconditions.checkNotNull( getMasterPassword(), "User is not authenticated: " + getFullName() );
|
||||
|
||||
MasterKey key = keyByVersion.get( algorithmVersion );
|
||||
if (key == null)
|
||||
putKey( key = MasterKey.create( algorithmVersion, getFullName(), masterPassword ) );
|
||||
if (!key.isValid())
|
||||
key.revalidate( masterPassword );
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
protected void putKey(MasterKey masterKey) {
|
||||
MasterKey oldKey = keyByVersion.put( masterKey.getAlgorithmVersion(), masterKey );
|
||||
if (oldKey != null)
|
||||
oldKey.invalidate();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
for (MasterKey key : keyByVersion.values())
|
||||
key.invalidate();
|
||||
}
|
||||
|
||||
public abstract Iterable<Site> findSitesByName(final String siteName);
|
||||
|
||||
public abstract void addSite(final Site site);
|
||||
|
||||
public abstract void deleteSite(final Site site);
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
return this == obj || obj instanceof User && Objects.equals( getFullName(), ((User) obj).getFullName() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode( getFullName() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getFullName();
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
*
|
||||
* @author lhunath, 15-02-04
|
||||
*/
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
@@ -0,0 +1,45 @@
|
||||
package com.lyndir.masterpassword.gui.platform.mac;
|
||||
|
||||
import com.apple.eawt.*;
|
||||
import com.lyndir.masterpassword.gui.GUI;
|
||||
import com.lyndir.masterpassword.gui.PasswordFrame;
|
||||
import com.lyndir.masterpassword.gui.User;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-10
|
||||
*/
|
||||
public class AppleGUI extends GUI {
|
||||
|
||||
public AppleGUI() {
|
||||
|
||||
Application application = Application.getApplication();
|
||||
application.addAppEventListener( new AppForegroundListener() {
|
||||
|
||||
@Override
|
||||
public void appMovedToBackground(AppEvent.AppForegroundEvent arg0) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appRaisedToForeground(AppEvent.AppForegroundEvent arg0) {
|
||||
open();
|
||||
}
|
||||
} );
|
||||
application.addAppEventListener( new AppReOpenedListener() {
|
||||
@Override
|
||||
public void appReOpened(AppEvent.AppReOpenedEvent arg0) {
|
||||
open();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PasswordFrame newPasswordFrame(final User user) {
|
||||
PasswordFrame frame = super.newPasswordFrame( user );
|
||||
frame.setDefaultCloseOperation( WindowConstants.HIDE_ON_CLOSE );
|
||||
|
||||
return frame;
|
||||
}
|
||||
}
|
@@ -0,0 +1,237 @@
|
||||
package com.lyndir.masterpassword.gui.util;
|
||||
|
||||
import com.lyndir.masterpassword.gui.Res;
|
||||
import java.awt.*;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.border.CompoundBorder;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-08
|
||||
*/
|
||||
public abstract class Components {
|
||||
|
||||
public static GradientPanel boxLayout(int axis, Component... components) {
|
||||
GradientPanel container = gradientPanel( null, null );
|
||||
// container.setBackground( Color.red );
|
||||
container.setLayout( new BoxLayout( container, axis ) );
|
||||
for (Component component : components)
|
||||
container.add( component );
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
public static GradientPanel borderPanel(final JComponent component, @Nullable final Border border) {
|
||||
return borderPanel( component, border, null );
|
||||
}
|
||||
|
||||
public static GradientPanel borderPanel(final JComponent component, @Nullable final Border border, @Nullable Color background) {
|
||||
GradientPanel box = boxLayout( BoxLayout.LINE_AXIS, component );
|
||||
|
||||
if (border != null)
|
||||
box.setBorder( border );
|
||||
|
||||
if (background != null)
|
||||
box.setBackground( background );
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
public static GradientPanel gradientPanel(@Nullable final LayoutManager layout, @Nullable final Color color) {
|
||||
return new GradientPanel( layout, color ) {
|
||||
{
|
||||
setOpaque( color != null );
|
||||
setBackground( null );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static JTextField textField() {
|
||||
return new JTextField() {
|
||||
{
|
||||
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
||||
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
|
||||
setFont( Res.valueFont().deriveFont( 12f ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static JPasswordField passwordField() {
|
||||
return new JPasswordField() {
|
||||
{
|
||||
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
||||
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static JButton button(String label) {
|
||||
return new JButton( label ) {
|
||||
{
|
||||
setFont( Res.controlFont().deriveFont( 12f ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( 20, getPreferredSize().height );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Component stud() {
|
||||
Dimension studDimension = new Dimension( 8, 8 );
|
||||
Box.Filler rigidArea = new Box.Filler( studDimension, studDimension, studDimension );
|
||||
rigidArea.setAlignmentX( Component.LEFT_ALIGNMENT );
|
||||
rigidArea.setAlignmentY( Component.BOTTOM_ALIGNMENT );
|
||||
rigidArea.setBackground( Color.red );
|
||||
return rigidArea;
|
||||
}
|
||||
|
||||
public static JSpinner spinner(final SpinnerModel model) {
|
||||
return new JSpinner( model ) {
|
||||
{
|
||||
CompoundBorder editorBorder = BorderFactory.createCompoundBorder(
|
||||
BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
||||
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) );
|
||||
((DefaultEditor) getEditor()).getTextField().setBorder( editorBorder );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
setBorder( null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( 20, getPreferredSize().height );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static JLabel label(@Nullable String label) {
|
||||
return label( label, SwingConstants.LEADING );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param horizontalAlignment One of the following constants
|
||||
* defined in <code>SwingConstants</code>:
|
||||
* <code>LEFT</code>,
|
||||
* <code>CENTER</code>,
|
||||
* <code>RIGHT</code>,
|
||||
* <code>LEADING</code> or
|
||||
* <code>TRAILING</code>.
|
||||
*/
|
||||
public static JLabel label(@Nullable final String label, final int horizontalAlignment) {
|
||||
return new JLabel( label, horizontalAlignment ) {
|
||||
{
|
||||
setFont( Res.controlFont().deriveFont( 12f ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static JCheckBox checkBox(final String label) {
|
||||
return new JCheckBox( label ) {
|
||||
{
|
||||
setFont( Res.controlFont().deriveFont( 12f ) );
|
||||
setBackground( null );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <V> JComboBox<V> comboBox(final V... values) {
|
||||
return comboBox( new DefaultComboBoxModel<>( values ) );
|
||||
}
|
||||
|
||||
public static <M> JComboBox<M> comboBox(final ComboBoxModel<M> model) {
|
||||
return new JComboBox<M>( model ) {
|
||||
{
|
||||
// CompoundBorder editorBorder = BorderFactory.createCompoundBorder(
|
||||
// BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
||||
// BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) );
|
||||
// ((JComponent) ((BasicComboBoxEditor) getEditor()).getEditorComponent()).setBorder(editorBorder);
|
||||
setFont( Res.controlFont().deriveFont( 12f ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
// setBorder(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static class GradientPanel extends JPanel {
|
||||
|
||||
@Nullable
|
||||
private Color gradientColor;
|
||||
|
||||
@Nullable
|
||||
private GradientPaint paint;
|
||||
|
||||
protected GradientPanel(@Nullable final LayoutManager layout, @Nullable final Color gradientColor) {
|
||||
super( layout );
|
||||
this.gradientColor = gradientColor;
|
||||
setBackground( null );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Color getGradientColor() {
|
||||
return gradientColor;
|
||||
}
|
||||
|
||||
public void setGradientColor(@Nullable final Color gradientColor) {
|
||||
this.gradientColor = gradientColor;
|
||||
revalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doLayout() {
|
||||
super.doLayout();
|
||||
|
||||
if (gradientColor != null) {
|
||||
paint = new GradientPaint( new Point( 0, 0 ), gradientColor, new Point( getWidth(), getHeight() ), gradientColor.darker() );
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(final Graphics g) {
|
||||
super.paintComponent( g );
|
||||
|
||||
if (paint != null) {
|
||||
((Graphics2D) g).setPaint( paint );
|
||||
g.fillRect( 0, 0, getWidth(), getHeight() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
package com.lyndir.masterpassword.gui.util;
|
||||
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2016-10-29
|
||||
*/
|
||||
public class UnsignedIntegerModel extends SpinnerNumberModel {
|
||||
|
||||
public UnsignedIntegerModel() {
|
||||
this( UnsignedInteger.ZERO, UnsignedInteger.ZERO, UnsignedInteger.MAX_VALUE, UnsignedInteger.ONE );
|
||||
}
|
||||
|
||||
public UnsignedIntegerModel(final UnsignedInteger value) {
|
||||
this( value, UnsignedInteger.ZERO, UnsignedInteger.MAX_VALUE, UnsignedInteger.ONE );
|
||||
}
|
||||
|
||||
public UnsignedIntegerModel(final UnsignedInteger value, final UnsignedInteger minimum) {
|
||||
this( value, minimum, UnsignedInteger.MAX_VALUE, UnsignedInteger.ONE );
|
||||
}
|
||||
|
||||
public UnsignedIntegerModel(final UnsignedInteger value, final UnsignedInteger minimum, final UnsignedInteger maximum) {
|
||||
this( value, minimum, maximum, UnsignedInteger.ONE );
|
||||
}
|
||||
|
||||
public UnsignedIntegerModel(final UnsignedInteger value, final UnsignedInteger minimum, final UnsignedInteger maximum,
|
||||
final UnsignedInteger stepSize) {
|
||||
super( value, minimum, maximum, stepSize );
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnsignedInteger getNumber() {
|
||||
return (UnsignedInteger) super.getNumber();
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
*
|
||||
* @author lhunath, 15-02-04
|
||||
*/
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
package com.lyndir.masterpassword.gui.util;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
BIN
platform-independent/gui-java/src/main/resources/fonts/Arimo-Bold.ttf
Executable file
BIN
platform-independent/gui-java/src/main/resources/fonts/Arimo-BoldItalic.ttf
Executable file
BIN
platform-independent/gui-java/src/main/resources/fonts/Arimo-Italic.ttf
Executable file
BIN
platform-independent/gui-java/src/main/resources/fonts/Arimo-Regular.ttf
Executable file
BIN
platform-independent/gui-java/src/main/resources/fonts/SourceSansPro-Black.otf
Executable file
BIN
platform-independent/gui-java/src/main/resources/fonts/SourceSansPro-BlackIt.otf
Executable file
BIN
platform-independent/gui-java/src/main/resources/fonts/SourceSansPro-Bold.otf
Executable file
BIN
platform-independent/gui-java/src/main/resources/fonts/SourceSansPro-BoldIt.otf
Executable file
BIN
platform-independent/gui-java/src/main/resources/fonts/SourceSansPro-It.otf
Executable file
BIN
platform-independent/gui-java/src/main/resources/fonts/SourceSansPro-Light.otf
Executable file
BIN
platform-independent/gui-java/src/main/resources/fonts/SourceSansPro-LightIt.otf
Executable file
BIN
platform-independent/gui-java/src/main/resources/fonts/SourceSansPro-Regular.otf
Executable file
15
platform-independent/gui-java/src/main/resources/logback.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<configuration scan="false">
|
||||
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%-8relative %22c{0} [%-5level] %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="com.lyndir" level="${mp.log.level:-INFO}" />
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="stdout" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 8.9 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 9.3 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 8.4 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 3.7 KiB |