From 8f7faa9e4e794a3ea7ad7245ef9522e8295b250e Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Mon, 23 Jul 2018 23:34:32 -0400 Subject: [PATCH] User preferences. --- .../lyndir/masterpassword/MPResultType.java | 40 +++++--- .../gui/util/CollectionListModel.java | 38 ++++++-- .../masterpassword/gui/util/Components.java | 96 +++++++++++++------ .../masterpassword/gui/view/FilesPanel.java | 43 +++++++-- .../gui/view/MasterPasswordFrame.java | 14 +-- .../masterpassword/gui/view/UserPanel.java | 11 ++- .../masterpassword/model/impl/MPFileSite.java | 2 + 7 files changed, 180 insertions(+), 64 deletions(-) diff --git a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPResultType.java b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPResultType.java index c24ea544..e13e358c 100644 --- a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPResultType.java +++ b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPResultType.java @@ -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.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.of(), // MPResultTypeClass.Stateful, 0x1, MPSiteFeature.DevicePrivate ), /** * 4160: Derive a unique binary key. */ - DeriveKey( "key", "Encryption key.", // + DeriveKey( "key", "Binary Key", null, "Encryption key.", // ImmutableList.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 templates; private final MPResultTypeClass typeClass; private final int typeIndex; private final ImmutableSet typeFeatures; - MPResultType(final String shortName, final String description, final List templates, + MPResultType(final String shortName, final String longName, @Nullable final String sample, final String description, + final List 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; diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/CollectionListModel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/CollectionListModel.java index 127d1b8f..6de1ae74 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/CollectionListModel.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/CollectionListModel.java @@ -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 extends AbstractListModel implements ComboBoxModel, ListSelectionListener { - private final List model = new LinkedList<>(); + private final List model = new LinkedList<>(); @Nullable - private E selectedItem; - private JList list; + private E selectedItem; + private JList list; + @Nullable + private Consumer selectionConsumer; - public CollectionListModel() { + @SafeVarargs + public static CollectionListModel copy(final E... elements) { + return copy( Arrays.asList( elements ) ); } - public CollectionListModel(final Collection model) { - this.model.addAll( model ); - fireIntervalAdded( this, 0, model.size() ); + public static CollectionListModel copy(final Collection elements) { + CollectionListModel model = new CollectionListModel<>(); + model.model.addAll( elements ); + model.fireIntervalAdded( model, 0, model.model.size() ); + + return model; } @Override @@ -79,11 +87,14 @@ public class CollectionListModel extends AbstractListModel 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 extends AbstractListModel implements Comb this.list.setModel( this ); } + public CollectionListModel selection(@Nullable final E selectedItem, @Nullable final Consumer 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 diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java index f8023f15..4864276e 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java @@ -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 JComboBox comboBox(final E[] values, final Function valueTransformer, final E selectedItem, + @Nullable final Consumer selectionConsumer) { + return comboBox( CollectionListModel.copy( values ).selection( selectedItem, selectionConsumer ), valueTransformer ); + } + + public static JComboBox comboBox(final Collection values, final Function valueTransformer, final E selectedItem, + @Nullable final Consumer selectionConsumer) { + return comboBox( CollectionListModel.copy( values ).selection( selectedItem, selectionConsumer ), valueTransformer ); + } + public static JComboBox comboBox(final ComboBoxModel model, final Function valueTransformer) { return new JComboBox( 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 diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java index 4290fbb9..f683ac28 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/FilesPanel.java @@ -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 listeners = new CopyOnWriteArraySet<>(); - private final JLabel avatarLabel = new JLabel(); - private final CollectionListModel> usersModel = new CollectionListModel<>(); - private final JComboBox> userField = + private final JLabel avatarLabel = new JLabel(); + private final CollectionListModel> usersModel = new CollectionListModel<>(); + private final JComboBox> 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 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 ); } diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/MasterPasswordFrame.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/MasterPasswordFrame.java index 8139fa9d..1b1bef97 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/MasterPasswordFrame.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/MasterPasswordFrame.java @@ -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 diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserPanel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserPanel.java index 86d3f3df..064cbc0f 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserPanel.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserPanel.java @@ -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 ); diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileSite.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileSite.java index cac0e91b..6c12c0dc 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileSite.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileSite.java @@ -95,6 +95,8 @@ public class MPFileSite extends MPBasicSite { uses++; lastUsed = new Instant(); user.use(); + + setChanged(); } public String getResult()