Reorganize core source and add Docker support to CLI.
@@ -0,0 +1,40 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||
import com.lyndir.masterpassword.model.MPConstant;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-08-31
|
||||
*/
|
||||
@SuppressWarnings("CallToSystemGetenv")
|
||||
public class Config {
|
||||
|
||||
private static final Config instance = new Config();
|
||||
|
||||
public static Config get() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public boolean checkForUpdates() {
|
||||
return ConversionUtils.toBoolean( System.getenv( MPConstant.env_checkUpdates ) ).orElse( true );
|
||||
}
|
||||
}
|
@@ -0,0 +1,128 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.io.CharSource;
|
||||
import com.google.common.io.Resources;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.TypeUtils;
|
||||
import com.lyndir.masterpassword.gui.view.PasswordFrame;
|
||||
import com.lyndir.masterpassword.gui.view.UnlockFrame;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Optional;
|
||||
import java.util.jar.*;
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
/**
|
||||
* <p> <i>Jun 10, 2008</i> </p>
|
||||
*
|
||||
* @author mbillemo
|
||||
*/
|
||||
public class GUI implements UnlockFrame.SignInCallback {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( GUI.class );
|
||||
|
||||
private final UnlockFrame unlockFrame = new UnlockFrame( this );
|
||||
private PasswordFrame<?, ?> passwordFrame;
|
||||
|
||||
public static void main(final String... args) {
|
||||
if (Config.get().checkForUpdates())
|
||||
checkUpdate();
|
||||
|
||||
// Try and set the system look & feel, if available.
|
||||
try {
|
||||
UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
|
||||
}
|
||||
catch (final UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) {
|
||||
}
|
||||
|
||||
try {
|
||||
// AppleGUI adds support for macOS features.
|
||||
Optional<Class<GUI>> appleGUI = TypeUtils.loadClass( "com.lyndir.masterpassword.gui.platform.mac.AppleGUI" );
|
||||
if (appleGUI.isPresent())
|
||||
appleGUI.get().getConstructor().newInstance().open();
|
||||
|
||||
else // No special platform handling.
|
||||
new GUI().open();
|
||||
}
|
||||
catch (final IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
|
||||
throw logger.bug( e );
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkUpdate() {
|
||||
try {
|
||||
Enumeration<URL> manifestURLs = Thread.currentThread().getContextClassLoader().getResources( JarFile.MANIFEST_NAME );
|
||||
while (manifestURLs.hasMoreElements())
|
||||
try (InputStream manifestStream = manifestURLs.nextElement().openStream()) {
|
||||
Attributes attributes = new Manifest( manifestStream ).getMainAttributes();
|
||||
if (!GUI.class.getCanonicalName().equals( attributes.getValue( Attributes.Name.MAIN_CLASS ) ))
|
||||
continue;
|
||||
|
||||
String manifestRevision = attributes.getValue( Attributes.Name.IMPLEMENTATION_VERSION );
|
||||
String upstreamRevisionURL = "https://masterpassword.app/masterpassword-gui.jar.rev";
|
||||
CharSource upstream = Resources.asCharSource( URI.create( upstreamRevisionURL ).toURL(), Charsets.UTF_8 );
|
||||
String upstreamRevision = upstream.readFirstLine();
|
||||
if ((manifestRevision != null) && (upstreamRevision != null) && !manifestRevision.equalsIgnoreCase(
|
||||
upstreamRevision )) {
|
||||
logger.inf( "Local Revision: <%s>", manifestRevision );
|
||||
logger.inf( "Upstream Revision: <%s>", upstreamRevision );
|
||||
logger.wrn( "You are not running the current official version. Please update from:%n%s",
|
||||
"https://masterpassword.app/masterpassword-gui.jar" );
|
||||
JOptionPane.showMessageDialog( null,
|
||||
strf( "A new version of Master Password is available.%n "
|
||||
+ "Please download the latest version from %s",
|
||||
"https://masterpassword.app" ),
|
||||
"Update Available", JOptionPane.WARNING_MESSAGE );
|
||||
}
|
||||
}
|
||||
catch (final IOException e) {
|
||||
logger.wrn( e, "Couldn't check for version update." );
|
||||
}
|
||||
}
|
||||
catch (final IOException e) {
|
||||
logger.wrn( e, "Couldn't inspect JAR." );
|
||||
}
|
||||
}
|
||||
|
||||
protected void open() {
|
||||
SwingUtilities.invokeLater( () -> {
|
||||
if (passwordFrame == null)
|
||||
unlockFrame.setVisible( true );
|
||||
else
|
||||
passwordFrame.setVisible( true );
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void signedIn(final PasswordFrame<?, ?> passwordFrame) {
|
||||
this.passwordFrame = passwordFrame;
|
||||
open();
|
||||
}
|
||||
}
|
@@ -0,0 +1,359 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.io.Resources;
|
||||
import com.google.common.util.concurrent.JdkFutureAdapters;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.masterpassword.MPIdenticon;
|
||||
import java.awt.*;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.image.ImageObserver;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.swing.*;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-11
|
||||
*/
|
||||
@SuppressWarnings({ "HardcodedFileSeparator", "MethodReturnAlwaysConstant", "SpellCheckingInspection" })
|
||||
public abstract class Res {
|
||||
|
||||
private static final int AVATAR_COUNT = 19;
|
||||
private static final Map<Window, ScheduledExecutorService> executorByWindow = new WeakHashMap<>();
|
||||
private static final Logger logger = Logger.get( Res.class );
|
||||
private static final Colors colors = new Colors();
|
||||
|
||||
public static Future<?> execute(final Window host, final Runnable job) {
|
||||
return schedule( host, job, 0, TimeUnit.MILLISECONDS );
|
||||
}
|
||||
|
||||
public static Future<?> schedule(final Window host, final Runnable job, final long delay, final TimeUnit timeUnit) {
|
||||
return getExecutor( host ).schedule( () -> {
|
||||
try {
|
||||
job.run();
|
||||
}
|
||||
catch (final Throwable t) {
|
||||
logger.err( t, "Unexpected: %s", t.getLocalizedMessage() );
|
||||
}
|
||||
}, delay, timeUnit );
|
||||
}
|
||||
|
||||
public static <V> ListenableFuture<V> execute(final Window host, final Callable<V> job) {
|
||||
return schedule( host, job, 0, TimeUnit.MILLISECONDS );
|
||||
}
|
||||
|
||||
public static <V> ListenableFuture<V> schedule(final Window host, final Callable<V> job, final long delay, final TimeUnit timeUnit) {
|
||||
ScheduledExecutorService executor = getExecutor( host );
|
||||
return JdkFutureAdapters.listenInPoolThread( executor.schedule( () -> {
|
||||
try {
|
||||
return job.call();
|
||||
}
|
||||
catch (final Throwable t) {
|
||||
logger.err( t, "Unexpected: %s", t.getLocalizedMessage() );
|
||||
throw Throwables.propagate( t );
|
||||
}
|
||||
}, delay, timeUnit ), executor );
|
||||
}
|
||||
|
||||
private static ScheduledExecutorService getExecutor(final Window host) {
|
||||
ScheduledExecutorService executor = executorByWindow.get( host );
|
||||
|
||||
if (executor == null) {
|
||||
executorByWindow.put( host, executor = Executors.newSingleThreadScheduledExecutor() );
|
||||
|
||||
host.addWindowListener( new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosed(final WindowEvent e) {
|
||||
ExecutorService executor = executorByWindow.remove( host );
|
||||
if (executor != null)
|
||||
executor.shutdownNow();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
return executor;
|
||||
}
|
||||
|
||||
public static Icon iconAdd() {
|
||||
return new RetinaIcon( Resources.getResource( "media/icon_add@2x.png" ) );
|
||||
}
|
||||
|
||||
public static Icon iconDelete() {
|
||||
return new RetinaIcon( Resources.getResource( "media/icon_delete@2x.png" ) );
|
||||
}
|
||||
|
||||
public static Icon iconQuestion() {
|
||||
return new RetinaIcon( Resources.getResource( "media/icon_question@2x.png" ) );
|
||||
}
|
||||
|
||||
public static Icon avatar(final int index) {
|
||||
return new RetinaIcon( Resources.getResource( strf( "media/avatar-%d@2x.png", index % avatars() ) ) );
|
||||
}
|
||||
|
||||
public static int avatars() {
|
||||
return AVATAR_COUNT;
|
||||
}
|
||||
|
||||
public static Font emoticonsFont() {
|
||||
return emoticonsRegular();
|
||||
}
|
||||
|
||||
public static Font controlFont() {
|
||||
return arimoRegular();
|
||||
}
|
||||
|
||||
public static Font valueFont() {
|
||||
return sourceSansProRegular();
|
||||
}
|
||||
|
||||
public static Font bigValueFont() {
|
||||
return sourceSansProBlack();
|
||||
}
|
||||
|
||||
public static Font emoticonsRegular() {
|
||||
return font( "fonts/Emoticons-Regular.otf" );
|
||||
}
|
||||
|
||||
public static Font sourceCodeProRegular() {
|
||||
return font( "fonts/SourceCodePro-Regular.otf" );
|
||||
}
|
||||
|
||||
public static Font sourceCodeProBlack() {
|
||||
return font( "fonts/SourceCodePro-Bold.otf" );
|
||||
}
|
||||
|
||||
public static Font sourceSansProRegular() {
|
||||
return font( "fonts/SourceSansPro-Regular.otf" );
|
||||
}
|
||||
|
||||
public static Font sourceSansProBlack() {
|
||||
return font( "fonts/SourceSansPro-Bold.otf" );
|
||||
}
|
||||
|
||||
public static Font exoBold() {
|
||||
return font( "fonts/Exo2.0-Bold.otf" );
|
||||
}
|
||||
|
||||
public static Font exoExtraBold() {
|
||||
return font( "fonts/Exo2.0-ExtraBold.otf" );
|
||||
}
|
||||
|
||||
public static Font exoRegular() {
|
||||
return font( "fonts/Exo2.0-Regular.otf" );
|
||||
}
|
||||
|
||||
public static Font exoThin() {
|
||||
return font( "fonts/Exo2.0-Thin.otf" );
|
||||
}
|
||||
|
||||
public static Font arimoBold() {
|
||||
return font( "fonts/Arimo-Bold.ttf" );
|
||||
}
|
||||
|
||||
public static Font arimoBoldItalic() {
|
||||
return font( "fonts/Arimo-BoldItalic.ttf" );
|
||||
}
|
||||
|
||||
public static Font arimoItalic() {
|
||||
return font( "fonts/Arimo-Italic.ttf" );
|
||||
}
|
||||
|
||||
public static Font arimoRegular() {
|
||||
return font( "fonts/Arimo-Regular.ttf" );
|
||||
}
|
||||
|
||||
private static Font font(@NonNls final String fontResourceName) {
|
||||
Map<String, SoftReference<Font>> fontsByResourceName = Maps.newHashMap();
|
||||
SoftReference<Font> fontRef = fontsByResourceName.get( fontResourceName );
|
||||
Font font = (fontRef == null)? null: fontRef.get();
|
||||
if (font == null)
|
||||
try {
|
||||
fontsByResourceName.put( fontResourceName, new SoftReference<>(
|
||||
font = Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( fontResourceName ).openStream() ) ) );
|
||||
}
|
||||
catch (final FontFormatException | IOException e) {
|
||||
throw Throwables.propagate( e );
|
||||
}
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
public static Colors colors() {
|
||||
return colors;
|
||||
}
|
||||
|
||||
private static final class RetinaIcon extends ImageIcon {
|
||||
|
||||
private static final Pattern scalePattern = Pattern.compile( ".*@(\\d+)x.[^.]+$" );
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final float scale;
|
||||
|
||||
private RetinaIcon(final URL url) {
|
||||
super( url );
|
||||
|
||||
Matcher scaleMatcher = scalePattern.matcher( url.getPath() );
|
||||
scale = scaleMatcher.matches()? Float.parseFloat( scaleMatcher.group( 1 ) ): 1;
|
||||
}
|
||||
|
||||
//private static URL retinaURL(final URL url) {
|
||||
// try {
|
||||
// final boolean[] isRetina = new boolean[1];
|
||||
// new apple.awt.CImage.HiDPIScaledImage(1,1, BufferedImage.TYPE_INT_ARGB) {
|
||||
// @Override
|
||||
// public void drawIntoImage(BufferedImage image, float v) {
|
||||
// isRetina[0] = v > 1;
|
||||
// }
|
||||
// };
|
||||
// return isRetina[0];
|
||||
// } catch (Throwable e) {
|
||||
// e.printStackTrace();
|
||||
// return url;
|
||||
// }
|
||||
//}
|
||||
|
||||
@Override
|
||||
public int getIconWidth() {
|
||||
return (int) (super.getIconWidth() / scale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconHeight() {
|
||||
return (int) (super.getIconHeight() / scale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void paintIcon(final Component c, final Graphics g, final int x, final int y) {
|
||||
ImageObserver observer = ifNotNullElse( getImageObserver(), c );
|
||||
|
||||
Image image = getImage();
|
||||
int width = image.getWidth( observer );
|
||||
int height = image.getHeight( observer );
|
||||
Graphics2D g2d = (Graphics2D) g.create( x, y, width, height );
|
||||
|
||||
g2d.scale( 1 / scale, 1 / scale );
|
||||
g2d.drawImage( image, 0, 0, observer );
|
||||
g2d.scale( 1, 1 );
|
||||
g2d.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Colors {
|
||||
|
||||
private final Color frameBg = Color.decode( "#5A5D6B" );
|
||||
private final Color controlBg = Color.decode( "#ECECEC" );
|
||||
private final Color controlBorder = Color.decode( "#BFBFBF" );
|
||||
|
||||
public Color frameBg() {
|
||||
return frameBg;
|
||||
}
|
||||
|
||||
public Color controlBg() {
|
||||
return controlBg;
|
||||
}
|
||||
|
||||
public Color controlBorder() {
|
||||
return controlBorder;
|
||||
}
|
||||
|
||||
public Color fromIdenticonColor(final MPIdenticon.Color identiconColor, final BackgroundMode backgroundMode) {
|
||||
switch (identiconColor) {
|
||||
case RED:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#dc322f" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#dc322f" );
|
||||
}
|
||||
break;
|
||||
case GREEN:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#859900" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#859900" );
|
||||
}
|
||||
break;
|
||||
case YELLOW:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#b58900" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#b58900" );
|
||||
}
|
||||
break;
|
||||
case BLUE:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#268bd2" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#268bd2" );
|
||||
}
|
||||
break;
|
||||
case MAGENTA:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#d33682" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#d33682" );
|
||||
}
|
||||
break;
|
||||
case CYAN:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#2aa198" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#2aa198" );
|
||||
}
|
||||
break;
|
||||
case MONO:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#93a1a1" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#586e75" );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException( strf( "Color: %s or mode: %s not supported: ", identiconColor, backgroundMode ) );
|
||||
}
|
||||
|
||||
public enum BackgroundMode {
|
||||
DARK, LIGHT
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.gui.model;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
|
||||
import com.lyndir.masterpassword.MPResultType;
|
||||
import com.lyndir.masterpassword.model.impl.MPBasicQuestion;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-05-16
|
||||
*/
|
||||
public class MPIncognitoQuestion extends MPBasicQuestion {
|
||||
|
||||
private final MPIncognitoSite site;
|
||||
|
||||
public MPIncognitoQuestion(final MPIncognitoSite site, final String keyword, @Nullable final MPResultType type) {
|
||||
super( keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) );
|
||||
|
||||
this.site = site;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPIncognitoSite getSite() {
|
||||
return site;
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.gui.model;
|
||||
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.masterpassword.MPAlgorithm;
|
||||
import com.lyndir.masterpassword.MPResultType;
|
||||
import com.lyndir.masterpassword.model.impl.MPBasicSite;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-16
|
||||
*/
|
||||
public class MPIncognitoSite extends MPBasicSite<MPIncognitoQuestion> {
|
||||
|
||||
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 name,
|
||||
@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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MPIncognitoUser getUser() {
|
||||
return user;
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.gui.model;
|
||||
|
||||
import com.lyndir.masterpassword.MPAlgorithm;
|
||||
import com.lyndir.masterpassword.model.impl.MPBasicUser;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-08
|
||||
*/
|
||||
public class MPIncognitoUser extends MPBasicUser<MPIncognitoSite> {
|
||||
|
||||
public MPIncognitoUser(final String fullName) {
|
||||
super( fullName, MPAlgorithm.Version.CURRENT.getAlgorithm() );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public byte[] getKeyID() {
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
/**
|
||||
* @author lhunath, 15-02-04
|
||||
*/
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
package com.lyndir.masterpassword.gui.model;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
@@ -0,0 +1,26 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
/**
|
||||
* @author lhunath, 15-02-04
|
||||
*/
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
@@ -0,0 +1,51 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.gui.platform.mac;
|
||||
|
||||
import com.apple.eawt.*;
|
||||
import com.lyndir.masterpassword.gui.GUI;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-10
|
||||
*/
|
||||
public class AppleGUI extends GUI {
|
||||
|
||||
public AppleGUI() {
|
||||
|
||||
Application application = Application.getApplication();
|
||||
application.addAppEventListener( new AppForegroundListener() {
|
||||
|
||||
@Override
|
||||
public void appMovedToBackground(final AppEvent.AppForegroundEvent arg0) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appRaisedToForeground(final AppEvent.AppForegroundEvent arg0) {
|
||||
open();
|
||||
}
|
||||
} );
|
||||
application.addAppEventListener( new AppReOpenedListener() {
|
||||
@Override
|
||||
public void appReOpened(final AppEvent.AppReOpenedEvent arg0) {
|
||||
open();
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-04-26
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
package com.lyndir.masterpassword.gui.platform.mac;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
@@ -0,0 +1,257 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.gui.util;
|
||||
|
||||
import com.lyndir.masterpassword.gui.Res;
|
||||
import java.awt.*;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.border.CompoundBorder;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-08
|
||||
*/
|
||||
public abstract class Components {
|
||||
|
||||
private static final float CONTROL_TEXT_SIZE = 12f;
|
||||
|
||||
public static GradientPanel boxLayout(final int axis, final Component... components) {
|
||||
GradientPanel container = gradientPanel( null, null );
|
||||
// container.setBackground( Color.red );
|
||||
container.setLayout( new BoxLayout( container, axis ) );
|
||||
for (final Component component : components)
|
||||
container.add( component );
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
public static GradientPanel borderPanel(final JComponent component, @Nullable final Border border) {
|
||||
return borderPanel( component, border, null );
|
||||
}
|
||||
|
||||
public static GradientPanel borderPanel(final JComponent component, @Nullable final Border border, @Nullable final Color background) {
|
||||
GradientPanel box = boxLayout( BoxLayout.LINE_AXIS, component );
|
||||
|
||||
if (border != null)
|
||||
box.setBorder( border );
|
||||
|
||||
if (background != null)
|
||||
box.setBackground( background );
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
public static GradientPanel gradientPanel(@Nullable final LayoutManager layout, @Nullable final Color color) {
|
||||
return new GradientPanel( layout, color ) {
|
||||
{
|
||||
setOpaque( color != null );
|
||||
setBackground( null );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static JTextField textField() {
|
||||
return new JTextField() {
|
||||
{
|
||||
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
||||
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
|
||||
setFont( Res.valueFont().deriveFont( CONTROL_TEXT_SIZE ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static JPasswordField passwordField() {
|
||||
return new JPasswordField() {
|
||||
{
|
||||
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
||||
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static JButton button(final String label) {
|
||||
return new JButton( label ) {
|
||||
{
|
||||
setFont( Res.controlFont().deriveFont( CONTROL_TEXT_SIZE ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( 20, getPreferredSize().height );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Component stud() {
|
||||
Dimension studDimension = new Dimension( 8, 8 );
|
||||
Box.Filler rigidArea = new Box.Filler( studDimension, studDimension, studDimension );
|
||||
rigidArea.setAlignmentX( Component.LEFT_ALIGNMENT );
|
||||
rigidArea.setAlignmentY( Component.BOTTOM_ALIGNMENT );
|
||||
rigidArea.setBackground( Color.red );
|
||||
return rigidArea;
|
||||
}
|
||||
|
||||
public static JSpinner spinner(final SpinnerModel model) {
|
||||
return new JSpinner( model ) {
|
||||
{
|
||||
CompoundBorder editorBorder = BorderFactory.createCompoundBorder(
|
||||
BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
||||
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) );
|
||||
((DefaultEditor) getEditor()).getTextField().setBorder( editorBorder );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
setBorder( null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( 20, getPreferredSize().height );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static JLabel label(@Nullable final String label) {
|
||||
return label( label, SwingConstants.LEADING );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param horizontalAlignment One of the following constants
|
||||
* defined in {@code SwingConstants}:
|
||||
* {@code LEFT},
|
||||
* {@code CENTER},
|
||||
* {@code RIGHT},
|
||||
* {@code LEADING} or
|
||||
* {@code TRAILING}.
|
||||
*/
|
||||
public static JLabel label(@Nullable final String label, final int horizontalAlignment) {
|
||||
return new JLabel( label, horizontalAlignment ) {
|
||||
{
|
||||
setFont( Res.controlFont().deriveFont( CONTROL_TEXT_SIZE ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static JCheckBox checkBox(final String label) {
|
||||
return new JCheckBox( label ) {
|
||||
{
|
||||
setFont( Res.controlFont().deriveFont( CONTROL_TEXT_SIZE ) );
|
||||
setBackground( null );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <V> JComboBox<V> comboBox(final V... values) {
|
||||
return comboBox( new DefaultComboBoxModel<>( values ) );
|
||||
}
|
||||
|
||||
public static <M> JComboBox<M> comboBox(final ComboBoxModel<M> model) {
|
||||
return new JComboBox<M>( model ) {
|
||||
{
|
||||
// CompoundBorder editorBorder = BorderFactory.createCompoundBorder(
|
||||
// BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
||||
// BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) );
|
||||
// ((JComponent) ((BasicComboBoxEditor) getEditor()).getEditorComponent()).setBorder(editorBorder);
|
||||
setFont( Res.controlFont().deriveFont( CONTROL_TEXT_SIZE ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
// setBorder(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static class GradientPanel extends JPanel {
|
||||
|
||||
@Nullable
|
||||
private Color gradientColor;
|
||||
|
||||
@Nullable
|
||||
private GradientPaint paint;
|
||||
|
||||
protected GradientPanel(@Nullable final LayoutManager layout, @Nullable final Color gradientColor) {
|
||||
super( layout );
|
||||
this.gradientColor = gradientColor;
|
||||
setBackground( null );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Color getGradientColor() {
|
||||
return gradientColor;
|
||||
}
|
||||
|
||||
public void setGradientColor(@Nullable final Color gradientColor) {
|
||||
this.gradientColor = gradientColor;
|
||||
revalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doLayout() {
|
||||
super.doLayout();
|
||||
|
||||
if (gradientColor != null) {
|
||||
paint = new GradientPaint( new Point( 0, 0 ), gradientColor, new Point( getWidth(), getHeight() ), gradientColor.darker() );
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(final Graphics g) {
|
||||
super.paintComponent( g );
|
||||
|
||||
if (paint != null) {
|
||||
((Graphics2D) g).setPaint( paint );
|
||||
g.fillRect( 0, 0, getWidth(), getHeight() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.gui.util;
|
||||
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2016-10-29
|
||||
*/
|
||||
public class UnsignedIntegerModel extends SpinnerNumberModel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public UnsignedIntegerModel() {
|
||||
this( UnsignedInteger.ZERO, UnsignedInteger.ZERO, UnsignedInteger.MAX_VALUE, UnsignedInteger.ONE );
|
||||
}
|
||||
|
||||
public UnsignedIntegerModel(final UnsignedInteger value) {
|
||||
this( value, UnsignedInteger.ZERO, UnsignedInteger.MAX_VALUE, UnsignedInteger.ONE );
|
||||
}
|
||||
|
||||
public UnsignedIntegerModel(final UnsignedInteger value, final UnsignedInteger minimum) {
|
||||
this( value, minimum, UnsignedInteger.MAX_VALUE, UnsignedInteger.ONE );
|
||||
}
|
||||
|
||||
public UnsignedIntegerModel(final UnsignedInteger value, final UnsignedInteger minimum, final UnsignedInteger maximum) {
|
||||
this( value, minimum, maximum, UnsignedInteger.ONE );
|
||||
}
|
||||
|
||||
@SuppressWarnings("TypeMayBeWeakened")
|
||||
public UnsignedIntegerModel(final UnsignedInteger value, final UnsignedInteger minimum, final UnsignedInteger maximum,
|
||||
final UnsignedInteger stepSize) {
|
||||
super( value, minimum, maximum, stepSize );
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnsignedInteger getNumber() {
|
||||
return (UnsignedInteger) super.getNumber();
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
/**
|
||||
* @author lhunath, 15-02-04
|
||||
*/
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
package com.lyndir.masterpassword.gui.util;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
@@ -0,0 +1,84 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.gui.view;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.lyndir.masterpassword.gui.Res;
|
||||
import com.lyndir.masterpassword.gui.util.Components;
|
||||
import com.lyndir.masterpassword.model.MPUser;
|
||||
import java.awt.*;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-11
|
||||
*/
|
||||
public abstract class AuthenticationPanel<U extends MPUser<?>> extends Components.GradientPanel {
|
||||
|
||||
protected final UnlockFrame unlockFrame;
|
||||
protected final JLabel avatarLabel;
|
||||
|
||||
protected AuthenticationPanel(final UnlockFrame unlockFrame) {
|
||||
super( null, null );
|
||||
this.unlockFrame = unlockFrame;
|
||||
|
||||
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
|
||||
|
||||
// Avatar
|
||||
add( Box.createVerticalGlue() );
|
||||
add( avatarLabel = new JLabel( Res.avatar( 0 ) ) {
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
|
||||
}
|
||||
} );
|
||||
add( Box.createVerticalGlue() );
|
||||
|
||||
avatarLabel.setToolTipText( "The avatar for your user. Click to change it." );
|
||||
}
|
||||
|
||||
protected void updateUser(final boolean repack) {
|
||||
unlockFrame.updateUser( getSelectedUser() );
|
||||
validate();
|
||||
|
||||
if (repack)
|
||||
unlockFrame.repack();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected abstract U getSelectedUser();
|
||||
|
||||
@Nonnull
|
||||
public abstract char[] getMasterPassword();
|
||||
|
||||
@Nullable
|
||||
public Component getFocusComponent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Iterable<? extends JButton> getButtons() {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
public abstract void reset();
|
||||
|
||||
public abstract PasswordFrame<?, ?> newPasswordFrame();
|
||||
}
|
@@ -0,0 +1,127 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.gui.view;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.masterpassword.MPAlgorithm;
|
||||
import com.lyndir.masterpassword.MPResultType;
|
||||
import com.lyndir.masterpassword.gui.Res;
|
||||
import com.lyndir.masterpassword.gui.model.MPIncognitoSite;
|
||||
import com.lyndir.masterpassword.gui.model.MPIncognitoUser;
|
||||
import com.lyndir.masterpassword.gui.util.Components;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-11
|
||||
*/
|
||||
@SuppressWarnings({ "serial", "MagicNumber" })
|
||||
public class IncognitoAuthenticationPanel extends AuthenticationPanel<MPIncognitoUser> implements DocumentListener, ActionListener {
|
||||
|
||||
private final JTextField fullNameField;
|
||||
private final JPasswordField masterPasswordField;
|
||||
|
||||
public IncognitoAuthenticationPanel(final UnlockFrame unlockFrame) {
|
||||
|
||||
// Full Name
|
||||
super( unlockFrame );
|
||||
add( Components.stud() );
|
||||
|
||||
JLabel fullNameLabel = Components.label( "Full Name:" );
|
||||
add( fullNameLabel );
|
||||
|
||||
fullNameField = Components.textField();
|
||||
fullNameField.setFont( Res.valueFont().deriveFont( 12f ) );
|
||||
fullNameField.getDocument().addDocumentListener( this );
|
||||
fullNameField.addActionListener( this );
|
||||
add( fullNameField );
|
||||
add( Components.stud() );
|
||||
|
||||
// Master Password
|
||||
JLabel masterPasswordLabel = Components.label( "Master Password:" );
|
||||
add( masterPasswordLabel );
|
||||
|
||||
masterPasswordField = Components.passwordField();
|
||||
masterPasswordField.addActionListener( this );
|
||||
masterPasswordField.getDocument().addDocumentListener( this );
|
||||
add( masterPasswordField );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getFocusComponent() {
|
||||
return fullNameField;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
masterPasswordField.setText( "" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public PasswordFrame<MPIncognitoUser, ?> newPasswordFrame() {
|
||||
return new PasswordFrame<MPIncognitoUser, MPIncognitoSite>( Preconditions.checkNotNull( getSelectedUser() ) ) {
|
||||
@Override
|
||||
protected MPIncognitoSite createSite(final MPIncognitoUser user, final String siteName, final UnsignedInteger siteCounter,
|
||||
final MPResultType resultType, final MPAlgorithm algorithm) {
|
||||
return new MPIncognitoSite( user, siteName, algorithm, siteCounter, resultType, null );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected MPIncognitoUser getSelectedUser() {
|
||||
return new MPIncognitoUser( fullNameField.getText() );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public char[] getMasterPassword() {
|
||||
return masterPasswordField.getPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(final DocumentEvent e) {
|
||||
updateUser( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(final DocumentEvent e) {
|
||||
updateUser( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(final DocumentEvent e) {
|
||||
updateUser( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
updateUser( false );
|
||||
unlockFrame.trySignIn( fullNameField, masterPasswordField );
|
||||
}
|
||||
}
|
@@ -0,0 +1,248 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.gui.view;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.masterpassword.MPAlgorithm;
|
||||
import com.lyndir.masterpassword.MPResultType;
|
||||
import com.lyndir.masterpassword.gui.Res;
|
||||
import com.lyndir.masterpassword.gui.util.Components;
|
||||
import com.lyndir.masterpassword.model.MPUser;
|
||||
import com.lyndir.masterpassword.model.impl.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.plaf.metal.MetalComboBoxEditor;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-11
|
||||
*/
|
||||
public class ModelAuthenticationPanel extends AuthenticationPanel<MPFileUser> implements ItemListener, ActionListener, DocumentListener {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( ModelAuthenticationPanel.class );
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final JComboBox<MPFileUser> userField;
|
||||
private final JLabel masterPasswordLabel;
|
||||
private final JPasswordField masterPasswordField;
|
||||
|
||||
public ModelAuthenticationPanel(final UnlockFrame unlockFrame) {
|
||||
super( unlockFrame );
|
||||
add( Components.stud() );
|
||||
|
||||
// Avatar
|
||||
avatarLabel.addMouseListener( new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(final MouseEvent e) {
|
||||
MPFileUser selectedUser = getSelectedUser();
|
||||
if (selectedUser != null) {
|
||||
selectedUser.setAvatar( selectedUser.getAvatar() + 1 );
|
||||
updateUser( false );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
// User
|
||||
JLabel userLabel = Components.label( "User:" );
|
||||
add( userLabel );
|
||||
|
||||
userField = Components.comboBox( readConfigUsers() );
|
||||
userField.setFont( Res.valueFont().deriveFont( userField.getFont().getSize2D() ) );
|
||||
userField.addItemListener( this );
|
||||
userField.addActionListener( this );
|
||||
userField.setRenderer( new DefaultListCellRenderer() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Component getListCellRendererComponent(final JList<?> list, final Object value, final int index,
|
||||
final boolean isSelected, final boolean cellHasFocus) {
|
||||
String userValue = ((MPUser<MPFileSite>) value).getFullName();
|
||||
return super.getListCellRendererComponent( list, userValue, index, isSelected, cellHasFocus );
|
||||
}
|
||||
} );
|
||||
userField.setEditor( new MetalComboBoxEditor() {
|
||||
@Override
|
||||
protected JTextField createEditorComponent() {
|
||||
JTextField editorComponents = Components.textField();
|
||||
editorComponents.setForeground( Color.red );
|
||||
return editorComponents;
|
||||
}
|
||||
} );
|
||||
|
||||
add( userField );
|
||||
add( Components.stud() );
|
||||
|
||||
// Master Password
|
||||
masterPasswordLabel = Components.label( "Master Password:" );
|
||||
add( masterPasswordLabel );
|
||||
|
||||
masterPasswordField = Components.passwordField();
|
||||
masterPasswordField.addActionListener( this );
|
||||
masterPasswordField.getDocument().addDocumentListener( this );
|
||||
add( masterPasswordField );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getFocusComponent() {
|
||||
return masterPasswordField.isVisible()? masterPasswordField: null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateUser(boolean repack) {
|
||||
MPFileUser selectedUser = getSelectedUser();
|
||||
if (selectedUser != null) {
|
||||
avatarLabel.setIcon( Res.avatar( selectedUser.getAvatar() ) );
|
||||
boolean showPasswordField = !selectedUser.isMasterKeyAvailable(); // TODO: is this the same as keySaved()?
|
||||
if (masterPasswordField.isVisible() != showPasswordField) {
|
||||
masterPasswordLabel.setVisible( showPasswordField );
|
||||
masterPasswordField.setVisible( showPasswordField );
|
||||
repack = true;
|
||||
}
|
||||
}
|
||||
|
||||
super.updateUser( repack );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected MPFileUser getSelectedUser() {
|
||||
int selectedIndex = userField.getSelectedIndex();
|
||||
if (selectedIndex < 0)
|
||||
return null;
|
||||
|
||||
return userField.getModel().getElementAt( selectedIndex );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public char[] getMasterPassword() {
|
||||
return masterPasswordField.getPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<? extends JButton> getButtons() {
|
||||
return ImmutableList.of( new JButton( Res.iconAdd() ) {
|
||||
{
|
||||
addActionListener( new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
String fullName = JOptionPane.showInputDialog( ModelAuthenticationPanel.this, //
|
||||
"Enter your full name, ensuring it is correctly spelled and capitalized:",
|
||||
"New User", JOptionPane.QUESTION_MESSAGE );
|
||||
MPFileUserManager.get().addUser( new MPFileUser( fullName ) );
|
||||
userField.setModel( new DefaultComboBoxModel<>( readConfigUsers() ) );
|
||||
updateUser( true );
|
||||
}
|
||||
} );
|
||||
setToolTipText( "Add a new user to the list." );
|
||||
}
|
||||
}, new JButton( Res.iconDelete() ) {
|
||||
{
|
||||
addActionListener( new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
MPFileUser deleteUser = getSelectedUser();
|
||||
if (deleteUser == null)
|
||||
return;
|
||||
|
||||
if (JOptionPane.showConfirmDialog( ModelAuthenticationPanel.this, //
|
||||
strf( "Are you sure you want to delete the user and sites remembered for:%n%s.",
|
||||
deleteUser.getFullName() ), //
|
||||
"Delete User", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE )
|
||||
== JOptionPane.CANCEL_OPTION)
|
||||
return;
|
||||
|
||||
MPFileUserManager.get().deleteUser( deleteUser );
|
||||
userField.setModel( new DefaultComboBoxModel<>( readConfigUsers() ) );
|
||||
updateUser( true );
|
||||
}
|
||||
} );
|
||||
setToolTipText( "Delete the selected user." );
|
||||
}
|
||||
}, new JButton( Res.iconQuestion() ) {
|
||||
{
|
||||
addActionListener( e -> JOptionPane.showMessageDialog(
|
||||
ModelAuthenticationPanel.this, //
|
||||
strf( "Reads users and sites from the directory at:%n%s",
|
||||
MPFileUserManager.get().getPath().getAbsolutePath() ), //
|
||||
"Help", JOptionPane.INFORMATION_MESSAGE ) );
|
||||
setToolTipText( "More information." );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
masterPasswordField.setText( "" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public PasswordFrame<MPFileUser, MPFileSite> newPasswordFrame() {
|
||||
return new PasswordFrame<MPFileUser, MPFileSite>( Preconditions.checkNotNull( getSelectedUser() ) ) {
|
||||
@Override
|
||||
protected MPFileSite createSite(final MPFileUser user, final String siteName, final UnsignedInteger siteCounter,
|
||||
final MPResultType resultType,
|
||||
final MPAlgorithm algorithm) {
|
||||
return new MPFileSite( user, siteName, algorithm, siteCounter, resultType );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static MPFileUser[] readConfigUsers() {
|
||||
return MPFileUserManager.get().getUsers().toArray( new MPFileUser[0] );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void itemStateChanged(final ItemEvent e) {
|
||||
updateUser( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
updateUser( false );
|
||||
unlockFrame.trySignIn( userField );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(final DocumentEvent e) {
|
||||
updateUser( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(final DocumentEvent e) {
|
||||
updateUser( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(final DocumentEvent e) {
|
||||
updateUser( false );
|
||||
}
|
||||
}
|
@@ -0,0 +1,281 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.gui.view;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.google.common.util.concurrent.*;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.gui.Res;
|
||||
import com.lyndir.masterpassword.gui.util.Components;
|
||||
import com.lyndir.masterpassword.gui.util.UnsignedIntegerModel;
|
||||
import com.lyndir.masterpassword.model.MPSite;
|
||||
import com.lyndir.masterpassword.model.MPUser;
|
||||
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;
|
||||
import java.awt.event.*;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-08
|
||||
*/
|
||||
public abstract class PasswordFrame<U extends MPUser<S>, S extends MPSite<?>> extends JFrame implements DocumentListener {
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final Components.GradientPanel root;
|
||||
private final JTextField siteNameField;
|
||||
private final JButton siteActionButton;
|
||||
private final JComboBox<MPAlgorithm.Version> siteVersionField;
|
||||
private final JSpinner siteCounterField;
|
||||
private final UnsignedIntegerModel siteCounterModel;
|
||||
private final JComboBox<MPResultType> resultTypeField;
|
||||
private final JPasswordField passwordField;
|
||||
private final JLabel tipLabel;
|
||||
private final JCheckBox maskPasswordField;
|
||||
private final char passwordEchoChar;
|
||||
private final Font passwordEchoFont;
|
||||
private final U user;
|
||||
|
||||
@Nullable
|
||||
private S currentSite;
|
||||
private boolean updatingUI;
|
||||
|
||||
@SuppressWarnings("MagicNumber")
|
||||
protected PasswordFrame(final U user) {
|
||||
super( "Master Password" );
|
||||
this.user = user;
|
||||
|
||||
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
|
||||
setContentPane( root = Components.gradientPanel( new FlowLayout(), Res.colors().frameBg() ) );
|
||||
root.setLayout( new BoxLayout( root, BoxLayout.PAGE_AXIS ) );
|
||||
root.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
|
||||
|
||||
// Site
|
||||
JPanel sitePanel = Components.boxLayout( BoxLayout.PAGE_AXIS );
|
||||
sitePanel.setOpaque( true );
|
||||
sitePanel.setBackground( Res.colors().controlBg() );
|
||||
sitePanel.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
|
||||
root.add( Components.borderPanel( sitePanel, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) );
|
||||
|
||||
// User
|
||||
sitePanel.add( Components.label( strf( "Generating passwords for: %s", user.getFullName() ), SwingConstants.CENTER ) );
|
||||
sitePanel.add( Components.stud() );
|
||||
|
||||
// Site Name
|
||||
sitePanel.add( Components.label( "Site Name:" ) );
|
||||
JComponent siteControls = Components.boxLayout( BoxLayout.LINE_AXIS, //
|
||||
siteNameField = Components.textField(), Components.stud(),
|
||||
siteActionButton = Components.button( "Add Site" ) );
|
||||
siteNameField.getDocument().addDocumentListener( this );
|
||||
siteNameField.addActionListener( new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
Futures.addCallback( updatePassword( true ), new FutureCallback<String>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable final String sitePassword) {
|
||||
Transferable clipboardContents = new StringSelection( sitePassword );
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
|
||||
|
||||
SwingUtilities.invokeLater( () -> {
|
||||
passwordField.setText( null );
|
||||
siteNameField.setText( null );
|
||||
|
||||
dispatchEvent( new WindowEvent( PasswordFrame.this, WindowEvent.WINDOW_CLOSING ) );
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@Nonnull final Throwable t) {
|
||||
}
|
||||
} );
|
||||
}
|
||||
} );
|
||||
siteActionButton.addActionListener( e -> {
|
||||
if (currentSite == null)
|
||||
return;
|
||||
if (currentSite instanceof MPFileSite)
|
||||
this.user.deleteSite( currentSite );
|
||||
else
|
||||
this.user.addSite( currentSite );
|
||||
siteNameField.requestFocus();
|
||||
|
||||
updatePassword( true );
|
||||
} );
|
||||
sitePanel.add( siteControls );
|
||||
sitePanel.add( Components.stud() );
|
||||
|
||||
// Site Type & Counter
|
||||
siteCounterModel = new UnsignedIntegerModel( UnsignedInteger.ONE, UnsignedInteger.ONE );
|
||||
MPResultType[] types = Iterables.toArray( MPResultType.forClass( MPResultTypeClass.Template ), MPResultType.class );
|
||||
JComponent siteSettings = Components.boxLayout( BoxLayout.LINE_AXIS, //
|
||||
resultTypeField = Components.comboBox( types ), //
|
||||
Components.stud(), //
|
||||
siteVersionField = Components.comboBox( MPAlgorithm.Version.values() ), //
|
||||
Components.stud(), //
|
||||
siteCounterField = Components.spinner( siteCounterModel ) );
|
||||
sitePanel.add( siteSettings );
|
||||
resultTypeField.setFont( Res.valueFont().deriveFont( resultTypeField.getFont().getSize2D() ) );
|
||||
resultTypeField.setSelectedItem( user.getAlgorithm().mpw_default_result_type() );
|
||||
resultTypeField.addItemListener( e -> updatePassword( true ) );
|
||||
|
||||
siteVersionField.setFont( Res.valueFont().deriveFont( siteVersionField.getFont().getSize2D() ) );
|
||||
siteVersionField.setAlignmentX( RIGHT_ALIGNMENT );
|
||||
siteVersionField.setSelectedItem( user.getAlgorithm() );
|
||||
siteVersionField.addItemListener( e -> updatePassword( true ) );
|
||||
|
||||
siteCounterField.setFont( Res.valueFont().deriveFont( siteCounterField.getFont().getSize2D() ) );
|
||||
siteCounterField.setAlignmentX( RIGHT_ALIGNMENT );
|
||||
siteCounterField.addChangeListener( e -> updatePassword( true ) );
|
||||
|
||||
// Mask
|
||||
maskPasswordField = Components.checkBox( "Hide Password" );
|
||||
maskPasswordField.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||
maskPasswordField.setSelected( true );
|
||||
maskPasswordField.addItemListener( e -> updateMask() );
|
||||
|
||||
// Password
|
||||
passwordField = Components.passwordField();
|
||||
passwordField.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||
passwordField.setHorizontalAlignment( SwingConstants.CENTER );
|
||||
passwordField.putClientProperty( "JPasswordField.cutCopyAllowed", true );
|
||||
passwordField.setEditable( false );
|
||||
passwordField.setBackground( null );
|
||||
passwordField.setBorder( null );
|
||||
passwordEchoChar = passwordField.getEchoChar();
|
||||
passwordEchoFont = passwordField.getFont().deriveFont( 40f );
|
||||
updateMask();
|
||||
|
||||
// Tip
|
||||
tipLabel = Components.label( " ", SwingConstants.CENTER );
|
||||
tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||
JPanel passwordContainer = Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, Box.createGlue(), passwordField,
|
||||
Box.createGlue(), tipLabel );
|
||||
passwordContainer.setOpaque( true );
|
||||
passwordContainer.setBackground( Color.white );
|
||||
passwordContainer.setBorder( BorderFactory.createEmptyBorder( 8, 8, 8, 8 ) );
|
||||
root.add( Box.createVerticalStrut( 8 ) );
|
||||
root.add( Components.borderPanel( passwordContainer, BorderFactory.createLoweredSoftBevelBorder(), Res.colors().frameBg() ),
|
||||
BorderLayout.SOUTH );
|
||||
|
||||
pack();
|
||||
setMinimumSize( new Dimension( Math.max( 600, getPreferredSize().width ), Math.max( 300, getPreferredSize().height ) ) );
|
||||
pack();
|
||||
|
||||
setLocationByPlatform( true );
|
||||
setLocationRelativeTo( null );
|
||||
}
|
||||
|
||||
@SuppressWarnings("MagicNumber")
|
||||
private void updateMask() {
|
||||
passwordField.setEchoChar( maskPasswordField.isSelected()? passwordEchoChar: (char) 0 );
|
||||
passwordField.setFont( maskPasswordField.isSelected()? passwordEchoFont: Res.bigValueFont().deriveFont( 40f ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private ListenableFuture<String> updatePassword(final boolean allowNameCompletion) {
|
||||
|
||||
String siteNameQuery = siteNameField.getText();
|
||||
if (updatingUI)
|
||||
return Futures.immediateCancelledFuture();
|
||||
if ((siteNameQuery == null) || siteNameQuery.isEmpty() || !user.isMasterKeyAvailable()) {
|
||||
siteActionButton.setVisible( false );
|
||||
tipLabel.setText( null );
|
||||
passwordField.setText( null );
|
||||
return Futures.immediateCancelledFuture();
|
||||
}
|
||||
|
||||
MPResultType resultType = resultTypeField.getModel().getElementAt( resultTypeField.getSelectedIndex() );
|
||||
MPAlgorithm siteAlgorithm = siteVersionField.getItemAt( siteVersionField.getSelectedIndex() ).getAlgorithm();
|
||||
UnsignedInteger siteCounter = siteCounterModel.getNumber();
|
||||
|
||||
Collection<S> siteResults = user.findSites( siteNameQuery );
|
||||
if (!allowNameCompletion)
|
||||
siteResults = siteResults.stream().filter(
|
||||
siteResult -> (siteResult != null) && siteNameQuery.equals( siteResult.getName() ) ).collect( Collectors.toList() );
|
||||
S site = ifNotNullElse( Iterables.getFirst( siteResults, null ),
|
||||
createSite( user, siteNameQuery, siteCounter, resultType, siteAlgorithm ) );
|
||||
if ((currentSite != null) && currentSite.getName().equals( site.getName() )) {
|
||||
site.setResultType( resultType );
|
||||
site.setAlgorithm( siteAlgorithm );
|
||||
site.setCounter( siteCounter );
|
||||
}
|
||||
|
||||
ListenableFuture<String> passwordFuture = Res.execute( this, () -> site.getResult( MPKeyPurpose.Authentication, null, null ) );
|
||||
Futures.addCallback( passwordFuture, new FutureCallback<String>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable final String sitePassword) {
|
||||
SwingUtilities.invokeLater( () -> {
|
||||
updatingUI = true;
|
||||
currentSite = site;
|
||||
siteActionButton.setVisible( user instanceof MPFileUser );
|
||||
if (currentSite instanceof MPFileSite)
|
||||
siteActionButton.setText( "Delete Site" );
|
||||
else
|
||||
siteActionButton.setText( "Add Site" );
|
||||
resultTypeField.setSelectedItem( currentSite.getResultType() );
|
||||
siteVersionField.setSelectedItem( currentSite.getAlgorithm() );
|
||||
siteCounterField.setValue( currentSite.getCounter() );
|
||||
siteNameField.setText( currentSite.getName() );
|
||||
if (siteNameField.getText().startsWith( siteNameQuery ))
|
||||
siteNameField.select( siteNameQuery.length(), siteNameField.getText().length() );
|
||||
|
||||
passwordField.setText( sitePassword );
|
||||
tipLabel.setText( "Press [Enter] to copy the password. Then paste it into the password field." );
|
||||
updatingUI = false;
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@Nonnull final Throwable t) {
|
||||
}
|
||||
} );
|
||||
|
||||
return passwordFuture;
|
||||
}
|
||||
|
||||
protected abstract S createSite(U user, String siteName, UnsignedInteger siteCounter, MPResultType resultType, MPAlgorithm algorithm);
|
||||
|
||||
@Override
|
||||
public void insertUpdate(final DocumentEvent e) {
|
||||
updatePassword( true );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(final DocumentEvent e) {
|
||||
updatePassword( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(final DocumentEvent e) {
|
||||
updatePassword( true );
|
||||
}
|
||||
}
|
@@ -0,0 +1,216 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.gui.view;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.lyndir.masterpassword.MPAlgorithmException;
|
||||
import com.lyndir.masterpassword.MPIdenticon;
|
||||
import com.lyndir.masterpassword.gui.Res;
|
||||
import com.lyndir.masterpassword.gui.util.Components;
|
||||
import com.lyndir.masterpassword.model.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-08
|
||||
*/
|
||||
@SuppressWarnings({ "MagicNumber", "serial" })
|
||||
public class UnlockFrame extends JFrame {
|
||||
|
||||
private final SignInCallback signInCallback;
|
||||
private final Components.GradientPanel root;
|
||||
private final JLabel identiconLabel;
|
||||
private final JButton signInButton;
|
||||
private final JPanel authenticationContainer;
|
||||
private AuthenticationPanel<?> authenticationPanel;
|
||||
private Future<?> identiconFuture;
|
||||
private boolean incognito;
|
||||
@Nullable
|
||||
private MPUser<? extends MPSite<?>> user;
|
||||
|
||||
public UnlockFrame(final SignInCallback signInCallback) {
|
||||
super( "Unlock Master Password" );
|
||||
this.signInCallback = signInCallback;
|
||||
|
||||
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
|
||||
addWindowFocusListener( new WindowAdapter() {
|
||||
@Override
|
||||
public void windowGainedFocus(final WindowEvent e) {
|
||||
root.setGradientColor( Res.colors().frameBg() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowLostFocus(final WindowEvent e) {
|
||||
root.setGradientColor( Color.RED );
|
||||
}
|
||||
} );
|
||||
|
||||
// Sign In
|
||||
JPanel signInBox = Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = Components.button( "Sign In" ),
|
||||
Box.createGlue() );
|
||||
signInBox.setBackground( null );
|
||||
|
||||
setContentPane( root = Components.gradientPanel( new FlowLayout(), Res.colors().frameBg() ) );
|
||||
root.setLayout( new BoxLayout( root, BoxLayout.PAGE_AXIS ) );
|
||||
root.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
|
||||
root.add( Components.borderPanel( authenticationContainer = Components.boxLayout( BoxLayout.PAGE_AXIS ),
|
||||
BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) );
|
||||
root.add( Box.createVerticalStrut( 8 ) );
|
||||
root.add( identiconLabel = Components.label( " ", SwingConstants.CENTER ) );
|
||||
root.add( Box.createVerticalStrut( 8 ) );
|
||||
root.add( signInBox );
|
||||
|
||||
authenticationContainer.setOpaque( true );
|
||||
authenticationContainer.setBackground( Res.colors().controlBg() );
|
||||
authenticationContainer.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
|
||||
identiconLabel.setFont( Res.emoticonsFont().deriveFont( 14.f ) );
|
||||
identiconLabel.setToolTipText(
|
||||
strf( "A representation of your identity across all Master Password apps.%nIt should always be the same." ) );
|
||||
signInButton.addActionListener( new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
trySignIn();
|
||||
}
|
||||
} );
|
||||
|
||||
createAuthenticationPanel();
|
||||
|
||||
setLocationByPlatform( true );
|
||||
setLocationRelativeTo( null );
|
||||
}
|
||||
|
||||
protected void repack() {
|
||||
pack();
|
||||
setMinimumSize( new Dimension( Math.max( 300, getPreferredSize().width ), Math.max( 300, getPreferredSize().height ) ) );
|
||||
pack();
|
||||
}
|
||||
|
||||
private void createAuthenticationPanel() {
|
||||
authenticationContainer.removeAll();
|
||||
|
||||
if (incognito) {
|
||||
authenticationPanel = new IncognitoAuthenticationPanel( this );
|
||||
} else {
|
||||
authenticationPanel = new ModelAuthenticationPanel( this );
|
||||
}
|
||||
authenticationPanel.updateUser( false );
|
||||
authenticationContainer.add( authenticationPanel );
|
||||
authenticationContainer.add( Components.stud() );
|
||||
|
||||
JCheckBox incognitoCheckBox = Components.checkBox( "Incognito" );
|
||||
incognitoCheckBox.setToolTipText( "Log in without saving any information." );
|
||||
incognitoCheckBox.setSelected( incognito );
|
||||
incognitoCheckBox.addItemListener( e -> {
|
||||
incognito = incognitoCheckBox.isSelected();
|
||||
SwingUtilities.invokeLater( this::createAuthenticationPanel );
|
||||
} );
|
||||
|
||||
JComponent toolsPanel = Components.boxLayout( BoxLayout.LINE_AXIS, incognitoCheckBox, Box.createGlue() );
|
||||
authenticationContainer.add( toolsPanel );
|
||||
for (final JButton button : authenticationPanel.getButtons()) {
|
||||
toolsPanel.add( button );
|
||||
button.setBorder( BorderFactory.createEmptyBorder() );
|
||||
button.setMargin( new Insets( 0, 0, 0, 0 ) );
|
||||
button.setAlignmentX( RIGHT_ALIGNMENT );
|
||||
button.setContentAreaFilled( false );
|
||||
}
|
||||
|
||||
checkSignIn();
|
||||
validate();
|
||||
repack();
|
||||
|
||||
SwingUtilities.invokeLater( () -> ifNotNullElse( authenticationPanel.getFocusComponent(), signInButton ).requestFocusInWindow() );
|
||||
}
|
||||
|
||||
void updateUser(@Nullable final MPUser<? extends MPSite<?>> user) {
|
||||
this.user = user;
|
||||
checkSignIn();
|
||||
}
|
||||
|
||||
boolean checkSignIn() {
|
||||
if (identiconFuture != null)
|
||||
identiconFuture.cancel( false );
|
||||
identiconFuture = Res.schedule( this, () -> SwingUtilities.invokeLater( () -> {
|
||||
String fullName = (user == null)? "": user.getFullName();
|
||||
char[] masterPassword = authenticationPanel.getMasterPassword();
|
||||
|
||||
if (fullName.isEmpty() || (masterPassword.length == 0)) {
|
||||
identiconLabel.setText( " " );
|
||||
return;
|
||||
}
|
||||
|
||||
MPIdenticon identicon = new MPIdenticon( fullName, masterPassword );
|
||||
identiconLabel.setText( identicon.getText() );
|
||||
identiconLabel.setForeground(
|
||||
Res.colors().fromIdenticonColor( identicon.getColor(), Res.Colors.BackgroundMode.DARK ) );
|
||||
} ), 300, TimeUnit.MILLISECONDS );
|
||||
|
||||
String fullName = (user == null)? "": user.getFullName();
|
||||
char[] masterPassword = authenticationPanel.getMasterPassword();
|
||||
boolean enabled = !fullName.isEmpty() && (masterPassword.length > 0);
|
||||
signInButton.setEnabled( enabled );
|
||||
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void trySignIn(final JComponent... signInComponents) {
|
||||
if ((user == null) || !checkSignIn())
|
||||
return;
|
||||
|
||||
for (final JComponent signInComponent : signInComponents)
|
||||
signInComponent.setEnabled( false );
|
||||
|
||||
signInButton.setEnabled( false );
|
||||
signInButton.setText( "Signing In..." );
|
||||
|
||||
Res.execute( this, () -> {
|
||||
try {
|
||||
user.authenticate( authenticationPanel.getMasterPassword() );
|
||||
|
||||
SwingUtilities.invokeLater( () -> {
|
||||
signInCallback.signedIn( authenticationPanel.newPasswordFrame() );
|
||||
dispose();
|
||||
} );
|
||||
}
|
||||
catch (final MPIncorrectMasterPasswordException | MPAlgorithmException e) {
|
||||
SwingUtilities.invokeLater( () -> {
|
||||
JOptionPane.showMessageDialog( null, e.getLocalizedMessage(), "Sign In Failed", JOptionPane.ERROR_MESSAGE );
|
||||
authenticationPanel.reset();
|
||||
signInButton.setText( "Sign In" );
|
||||
for (final JComponent signInComponent : signInComponents)
|
||||
signInComponent.setEnabled( true );
|
||||
checkSignIn();
|
||||
} );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface SignInCallback {
|
||||
|
||||
void signedIn(PasswordFrame<?, ?> passwordFrame);
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-04-26
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
package com.lyndir.masterpassword.gui.view;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
BIN
platform-independent/java/gui/src/main/resources/fonts/Arimo-Bold.ttf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/Arimo-BoldItalic.ttf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/Arimo-Italic.ttf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/Arimo-Regular.ttf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-Black.otf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-BlackIt.otf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-Bold.otf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-BoldIt.otf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-It.otf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-Light.otf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-LightIt.otf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-Regular.otf
Executable file
15
platform-independent/java/gui/src/main/resources/logback.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<configuration scan="false">
|
||||
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%-8relative %22c{0} [%-5level] %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="com.lyndir" level="${mp.log.level:-INFO}" />
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="stdout" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 8.9 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 9.3 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 8.4 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 3.7 KiB |