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

@@ -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 );