2
0

User preferences.

This commit is contained in:
Maarten Billemont 2018-07-23 23:34:32 -04:00
parent 16cdcda94b
commit 8f7faa9e4e
7 changed files with 180 additions and 64 deletions

View File

@ -41,7 +41,7 @@ public enum MPResultType {
/**
* 16: pg^VMAUBk5x3p%HP%i4=
*/
GeneratedMaximum( "maximum", "20 characters, contains symbols.", //
GeneratedMaximum( "maximum", "Maximum Security", "pg^VMAUBk5x3p%HP%i4=", "20 characters, contains symbols.", //
ImmutableList.of( new MPTemplate( "anoxxxxxxxxxxxxxxxxx" ),
new MPTemplate( "axxxxxxxxxxxxxxxxxno" ) ), //
MPResultTypeClass.Template, 0x0 ),
@ -49,7 +49,7 @@ public enum MPResultType {
/**
* 17: BiroYena8:Kixa
*/
GeneratedLong( "long", "Copy-friendly, 14 characters, contains symbols.", //
GeneratedLong( "long", "Long Password", "BiroYena8:Kixa", "Copy-friendly, 14 characters, contains symbols.", //
ImmutableList.of( new MPTemplate( "CvcvnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvnoCvcv" ),
new MPTemplate( "CvcvCvcvCvcvno" ), new MPTemplate( "CvccnoCvcvCvcv" ),
new MPTemplate( "CvccCvcvnoCvcv" ), new MPTemplate( "CvccCvcvCvcvno" ),
@ -66,7 +66,7 @@ public enum MPResultType {
/**
* 18: BirSuj0-
*/
GeneratedMedium( "medium", "Copy-friendly, 8 characters, contains symbols.", //
GeneratedMedium( "medium", "Medium Password", "BirSuj0-", "Copy-friendly, 8 characters, contains symbols.", //
ImmutableList.of( new MPTemplate( "CvcnoCvc" ),
new MPTemplate( "CvcCvcno" ) ), //
MPResultTypeClass.Template, 0x2 ),
@ -74,14 +74,14 @@ public enum MPResultType {
/**
* 19: Bir8
*/
GeneratedShort( "short", "Copy-friendly, 4 characters, no symbols.", //
GeneratedShort( "short", "Short Password", "Bir8", "Copy-friendly, 4 characters, no symbols.", //
ImmutableList.of( new MPTemplate( "Cvcn" ) ), //
MPResultTypeClass.Template, 0x3 ),
/**
* 20: pO98MoD0
*/
GeneratedBasic( "basic", "8 characters, no symbols.", //
GeneratedBasic( "basic", "Basic Password", "pO98MoD0", "8 characters, no symbols.", //
ImmutableList.of( new MPTemplate( "aaanaaan" ),
new MPTemplate( "aannaaan" ),
new MPTemplate( "aaannaaa" ) ), //
@ -90,21 +90,21 @@ public enum MPResultType {
/**
* 21: 2798
*/
GeneratedPIN( "pin", "4 numbers.", //
GeneratedPIN( "pin", "PIN Code", "2798", "4 numbers.", //
ImmutableList.of( new MPTemplate( "nnnn" ) ), //
MPResultTypeClass.Template, 0x5 ),
/**
* 30: birsujano
*/
GeneratedName( "name", "9 letter name.", //
GeneratedName( "name", "Name", "birsujano", "9 letter name.", //
ImmutableList.of( new MPTemplate( "cvccvcvcv" ) ), //
MPResultTypeClass.Template, 0xE ),
/**
* 31: bir yennoquce fefi
*/
GeneratedPhrase( "phrase", "20 character sentence.", //
GeneratedPhrase( "phrase", "Phrase", "bir yennoquce fefi", "20 character sentence.", //
ImmutableList.of( new MPTemplate( "cvcc cvc cvccvcv cvc" ),
new MPTemplate( "cvc cvccvcvcv cvcv" ),
new MPTemplate( "cv cvccv cvc cvcvccv" ) ), //
@ -113,37 +113,44 @@ public enum MPResultType {
/**
* 1056: Custom saved password.
*/
StoredPersonal( "personal", "AES-encrypted, exportable.", //
StoredPersonal( "personal", "Saved Password", null, "AES-encrypted, exportable.", //
ImmutableList.<MPTemplate>of(), //
MPResultTypeClass.Stateful, 0x0, MPSiteFeature.ExportContent ),
/**
* 2081: Custom saved password that should not be exported from the device.
*/
StoredDevicePrivate( "device", "AES-encrypted, not exported.", //
StoredDevicePrivate( "device", "Private Password", null, "AES-encrypted, not exported.", //
ImmutableList.<MPTemplate>of(), //
MPResultTypeClass.Stateful, 0x1, MPSiteFeature.DevicePrivate ),
/**
* 4160: Derive a unique binary key.
*/
DeriveKey( "key", "Encryption key.", //
DeriveKey( "key", "Binary Key", null, "Encryption key.", //
ImmutableList.<MPTemplate>of(), //
MPResultTypeClass.Derive, 0x0, MPSiteFeature.Alternative );
static final Logger logger = Logger.get( MPResultType.class );
private final String shortName;
private final String longName;
@Nullable
private final String sample;
private final String description;
private final List<MPTemplate> templates;
private final MPResultTypeClass typeClass;
private final int typeIndex;
private final ImmutableSet<MPSiteFeature> typeFeatures;
MPResultType(final String shortName, final String description, final List<MPTemplate> templates,
MPResultType(final String shortName, final String longName, @Nullable final String sample, final String description,
final List<MPTemplate> templates,
final MPResultTypeClass typeClass, final int typeIndex, final MPSiteFeature... typeFeatures) {
this.shortName = shortName;
this.longName = longName;
this.sample = sample;
this.description = description;
this.templates = templates;
this.typeClass = typeClass;
@ -160,6 +167,15 @@ public enum MPResultType {
return shortName;
}
public String getLongName() {
return longName;
}
@Nullable
public String getSample() {
return sample;
}
public String getDescription() {
return description;

View File

@ -2,6 +2,7 @@ package com.lyndir.masterpassword.gui.util;
import com.google.common.collect.ImmutableList;
import java.util.*;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
@ -14,17 +15,24 @@ import javax.swing.event.ListSelectionListener;
@SuppressWarnings("serial")
public class CollectionListModel<E> extends AbstractListModel<E> implements ComboBoxModel<E>, ListSelectionListener {
private final List<E> model = new LinkedList<>();
private final List<E> model = new LinkedList<>();
@Nullable
private E selectedItem;
private JList<E> list;
private E selectedItem;
private JList<E> list;
@Nullable
private Consumer<E> selectionConsumer;
public CollectionListModel() {
@SafeVarargs
public static <E> CollectionListModel<E> copy(final E... elements) {
return copy( Arrays.asList( elements ) );
}
public CollectionListModel(final Collection<E> model) {
this.model.addAll( model );
fireIntervalAdded( this, 0, model.size() );
public static <E> CollectionListModel<E> copy(final Collection<E> elements) {
CollectionListModel<E> model = new CollectionListModel<>();
model.model.addAll( elements );
model.fireIntervalAdded( model, 0, model.model.size() );
return model;
}
@Override
@ -79,11 +87,14 @@ public class CollectionListModel<E> extends AbstractListModel<E> implements Comb
public synchronized void setSelectedItem(@Nullable final Object newSelectedItem) {
if (!Objects.equals( selectedItem, newSelectedItem ) && model.contains( newSelectedItem )) {
selectedItem = (E) newSelectedItem;
fireContentsChanged( this, -1, -1 );
fireContentsChanged( this, -1, -1 );
//noinspection ObjectEquality
if ((list != null) && (list.getModel() == this))
list.setSelectedValue( selectedItem, true );
if (selectionConsumer != null)
selectionConsumer.accept( selectedItem );
}
}
@ -103,6 +114,17 @@ public class CollectionListModel<E> extends AbstractListModel<E> implements Comb
this.list.setModel( this );
}
public CollectionListModel<E> selection(@Nullable final E selectedItem, @Nullable final Consumer<E> selectionConsumer) {
this.selectionConsumer = null;
setSelectedItem( selectedItem );
this.selectionConsumer = selectionConsumer;
if (this.selectionConsumer != null)
this.selectionConsumer.accept( selectedItem );
return this;
}
@Override
public synchronized void valueChanged(final ListSelectionEvent event) {
//noinspection ObjectEquality

View File

@ -20,6 +20,8 @@ package com.lyndir.masterpassword.gui.util;
import com.lyndir.masterpassword.gui.Res;
import java.awt.*;
import java.util.Collection;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.annotation.Nullable;
import javax.swing.*;
@ -30,15 +32,20 @@ import javax.swing.border.CompoundBorder;
/**
* @author lhunath, 2014-06-08
*/
@SuppressWarnings("SerializableStoresNonSerializable")
public abstract class Components {
public static final float TEXT_SIZE_HEADING = 19f;
public static final float TEXT_SIZE_CONTROL = 13f;
public static final int SIZE_MARGIN = 20;
public static final int SIZE_MARGIN = 12;
public static final int SIZE_PADDING = 8;
public static GradientPanel boxPanel(final int axis, final Component... components) {
GradientPanel container = gradientPanel( null, null );
public static GradientPanel panel(final int axis, final Component... components) {
return panel( axis, null, components );
}
public static GradientPanel panel(final int axis, @Nullable final Color background, final Component... components) {
GradientPanel container = gradientPanel( background, null );
container.setLayout( new BoxLayout( container, axis ) );
for (final Component component : components)
container.add( component );
@ -46,20 +53,24 @@ public abstract class Components {
return container;
}
public static GradientPanel borderPanel(@Nullable final Border border, final Component... components) {
return borderPanel( border, null, components );
public static GradientPanel borderPanel(final int axis, final Component... components) {
return borderPanel( marginBorder(), null, axis, components );
}
public static GradientPanel borderPanel(@Nullable final Border border, @Nullable final Color background,
final Component... components) {
GradientPanel box = boxPanel( BoxLayout.LINE_AXIS, components );
public static GradientPanel borderPanel(@Nullable final Border border, final int axis, final Component... components) {
return borderPanel( border, null, axis, components );
}
public static GradientPanel borderPanel(@Nullable final Color background, final int axis, final Component... components) {
return borderPanel( marginBorder(), background, axis, components );
}
public static GradientPanel borderPanel(@Nullable final Border border, @Nullable final Color background, final int axis,
final Component... components) {
GradientPanel box = panel( axis, background, components );
if (border != null)
box.setBorder( border );
if (background != null)
box.setBackground( background );
return box;
}
@ -69,11 +80,39 @@ public abstract class Components {
setOpaque( color != null );
setBackground( color );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
};
}
public static JDialog showDialog(@Nullable final Component owner, @Nullable final String title, final JOptionPane pane) {
JDialog dialog = pane.createDialog( owner, title );
dialog.setModalityType( Dialog.ModalityType.DOCUMENT_MODAL );
return showDialog( dialog );
}
public static JDialog showDialog(@Nullable final Component owner, @Nullable final String title, final Container content) {
JDialog dialog = new JDialog( (owner != null)? SwingUtilities.windowForComponent( owner ): null,
title, Dialog.ModalityType.DOCUMENT_MODAL );
dialog.setMinimumSize( new Dimension( 320, 0 ) );
dialog.setLocationRelativeTo( owner );
dialog.setLocationByPlatform( true );
dialog.setContentPane( content );
return showDialog( dialog );
}
private static JDialog showDialog(final JDialog dialog) {
// OpenJDK does not correctly implement this setting in native code.
dialog.getRootPane().putClientProperty( "apple.awt.documentModalSheet", Boolean.TRUE );
dialog.getRootPane().putClientProperty( "Window.style", "small" );
dialog.pack();
dialog.setVisible( true );
return dialog;
}
public static JTextField textField() {
return new JTextField() {
{
@ -81,7 +120,6 @@ public abstract class Components {
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@Override
@ -97,7 +135,6 @@ public abstract class Components {
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@Override
@ -126,7 +163,6 @@ public abstract class Components {
}
} );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@Override
@ -141,22 +177,21 @@ public abstract class Components {
{
setBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
};
}
public static JButton button(final String label) {
return button( label, null );
}
public static JButton button(final String label, @Nullable final Action action) {
return new JButton( label ) {
{
setFont( Res.fonts().controlFont( TEXT_SIZE_CONTROL ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@Override
public Dimension getMaximumSize() {
return new Dimension( 20, getPreferredSize().height );
if (action != null)
setAction( action );
}
};
}
@ -169,7 +204,6 @@ public abstract class Components {
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 );
rigidArea.setBackground( Color.red );
return rigidArea;
}
@ -194,7 +228,6 @@ public abstract class Components {
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) );
((DefaultEditor) getEditor()).getTextField().setBorder( editorBorder );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
setBorder( null );
}
@ -231,7 +264,6 @@ public abstract class Components {
{
setFont( Res.fonts().controlFont( TEXT_SIZE_HEADING ).deriveFont( Font.BOLD ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@Override
@ -267,7 +299,6 @@ public abstract class Components {
{
setFont( Res.fonts().controlFont( TEXT_SIZE_CONTROL ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@Override
@ -283,7 +314,6 @@ public abstract class Components {
setFont( Res.fonts().controlFont( TEXT_SIZE_CONTROL ) );
setBackground( null );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
};
}
@ -293,6 +323,16 @@ public abstract class Components {
return comboBox( new DefaultComboBoxModel<>( values ), valueTransformer );
}
public static <E> JComboBox<E> comboBox(final E[] values, final Function<E, String> valueTransformer, final E selectedItem,
@Nullable final Consumer<E> selectionConsumer) {
return comboBox( CollectionListModel.copy( values ).selection( selectedItem, selectionConsumer ), valueTransformer );
}
public static <E> JComboBox<E> comboBox(final Collection<E> values, final Function<E, String> valueTransformer, final E selectedItem,
@Nullable final Consumer<E> selectionConsumer) {
return comboBox( CollectionListModel.copy( values ).selection( selectedItem, selectionConsumer ), valueTransformer );
}
public static <E> JComboBox<E> comboBox(final ComboBoxModel<E> model, final Function<E, String> valueTransformer) {
return new JComboBox<E>( model ) {
{
@ -311,8 +351,8 @@ public abstract class Components {
list, valueTransformer.apply( (E) value ), index, isSelected, cellHasFocus );
}
} );
putClientProperty( "JComboBox.isPopDown", Boolean.TRUE );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@Override

View File

@ -1,13 +1,16 @@
package com.lyndir.masterpassword.gui.view;
import com.google.common.collect.ImmutableList;
import com.lyndir.masterpassword.MPAlgorithm;
import com.lyndir.masterpassword.MPResultType;
import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.util.CollectionListModel;
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.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.*;
import java.util.Collection;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.annotation.Nullable;
@ -17,14 +20,16 @@ import javax.swing.*;
/**
* @author lhunath, 2018-07-14
*/
@SuppressWarnings("serial")
public class FilesPanel extends JPanel implements ItemListener {
private final Collection<Listener> listeners = new CopyOnWriteArraySet<>();
private final JLabel avatarLabel = new JLabel();
private final CollectionListModel<MPUser<?>> usersModel = new CollectionListModel<>();
private final JComboBox<MPUser<?>> userField =
private final JLabel avatarLabel = new JLabel();
private final CollectionListModel<MPUser<?>> usersModel = new CollectionListModel<>();
private final JComboBox<MPUser<?>> userField =
Components.comboBox( usersModel, user -> (user != null)? user.getFullName(): null );
private final JButton preferencesButton = Components.button( "..." );
protected FilesPanel() {
setOpaque( false );
@ -41,10 +46,34 @@ public class FilesPanel extends JPanel implements ItemListener {
avatarLabel.setToolTipText( "The avatar for your user. Click to change it." );
// -
add( Components.strut( 20 ) );
add( Components.strut( Components.margin() ) );
// User Selection
add( userField );
add( Components.panel( BoxLayout.LINE_AXIS, userField, preferencesButton ) );
preferencesButton.setAction( new AbstractAction() {
@Override
public void actionPerformed(final ActionEvent e) {
MPUser<?> user = usersModel.getSelectedItem();
if (user == null)
return;
MPFileUser fileUser = (user instanceof MPFileUser)? (MPFileUser) user: null;
ImmutableList.Builder<Component> components = ImmutableList.builder();
if (fileUser != null)
components.add( Components.label( "Default Password Type:" ),
Components.comboBox( MPResultType.values(), MPResultType::getLongName,
fileUser.getDefaultType(), fileUser::setDefaultType ),
Components.strut() );
components.add( Components.label( "Default Algorithm:" ),
Components.comboBox( MPAlgorithm.Version.values(), MPAlgorithm.Version::name,
user.getAlgorithm().version(),
version -> user.setAlgorithm( version.getAlgorithm() ) ) );
Components.showDialog( preferencesButton, user.getFullName(), new JOptionPane( Components.panel(
BoxLayout.PAGE_AXIS, components.build().toArray( new Component[0] ) ) ) );
}
} );
userField.addItemListener( this );
}

View File

@ -9,6 +9,7 @@ import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.border.BevelBorder;
/**
@ -29,24 +30,23 @@ public class MasterPasswordFrame extends JFrame implements FilesPanel.Listener,
super( "Master Password" );
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
setContentPane( root = Components.gradientPanel( Res.colors().frameBg(), new FlowLayout() ) );
root.setLayout( new BoxLayout( root, BoxLayout.PAGE_AXIS ) );
root.setBorder( Components.marginBorder() );
setContentPane( root = Components.borderPanel( Res.colors().frameBg(), BoxLayout.PAGE_AXIS ) );
root.add( filesPanel );
root.add( new JSeparator( SwingConstants.HORIZONTAL ) );
root.add( Components.strut() );
root.add( Components.borderPanel( BorderFactory.createRaisedBevelBorder(), Res.colors().controlBg(), userPanel ) );
root.add( Components.borderPanel(
BorderFactory.createBevelBorder( BevelBorder.RAISED, Res.colors().controlBorder(), Res.colors().frameBg() ),
Res.colors().controlBg(), BoxLayout.PAGE_AXIS, userPanel ) );
filesPanel.addListener( this );
filesPanel.reload();
addComponentListener(this );
addComponentListener( this );
setPreferredSize( new Dimension( 640, 480 ) );
pack();
setLocationByPlatform( true );
setLocationRelativeTo( null );
setLocationByPlatform( true );
}
@Override

View File

@ -10,6 +10,8 @@ import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.util.CollectionListModel;
import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.model.*;
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;
@ -129,7 +131,7 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
@Override
public void actionPerformed(final ActionEvent event) {
updateIdenticon();
char[] masterPassword = masterPasswordField.getPassword();
Res.job( () -> {
try {
@ -223,6 +225,7 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
add( queryLabel );
queryLabel.setText( strf( "%s's password for:", user.getFullName() ) );
add( queryField );
queryField.putClientProperty( "JTextField.variant", "search" );
queryField.addActionListener( this );
queryField.addKeyListener( this );
queryField.getDocument().addDocumentListener( this );
@ -236,10 +239,14 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen
@Override
public void actionPerformed(final ActionEvent event) {
showSiteResult( sitesList.getSelectedValue(), result -> {
MPSite<?> site = sitesList.getSelectedValue();
showSiteResult( site, result -> {
if (result == null)
return;
if (site instanceof MPFileSite)
((MPFileSite) site).use();
Transferable clipboardContents = new StringSelection( result );
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );

View File

@ -95,6 +95,8 @@ public class MPFileSite extends MPBasicSite<MPFileQuestion> {
uses++;
lastUsed = new Instant();
user.use();
setChanged();
}
public String getResult()