2
0

Global hotkey, iconifying and application activation, help text.

This commit is contained in:
Maarten Billemont 2018-07-31 14:55:19 -04:00
parent f5c0c4d787
commit 3fc8acba70
11 changed files with 158 additions and 18 deletions

View File

@ -11,6 +11,7 @@ dependencies {
implementation group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.7-p2' implementation group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.7-p2'
implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.2' implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.2'
implementation group: 'com.yuvimasory', name: 'orange-extensions', version: '1.3.0' implementation group: 'com.yuvimasory', name: 'orange-extensions', version: '1.3.0'
implementation group: 'com.github.tulskiy', name: 'jkeymaster', version: '1.2'
compile project( ':masterpassword-model' ) compile project( ':masterpassword-model' )
} }

View File

@ -4,6 +4,10 @@ import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.gui.util.Platform; import com.lyndir.masterpassword.gui.util.Platform;
import com.lyndir.masterpassword.gui.util.Res; import com.lyndir.masterpassword.gui.util.Res;
import com.lyndir.masterpassword.gui.view.MasterPasswordFrame; import com.lyndir.masterpassword.gui.view.MasterPasswordFrame;
import com.tulskiy.keymaster.common.Provider;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/** /**
@ -18,9 +22,18 @@ public class GUI {
public GUI() { public GUI() {
Platform.get().installAppForegroundHandler( this::open ); Platform.get().installAppForegroundHandler( this::open );
Platform.get().installAppReopenHandler( this::open ); Platform.get().installAppReopenHandler( this::open );
KeyStroke keyStroke = KeyStroke.getKeyStroke( KeyEvent.VK_P, InputEvent.CTRL_DOWN_MASK | InputEvent.META_DOWN_MASK );
Provider.getCurrentProvider( true ).register( keyStroke, hotKey -> open() );
} }
public void open() { public void open() {
Res.ui( () -> frame.setVisible( true ) ); Res.ui( () -> {
frame.setAlwaysOnTop( true );
frame.setVisible( true );
frame.setExtendedState( Frame.NORMAL );
frame.setAlwaysOnTop( false );
Platform.get().requestForeground();
} );
} }
} }

View File

@ -18,10 +18,12 @@
package com.lyndir.masterpassword.gui.util; package com.lyndir.masterpassword.gui.util;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.io.File; import java.io.File;
import java.net.URISyntaxException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -30,9 +32,9 @@ import javax.annotation.Nullable;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.Border; import javax.swing.border.Border;
import javax.swing.border.CompoundBorder; import javax.swing.border.CompoundBorder;
import javax.swing.event.DocumentEvent; import javax.swing.event.*;
import javax.swing.event.DocumentListener;
import javax.swing.text.DefaultFormatterFactory; import javax.swing.text.DefaultFormatterFactory;
import org.jetbrains.annotations.NonNls;
/** /**
@ -41,6 +43,8 @@ import javax.swing.text.DefaultFormatterFactory;
@SuppressWarnings({ "SerializableStoresNonSerializable", "serial" }) @SuppressWarnings({ "SerializableStoresNonSerializable", "serial" })
public abstract class Components { public abstract class Components {
private static final Logger logger = Logger.get( Components.class );
public static final float TEXT_SIZE_HEADING = 19f; public static final float TEXT_SIZE_HEADING = 19f;
public static final float TEXT_SIZE_CONTROL = 13f; public static final float TEXT_SIZE_CONTROL = 13f;
public static final int SIZE_MARGIN = 12; public static final int SIZE_MARGIN = 12;
@ -100,11 +104,11 @@ public abstract class Components {
showDialog( dialog ); showDialog( dialog );
Object selectedValue = pane.getValue(); Object selectedValue = pane.getValue();
if(selectedValue == null) if (selectedValue == null)
return JOptionPane.CLOSED_OPTION; return JOptionPane.CLOSED_OPTION;
Object[] options = pane.getOptions(); Object[] options = pane.getOptions();
if(options == null) if (options == null)
return (selectedValue instanceof Integer)? (Integer) selectedValue: JOptionPane.CLOSED_OPTION; return (selectedValue instanceof Integer)? (Integer) selectedValue: JOptionPane.CLOSED_OPTION;
int option = Arrays.binarySearch( options, selectedValue ); int option = Arrays.binarySearch( options, selectedValue );
@ -337,7 +341,7 @@ public abstract class Components {
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ); BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) );
DefaultFormatterFactory formatterFactory = new DefaultFormatterFactory(); DefaultFormatterFactory formatterFactory = new DefaultFormatterFactory();
if (model instanceof UnsignedIntegerModel) if (model instanceof UnsignedIntegerModel)
formatterFactory.setDefaultFormatter( ((UnsignedIntegerModel)model).getFormatter() ); formatterFactory.setDefaultFormatter( ((UnsignedIntegerModel) model).getFormatter() );
((DefaultEditor) getEditor()).getTextField().setFormatterFactory( formatterFactory ); ((DefaultEditor) getEditor()).getTextField().setFormatterFactory( formatterFactory );
((DefaultEditor) getEditor()).getTextField().setBorder( editorBorder ); ((DefaultEditor) getEditor()).getTextField().setBorder( editorBorder );
setAlignmentX( LEFT_ALIGNMENT ); setAlignmentX( LEFT_ALIGNMENT );
@ -474,6 +478,24 @@ public abstract class Components {
}; };
} }
public static JEditorPane linkLabel(@NonNls final String html) {
return new JEditorPane( "text/html", "<html><body style='width:640'>" + html ) {
{
setOpaque( false );
setEditable( false );
addHyperlinkListener( event -> {
if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
try {
Platform.get().open( event.getURL().toURI() );
}
catch (final URISyntaxException e) {
logger.err( e, "After triggering hyperlink: %s", event );
}
} );
}
};
}
public static class GradientPanel extends JPanel { public static class GradientPanel extends JPanel {
@Nullable @Nullable

View File

@ -138,6 +138,10 @@ public abstract class Res {
return icon( "media/icon_import.png" ); return icon( "media/icon_import.png" );
} }
public Icon help() {
return icon( "media/icon_help.png" );
}
public Icon export() { public Icon export() {
return icon( "media/icon_export.png" ); return icon( "media/icon_export.png" );
} }

View File

@ -3,9 +3,9 @@ package com.lyndir.masterpassword.gui.util.platform;
import com.apple.eawt.*; import com.apple.eawt.*;
import com.apple.eio.FileManager; import com.apple.eio.FileManager;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Throwables; import com.lyndir.lhunath.opal.system.logging.Logger;
import java.io.File; import java.io.*;
import java.io.FileNotFoundException; import java.net.URI;
/** /**
@ -13,7 +13,8 @@ import java.io.FileNotFoundException;
*/ */
public class ApplePlatform implements IPlatform { public class ApplePlatform implements IPlatform {
static Application application = Preconditions.checkNotNull( private static final Logger logger = Logger.get( ApplePlatform.class );
private static final Application application = Preconditions.checkNotNull(
Application.getApplication(), "Not an Apple Java application." ); Application.getApplication(), "Not an Apple Java application." );
@Override @Override
@ -37,12 +38,31 @@ public class ApplePlatform implements IPlatform {
return true; return true;
} }
@Override
public boolean requestForeground() {
application.requestForeground( true );
return true;
}
@Override @Override
public boolean show(final File file) { public boolean show(final File file) {
try { try {
return FileManager.revealInFinder( file ); return FileManager.revealInFinder( file );
} }
catch (final FileNotFoundException ignored) { catch (final FileNotFoundException e) {
logger.err( e, "While showing: %s", file );
return false;
}
}
@Override
public boolean open(final URI url) {
try {
FileManager.openURL( url.toString() );
return true;
}
catch (final IOException e) {
logger.err( e, "While opening: %s", url );
return false; return false;
} }
} }

View File

@ -1,6 +1,7 @@
package com.lyndir.masterpassword.gui.util.platform; package com.lyndir.masterpassword.gui.util.platform;
import java.io.File; import java.io.File;
import java.net.URI;
/** /**
@ -18,8 +19,18 @@ public class BasePlatform implements IPlatform {
return false; return false;
} }
@Override
public boolean requestForeground() {
return false;
}
@Override @Override
public boolean show(final File file) { public boolean show(final File file) {
return false; return false;
} }
@Override
public boolean open(final URI url) {
return false;
}
} }

View File

@ -1,6 +1,8 @@
package com.lyndir.masterpassword.gui.util.platform; package com.lyndir.masterpassword.gui.util.platform;
import java.io.File; import java.io.File;
import java.net.URI;
import java.net.URL;
/** /**
@ -12,5 +14,9 @@ public interface IPlatform {
boolean installAppReopenHandler(Runnable handler); boolean installAppReopenHandler(Runnable handler);
boolean requestForeground();
boolean show(File file); boolean show(File file);
boolean open(URI url);
} }

View File

@ -1,8 +1,11 @@
package com.lyndir.masterpassword.gui.util.platform; package com.lyndir.masterpassword.gui.util.platform;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.awt.*; import java.awt.*;
import java.awt.desktop.*; import java.awt.desktop.*;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.net.URI;
/** /**
@ -11,9 +14,12 @@ import java.io.File;
@SuppressWarnings("Since15") @SuppressWarnings("Since15")
public class JDK9Platform implements IPlatform { public class JDK9Platform implements IPlatform {
private static final Logger logger = Logger.get( JDK9Platform.class );
private static final Desktop desktop = Desktop.getDesktop();
@Override @Override
public boolean installAppForegroundHandler(final Runnable handler) { public boolean installAppForegroundHandler(final Runnable handler) {
Desktop.getDesktop().addAppEventListener( new AppForegroundListener() { desktop.addAppEventListener( new AppForegroundListener() {
@Override @Override
public void appRaisedToForeground(final AppForegroundEvent e) { public void appRaisedToForeground(final AppForegroundEvent e) {
handler.run(); handler.run();
@ -28,7 +34,13 @@ public class JDK9Platform implements IPlatform {
@Override @Override
public boolean installAppReopenHandler(final Runnable handler) { public boolean installAppReopenHandler(final Runnable handler) {
Desktop.getDesktop().addAppEventListener( (AppReopenedListener) e -> handler.run() ); desktop.addAppEventListener( (AppReopenedListener) e -> handler.run() );
return true;
}
@Override
public boolean requestForeground() {
desktop.requestForeground( true );
return true; return true;
} }
@ -37,7 +49,19 @@ public class JDK9Platform implements IPlatform {
if (!file.exists()) if (!file.exists())
return false; return false;
Desktop.getDesktop().browseFileDirectory( file ); desktop.browseFileDirectory( file );
return true; return true;
} }
@Override
public boolean open(final URI url) {
try {
desktop.browse( url );
return true;
}
catch (final IOException e) {
logger.err( e, "While opening: %s", url );
return false;
}
}
} }

View File

@ -49,6 +49,8 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
"Add a new user to Master Password." ); "Add a new user to Master Password." );
private final JButton importButton = Components.button( Res.icons().import_(), event -> importUser(), private final JButton importButton = Components.button( Res.icons().import_(), event -> importUser(),
"Import a user from a backup file into Master Password." ); "Import a user from a backup file into Master Password." );
private final JButton helpButton = Components.button( Res.icons().help(), event -> showHelp(),
"Show information on how to use Master Password." );
private final JPanel userToolbar = Components.panel( BoxLayout.PAGE_AXIS ); private final JPanel userToolbar = Components.panel( BoxLayout.PAGE_AXIS );
private final JPanel siteToolbar = Components.panel( BoxLayout.PAGE_AXIS ); private final JPanel siteToolbar = Components.panel( BoxLayout.PAGE_AXIS );
@ -208,7 +210,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
this, strf( "<html>Couldn't read import file:<br><pre>%s</pre></html>.", e.getLocalizedMessage() ), this, strf( "<html>Couldn't read import file:<br><pre>%s</pre></html>.", e.getLocalizedMessage() ),
"Import Failed", JOptionPane.ERROR_MESSAGE ); "Import Failed", JOptionPane.ERROR_MESSAGE );
} }
catch (MPMarshalException e) { catch (final MPMarshalException e) {
logger.err( e, "While parsing user import file." ); logger.err( e, "While parsing user import file." );
JOptionPane.showMessageDialog( JOptionPane.showMessageDialog(
this, strf( "<html>Couldn't parse import file:<br><pre>%s</pre></html>.", e.getLocalizedMessage() ), this, strf( "<html>Couldn't parse import file:<br><pre>%s</pre></html>.", e.getLocalizedMessage() ),
@ -216,6 +218,31 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
} }
} }
private void showHelp() {
JOptionPane.showMessageDialog( this, Components.linkLabel( strf(
"<h1>Master Password</h1>"
+ "<p>The primary goal of this application is to provide a reliable security solution that also "
+ "makes you independent from your computer. If you lose access to this computer or your data, "
+ "the application can regenerate all your secrets from scratch on any new device.</p>"
+ "<h2>Opening Master Password</h2>"
+ "<p>To use Master Password, simply open the application on your computer. "
+ "Once running, you can bring up the user interface at any time by pressing the keys "
+ "<strong><code>%s + %s + p</code></strong>."
+ "<h2>Persistence</h2>"
+ "<p>Though at the core, Master Password does not require the use of any form of data "
+ "storage, the application does remember the names of the sites you've used in the past to "
+ "make it easier for you to use them again in the future. All user information is saved in "
+ "files on your computer at the following location:<br><pre>%s</pre></p>"
+ "<p>You can read, modify, backup or place new files in this location as you see fit. "
+ "Some people even configure this location to be synced between their different computers "
+ "using services such as those provided by SpiderOak or Dropbox.</p>"
+ "<hr><p><a href='https://masterpassword.app'>https://masterpassword.app</a> — by Maarten Billemont</p>",
KeyEvent.getKeyText( KeyEvent.VK_CONTROL ),
KeyEvent.getKeyText( KeyEvent.VK_META ),
MPFileUserManager.get().getPath().getAbsolutePath() ) ),
"About Master Password", JOptionPane.INFORMATION_MESSAGE );
}
private enum ContentMode { private enum ContentMode {
NO_USER, NO_USER,
AUTHENTICATE, AUTHENTICATE,
@ -239,6 +266,8 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
userToolbar.add( addButton ); userToolbar.add( addButton );
userToolbar.add( importButton ); userToolbar.add( importButton );
userToolbar.add( Box.createGlue() );
userToolbar.add( helpButton );
add( Box.createGlue() ); add( Box.createGlue() );
add( Components.heading( "Select a user to proceed." ) ); add( Components.heading( "Select a user to proceed." ) );
@ -275,6 +304,8 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
userToolbar.add( exportButton ); userToolbar.add( exportButton );
userToolbar.add( deleteButton ); userToolbar.add( deleteButton );
userToolbar.add( resetButton ); userToolbar.add( resetButton );
userToolbar.add( Box.createGlue() );
userToolbar.add( helpButton );
add( Components.heading( user.getFullName(), SwingConstants.CENTER ) ); add( Components.heading( user.getFullName(), SwingConstants.CENTER ) );
add( Components.strut() ); add( Components.strut() );
@ -461,6 +492,8 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
userToolbar.add( addButton ); userToolbar.add( addButton );
userToolbar.add( userButton ); userToolbar.add( userButton );
userToolbar.add( logoutButton ); userToolbar.add( logoutButton );
userToolbar.add( Box.createGlue() );
userToolbar.add( helpButton );
siteToolbar.add( settingsButton ); siteToolbar.add( settingsButton );
siteToolbar.add( questionsButton ); siteToolbar.add( questionsButton );
@ -615,8 +648,14 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
Res.ui( () -> { Res.ui( () -> {
Window window = SwingUtilities.windowForComponent( UserContentPanel.this ); Window window = SwingUtilities.windowForComponent( UserContentPanel.this );
if (window != null) if (window instanceof Frame) {
window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_CLOSING ) ); ((Frame) window).setExtendedState( Frame.ICONIFIED );
window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_DEACTIVATED ) );
window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_ICONIFIED ) );
window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_LOST_FOCUS ) );
window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_CLOSED ) );
}
// window.dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_ICONIFIED ) );
} ); } );
} ); } );
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB