From 46d301df94f0da52892cbfd4d09d391ea2d318c5 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Sat, 28 Jul 2018 14:03:49 -0400 Subject: [PATCH] Site settings & add sites. --- .../java/algorithm/build.gradle | 2 +- .../lyndir/masterpassword/util/Utilities.java | 9 + .../com/lyndir/masterpassword/gui/GUI.java | 4 +- .../gui/model/MPIncognitoSite.java | 20 +- .../gui/model/MPIncognitoUser.java | 5 + .../masterpassword/gui/model/MPNewSite.java | 15 ++ .../gui/util/CollectionListModel.java | 27 ++- .../masterpassword/gui/util/Components.java | 66 +++++-- .../lyndir/masterpassword/gui/util/Res.java | 11 ++ .../masterpassword/gui/util/Selectable.java | 15 ++ .../gui/util/UnsignedIntegerModel.java | 36 +++- .../gui/view/MasterPasswordFrame.java | 1 - .../masterpassword/gui/view/UserPanel.java | 181 ++++++++++++------ .../main/resources/media/icon_settings.png | Bin 0 -> 1551 bytes .../main/resources/media/icon_settings@2x.png | Bin 0 -> 2552 bytes .../src/main/resources/media/icon_user.png | Bin 0 -> 1586 bytes .../src/main/resources/media/icon_user@2x.png | Bin 0 -> 2352 bytes .../lyndir/masterpassword/model/MPSite.java | 26 ++- .../lyndir/masterpassword/model/MPUser.java | 9 +- .../model/impl/MPBasicQuestion.java | 2 +- .../model/impl/MPBasicSite.java | 49 ++--- .../model/impl/MPBasicUser.java | 18 +- .../masterpassword/model/impl/MPFileSite.java | 27 +-- .../masterpassword/model/impl/MPFileUser.java | 8 +- .../model/impl/MPFlatMarshaller.java | 2 +- .../masterpassword/model/impl/MPJSONFile.java | 6 +- 26 files changed, 377 insertions(+), 162 deletions(-) create mode 100644 platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPNewSite.java create mode 100644 platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Selectable.java create mode 100644 platform-independent/java/gui/src/main/resources/media/icon_settings.png create mode 100644 platform-independent/java/gui/src/main/resources/media/icon_settings@2x.png create mode 100644 platform-independent/java/gui/src/main/resources/media/icon_user.png create mode 100644 platform-independent/java/gui/src/main/resources/media/icon_user@2x.png diff --git a/platform-independent/java/algorithm/build.gradle b/platform-independent/java/algorithm/build.gradle index 645e689c..901f9d30 100644 --- a/platform-independent/java/algorithm/build.gradle +++ b/platform-independent/java/algorithm/build.gradle @@ -6,7 +6,7 @@ description = 'Master Password Algorithm Implementation' tasks.withType( JavaCompile ) { // Native headers - options.compilerArgs += ["-h", new File( new File( project( ':masterpassword-core' ).projectDir, 'src' ), 'java' ).absolutePath] + options.compilerArgs += ["-h", new File( new File( project.project( ':masterpassword-core' ).projectDir, 'src' ), 'java' ).absolutePath] } configurations { diff --git a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/util/Utilities.java b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/util/Utilities.java index e0e83937..1bbe3602 100644 --- a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/util/Utilities.java +++ b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/util/Utilities.java @@ -2,6 +2,7 @@ package com.lyndir.masterpassword.util; import java.util.function.Consumer; import java.util.function.Function; +import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -18,6 +19,14 @@ public final class Utilities { return consumer.apply( value ); } + @Nonnull + public static R ifNotNullElse(@Nullable final T value, final Function consumer, @Nonnull final R nullValue) { + if (value == null) + return nullValue; + + return consumer.apply( value ); + } + public static void ifNotNullDo(@Nullable final T value, final Consumer consumer) { if (value != null) consumer.accept( value ); diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/GUI.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/GUI.java index e6a5ea8c..65917500 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/GUI.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/GUI.java @@ -47,8 +47,8 @@ public class GUI { private final MasterPasswordFrame frame = new MasterPasswordFrame(); public static void main(final String... args) { - Thread.setDefaultUncaughtExceptionHandler( - (t, e) -> logger.bug( e, "Uncaught: %s", e.getLocalizedMessage() ) ); +// Thread.setDefaultUncaughtExceptionHandler( +// (t, e) -> logger.bug( e, "Uncaught: %s", e.getLocalizedMessage() ) ); if (Config.get().checkForUpdates()) checkUpdate(); diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPIncognitoSite.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPIncognitoSite.java index 44efed4e..32d47662 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPIncognitoSite.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPIncognitoSite.java @@ -29,25 +29,15 @@ import javax.annotation.Nullable; /** * @author lhunath, 14-12-16 */ -public class MPIncognitoSite extends MPBasicSite { +public class MPIncognitoSite extends MPBasicSite { - private final MPIncognitoUser user; - - public MPIncognitoSite(final MPIncognitoUser user, final String name) { - this( user, name, null, null, null, null ); + public MPIncognitoSite(final MPIncognitoUser user, final String siteName) { + this( user, siteName, null, null, null, null ); } - public MPIncognitoSite(final MPIncognitoUser user, final String name, + public MPIncognitoSite(final MPIncognitoUser user, final String siteName, @Nullable final MPAlgorithm algorithm, @Nullable final UnsignedInteger counter, @Nullable final MPResultType resultType, @Nullable final MPResultType loginType) { - super( name, (algorithm == null)? user.getAlgorithm(): algorithm, counter, resultType, loginType ); - - this.user = user; - } - - @Nonnull - @Override - public MPIncognitoUser getUser() { - return user; + super( user, siteName, (algorithm == null)? user.getAlgorithm(): algorithm, counter, resultType, loginType ); } } diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPIncognitoUser.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPIncognitoUser.java index 80e929e4..20d2e053 100755 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPIncognitoUser.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPIncognitoUser.java @@ -37,4 +37,9 @@ public class MPIncognitoUser extends MPBasicUser { public byte[] getKeyID() { return null; } + + @Override + public MPIncognitoSite addSite(final String siteName) { + return addSite( new MPIncognitoSite( this, siteName ) ); + } } diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPNewSite.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPNewSite.java new file mode 100644 index 00000000..8cc864d7 --- /dev/null +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPNewSite.java @@ -0,0 +1,15 @@ +package com.lyndir.masterpassword.gui.model; + +import com.lyndir.masterpassword.model.*; +import com.lyndir.masterpassword.model.impl.*; + + +/** + * @author lhunath, 2018-07-27 + */ +public class MPNewSite extends MPBasicSite, MPQuestion> { + + public MPNewSite(final MPUser user, final String siteName) { + super( user, siteName, user.getAlgorithm() ); + } +} 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 9ab70ae4..cc357770 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 @@ -13,7 +13,8 @@ import javax.swing.event.ListSelectionListener; * @author lhunath, 2018-07-19 */ @SuppressWarnings("serial") -public class CollectionListModel extends AbstractListModel implements ComboBoxModel, ListSelectionListener { +public class CollectionListModel extends AbstractListModel + implements ComboBoxModel, ListSelectionListener, Selectable> { private final List model = new LinkedList<>(); @Nullable @@ -55,13 +56,16 @@ public class CollectionListModel extends AbstractListModel implements Comb * This operation will mutate the internal model to reflect the given model. * The given model will remain untouched and independent from this object. */ - @SuppressWarnings("AssignmentToForLoopParameter") - public synchronized void set(final Collection newModel) { - ImmutableList newModelList = ImmutableList.copyOf( newModel ); + @SuppressWarnings({ "unchecked", "SuspiciousToArrayCall" }) + public synchronized void set(final Collection elements) { + set( (E[]) elements.toArray( new Object[0] ) ); + } + @SuppressWarnings("AssignmentToForLoopParameter") + public synchronized void set(final E... elements) { ListIterator oldIt = model.listIterator(); for (int from = 0; oldIt.hasNext(); ++from) { - int to = newModelList.indexOf( oldIt.next() ); + int to = Arrays.binarySearch( elements, oldIt.next() ); if (to != from) { oldIt.remove(); @@ -70,9 +74,8 @@ public class CollectionListModel extends AbstractListModel implements Comb } } - Iterator newIt = newModelList.iterator(); - for (int to = 0; newIt.hasNext(); ++to) { - E newSite = newIt.next(); + for (int to = 0; to < elements.length; ++to) { + E newSite = elements[to]; if ((to >= model.size()) || !Objects.equals( model.get( to ), newSite )) { model.add( to, newSite ); @@ -116,6 +119,7 @@ public class CollectionListModel extends AbstractListModel implements Comb this.list.setModel( this ); } + @Override public synchronized CollectionListModel selection(@Nullable final Consumer selectionConsumer) { this.selectionConsumer = selectionConsumer; if (selectionConsumer != null) @@ -124,6 +128,7 @@ public class CollectionListModel extends AbstractListModel implements Comb return this; } + @Override public synchronized CollectionListModel selection(@Nullable final E selectedItem, @Nullable final Consumer selectionConsumer) { this.selectionConsumer = null; setSelectedItem( selectedItem ); @@ -134,7 +139,11 @@ public class CollectionListModel extends AbstractListModel implements Comb @Override public synchronized void valueChanged(final ListSelectionEvent event) { //noinspection ObjectEquality - if ((event.getSource() == list) && (list.getModel() == this)) + if (!event.getValueIsAdjusting() && (event.getSource() == list) && (list.getModel() == this)) { selectedItem = list.getSelectedValue(); + + if (selectionConsumer != null) + selectionConsumer.accept( selectedItem ); + } } } 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 294337c7..3bd1a4c7 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 @@ -28,6 +28,8 @@ import javax.annotation.Nullable; import javax.swing.*; import javax.swing.border.Border; import javax.swing.border.CompoundBorder; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; /** @@ -41,6 +43,12 @@ public abstract class Components { public static final int SIZE_MARGIN = 12; public static final int SIZE_PADDING = 8; + public static GradientPanel panel(final Component... components) { + GradientPanel panel = panel( BoxLayout.LINE_AXIS, null, components ); + panel.setLayout( new OverlayLayout( panel ) ); + return panel; + } + public static GradientPanel panel(final int axis, final Component... components) { return panel( axis, null, components ); } @@ -124,6 +132,40 @@ public abstract class Components { }; } + public static JTextField textField(@Nullable final String text, @Nullable final Consumer selection) { + return new JTextField( text ) { + { + setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ), + BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) ); + setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) ); + setAlignmentX( LEFT_ALIGNMENT ); + + if (selection != null) + getDocument().addDocumentListener( new DocumentListener() { + @Override + public void insertUpdate(final DocumentEvent e) { + selection.accept( getText() ); + } + + @Override + public void removeUpdate(final DocumentEvent e) { + selection.accept( getText() ); + } + + @Override + public void changedUpdate(final DocumentEvent e) { + selection.accept( getText() ); + } + } ); + } + + @Override + public Dimension getMaximumSize() { + return new Dimension( Integer.MAX_VALUE, getPreferredSize().height ); + } + }; + } + public static JPasswordField passwordField() { return new JPasswordField() { { @@ -143,18 +185,16 @@ public abstract class Components { return new JList( model ) { { setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) ); - setBorder( BorderFactory.createEmptyBorder( 4, 0, 4, 0 ) ); setCellRenderer( new DefaultListCellRenderer() { - { - setBorder( BorderFactory.createEmptyBorder( 0, 4, 0, 4 ) ); - } - @Override @SuppressWarnings({ "unchecked", "SerializableStoresNonSerializable" }) public Component getListCellRendererComponent(final JList list, final Object value, final int index, final boolean isSelected, final boolean cellHasFocus) { - return super.getListCellRendererComponent( + super.getListCellRendererComponent( list, valueTransformer.apply( (E) value ), index, isSelected, cellHasFocus ); + setBorder( BorderFactory.createEmptyBorder( 2, 4, 2, 4 ) ); + + return this; } } ); setAlignmentX( LEFT_ALIGNMENT ); @@ -248,11 +288,6 @@ public abstract class Components { setAlignmentX( LEFT_ALIGNMENT ); setBorder( null ); } - - @Override - public Dimension getMaximumSize() { - return new Dimension( 20, getPreferredSize().height ); - } }; } @@ -362,16 +397,15 @@ public abstract class Components { setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) ); setBorder( BorderFactory.createEmptyBorder( 4, 0, 4, 0 ) ); setRenderer( new DefaultListCellRenderer() { - { - setBorder( BorderFactory.createEmptyBorder( 0, 4, 0, 4 ) ); - } - @Override @SuppressWarnings({ "unchecked", "SerializableStoresNonSerializable" }) public Component getListCellRendererComponent(final JList list, final Object value, final int index, final boolean isSelected, final boolean cellHasFocus) { - return super.getListCellRendererComponent( + super.getListCellRendererComponent( list, valueTransformer.apply( (E) value ), index, isSelected, cellHasFocus ); + setBorder( BorderFactory.createEmptyBorder( 0, 4, 0, 4 ) ); + + return this; } } ); putClientProperty( "JComboBox.isPopDown", Boolean.TRUE ); diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java index dbdaa367..bd707a9c 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java @@ -34,6 +34,9 @@ import java.util.concurrent.*; import javax.annotation.Nullable; import javax.swing.*; import org.jetbrains.annotations.NonNls; +import org.joda.time.ReadableInstant; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; /** @@ -103,6 +106,10 @@ public abstract class Res { return colors; } + public static String format(final ReadableInstant instant) { + return DateTimeFormat.mediumDateTime().print( instant ); + } + public static final class Icons { public Icon add() { @@ -121,6 +128,10 @@ public abstract class Res { return icon( "media/icon_user.png" ); } + public Icon settings() { + return icon( "media/icon_settings.png" ); + } + public Icon avatar(final int index) { return icon( strf( "media/avatar-%d.png", index % avatars() ) ); } diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Selectable.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Selectable.java new file mode 100644 index 00000000..f7f1fc40 --- /dev/null +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Selectable.java @@ -0,0 +1,15 @@ +package com.lyndir.masterpassword.gui.util; + +import java.util.function.Consumer; +import javax.annotation.Nullable; + + +/** + * @author lhunath, 2018-07-26 + */ +public interface Selectable { + + T selection(@Nullable Consumer selectionConsumer); + + T selection(E selectedItem, @Nullable Consumer selectionConsumer); +} diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/UnsignedIntegerModel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/UnsignedIntegerModel.java index ae2655e4..02fa57fb 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/UnsignedIntegerModel.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/UnsignedIntegerModel.java @@ -19,16 +19,24 @@ package com.lyndir.masterpassword.gui.util; import com.google.common.primitives.UnsignedInteger; +import java.util.function.Consumer; +import javax.annotation.Nullable; import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; /** * @author lhunath, 2016-10-29 */ -public class UnsignedIntegerModel extends SpinnerNumberModel { +public class UnsignedIntegerModel extends SpinnerNumberModel +implements Selectable { private static final long serialVersionUID = 1L; + @Nullable + private ChangeListener changeListener; + public UnsignedIntegerModel() { this( UnsignedInteger.ZERO, UnsignedInteger.ZERO, UnsignedInteger.MAX_VALUE, UnsignedInteger.ONE ); } @@ -55,4 +63,30 @@ public class UnsignedIntegerModel extends SpinnerNumberModel { public UnsignedInteger getNumber() { return (UnsignedInteger) super.getNumber(); } + + @Override + public UnsignedIntegerModel selection(@Nullable final Consumer selectionConsumer) { + if (changeListener != null) { + removeChangeListener( changeListener ); + changeListener = null; + } + + if (selectionConsumer != null) { + addChangeListener( changeListener = e -> selectionConsumer.accept( getNumber() ) ); + selectionConsumer.accept( getNumber() ); + } + + return this; + } + + @Override + public UnsignedIntegerModel selection(final UnsignedInteger selectedItem, @Nullable final Consumer selectionConsumer) { + if (changeListener != null) { + removeChangeListener( changeListener ); + changeListener = null; + } + + setValue( selectedItem ); + return selection( selectionConsumer ); + } } 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 96c0220d..50da1ea9 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 @@ -32,7 +32,6 @@ public class MasterPasswordFrame extends JFrame implements FilesPanel.Listener, setDefaultCloseOperation( DISPOSE_ON_CLOSE ); 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.createBevelBorder( BevelBorder.RAISED, Res.colors().controlBorder(), Res.colors().frameBg() ), 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 6a331d33..8b0e64b6 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 @@ -2,14 +2,14 @@ package com.lyndir.masterpassword.gui.view; import static com.lyndir.lhunath.opal.system.util.StringUtils.*; -import com.google.common.collect.ImmutableCollection; +import com.google.common.base.Joiner; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.primitives.UnsignedInteger; import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.masterpassword.*; -import com.lyndir.masterpassword.gui.util.Res; -import com.lyndir.masterpassword.gui.util.CollectionListModel; -import com.lyndir.masterpassword.gui.util.Components; +import com.lyndir.masterpassword.gui.model.MPNewSite; +import com.lyndir.masterpassword.gui.util.*; import com.lyndir.masterpassword.model.*; import com.lyndir.masterpassword.model.impl.MPFileSite; import com.lyndir.masterpassword.model.impl.MPFileUser; @@ -17,15 +17,15 @@ import java.awt.*; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.awt.event.*; -import java.util.Objects; -import java.util.Random; +import java.util.*; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.swing.*; -import javax.swing.event.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; /** @@ -166,7 +166,7 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen } private synchronized void update() { - errorLabel.setText( null ); + errorLabel.setText( " " ); if (identiconJob != null) identiconJob.cancel( true ); @@ -193,38 +193,47 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen } - private static final class AuthenticatedUserPanel extends JPanel implements ActionListener, DocumentListener, ListSelectionListener, - KeyListener { + private static final class AuthenticatedUserPanel extends JPanel implements KeyListener { public static final int SIZE_RESULT = 48; @Nonnull private final MPUser user; - private final JLabel passwordLabel = Components.label( SwingConstants.CENTER ); - private final JLabel passwordField = Components.heading( SwingConstants.CENTER ); - private final JLabel queryLabel = Components.label(); - private final JTextField queryField = Components.textField(); - private final CollectionListModel> sitesModel = new CollectionListModel<>(); - private final JList> sitesList = Components.list( sitesModel, - value -> (value != null)? value.getName(): null ); - private Future updateSitesJob; + private final JLabel passwordLabel = Components.label( SwingConstants.CENTER ); + private final JLabel passwordField = Components.heading( SwingConstants.CENTER ); + private final JButton passwordButton = + Components.button( Res.icons().settings(), event -> showSiteSettings() ); + private final JLabel queryLabel = Components.label(); + private final JTextField queryField = Components.textField( null, this::updateSites ); + private final CollectionListModel> sitesModel = + new CollectionListModel>().selection( this::showSiteResult ); + private final JList> sitesList = + Components.list( sitesModel, this::getSiteDescription ); + + private Future updateSitesJob; private AuthenticatedUserPanel(@Nonnull final MPUser user) { setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) ); this.user = user; - add( new Components.GradientPanel( new BorderLayout() ) { - { - add( Components.heading( user.getFullName(), SwingConstants.CENTER ), BorderLayout.CENTER ); - add( Components.button( Res.icons().user(), event -> showPreferences() ), BorderLayout.LINE_END ); - } - } ); + add( Components.panel( + Components.heading( user.getFullName(), SwingConstants.CENTER ), + Components.panel( + BoxLayout.LINE_AXIS, + Box.createGlue(), + Components.button( Res.icons().user(), event -> showUserPreferences() ) ) ) ); add( passwordLabel ); - add( passwordField ); + add( Components.panel( + passwordField, + Components.panel( + BoxLayout.LINE_AXIS, + Box.createGlue(), + passwordButton ) ) ); passwordField.setForeground( Res.colors().highlightFg() ); passwordField.setFont( Res.fonts().bigValueFont( SIZE_RESULT ) ); + passwordButton.setVisible( false ); add( Box.createGlue() ); add( Components.strut() ); @@ -232,18 +241,16 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen queryLabel.setText( strf( "%s's password for:", user.getFullName() ) ); add( queryField ); queryField.putClientProperty( "JTextField.variant", "search" ); - queryField.addActionListener( this ); + queryField.addActionListener( event -> useSite() ); queryField.addKeyListener( this ); - queryField.getDocument().addDocumentListener( this ); queryField.requestFocusInWindow(); add( Components.strut() ); add( Components.scrollPane( sitesList ) ); sitesModel.registerList( sitesList ); - sitesList.addListSelectionListener( this ); add( Box.createGlue() ); } - public void showPreferences() { + public void showUserPreferences() { ImmutableList.Builder components = ImmutableList.builder(); MPFileUser fileUser = (user instanceof MPFileUser)? (MPFileUser) user: null; @@ -262,9 +269,79 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen BoxLayout.PAGE_AXIS, components.build().toArray( new Component[0] ) ) ) ); } - @Override - public void actionPerformed(final ActionEvent event) { - MPSite site = sitesList.getSelectedValue(); + public void showSiteSettings() { + ImmutableList.Builder components = ImmutableList.builder(); + + MPSite site = sitesModel.getSelectedItem(); + if (site == null) + return; + + components.add( Components.label( "Algorithm:" ), + Components.comboBox( MPAlgorithm.Version.values(), MPAlgorithm.Version::name, + site.getAlgorithm().version(), + version -> site.setAlgorithm( version.getAlgorithm() ) ) ); + + components.add( Components.label( "Counter:" ), + Components.spinner( new UnsignedIntegerModel( site.getCounter(), UnsignedInteger.ONE ) + .selection( site::setCounter ) ), + Components.strut() ); + + components.add( Components.label( "Password Type:" ), + Components.comboBox( MPResultType.values(), MPResultType::getLongName, + site.getResultType(), site::setResultType ), + Components.strut() ); + + components.add( Components.label( "Login Type:" ), + Components.comboBox( MPResultType.values(), MPResultType::getLongName, + site.getLoginType(), site::setLoginType ), + Components.strut() ); + + MPFileSite fileSite = (site instanceof MPFileSite)? (MPFileSite) site: null; + if (fileSite != null) + components.add( Components.label( "URL:" ), + Components.textField( fileSite.getUrl(), fileSite::setUrl ), + Components.strut() ); + + Components.showDialog( this, site.getSiteName(), new JOptionPane( Components.panel( + BoxLayout.PAGE_AXIS, components.build().toArray( new Component[0] ) ) ) ); + } + + private String getSiteDescription(@Nonnull final MPSite site) { + if (site instanceof MPNewSite) + return strf( "%s <Add new site>", queryField.getText() ); + + ImmutableList.Builder parameters = ImmutableList.builder(); + try { + MPFileSite fileSite = (site instanceof MPFileSite)? (MPFileSite) site: null; + if (fileSite != null) + parameters.add( Res.format( fileSite.getLastUsed() ) ); + parameters.add( site.getAlgorithm().version() ); + parameters.add( strf( "#%d", site.getCounter().longValue() ) ); + parameters.add( strf( "%s", site.getLogin() ) ); + if ((fileSite != null) && (fileSite.getUrl() != null)) + parameters.add( fileSite.getUrl() ); + } + catch (final MPAlgorithmException | MPKeyUnavailableException e) { + logger.err( e, "While generating site description." ); + parameters.add( e.getLocalizedMessage() ); + } + + return strf( "%s (%s)", site.getSiteName(), + Joiner.on( " - " ).skipNulls().join( parameters.build() ) ); + } + + private void useSite() { + MPSite site = sitesModel.getSelectedItem(); + if (site instanceof MPNewSite) { + if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog( + this, strf( "Remember the site [%s]?", site.getSiteName() ), + "New Site", JOptionPane.YES_NO_OPTION )) { + sitesModel.setSelectedItem( user.addSite( site.getSiteName() ) ); + useSite(); + } + return; + } + showSiteResult( site, result -> { if (result == null) return; @@ -282,24 +359,8 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen } ); } - @Override - public void insertUpdate(final DocumentEvent event) { - updateSites(); - } - - @Override - public void removeUpdate(final DocumentEvent event) { - updateSites(); - } - - @Override - public void changedUpdate(final DocumentEvent event) { - updateSites(); - } - - @Override - public void valueChanged(final ListSelectionEvent event) { - showSiteResult( event.getValueIsAdjusting()? null: sitesList.getSelectedValue(), null ); + private void showSiteResult(@Nullable final MPSite site) { + showSiteResult( site, null ); } private void showSiteResult(@Nullable final MPSite site, @Nullable final Consumer resultCallback) { @@ -309,22 +370,21 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen Res.ui( () -> { passwordLabel.setText( " " ); passwordField.setText( " " ); + passwordButton.setVisible( false ); } ); return; } - String siteName = site.getName(); Res.job( () -> { try { - String result = user.getMasterKey().siteResult( - siteName, user.getAlgorithm(), UnsignedInteger.ONE, - MPKeyPurpose.Authentication, null, MPResultType.GeneratedLong, null ); + String result = site.getResult(); if (resultCallback != null) resultCallback.accept( result ); Res.ui( () -> { - passwordLabel.setText( strf( "Your password for %s:", siteName ) ); + passwordLabel.setText( strf( "Your password for %s:", site.getSiteName() ) ); passwordField.setText( result ); + passwordButton.setVisible( true ); } ); } catch (final MPKeyUnavailableException | MPAlgorithmException e) { @@ -349,12 +409,19 @@ public class UserPanel extends Components.GradientPanel implements MPUser.Listen sitesList.dispatchEvent( event ); } - private synchronized void updateSites() { + private synchronized void updateSites(@Nullable final String query) { if (updateSitesJob != null) updateSitesJob.cancel( true ); updateSitesJob = Res.job( () -> { - ImmutableCollection> sites = user.findSites( queryField.getText() ); + Collection> sites = new LinkedList<>(); + if (!Strings.isNullOrEmpty( query )) { + sites.addAll( new LinkedList<>( user.findSites( query ) ) ); + + if (sites.stream().noneMatch( site -> site.getSiteName().equalsIgnoreCase( query ) )) + sites.add( new MPNewSite( user, query ) ); + } + Res.ui( () -> sitesModel.set( sites ) ); } ); } diff --git a/platform-independent/java/gui/src/main/resources/media/icon_settings.png b/platform-independent/java/gui/src/main/resources/media/icon_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..c0df9db90ab213e2ed7139019b15b4fc5aaeaeb1 GIT binary patch literal 1551 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=hEVFsEgPM3hAM`dB6B=jtV<?;Zqle1Gx6p~WYGxKbf-tXS8q>!0ns}yePYv5bpoSKp8QB{;0T;&&% zT$P<{nWAKGr(jcI1vDTxwIorYA~z?m*s8)-32d$vkPQ;nS5g2gDap1~as*kZ5aAo3 z;GAESs$i;Tpqp%9W}skZsAp(wVs37(qhMrUXrOOkq;F`XYiMp|Y-D9%pa2C*K--E^ z(yW49+@N*=dA3R!B_#z``ugSN<$C4Ddih1^`i7R4mLM~XjC6r2bc-wVN)jt{^NN*W zCb*;)Cl_TFlw{`TDS%8&Ov*1Uu~h=P6yk;40$*Ra!Fk2dfC2`Yennz|zM-Cher_(v zUtrb6B|)hOXJA!b98y`3svneEoL^d$oC;K~4ATq@JNy=b6armime z%DE^tu_V7JBtJg~mI4AY@=NlIGx7@*oP$jjd=ry1^FVx1^gw*;l3J8mmYU*Ll%J~r z4qvNGEcUxuqWasy(agow$j#8g$jQXi&CpT_syBt4Fw+M*MjwzuQ{b1fL2Z6SURUZCd!Zcl5y%mZqJ}}fa%Y5e8`?TxUDwmViA6!DGe&TTn zU8=#zG-KDc$q6~_2U7l@n$05|9P#slOvKB{b$_k7?Pot8gRVK#%lp2McN z_QW5|4x9J-*!7e$@0or6Im?2ZaccKf98%3@3pS)xpZ@cGYt&oihPbeNp#!t--4_Om zov+y%w>4@l^ZoC)8KmN(_69cW-mS?f>F_J->0^tsWx-$FxH2j-zq&ClvQSfC_K07* z=ym0~Zwnu)Rxi!kTE(%zmubqkD+~8NQQ$Y=RA#a{@bRN(vBef<244T|$3Fitd$2ZA zRKfDa*2Xt4D~uEmFzpGfS@GuGHO7)hPY-JpsZHcs({K%>Wc|Cf!Yj-fFYH-v{@9Sw zuBD4ay*Ze$Aht2b%Zi#OWOsezT@Avq{ikmNESsAL{ zGQ>;wK670z&5$8?;OlzM6A!2SOU~AsYIR|4r22vT&8ej;bod@!j+|D?@ua|NoqIxP z_BE+Pso3}8t90P~ z@1Lp1uiBZ+=Z&2IOq%rtZw}D2%g@KpO1H0PWthc2;cMo;z=phf2M52e=lpi^9VxI| z?fbP0l+XkKOS?mm literal 0 HcmV?d00001 diff --git a/platform-independent/java/gui/src/main/resources/media/icon_settings@2x.png b/platform-independent/java/gui/src/main/resources/media/icon_settings@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..960dc96434f49d5030a7c6bc9cdc62d6c551deef GIT binary patch literal 2552 zcmbVLdpK0<8ecQ6If+8Xi7cbrPR-3^E}Ix;Mzb@BIjB%$W?_uEG_x>-rV?G0OQ%q% zc2{!QT~v>1r;c2*w}`gNJ(Y4v652g3XN~Bb^E^HKkF%cVTi^G7@9+J+m+yTKcze1V z8vNY=002YI2Br_PyJ(iaF7huF?dSjitxyp!01n`K&;>FHjxUsjKsc2|j_3iv(OD(u z3&ao%hCpE=sS|eS_B|{p5;|f1*KrA4xf>KN+7PFJeB(TMf;h2&Cd4|g2OU*(gg^qp zd{8Bclq%^eC+rL_9XV^R@mO#M0*jrn42?rDfa?vq$rKQ{4o6rkAUF^}2O^GWPo_{P zYd{i#NWc?Fcp_;nkwUj8(Fp`__QN9H6v9xt50gFX3z3|#;V>+xv%4F`#|7D234#!6wnYOPdhJIBC;lmdlF4vS<`P{YU%kww8ySR~S~DM;j8?l)qo za<))N$?z(^98bg%@Dhn8uNkxw_JRHr#&^+5UaTC#`#?%rltKU@aN9X!q;`MpNCQNo zp?fJr$fNKhnKD6?1d_rWrV|#?;DjO}o$X3t6NwZufkGlUP>Dnukw9a(G8hChkwsy% zD03X&v!#(pWTw46iN>HZi9{BW=EiWKGMN;6R|c6yabVBca->R_FBL#@enp7iSzF>? z+S1(=5FeH)crsb!TnBiE%V3!@TqXzIeAj_ozCa|^$eQQ*nk^Gjh@v4OTOpHxGxep5 zzT=-w``^5&3E*bBulPB(MnSb>8^RnE6XBmm_Ughoz4O<`9 z`%I@QpnI2}jb6G|4eAx30BCAUmj&RAxa{OD#;&1sPwb#DyY%6y>(=~I{{>xNVEVev zWMkWl*%yB=eUat~h#U8YhK#&C5t8q4FT$CQYRhlftFrl~UUIZHY1q}1C==~E+o;-7 z?6=Kj|IvxU$+z0S(3W~(@UKiqj*n(>#yM%WHaNSnWkK8Q)Qzg&ROe9vRh>UHQZpCK5lkr=*gQclXd&jZRu@=%^MT#g8j|Mem>et@WJM17sC&~- zO*zfK4d>GD_sECx#I@&7E4%ell+()I`ZPyrCQ9wGh z%c!}dAH9;J_FP(x$z+wRDJ?3ZH#NoXiRfb~1!qm=t9!2hxpid3sJONk%ZSQU`-K@% zuR0!4*68dvL?Ahv3_eTZEe`+anSXicJfj>JRBP9m~dzCxRFLIwYH~tMzuNr8)%db(fZ&(7(<)4(*6%tiNhJJR1o>OXA zimG@lV?aEoI(?%RIKr%GuJbSLth(kF+?6}fb?(}FbMVm;Gw))G+SWuaiH;tuYss{J z?3?yRh=%{@MKfcsq1%GGYC6!c;*PL&U-c5NM_W&=AJ;D|xJak<6-}oAjE+m{gIo3L zS3T}4?^mLJzOxDSV;NnZS>HE){J5W_wcdocGS{+fhU_f*#$}w`bEjXHRnk;Qf(S8H zldA+7B z7Il}$1fQk>q3-7W+1=9>`c*k^xcXGYoyh literal 0 HcmV?d00001 diff --git a/platform-independent/java/gui/src/main/resources/media/icon_user.png b/platform-independent/java/gui/src/main/resources/media/icon_user.png new file mode 100644 index 0000000000000000000000000000000000000000..534bb09118a37f315cfb6283c13f3cc1e477a55a GIT binary patch literal 1586 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=hEVFsEgPM3hAM`dB6B=jtV<?;Zqle1Gx6p~WYGxKbf-tXS8q>!0ns}yePYv5bpoSKp8QB{;0T;&&% zT$P<{nWAKGr(jcI1vDTxwIorYA~z?m*s8)-32d$vkPQ;nS5g2gDap1~as*kZ5aAo3 z;GAESs$i;Tpqp%9W}skZsAp(wVs37(qhMrUXrOOkq;F`XYiMp|Y-D9%pa2C*K--E^ z(yW49+@N*=dA3R!B_#z``ugSN<$C4Ddih1^`i7R4mLM~XjC6r2bc-wVN)jt{^NN*W zCb*;)Cl_TFlw{`TDS%8&Ov*1Uu~h=P6yk;40$*Ra!Fk2dfC2`Yennz|zM-Cher_(v zUtrb6B|)hOXJA!b98y`3svneEoL^d$oC;K~4ATq@JNy=b6armi7u7M@JCVPIfN z@^ozb>TAJjU;`Q>awmMZY@UtZYv)@;ot!$hkY zqPMsfiB$>kxBladYh5V2n`76uEbGl?zRBsc(_Y?EsjaM$n-QS8?4n6U<^$%XKmX0E zn`g4W{RkVww*Q^E3I+lf{%rc5s4v2m{I%+D?DETj6WQwGPCYFOe;Kp>Xd0``OrK-@ zmIvhj%`LNKgaC#+cN1b0RlYMu z++g$VQ-MCetDhV^+&W!4m2ub3$NRnS%5N?bv9{=wULUslG_Px8{{7SY7p&0geZ-Qw z?fvw9oHr!fcIuq|B!26;=$<<@na;vi?viKLrEb2d9TomKpQTNx^9V=T*QjHC-ZxZ_ zPv>!rjB?Ii>Gs_>GvvU@&wrX$ttyHXXwd!ipJnY*siR4S|DNAZayf8v(;MNyRgUu> zD~4;#YRbw<(p}}bwU^_Kb~sDw+@}lX2l%eab$Re2>s4^;N$~~qRW8q89df(lz{2S5 z>T>%dt4`OS-FSijUVX$`Umri#jo%WLnM_WeQ%LTRpJ{l~_K8~2G`Z_N92f50ZIyB9 qQ&#saYO=at7q*2z&WDZZ?jHuheO7XU8A1`DqTbWh&t;ucLK6VB$y0U! literal 0 HcmV?d00001 diff --git a/platform-independent/java/gui/src/main/resources/media/icon_user@2x.png b/platform-independent/java/gui/src/main/resources/media/icon_user@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..602ce16e5afc4c63737cf0d01c80a2eefc38ec2d GIT binary patch literal 2352 zcmbVOc~ld39uH}PARZ{7AW)|PT+SpD0wE(10!bt*AcmkQ$P$u)M94uBAW+bVbuEg; zSczZ-Q4~aq$h{RUR8fN$sGwry)CNVZ2Obm!J5h1B@A>-2&hO3quJ7mbz2BSM@UUgG zXD*tFLZN1}gBcuT4lukX#>n@mD5W2nY?RC>WrQ?Ana-2LD7ruz4+Cs5FA?UzJV8cE z2keVNeUTvIMk%90Lm<9XjOQ6-@G7wkL8DN23xb8B;7mCjkr~G2XD0Ef0vydB@Kr$wff!cu0F^jdqJUI>xc9sevNjwOaKL*J zWs)E6gHchT;Xsg74g+3zq6eSoO$59_Jm^WLP$=#Ii3k!2L=picd4LqilLQfoz{dxN zXp;*PAP$4|Q5UlF!wHp28AKqYr>Eo7J@Ha`A_1gQsRj-b$pew_P-IAyJe7w;;V{j> zfE9eXNTw7?C4hmE7cWg!`r!~uKe!;4g@%42mMA_33JDoO#gh?0Jdq$48~l1Ntx$5{ z|C;exX$3b!1`{~2LYgY)Bl$>hm9jIM9F6O;gB1= zKqP=zffN=9QpiLKiRkSEf>e-5r3cdKL^8;vu$Yu-j?ZDKo}NBI6rv}U?!y2?LFcM&KU%vy z**4NLyCh|@l16vdn47q6>OO6Jqpx-_q8q>zxc=h$Ep{OJNC0+sL)A8{NBo&`%%LoA zZjN?EZg51dxk>BJ_J97+By!;F?asB>IBe5-z62aua(x%mGXHT=Zbea&edjGtX@B-e z-`4}#H(wQ8gqw=D@PCc*MgMh#0l@bizOkY~ z+j*PH$TZqJ^*W5te9X|+EN4rIWgj0<&My12qckUYeLgJ|2Z2D%DjM84L z))my9VL74n(cz=1M&};6^h1FtEvY(op*irvnKHlBA#=||C-vQozB!(5O*LW6qV_oDYn=hUi)v86T5=_Z{z z+ryqol%$?<{e;I)y=mwCr>3-8?IhnK_pT<`F=j`OS%=`&kYCHMDVbkpUy&Z#UX-KL zu2E}H_c)c^jRy|wUsC1uZfa_3;OZ1~eE4>b2?L||e=-Wyh}QN#8id#2H)IWuQyUH! z?YR@8Hg+$zwH|-7XK2W!9+RceUs_yWU%$uY--Gq~wdh$Cz+$SRzU=POmF~_3=mE}p zmyL(&$7VDX9j;c3qa*eEy3*3{nnG(@>gYw9TlHji_9+K5vySb;?;H4kD3Yy+-@JOZ z!gbs8ykie%FxfvZtLZ8k@ws8SUEej5@7wu$xN5@%mmg!XkCv#lCs(V_v}HFt9CQTf zcTOI+$Y-tbv5a}wWB2r{E~ZiKRa29=@_|90-?R5M|5(2H=mpD0T^u&#`enGmZo&+M zcE{lE3tyC+uFoIp-5&G^rWNDSGixGh>RQFpMeAgiQ+ z9Ibu)?)9?`G%S0;EKH%svAERQ$R2y%Bw*ER67I#aaaDRHH=a6cpNWj4a~iI-9vkz0 z(16;$6eFKVc0!4rbuZiO=3bL)JIz9zgOjVK(w1kQ9b8Z=6uh*v7{e~nH%8O1anLea z{+zI~&8y~ZzH;oI$Lq07^l$4zQ`)zhYo1!U52=4^q0`O#+b=ca6YJcIFULP!#E*QZ zPnt`+xq=)PQaokjdKek!CxRqO3$a(AVIz7L>eO(#edzzvIRF3v literal 0 HcmV?d00001 diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPSite.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPSite.java index 521821e8..115ad20a 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPSite.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPSite.java @@ -33,7 +33,7 @@ public interface MPSite extends Comparable> { // - Meta @Nonnull - String getName(); + String getSiteName(); // - Algorithm @@ -57,10 +57,34 @@ public interface MPSite extends Comparable> { void setLoginType(@Nullable MPResultType loginType); + default String getResult() + throws MPKeyUnavailableException, MPAlgorithmException { + + return getResult( MPKeyPurpose.Authentication ); + } + + @Nonnull + default String getResult(final MPKeyPurpose keyPurpose) + throws MPKeyUnavailableException, MPAlgorithmException { + return getResult( keyPurpose, null ); + } + + @Nonnull + default String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext) + throws MPKeyUnavailableException, MPAlgorithmException { + return getResult( keyPurpose, keyContext, null ); + } + @Nonnull String getResult(MPKeyPurpose keyPurpose, @Nullable String keyContext, @Nullable String state) throws MPKeyUnavailableException, MPAlgorithmException; + @Nonnull + default String getLogin() + throws MPKeyUnavailableException, MPAlgorithmException { + return getLogin( null ); + } + @Nonnull String getLogin(@Nullable String state) throws MPKeyUnavailableException, MPAlgorithmException; diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java index 0c025269..5da8f400 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java @@ -20,8 +20,6 @@ package com.lyndir.masterpassword.model; import com.google.common.collect.ImmutableCollection; import com.lyndir.masterpassword.*; -import com.lyndir.masterpassword.model.impl.MPBasicSite; -import com.lyndir.masterpassword.model.impl.MPBasicUser; import java.util.Collection; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -87,7 +85,10 @@ public interface MPUser> extends Comparable> { // - Relations - void addSite(S site); + S addSite(String siteName); + + @Nonnull + S addSite(S site); void deleteSite(S site); @@ -95,7 +96,7 @@ public interface MPUser> extends Comparable> { Collection getSites(); @Nonnull - ImmutableCollection findSites(String query); + ImmutableCollection findSites(@Nullable String query); boolean addListener(Listener listener); diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicQuestion.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicQuestion.java index 4285032d..cba4538b 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicQuestion.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicQuestion.java @@ -70,7 +70,7 @@ public abstract class MPBasicQuestion extends Changeable implements MPQuestion { @Nonnull @Override - public abstract MPBasicSite getSite(); + public abstract MPBasicSite getSite(); @Override protected void onChanged() { diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicSite.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicSite.java index e0163fc9..a9aabfb5 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicSite.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicSite.java @@ -23,34 +23,36 @@ import static com.lyndir.lhunath.opal.system.util.StringUtils.*; import com.google.common.primitives.UnsignedInteger; import com.lyndir.masterpassword.*; -import com.lyndir.masterpassword.model.MPQuestion; -import com.lyndir.masterpassword.model.MPSite; +import com.lyndir.masterpassword.model.*; import java.util.*; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.jetbrains.annotations.NotNull; /** * @author lhunath, 14-12-16 */ -public abstract class MPBasicSite extends Changeable implements MPSite { +public abstract class MPBasicSite, Q extends MPQuestion> extends Changeable + implements MPSite { + + private final Collection questions = new LinkedHashSet<>(); + private final U user; + private final String siteName; - private String name; private MPAlgorithm algorithm; private UnsignedInteger counter; private MPResultType resultType; private MPResultType loginType; - private final Collection questions = new LinkedHashSet<>(); - - protected MPBasicSite(final String name, final MPAlgorithm algorithm) { - this( name, algorithm, null, null, null ); + protected MPBasicSite(final U user, final String siteName, final MPAlgorithm algorithm) { + this( user, siteName, algorithm, null, null, null ); } - protected MPBasicSite(final String name, final MPAlgorithm algorithm, @Nullable final UnsignedInteger counter, + protected MPBasicSite(final U user, final String siteName, final MPAlgorithm algorithm, + @Nullable final UnsignedInteger counter, @Nullable final MPResultType resultType, @Nullable final MPResultType loginType) { - this.name = name; + this.user = user; + this.siteName = siteName; this.algorithm = algorithm; this.counter = (counter == null)? algorithm.mpw_default_counter(): counter; this.resultType = (resultType == null)? algorithm.mpw_default_result_type(): resultType; @@ -59,8 +61,8 @@ public abstract class MPBasicSite extends Changeable imple @Nonnull @Override - public String getName() { - return name; + public String getSiteName() { + return siteName; } @Nonnull @@ -128,7 +130,7 @@ public abstract class MPBasicSite extends Changeable imple throws MPKeyUnavailableException, MPAlgorithmException { return getUser().getMasterKey().siteResult( - getName(), getAlgorithm(), ifNotNullElse( counter, getAlgorithm().mpw_default_counter() ), + getSiteName(), getAlgorithm(), ifNotNullElse( counter, getAlgorithm().mpw_default_counter() ), keyPurpose, keyContext, type, state ); } @@ -137,7 +139,7 @@ public abstract class MPBasicSite extends Changeable imple throws MPKeyUnavailableException, MPAlgorithmException { return getUser().getMasterKey().siteState( - getName(), getAlgorithm(), ifNotNullElse( counter, getAlgorithm().mpw_default_counter() ), + getSiteName(), getAlgorithm(), ifNotNullElse( counter, getAlgorithm().mpw_default_counter() ), keyPurpose, keyContext, type, state ); } @@ -171,32 +173,35 @@ public abstract class MPBasicSite extends Changeable imple @Nonnull @Override - public abstract MPBasicUser getUser(); + public U getUser() { + return user; + } @Override protected void onChanged() { super.onChanged(); - getUser().setChanged(); + if (user instanceof Changeable) + ((Changeable) user).setChanged(); } @Override public int hashCode() { - return Objects.hashCode( getName() ); + return Objects.hashCode( getSiteName() ); } @Override public boolean equals(final Object obj) { - return (this == obj) || ((obj instanceof MPSite) && Objects.equals( getName(), ((MPSite) obj).getName() )); + return (this == obj) || ((obj instanceof MPSite) && Objects.equals( getSiteName(), ((MPSite) obj).getSiteName() )); } @Override - public int compareTo(@NotNull final MPSite o) { - return getName().compareTo( o.getName() ); + public int compareTo(@Nonnull final MPSite o) { + return getSiteName().compareTo( o.getSiteName() ); } @Override public String toString() { - return strf( "{%s: %s}", getClass().getSimpleName(), getName() ); + return strf( "{%s: %s}", getClass().getSimpleName(), getSiteName() ); } } diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java index 97c14b07..1e153bfa 100755 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java @@ -36,7 +36,7 @@ import javax.annotation.Nullable; /** * @author lhunath, 2014-06-08 */ -public abstract class MPBasicUser> extends Changeable implements MPUser { +public abstract class MPBasicUser> extends Changeable implements MPUser { protected final Logger logger = Logger.get( getClass() ); private final Set listeners = new CopyOnWriteArraySet<>(); @@ -152,10 +152,11 @@ public abstract class MPBasicUser> extends Changeable i } @Override - public void addSite(final S site) { - sites.put( site.getName(), site ); + public S addSite(final S site) { + sites.put( site.getSiteName(), site ); setChanged(); + return site; } @Override @@ -173,11 +174,12 @@ public abstract class MPBasicUser> extends Changeable i @Nonnull @Override - public ImmutableCollection findSites(final String query) { + public ImmutableCollection findSites(@Nullable final String query) { ImmutableSortedSet.Builder results = ImmutableSortedSet.naturalOrder(); - for (final S site : getSites()) - if (site.getName().startsWith( query )) - results.add( site ); + if (query != null) + for (final S site : getSites()) + if (site.getSiteName().startsWith( query )) + results.add( site ); return results.build(); } @@ -211,7 +213,7 @@ public abstract class MPBasicUser> extends Changeable i } @Override - public int compareTo(final MPUser o) { + public int compareTo(@Nonnull final MPUser o) { return getFullName().compareTo( o.getFullName() ); } 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 6c12c0dc..74ed7bad 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 @@ -31,9 +31,7 @@ import org.joda.time.ReadableInstant; * @author lhunath, 14-12-05 */ @SuppressWarnings("ComparableImplementedButEqualsNotOverridden") -public class MPFileSite extends MPBasicSite { - - private final MPFileUser user; +public class MPFileSite extends MPBasicSite { @Nullable private String url; @@ -61,10 +59,9 @@ public class MPFileSite extends MPBasicSite { @Nullable final MPResultType resultType, @Nullable final String resultState, @Nullable final MPResultType loginType, @Nullable final String loginState, @Nullable final String url, final int uses, final ReadableInstant lastUsed) { - super( name, (algorithm == null)? user.getAlgorithm(): algorithm, counter, + super( user, name, (algorithm == null)? user.getAlgorithm(): algorithm, counter, (resultType == null)? user.getDefaultType(): resultType, loginType ); - this.user = user; this.resultState = resultState; this.loginState = loginState; this.url = url; @@ -94,23 +91,21 @@ public class MPFileSite extends MPBasicSite { public void use() { uses++; lastUsed = new Instant(); - user.use(); + getUser().use(); setChanged(); } - public String getResult() - throws MPKeyUnavailableException, MPAlgorithmException { - - return getResult( MPKeyPurpose.Authentication, null ); - } - + @Nonnull + @Override public String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext) throws MPKeyUnavailableException, MPAlgorithmException { return getResult( keyPurpose, keyContext, getResultState() ); } + @Nonnull + @Override public String getLogin() throws MPKeyUnavailableException, MPAlgorithmException { @@ -153,14 +148,8 @@ public class MPFileSite extends MPBasicSite { setChanged(); } - @Nonnull @Override - public MPFileUser getUser() { - return user; - } - - @Override - public int compareTo(final MPSite o) { + public int compareTo(@Nonnull final MPSite o) { int comparison = (o instanceof MPFileSite)? ((MPFileSite) o).getLastUsed().compareTo( getLastUsed() ): 0; if (comparison != 0) return comparison; diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUser.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUser.java index fd826e99..f3c8d7f9 100755 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUser.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUser.java @@ -23,6 +23,7 @@ import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException; import com.lyndir.masterpassword.model.MPUser; import java.io.File; import java.io.IOException; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.joda.time.Instant; import org.joda.time.ReadableInstant; @@ -163,6 +164,11 @@ public class MPFileUser extends MPBasicUser { } } + @Override + public MPFileSite addSite(final String siteName) { + return addSite( new MPFileSite( this, siteName ) ); + } + @Override protected void onChanged() { try { @@ -180,7 +186,7 @@ public class MPFileUser extends MPBasicUser { } @Override - public int compareTo(final MPUser o) { + public int compareTo(@Nonnull final MPUser o) { int comparison = (o instanceof MPFileUser)? ((MPFileUser) o).getLastUsed().compareTo( getLastUsed() ): 0; if (comparison != 0) return comparison; diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFlatMarshaller.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFlatMarshaller.java index 3e5265af..4e92aa56 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFlatMarshaller.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFlatMarshaller.java @@ -79,7 +79,7 @@ public class MPFlatMarshaller implements MPMarshaller { site.getAlgorithm().version().toInt(), // algorithm site.getCounter().intValue() ), // counter ifNotNullElse( loginName, "" ), // loginName - site.getName(), // siteName + site.getSiteName(), // siteName ifNotNullElse( password, "" ) // password ) ); } diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPJSONFile.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPJSONFile.java index acbea3e4..29b585f3 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPJSONFile.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPJSONFile.java @@ -90,7 +90,7 @@ public class MPJSONFile extends MPJSONAnyObject { // Clear Text content = modelSite.getResult(); loginContent = modelUser.getMasterKey().siteResult( - modelSite.getName(), modelSite.getAlgorithm(), modelSite.getAlgorithm().mpw_default_counter(), + modelSite.getSiteName(), modelSite.getAlgorithm(), modelSite.getAlgorithm().mpw_default_counter(), MPKeyPurpose.Identification, null, modelSite.getLoginType(), modelSite.getLoginState() ); } else { // Redacted @@ -100,9 +100,9 @@ public class MPJSONFile extends MPJSONAnyObject { loginContent = modelSite.getLoginState(); } - Site site = sites.get( modelSite.getName() ); + Site site = sites.get( modelSite.getSiteName() ); if (site == null) - sites.put( modelSite.getName(), site = new Site() ); + sites.put( modelSite.getSiteName(), site = new Site() ); site.type = modelSite.getResultType(); site.counter = modelSite.getCounter().longValue(); site.algorithm = modelSite.getAlgorithm().version();