Craig Raw
5 years ago
27 changed files with 1184 additions and 40 deletions
Binary file not shown.
@ -0,0 +1,5 @@ |
|||
package com.sparrowwallet.sparrow; |
|||
|
|||
public enum Mode { |
|||
OFFLINE, ONLINE |
|||
} |
@ -0,0 +1,102 @@ |
|||
package com.sparrowwallet.sparrow.control; |
|||
|
|||
import java.text.DecimalFormatSymbols; |
|||
import java.util.regex.Pattern; |
|||
|
|||
import javafx.beans.NamedArg; |
|||
import javafx.scene.control.TextFormatter; |
|||
import javafx.scene.control.TextFormatter.Change; |
|||
|
|||
public class TextFieldValidator { |
|||
|
|||
private static final String CURRENCY_SYMBOL = DecimalFormatSymbols.getInstance().getCurrencySymbol(); |
|||
private static final char DECIMAL_SEPARATOR = DecimalFormatSymbols.getInstance().getDecimalSeparator(); |
|||
|
|||
private final Pattern INPUT_PATTERN; |
|||
|
|||
public TextFieldValidator(@NamedArg("modus") ValidationModus modus, @NamedArg("countOf") int countOf) { |
|||
this(modus.createPattern(countOf)); |
|||
} |
|||
|
|||
public TextFieldValidator(@NamedArg("regex") String regex) { |
|||
this(Pattern.compile(regex)); |
|||
} |
|||
|
|||
public TextFieldValidator(Pattern inputPattern) { |
|||
INPUT_PATTERN = inputPattern; |
|||
} |
|||
|
|||
public static TextFieldValidator maxFractionDigits(int countOf) { |
|||
return new TextFieldValidator(maxFractionPattern(countOf)); |
|||
} |
|||
|
|||
public static TextFieldValidator maxIntegers(int countOf) { |
|||
return new TextFieldValidator(maxIntegerPattern(countOf)); |
|||
} |
|||
|
|||
public static TextFieldValidator integersOnly() { |
|||
return new TextFieldValidator(integersOnlyPattern()); |
|||
} |
|||
|
|||
public TextFormatter<Object> getFormatter() { |
|||
return new TextFormatter<>(this::validateChange); |
|||
} |
|||
|
|||
private Change validateChange(Change c) { |
|||
if (validate(c.getControlNewText())) { |
|||
return c; |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
public boolean validate(String input) { |
|||
return INPUT_PATTERN.matcher(input).matches(); |
|||
} |
|||
|
|||
private static Pattern maxFractionPattern(int countOf) { |
|||
return Pattern.compile("\\d*(\\\\" + DECIMAL_SEPARATOR + "\\d{0," + countOf + "})?"); |
|||
} |
|||
|
|||
private static Pattern maxCurrencyFractionPattern(int countOf) { |
|||
return Pattern.compile("^\\\\" + CURRENCY_SYMBOL + "?\\s?\\d*(\\\\" + DECIMAL_SEPARATOR + "\\d{0," + countOf + "})?\\s?\\\\" + CURRENCY_SYMBOL + "?"); |
|||
} |
|||
|
|||
private static Pattern maxIntegerPattern(int countOf) { |
|||
return Pattern.compile("\\d{0," + countOf + "}"); |
|||
} |
|||
|
|||
private static Pattern integersOnlyPattern() { |
|||
return Pattern.compile("\\d*"); |
|||
} |
|||
|
|||
public enum ValidationModus { |
|||
MAX_CURRENCY_FRACTION_DIGITS { |
|||
@Override |
|||
public Pattern createPattern(int countOf) { |
|||
return maxCurrencyFractionPattern(countOf); |
|||
} |
|||
}, |
|||
|
|||
MAX_FRACTION_DIGITS { |
|||
@Override |
|||
public Pattern createPattern(int countOf) { |
|||
return maxFractionPattern(countOf); |
|||
} |
|||
}, |
|||
MAX_INTEGERS { |
|||
@Override |
|||
public Pattern createPattern(int countOf) { |
|||
return maxIntegerPattern(countOf); |
|||
} |
|||
}, |
|||
|
|||
INTEGERS_ONLY { |
|||
@Override |
|||
public Pattern createPattern(int countOf) { |
|||
return integersOnlyPattern(); |
|||
} |
|||
}; |
|||
|
|||
public abstract Pattern createPattern(int countOf); |
|||
} |
|||
} |
@ -0,0 +1,16 @@ |
|||
package com.sparrowwallet.sparrow.control; |
|||
|
|||
import impl.org.controlsfx.skin.ToggleSwitchSkin; |
|||
import javafx.scene.control.Skin; |
|||
import org.controlsfx.control.ToggleSwitch; |
|||
|
|||
public class UnlabeledToggleSwitch extends ToggleSwitch { |
|||
@Override protected Skin<?> createDefaultSkin() { |
|||
return new ToggleSwitchSkin(this) { |
|||
@Override |
|||
protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { |
|||
return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) - 20; |
|||
} |
|||
}; |
|||
} |
|||
} |
@ -0,0 +1,105 @@ |
|||
package com.sparrowwallet.sparrow.control; |
|||
|
|||
import com.sparrowwallet.sparrow.AppController; |
|||
import com.sparrowwallet.sparrow.Mode; |
|||
import javafx.application.HostServices; |
|||
import javafx.geometry.Insets; |
|||
import javafx.scene.control.*; |
|||
import javafx.scene.image.Image; |
|||
import javafx.scene.image.ImageView; |
|||
import javafx.scene.layout.Region; |
|||
import javafx.scene.layout.VBox; |
|||
import org.controlsfx.control.HyperlinkLabel; |
|||
import org.controlsfx.control.StatusBar; |
|||
import org.controlsfx.control.ToggleSwitch; |
|||
|
|||
public class WelcomeDialog extends Dialog<Mode> { |
|||
private static final String[] ELECTRUM_SERVERS = new String[]{ |
|||
"ElectrumX", "https://github.com/spesmilo/electrumx", |
|||
"electrs", "https://github.com/romanz/electrs", |
|||
"esplora-electrs", "https://github.com/Blockstream/electrs", |
|||
"Electrum Personal Server", "https://github.com/chris-belcher/electrum-personal-server", |
|||
"Bitcoin Wallet Tracker", "https://github.com/shesek/bwt"}; |
|||
|
|||
private final HostServices hostServices; |
|||
|
|||
public WelcomeDialog(HostServices services) { |
|||
this.hostServices = services; |
|||
|
|||
final DialogPane dialogPane = getDialogPane(); |
|||
|
|||
setTitle("Welcome to Sparrow"); |
|||
dialogPane.setHeaderText("Welcome to Sparrow!"); |
|||
dialogPane.getStylesheets().add(AppController.class.getResource("general.css").toExternalForm()); |
|||
dialogPane.setPrefWidth(600); |
|||
dialogPane.setPrefHeight(500); |
|||
|
|||
Image image = new Image("image/sparrow-small.png", 50, 50, false, false); |
|||
if (!image.isError()) { |
|||
ImageView imageView = new ImageView(); |
|||
imageView.setSmooth(false); |
|||
imageView.setImage(image); |
|||
dialogPane.setGraphic(imageView); |
|||
} |
|||
|
|||
final ButtonType onlineButtonType = new javafx.scene.control.ButtonType("Configure Now", ButtonBar.ButtonData.OK_DONE); |
|||
final ButtonType offlineButtonType = new javafx.scene.control.ButtonType("Configure Later or Use Offline", ButtonBar.ButtonData.CANCEL_CLOSE); |
|||
dialogPane.getButtonTypes().addAll(onlineButtonType, offlineButtonType); |
|||
|
|||
final VBox content = new VBox(20); |
|||
content.setPadding(new Insets(20, 20, 20, 20)); |
|||
content.getChildren().add(createParagraph("Sparrow can operate in both an online and offline mode. In the online mode it connects to your Electrum server to display transaction history. In the offline mode it is useful as a transaction editor and as an airgapped multisig coordinator.")); |
|||
content.getChildren().add(createParagraph("For privacy and security reasons it is not recommended to use a public Electrum server. Install an Electrum server that connects to your full node to index the blockchain and provide full privacy. Examples include:")); |
|||
|
|||
VBox linkBox = new VBox(); |
|||
for(int i = 0; i < ELECTRUM_SERVERS.length; i+=2) { |
|||
linkBox.getChildren().add(createBulletedLink(ELECTRUM_SERVERS[i], ELECTRUM_SERVERS[i+1])); |
|||
} |
|||
content.getChildren().add(linkBox); |
|||
|
|||
content.getChildren().add(createParagraph("You can change your mode at any time using the toggle in the status bar:")); |
|||
content.getChildren().add(createStatusBar(onlineButtonType, offlineButtonType)); |
|||
|
|||
dialogPane.setContent(content); |
|||
|
|||
setResultConverter(dialogButton -> dialogButton == onlineButtonType ? Mode.ONLINE : Mode.OFFLINE); |
|||
} |
|||
|
|||
private Label createParagraph(String text) { |
|||
Label label = new Label(text); |
|||
label.setWrapText(true); |
|||
|
|||
return label; |
|||
} |
|||
|
|||
private HyperlinkLabel createBulletedLink(String name, String url) { |
|||
HyperlinkLabel label = new HyperlinkLabel(" \u2022 [" + name + "]"); |
|||
label.setOnAction(event -> { |
|||
hostServices.showDocument(url); |
|||
}); |
|||
|
|||
return label; |
|||
} |
|||
|
|||
private StatusBar createStatusBar(ButtonType onlineButtonType, ButtonType offlineButtonType) { |
|||
StatusBar statusBar = new StatusBar(); |
|||
statusBar.setText("Online Mode"); |
|||
statusBar.getRightItems().add(createToggle(statusBar, onlineButtonType, offlineButtonType)); |
|||
|
|||
return statusBar; |
|||
} |
|||
|
|||
private ToggleSwitch createToggle(StatusBar statusBar, ButtonType onlineButtonType, ButtonType offlineButtonType) { |
|||
ToggleSwitch toggleSwitch = new UnlabeledToggleSwitch(); |
|||
toggleSwitch.selectedProperty().addListener((observable, oldValue, newValue) -> { |
|||
Button onlineButton = (Button) getDialogPane().lookupButton(onlineButtonType); |
|||
onlineButton.setDefaultButton(newValue); |
|||
Button offlineButton = (Button) getDialogPane().lookupButton(offlineButtonType); |
|||
offlineButton.setDefaultButton(!newValue); |
|||
statusBar.setText(newValue ? "Online Mode" : "Offline Mode"); |
|||
}); |
|||
|
|||
toggleSwitch.setSelected(true); |
|||
return toggleSwitch; |
|||
} |
|||
} |
@ -0,0 +1,5 @@ |
|||
package com.sparrowwallet.sparrow.preferences; |
|||
|
|||
public enum PreferenceGroup { |
|||
GENERAL, SERVER; |
|||
} |
@ -0,0 +1,78 @@ |
|||
package com.sparrowwallet.sparrow.preferences; |
|||
|
|||
import com.sparrowwallet.drongo.wallet.KeystoreSource; |
|||
import com.sparrowwallet.drongo.wallet.Wallet; |
|||
import com.sparrowwallet.sparrow.AppController; |
|||
import com.sparrowwallet.sparrow.io.Config; |
|||
import com.sparrowwallet.sparrow.keystoreimport.KeystoreImportDetailController; |
|||
import javafx.application.Platform; |
|||
import javafx.fxml.FXML; |
|||
import javafx.fxml.FXMLLoader; |
|||
import javafx.fxml.Initializable; |
|||
import javafx.scene.Node; |
|||
import javafx.scene.control.Toggle; |
|||
import javafx.scene.control.ToggleGroup; |
|||
import javafx.scene.layout.StackPane; |
|||
|
|||
import java.io.IOException; |
|||
import java.net.URL; |
|||
import java.util.ResourceBundle; |
|||
|
|||
public class PreferencesController implements Initializable { |
|||
private Config config; |
|||
|
|||
@FXML |
|||
private ToggleGroup preferencesMenu; |
|||
|
|||
@FXML |
|||
private StackPane preferencesPane; |
|||
|
|||
@Override |
|||
public void initialize(URL location, ResourceBundle resources) { |
|||
|
|||
} |
|||
|
|||
public Config getConfig() { |
|||
return config; |
|||
} |
|||
|
|||
public void initializeView(Config config) { |
|||
this.config = config; |
|||
preferencesMenu.selectedToggleProperty().addListener((observable, oldValue, selectedToggle) -> { |
|||
if(selectedToggle == null) { |
|||
oldValue.setSelected(true); |
|||
return; |
|||
} |
|||
|
|||
PreferenceGroup preferenceGroup = (PreferenceGroup) selectedToggle.getUserData(); |
|||
String fxmlName = preferenceGroup.toString().toLowerCase(); |
|||
setPreferencePane(fxmlName); |
|||
}); |
|||
} |
|||
|
|||
public void selectGroup(PreferenceGroup preferenceGroup) { |
|||
for(Toggle toggle : preferencesMenu.getToggles()) { |
|||
if(toggle.getUserData().equals(preferenceGroup)) { |
|||
Platform.runLater(() -> preferencesMenu.selectToggle(toggle)); |
|||
return; |
|||
} |
|||
} |
|||
} |
|||
|
|||
FXMLLoader setPreferencePane(String fxmlName) { |
|||
preferencesPane.getChildren().removeAll(preferencesPane.getChildren()); |
|||
|
|||
try { |
|||
FXMLLoader preferencesDetailLoader = new FXMLLoader(AppController.class.getResource("preferences/" + fxmlName + ".fxml")); |
|||
Node preferenceGroupNode = preferencesDetailLoader.load(); |
|||
PreferencesDetailController controller = preferencesDetailLoader.getController(); |
|||
controller.setMasterController(this); |
|||
controller.initializeView(config); |
|||
preferencesPane.getChildren().add(preferenceGroupNode); |
|||
|
|||
return preferencesDetailLoader; |
|||
} catch (IOException e) { |
|||
throw new IllegalStateException("Can't find pane", e); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
package com.sparrowwallet.sparrow.preferences; |
|||
|
|||
import com.sparrowwallet.sparrow.io.Config; |
|||
import javafx.fxml.Initializable; |
|||
|
|||
public abstract class PreferencesDetailController { |
|||
private PreferencesController masterController; |
|||
|
|||
public PreferencesController getMasterController() { |
|||
return masterController; |
|||
} |
|||
|
|||
void setMasterController(PreferencesController masterController) { |
|||
this.masterController = masterController; |
|||
} |
|||
|
|||
public void initializeView(Config config) { |
|||
|
|||
} |
|||
} |
@ -0,0 +1,33 @@ |
|||
package com.sparrowwallet.sparrow.preferences; |
|||
|
|||
import com.sparrowwallet.sparrow.AppController; |
|||
import com.sparrowwallet.sparrow.io.Config; |
|||
import javafx.fxml.FXMLLoader; |
|||
import javafx.scene.control.ButtonBar; |
|||
import javafx.scene.control.ButtonType; |
|||
import javafx.scene.control.Dialog; |
|||
import javafx.scene.control.DialogPane; |
|||
import org.controlsfx.tools.Borders; |
|||
|
|||
import java.io.IOException; |
|||
|
|||
public class PreferencesDialog extends Dialog<Void> { |
|||
public PreferencesDialog(PreferenceGroup initialGroup) { |
|||
final DialogPane dialogPane = getDialogPane(); |
|||
|
|||
try { |
|||
FXMLLoader preferencesLoader = new FXMLLoader(AppController.class.getResource("preferences/preferences.fxml")); |
|||
dialogPane.setContent(Borders.wrap(preferencesLoader.load()).lineBorder().outerPadding(0).innerPadding(0).buildAll()); |
|||
PreferencesController preferencesController = preferencesLoader.getController(); |
|||
preferencesController.initializeView(Config.get()); |
|||
preferencesController.selectGroup(initialGroup); |
|||
|
|||
final ButtonType closeButtonType = new javafx.scene.control.ButtonType("Close", ButtonBar.ButtonData.CANCEL_CLOSE); |
|||
dialogPane.getButtonTypes().addAll(closeButtonType); |
|||
dialogPane.setPrefWidth(650); |
|||
dialogPane.setPrefHeight(500); |
|||
} catch(IOException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,306 @@ |
|||
package com.sparrowwallet.sparrow.preferences; |
|||
|
|||
import com.google.common.net.HostAndPort; |
|||
import com.sparrowwallet.sparrow.control.TextFieldValidator; |
|||
import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch; |
|||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; |
|||
import com.sparrowwallet.sparrow.io.Config; |
|||
import com.sparrowwallet.sparrow.io.ElectrumServer; |
|||
import com.sparrowwallet.sparrow.io.ServerException; |
|||
import javafx.application.Platform; |
|||
import javafx.beans.value.ChangeListener; |
|||
import javafx.fxml.FXML; |
|||
import javafx.scene.control.Button; |
|||
import javafx.scene.control.Control; |
|||
import javafx.scene.control.TextArea; |
|||
import javafx.scene.control.TextField; |
|||
import javafx.scene.paint.Color; |
|||
import javafx.stage.FileChooser; |
|||
import javafx.stage.Stage; |
|||
import org.controlsfx.glyphfont.Glyph; |
|||
import org.controlsfx.validation.ValidationResult; |
|||
import org.controlsfx.validation.ValidationSupport; |
|||
import org.controlsfx.validation.Validator; |
|||
import org.controlsfx.validation.decoration.StyleClassValidationDecoration; |
|||
import org.jetbrains.annotations.NotNull; |
|||
|
|||
import javax.net.ssl.SSLHandshakeException; |
|||
import java.io.File; |
|||
import java.io.FileInputStream; |
|||
import java.security.cert.CertificateFactory; |
|||
import java.util.List; |
|||
|
|||
public class ServerPreferencesController extends PreferencesDetailController { |
|||
@FXML |
|||
private TextField host; |
|||
|
|||
@FXML |
|||
private TextField port; |
|||
|
|||
@FXML |
|||
private UnlabeledToggleSwitch useSsl; |
|||
|
|||
@FXML |
|||
private TextField certificate; |
|||
|
|||
@FXML |
|||
private Button certificateSelect; |
|||
|
|||
@FXML |
|||
private UnlabeledToggleSwitch useProxy; |
|||
|
|||
@FXML |
|||
private TextField proxyHost; |
|||
|
|||
@FXML |
|||
private TextField proxyPort; |
|||
|
|||
@FXML |
|||
private Button testConnection; |
|||
|
|||
@FXML |
|||
private TextArea testResults; |
|||
|
|||
private final ValidationSupport validationSupport = new ValidationSupport(); |
|||
|
|||
@Override |
|||
public void initializeView(Config config) { |
|||
Platform.runLater(this::setupValidation); |
|||
|
|||
port.setTextFormatter(new TextFieldValidator(TextFieldValidator.ValidationModus.MAX_INTEGERS, 5).getFormatter()); |
|||
proxyPort.setTextFormatter(new TextFieldValidator(TextFieldValidator.ValidationModus.MAX_INTEGERS, 5).getFormatter()); |
|||
|
|||
host.textProperty().addListener(getElectrumServerListener(config)); |
|||
port.textProperty().addListener(getElectrumServerListener(config)); |
|||
|
|||
proxyHost.textProperty().addListener(getProxyListener(config)); |
|||
proxyPort.textProperty().addListener(getProxyListener(config)); |
|||
|
|||
useSsl.selectedProperty().addListener((observable, oldValue, newValue) -> { |
|||
setElectrumServerInConfig(config); |
|||
certificate.setDisable(!newValue); |
|||
certificateSelect.setDisable(!newValue); |
|||
}); |
|||
|
|||
certificate.textProperty().addListener((observable, oldValue, newValue) -> { |
|||
File crtFile = getCertificate(newValue); |
|||
if(crtFile != null) { |
|||
config.setElectrumServerCert(crtFile); |
|||
} else { |
|||
config.setElectrumServerCert(null); |
|||
} |
|||
}); |
|||
|
|||
certificateSelect.setOnAction(event -> { |
|||
Stage window = new Stage(); |
|||
|
|||
FileChooser fileChooser = new FileChooser(); |
|||
fileChooser.setTitle("Select Electrum Server certificate"); |
|||
fileChooser.getExtensionFilters().addAll( |
|||
new FileChooser.ExtensionFilter("All Files", "*.*"), |
|||
new FileChooser.ExtensionFilter("CRT", "*.crt") |
|||
); |
|||
|
|||
File file = fileChooser.showOpenDialog(window); |
|||
if(file != null) { |
|||
certificate.setText(file.getAbsolutePath()); |
|||
} |
|||
}); |
|||
|
|||
useProxy.selectedProperty().addListener((observable, oldValue, newValue) -> { |
|||
config.setUseProxy(newValue); |
|||
proxyHost.setText(proxyHost.getText() + " "); |
|||
proxyHost.setText(proxyHost.getText().trim()); |
|||
proxyHost.setDisable(!newValue); |
|||
proxyPort.setDisable(!newValue); |
|||
|
|||
if(newValue) { |
|||
useSsl.setSelected(true); |
|||
useSsl.setDisable(true); |
|||
} else { |
|||
useSsl.setDisable(false); |
|||
} |
|||
}); |
|||
|
|||
testConnection.setOnAction(event -> { |
|||
try { |
|||
ElectrumServer.closeActiveConnection(); |
|||
} catch (ServerException e) { |
|||
testResults.setText("Failed to disconnect:\n" + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage())); |
|||
} |
|||
|
|||
ElectrumServer.ServerVersionService serverVersionService = new ElectrumServer.ServerVersionService(); |
|||
serverVersionService.setOnSucceeded(successEvent -> { |
|||
List<String> serverVersion = serverVersionService.getValue(); |
|||
testResults.setText("Connected to " + serverVersion.get(0) + " on protocol version " + serverVersion.get(1)); |
|||
testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.CHECK_CIRCLE, Color.rgb(80, 161, 79))); |
|||
|
|||
ElectrumServer.ServerBannerService serverBannerService = new ElectrumServer.ServerBannerService(); |
|||
serverBannerService.setOnSucceeded(bannerSuccessEvent -> { |
|||
testResults.setText(testResults.getText() + "\nServer Banner: " + serverBannerService.getValue()); |
|||
}); |
|||
serverBannerService.setOnFailed(bannerFailEvent -> { |
|||
testResults.setText(testResults.getText() + "\nServer Banner: None"); |
|||
}); |
|||
serverBannerService.start(); |
|||
}); |
|||
serverVersionService.setOnFailed(failEvent -> { |
|||
Throwable e = failEvent.getSource().getException(); |
|||
String reason = e.getCause() != null ? e.getCause().getMessage() : e.getMessage(); |
|||
if(e.getCause() != null && e.getCause() instanceof SSLHandshakeException) { |
|||
reason = "SSL Handshake Error\n" + reason; |
|||
} |
|||
|
|||
testResults.setText("Could not connect:\n\n" + reason); |
|||
testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.EXCLAMATION_CIRCLE, Color.rgb(202, 18, 67))); |
|||
}); |
|||
testResults.setText("Connecting to " + config.getElectrumServer() + "..."); |
|||
testConnection.setGraphic(getGlyph(FontAwesome5.Glyph.ELLIPSIS_H, null)); |
|||
serverVersionService.start(); |
|||
}); |
|||
|
|||
String electrumServer = config.getElectrumServer(); |
|||
if(electrumServer != null) { |
|||
ElectrumServer.Protocol protocol = ElectrumServer.Protocol.getProtocol(electrumServer); |
|||
|
|||
if(protocol != null) { |
|||
boolean ssl = protocol.equals(ElectrumServer.Protocol.SSL); |
|||
useSsl.setSelected(ssl); |
|||
certificate.setDisable(!ssl); |
|||
certificateSelect.setDisable(!ssl); |
|||
|
|||
HostAndPort server = protocol.getServerHostAndPort(electrumServer); |
|||
host.setText(server.getHost()); |
|||
if(server.hasPort()) { |
|||
port.setText(Integer.toString(server.getPort())); |
|||
} |
|||
} |
|||
} |
|||
|
|||
File certificateFile = config.getElectrumServerCert(); |
|||
if(certificateFile != null) { |
|||
certificate.setText(certificateFile.getAbsolutePath()); |
|||
} |
|||
|
|||
useProxy.setSelected(config.isUseProxy()); |
|||
proxyHost.setDisable(!config.isUseProxy()); |
|||
proxyPort.setDisable(!config.isUseProxy()); |
|||
|
|||
if(config.isUseProxy()) { |
|||
useSsl.setSelected(true); |
|||
useSsl.setDisable(true); |
|||
} |
|||
|
|||
String proxyServer = config.getProxyServer(); |
|||
if(proxyServer != null) { |
|||
HostAndPort server = HostAndPort.fromString(proxyServer); |
|||
proxyHost.setText(server.getHost()); |
|||
if(server.hasPort()) { |
|||
proxyPort.setText(Integer.toString(server.getPort())); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void setupValidation() { |
|||
validationSupport.registerValidator(host, Validator.combine( |
|||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid host name", getHost(newValue) == null) |
|||
)); |
|||
|
|||
validationSupport.registerValidator(port, Validator.combine( |
|||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid port", !newValue.isEmpty() && !isValidPort(Integer.parseInt(newValue))) |
|||
)); |
|||
|
|||
validationSupport.registerValidator(proxyHost, Validator.combine( |
|||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Proxy host required", useProxy.isSelected() && newValue.isEmpty()), |
|||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid host name", getHost(newValue) == null) |
|||
)); |
|||
|
|||
validationSupport.registerValidator(proxyPort, Validator.combine( |
|||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid proxy port", !newValue.isEmpty() && !isValidPort(Integer.parseInt(newValue))) |
|||
)); |
|||
|
|||
validationSupport.registerValidator(certificate, Validator.combine( |
|||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid certificate file", newValue != null && !newValue.isEmpty() && getCertificate(newValue) == null) |
|||
)); |
|||
|
|||
validationSupport.setValidationDecorator(new StyleClassValidationDecoration()); |
|||
} |
|||
|
|||
@NotNull |
|||
private ChangeListener<String> getElectrumServerListener(Config config) { |
|||
return (observable, oldValue, newValue) -> { |
|||
setElectrumServerInConfig(config); |
|||
}; |
|||
} |
|||
|
|||
private void setElectrumServerInConfig(Config config) { |
|||
String hostAsString = getHost(host.getText()); |
|||
Integer portAsInteger = getPort(port.getText()); |
|||
if(hostAsString != null && portAsInteger != null && isValidPort(portAsInteger)) { |
|||
config.setElectrumServer(getProtocol().toUrlString(hostAsString, portAsInteger)); |
|||
} else if(hostAsString != null) { |
|||
config.setElectrumServer(getProtocol().toUrlString(hostAsString)); |
|||
} |
|||
} |
|||
|
|||
@NotNull |
|||
private ChangeListener<String> getProxyListener(Config config) { |
|||
return (observable, oldValue, newValue) -> { |
|||
String hostAsString = getHost(proxyHost.getText()); |
|||
Integer portAsInteger = getPort(proxyPort.getText()); |
|||
if(hostAsString != null && portAsInteger != null && isValidPort(portAsInteger)) { |
|||
config.setProxyServer(HostAndPort.fromParts(hostAsString, portAsInteger).toString()); |
|||
} else if(hostAsString != null) { |
|||
config.setProxyServer(HostAndPort.fromHost(hostAsString).toString()); |
|||
} |
|||
}; |
|||
} |
|||
|
|||
private ElectrumServer.Protocol getProtocol() { |
|||
return (useSsl.isSelected() ? ElectrumServer.Protocol.SSL : ElectrumServer.Protocol.TCP); |
|||
} |
|||
|
|||
private String getHost(String text) { |
|||
try { |
|||
return HostAndPort.fromHost(text).getHost(); |
|||
} catch(IllegalArgumentException e) { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private Integer getPort(String text) { |
|||
try { |
|||
return Integer.parseInt(text); |
|||
} catch(NumberFormatException e) { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private File getCertificate(String crtFileLocation) { |
|||
try { |
|||
File crtFile = new File(crtFileLocation); |
|||
if(!crtFile.exists()) { |
|||
return null; |
|||
} |
|||
|
|||
CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(crtFile)); |
|||
return crtFile; |
|||
} catch (Exception e) { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private Glyph getGlyph(FontAwesome5.Glyph glyphName, Color color) { |
|||
Glyph glyph = new Glyph(FontAwesome5.FONT_NAME, glyphName); |
|||
glyph.setFontSize(13); |
|||
if(color != null) { |
|||
glyph.setColor(color); |
|||
} |
|||
|
|||
return glyph; |
|||
} |
|||
|
|||
private static boolean isValidPort(int port) { |
|||
return port >= 0 && port <= 65535; |
|||
} |
|||
} |
Binary file not shown.
@ -0,0 +1,34 @@ |
|||
.dialog-pane .content { |
|||
-fx-padding: 0; |
|||
} |
|||
|
|||
.list-menu { |
|||
-fx-pref-width: 160; |
|||
-fx-background-color: #3da0e3; |
|||
} |
|||
|
|||
.list-item { |
|||
-fx-pref-width: 160; |
|||
-fx-padding: 0 20 0 20; |
|||
-fx-background-color: #3da0e3; |
|||
} |
|||
|
|||
.list-item * { |
|||
-fx-fill: #fff; |
|||
} |
|||
|
|||
.list-item:hover { |
|||
-fx-background-color: #4aa7e5; |
|||
} |
|||
|
|||
.list-item:selected { |
|||
-fx-background-color: #1e88cf; |
|||
} |
|||
|
|||
#preferencesPane { |
|||
-fx-background-color: -fx-background; |
|||
} |
|||
|
|||
.scroll-pane { |
|||
-fx-background-color: transparent; |
|||
} |
@ -0,0 +1,36 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
|
|||
<?import java.lang.*?> |
|||
<?import java.util.*?> |
|||
<?import javafx.scene.*?> |
|||
<?import javafx.scene.control.*?> |
|||
<?import javafx.scene.layout.*?> |
|||
|
|||
<?import javafx.geometry.Insets?> |
|||
<?import com.sparrowwallet.sparrow.preferences.PreferenceGroup?> |
|||
<?import org.controlsfx.glyphfont.Glyph?> |
|||
<BorderPane stylesheets="@../general.css, @preferences.css" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="com.sparrowwallet.sparrow.preferences.PreferencesController"> |
|||
<padding> |
|||
<Insets top="0" left="0" right="0" bottom="0" /> |
|||
</padding> |
|||
<left> |
|||
<VBox styleClass="list-menu"> |
|||
<ToggleButton VBox.vgrow="ALWAYS" text="Server" wrapText="true" textAlignment="CENTER" contentDisplay="TOP" styleClass="list-item" maxHeight="Infinity" toggleGroup="$preferencesMenu"> |
|||
<toggleGroup> |
|||
<ToggleGroup fx:id="preferencesMenu" /> |
|||
</toggleGroup> |
|||
<graphic> |
|||
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="SERVER" /> |
|||
</graphic> |
|||
<userData> |
|||
<PreferenceGroup fx:constant="SERVER"/> |
|||
</userData> |
|||
</ToggleButton> |
|||
</VBox> |
|||
</left> |
|||
<center> |
|||
<StackPane fx:id="preferencesPane"> |
|||
|
|||
</StackPane> |
|||
</center> |
|||
</BorderPane> |
@ -0,0 +1,71 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
|
|||
<?import java.lang.*?> |
|||
<?import java.util.*?> |
|||
<?import javafx.scene.*?> |
|||
<?import javafx.scene.control.*?> |
|||
<?import javafx.scene.layout.*?> |
|||
|
|||
<?import javafx.geometry.Insets?> |
|||
<?import tornadofx.control.Form?> |
|||
<?import tornadofx.control.Fieldset?> |
|||
<?import tornadofx.control.Field?> |
|||
<?import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch?> |
|||
<?import org.controlsfx.glyphfont.Glyph?> |
|||
<GridPane hgap="10.0" vgap="10.0" stylesheets="@preferences.css, @../general.css" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.preferences.ServerPreferencesController"> |
|||
<padding> |
|||
<Insets left="25.0" right="25.0" top="25.0" /> |
|||
</padding> |
|||
<columnConstraints> |
|||
<ColumnConstraints percentWidth="100" /> |
|||
</columnConstraints> |
|||
<rowConstraints> |
|||
<RowConstraints /> |
|||
</rowConstraints> |
|||
|
|||
<Form GridPane.columnIndex="0" GridPane.rowIndex="0"> |
|||
<Fieldset inputGrow="SOMETIMES" text="Electrum Server"> |
|||
<Field text="URL:"> |
|||
<TextField fx:id="host" promptText="e.g. 127.0.0.1"/> |
|||
<TextField fx:id="port" promptText="e.g. 50002" prefWidth="80" /> |
|||
</Field> |
|||
<Field text="Use SSL:"> |
|||
<UnlabeledToggleSwitch fx:id="useSsl"/> |
|||
</Field> |
|||
<Field text="Certificate:" styleClass="label-button"> |
|||
<TextField fx:id="certificate" editable="false" promptText="Optional server certificate (.crt)"/> |
|||
<Button fx:id="certificateSelect" maxWidth="25" minWidth="-Infinity" prefWidth="30" text="Ed"> |
|||
<graphic> |
|||
<Glyph fontFamily="FontAwesome" icon="EDIT" prefWidth="15" /> |
|||
</graphic> |
|||
</Button> |
|||
</Field> |
|||
</Fieldset> |
|||
|
|||
<Fieldset inputGrow="SOMETIMES" text="Proxy"> |
|||
<Field text="Use Proxy:"> |
|||
<UnlabeledToggleSwitch fx:id="useProxy"/> |
|||
</Field> |
|||
<Field text="Proxy URL:"> |
|||
<TextField fx:id="proxyHost" /> |
|||
<TextField fx:id="proxyPort" prefWidth="80" /> |
|||
</Field> |
|||
</Fieldset> |
|||
</Form> |
|||
|
|||
<StackPane GridPane.columnIndex="0" GridPane.rowIndex="1"> |
|||
<Button fx:id="testConnection" text="Test Connection"> |
|||
<graphic> |
|||
<Glyph fontFamily="FontAwesome" icon="QUESTION_CIRCLE" prefWidth="13" /> |
|||
</graphic> |
|||
</Button> |
|||
</StackPane> |
|||
|
|||
<StackPane GridPane.columnIndex="0" GridPane.rowIndex="2"> |
|||
<padding> |
|||
<Insets top="10.0" bottom="20.0"/> |
|||
</padding> |
|||
<TextArea fx:id="testResults" editable="false" wrapText="true"/> |
|||
</StackPane> |
|||
|
|||
</GridPane> |
Binary file not shown.
After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 17 KiB |
Loading…
Reference in new issue