42 changed files with 3647 additions and 141 deletions
@ -0,0 +1,65 @@ |
|||||
|
package com.sparrowwallet.sparrow; |
||||
|
|
||||
|
import com.sparrowwallet.drongo.wallet.Keystore; |
||||
|
import com.sparrowwallet.sparrow.control.KeystorePassphraseDialog; |
||||
|
import com.sparrowwallet.sparrow.control.TextUtils; |
||||
|
import javafx.scene.Node; |
||||
|
import javafx.scene.control.Alert; |
||||
|
import javafx.scene.control.ButtonType; |
||||
|
import javafx.scene.control.Label; |
||||
|
import javafx.scene.text.Font; |
||||
|
import org.controlsfx.control.HyperlinkLabel; |
||||
|
|
||||
|
import java.util.Arrays; |
||||
|
import java.util.Optional; |
||||
|
import java.util.regex.Matcher; |
||||
|
import java.util.regex.Pattern; |
||||
|
|
||||
|
import static com.sparrowwallet.sparrow.AppServices.moveToActiveWindowScreen; |
||||
|
import static com.sparrowwallet.sparrow.AppServices.setStageIcon; |
||||
|
|
||||
|
public class DefaultInteractionServices implements InteractionServices { |
||||
|
@Override |
||||
|
public Optional<ButtonType> showAlert(String title, String content, Alert.AlertType alertType, Node graphic, ButtonType... buttons) { |
||||
|
Alert alert = new Alert(alertType, content, buttons); |
||||
|
setStageIcon(alert.getDialogPane().getScene().getWindow()); |
||||
|
alert.getDialogPane().getScene().getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm()); |
||||
|
alert.setTitle(title); |
||||
|
alert.setHeaderText(title); |
||||
|
if(graphic != null) { |
||||
|
alert.setGraphic(graphic); |
||||
|
} |
||||
|
|
||||
|
Pattern linkPattern = Pattern.compile("\\[(http.+)]"); |
||||
|
Matcher matcher = linkPattern.matcher(content); |
||||
|
if(matcher.find()) { |
||||
|
String link = matcher.group(1); |
||||
|
HyperlinkLabel hyperlinkLabel = new HyperlinkLabel(content); |
||||
|
hyperlinkLabel.setMaxWidth(Double.MAX_VALUE); |
||||
|
hyperlinkLabel.setMaxHeight(Double.MAX_VALUE); |
||||
|
hyperlinkLabel.getStyleClass().add("content"); |
||||
|
Label label = new Label(); |
||||
|
hyperlinkLabel.setPrefWidth(Math.max(360, TextUtils.computeTextWidth(label.getFont(), link, 0.0D) + 50)); |
||||
|
hyperlinkLabel.setOnAction(event -> { |
||||
|
alert.close(); |
||||
|
AppServices.get().getApplication().getHostServices().showDocument(link); |
||||
|
}); |
||||
|
alert.getDialogPane().setContent(hyperlinkLabel); |
||||
|
} |
||||
|
|
||||
|
String[] lines = content.split("\r\n|\r|\n"); |
||||
|
if(lines.length > 3 || org.controlsfx.tools.Platform.getCurrent() == org.controlsfx.tools.Platform.WINDOWS) { |
||||
|
double numLines = Arrays.stream(lines).mapToDouble(line -> Math.ceil(TextUtils.computeTextWidth(Font.getDefault(), line, 0) / 300)).sum(); |
||||
|
alert.getDialogPane().setPrefHeight(200 + numLines * 20); |
||||
|
} |
||||
|
|
||||
|
moveToActiveWindowScreen(alert); |
||||
|
return alert.showAndWait(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Optional<String> requestPassphrase(String walletName, Keystore keystore) { |
||||
|
KeystorePassphraseDialog passphraseDialog = new KeystorePassphraseDialog(walletName, keystore); |
||||
|
return passphraseDialog.showAndWait(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,13 @@ |
|||||
|
package com.sparrowwallet.sparrow; |
||||
|
|
||||
|
import com.sparrowwallet.drongo.wallet.Keystore; |
||||
|
import javafx.scene.Node; |
||||
|
import javafx.scene.control.Alert; |
||||
|
import javafx.scene.control.ButtonType; |
||||
|
|
||||
|
import java.util.Optional; |
||||
|
|
||||
|
public interface InteractionServices { |
||||
|
Optional<ButtonType> showAlert(String title, String content, Alert.AlertType alertType, Node graphic, ButtonType... buttons); |
||||
|
Optional<String> requestPassphrase(String walletName, Keystore keystore); |
||||
|
} |
@ -0,0 +1,77 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal; |
||||
|
|
||||
|
import com.googlecode.lanterna.TerminalSize; |
||||
|
import com.googlecode.lanterna.gui2.ActionListBox; |
||||
|
import com.googlecode.lanterna.gui2.dialogs.ActionListDialogBuilder; |
||||
|
import com.googlecode.lanterna.gui2.dialogs.FileDialogBuilder; |
||||
|
import com.sparrowwallet.drongo.wallet.Wallet; |
||||
|
import com.sparrowwallet.sparrow.AppServices; |
||||
|
import com.sparrowwallet.sparrow.io.Config; |
||||
|
import com.sparrowwallet.sparrow.io.Storage; |
||||
|
import com.sparrowwallet.sparrow.terminal.preferences.GeneralDialog; |
||||
|
import com.sparrowwallet.sparrow.terminal.preferences.ServerStatusDialog; |
||||
|
import com.sparrowwallet.sparrow.terminal.preferences.ServerTypeDialog; |
||||
|
import com.sparrowwallet.sparrow.terminal.wallet.LoadWallet; |
||||
|
|
||||
|
import java.io.File; |
||||
|
import java.util.Map; |
||||
|
import java.util.Optional; |
||||
|
|
||||
|
public class MasterActionListBox extends ActionListBox { |
||||
|
public static final int MAX_RECENT_WALLETS = 6; |
||||
|
|
||||
|
public MasterActionListBox(SparrowTerminal sparrowTerminal) { |
||||
|
super(new TerminalSize(14, 3)); |
||||
|
|
||||
|
addItem("Wallets", () -> { |
||||
|
ActionListDialogBuilder builder = new ActionListDialogBuilder(); |
||||
|
builder.setTitle("Wallets"); |
||||
|
for(int i = 0; i < Config.get().getRecentWalletFiles().size() && i < MAX_RECENT_WALLETS; i++) { |
||||
|
File recentWalletFile = Config.get().getRecentWalletFiles().get(i); |
||||
|
Storage storage = new Storage(recentWalletFile); |
||||
|
|
||||
|
Optional<Wallet> optWallet = AppServices.get().getOpenWallets().entrySet().stream() |
||||
|
.filter(entry -> entry.getValue().getWalletFile().equals(recentWalletFile)).map(Map.Entry::getKey) |
||||
|
.map(wallet -> wallet.isMasterWallet() ? wallet : wallet.getMasterWallet()).findFirst(); |
||||
|
if(optWallet.isPresent()) { |
||||
|
builder.addAction(storage.getWalletName(null) + "*", () -> LoadWallet.getOpeningDialog(optWallet.get()).showDialog(SparrowTerminal.get().getGui())); |
||||
|
} else { |
||||
|
builder.addAction(storage.getWalletName(null), new LoadWallet(storage)); |
||||
|
} |
||||
|
} |
||||
|
builder.addAction("Open Wallet...", () -> { |
||||
|
FileDialogBuilder openBuilder = new FileDialogBuilder().setTitle("Open Wallet"); |
||||
|
openBuilder.setShowHiddenDirectories(true); |
||||
|
openBuilder.setSelectedFile(Storage.getWalletsDir()); |
||||
|
File file = openBuilder.build().showDialog(SparrowTerminal.get().getGui()); |
||||
|
if(file != null) { |
||||
|
LoadWallet loadWallet = new LoadWallet(new Storage(file)); |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(loadWallet); |
||||
|
} |
||||
|
}); |
||||
|
builder.build().showDialog(SparrowTerminal.get().getGui()); |
||||
|
}); |
||||
|
|
||||
|
addItem("Preferences", () -> { |
||||
|
new ActionListDialogBuilder() |
||||
|
.setTitle("Preferences") |
||||
|
.addAction("General", () -> { |
||||
|
GeneralDialog generalDialog = new GeneralDialog(); |
||||
|
generalDialog.showDialog(sparrowTerminal.getGui()); |
||||
|
}) |
||||
|
.addAction("Server", () -> { |
||||
|
if(Config.get().hasServer()) { |
||||
|
ServerStatusDialog serverStatusDialog = new ServerStatusDialog(); |
||||
|
serverStatusDialog.showDialog(sparrowTerminal.getGui()); |
||||
|
} else { |
||||
|
ServerTypeDialog serverTypeDialog = new ServerTypeDialog(); |
||||
|
serverTypeDialog.showDialog(sparrowTerminal.getGui()); |
||||
|
} |
||||
|
}) |
||||
|
.build() |
||||
|
.showDialog(sparrowTerminal.getGui()); |
||||
|
}); |
||||
|
|
||||
|
addItem("Quit", sparrowTerminal::stop); |
||||
|
} |
||||
|
} |
@ -0,0 +1,20 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal; |
||||
|
|
||||
|
import com.googlecode.lanterna.gui2.BasicWindow; |
||||
|
import com.googlecode.lanterna.gui2.BorderLayout; |
||||
|
import com.googlecode.lanterna.gui2.Panel; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
public class MasterActionWindow extends BasicWindow { |
||||
|
public MasterActionWindow(SparrowTerminal sparrowTerminal) { |
||||
|
setHints(List.of(Hint.CENTERED)); |
||||
|
|
||||
|
Panel panel = new Panel(new BorderLayout()); |
||||
|
|
||||
|
MasterActionListBox masterActionListBox = new MasterActionListBox(sparrowTerminal); |
||||
|
panel.addComponent(masterActionListBox, BorderLayout.Location.CENTER); |
||||
|
|
||||
|
setComponent(panel); |
||||
|
} |
||||
|
} |
@ -0,0 +1,79 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal; |
||||
|
|
||||
|
import com.googlecode.lanterna.TextColor; |
||||
|
import com.googlecode.lanterna.gui2.DefaultWindowManager; |
||||
|
import com.googlecode.lanterna.gui2.EmptySpace; |
||||
|
import com.googlecode.lanterna.screen.Screen; |
||||
|
import com.googlecode.lanterna.screen.TerminalScreen; |
||||
|
import com.googlecode.lanterna.terminal.DefaultTerminalFactory; |
||||
|
import com.googlecode.lanterna.terminal.Terminal; |
||||
|
import com.sparrowwallet.drongo.wallet.Wallet; |
||||
|
import com.sparrowwallet.sparrow.AppServices; |
||||
|
import com.sparrowwallet.sparrow.EventManager; |
||||
|
import com.sparrowwallet.sparrow.terminal.wallet.WalletData; |
||||
|
import javafx.application.Platform; |
||||
|
import org.slf4j.Logger; |
||||
|
import org.slf4j.LoggerFactory; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
public class SparrowTerminal { |
||||
|
private static final Logger log = LoggerFactory.getLogger(SparrowTerminal.class); |
||||
|
|
||||
|
private static SparrowTerminal sparrowTerminal; |
||||
|
|
||||
|
private final Terminal terminal; |
||||
|
private final Screen screen; |
||||
|
private final SparrowTextGui gui; |
||||
|
|
||||
|
private final Map<Wallet, WalletData> walletData = new HashMap<>(); |
||||
|
|
||||
|
private SparrowTerminal() throws IOException { |
||||
|
this.terminal = new DefaultTerminalFactory().createTerminal(); |
||||
|
this.screen = new TerminalScreen(terminal); |
||||
|
this.gui = new SparrowTextGui(this, screen, new DefaultWindowManager(), new EmptySpace(TextColor.ANSI.BLUE)); |
||||
|
EventManager.get().register(gui); |
||||
|
} |
||||
|
|
||||
|
public Screen getScreen() { |
||||
|
return screen; |
||||
|
} |
||||
|
|
||||
|
public SparrowTextGui getGui() { |
||||
|
return gui; |
||||
|
} |
||||
|
|
||||
|
public Map<Wallet, WalletData> getWalletData() { |
||||
|
return walletData; |
||||
|
} |
||||
|
|
||||
|
public void stop() { |
||||
|
try { |
||||
|
screen.stopScreen(); |
||||
|
terminal.exitPrivateMode(); |
||||
|
Platform.runLater(() -> { |
||||
|
AppServices.get().stop(); |
||||
|
Platform.exit(); |
||||
|
}); |
||||
|
} catch(Exception e) { |
||||
|
log.error("Could not stop terminal", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void startTerminal() { |
||||
|
try { |
||||
|
sparrowTerminal = new SparrowTerminal(); |
||||
|
sparrowTerminal.getScreen().startScreen(); |
||||
|
sparrowTerminal.getGui().getMainWindow().waitUntilClosed(); |
||||
|
} catch(Exception e) { |
||||
|
log.error("Could not start terminal", e); |
||||
|
System.err.println("Could not start terminal: " + e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static SparrowTerminal get() { |
||||
|
return sparrowTerminal; |
||||
|
} |
||||
|
} |
@ -0,0 +1,166 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal; |
||||
|
|
||||
|
import com.google.common.eventbus.Subscribe; |
||||
|
import com.googlecode.lanterna.TerminalSize; |
||||
|
import com.googlecode.lanterna.TextColor; |
||||
|
import com.googlecode.lanterna.gui2.*; |
||||
|
import com.googlecode.lanterna.screen.Screen; |
||||
|
import com.sparrowwallet.sparrow.AppServices; |
||||
|
import com.sparrowwallet.sparrow.event.*; |
||||
|
import com.sparrowwallet.sparrow.io.Config; |
||||
|
import com.sparrowwallet.sparrow.net.ServerType; |
||||
|
import javafx.animation.*; |
||||
|
import javafx.beans.property.DoubleProperty; |
||||
|
import javafx.beans.property.SimpleDoubleProperty; |
||||
|
import javafx.util.Duration; |
||||
|
|
||||
|
public class SparrowTextGui extends MultiWindowTextGUI { |
||||
|
private final BasicWindow mainWindow; |
||||
|
|
||||
|
private final Label connectedLabel; |
||||
|
private final Label statusLabel; |
||||
|
private final ProgressBar statusProgress; |
||||
|
|
||||
|
private PauseTransition wait; |
||||
|
private Timeline statusTimeline; |
||||
|
private final DoubleProperty progressProperty = new SimpleDoubleProperty(); |
||||
|
|
||||
|
public SparrowTextGui(SparrowTerminal sparrowTerminal, Screen screen, WindowManager windowManager, Component background) { |
||||
|
super(screen, windowManager, background); |
||||
|
|
||||
|
this.mainWindow = new MasterActionWindow(sparrowTerminal); |
||||
|
addWindow(mainWindow); |
||||
|
|
||||
|
Panel panel = new Panel(new BorderLayout()); |
||||
|
|
||||
|
Panel titleBar = new Panel(new GridLayout(2)); |
||||
|
new Label("Sparrow Terminal").addTo(titleBar); |
||||
|
this.connectedLabel = new Label("Disconnected"); |
||||
|
titleBar.addComponent(connectedLabel, GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER, true, false)); |
||||
|
panel.addComponent(titleBar, BorderLayout.Location.TOP); |
||||
|
|
||||
|
panel.addComponent(new EmptySpace(TextColor.ANSI.BLUE)); |
||||
|
|
||||
|
Panel statusBar = new Panel(new GridLayout(2)); |
||||
|
this.statusLabel = new Label("").addTo(statusBar); |
||||
|
this.statusProgress = new ProgressBar(0, 100, 10); |
||||
|
statusBar.addComponent(statusProgress, GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER, true, false)); |
||||
|
statusProgress.setVisible(false); |
||||
|
statusProgress.setLabelFormat(null); |
||||
|
progressProperty.addListener((observable, oldValue, newValue) -> statusProgress.setValue((int) (newValue.doubleValue() * 100))); |
||||
|
|
||||
|
panel.addComponent(statusBar, BorderLayout.Location.BOTTOM); |
||||
|
getBackgroundPane().setComponent(panel); |
||||
|
|
||||
|
getMainWindow().addWindowListener(new WindowListenerAdapter() { |
||||
|
@Override |
||||
|
public void onResized(Window window, TerminalSize oldSize, TerminalSize newSize) { |
||||
|
titleBar.invalidate(); |
||||
|
statusBar.invalidate(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
AppServices.get().start(); |
||||
|
} |
||||
|
|
||||
|
public BasicWindow getMainWindow() { |
||||
|
return mainWindow; |
||||
|
} |
||||
|
|
||||
|
private void setConnectedLabel(boolean connected) { |
||||
|
getGUIThread().invokeLater(() -> { |
||||
|
connectedLabel.setText(connected ? "Connected" : "Disconnected"); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void connectionStart(ConnectionStartEvent event) { |
||||
|
statusUpdated(new StatusEvent(event.getStatus(), 120)); |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void connectionFailed(ConnectionFailedEvent event) { |
||||
|
setConnectedLabel(false); |
||||
|
statusUpdated(new StatusEvent("Connection failed: " + event.getMessage())); |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void connection(ConnectionEvent event) { |
||||
|
setConnectedLabel(true); |
||||
|
statusUpdated(new StatusEvent("Connected to " + Config.get().getServerDisplayName() + " at height " + event.getBlockHeight())); |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void disconnection(DisconnectionEvent event) { |
||||
|
if(!AppServices.isConnecting() && !AppServices.isConnected()) { |
||||
|
setConnectedLabel(false); |
||||
|
statusUpdated(new StatusEvent("Disconnected")); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void statusUpdated(StatusEvent event) { |
||||
|
getGUIThread().invokeLater(() -> statusLabel.setText(event.getStatus())); |
||||
|
|
||||
|
if(wait != null && wait.getStatus() == Animation.Status.RUNNING) { |
||||
|
wait.stop(); |
||||
|
} |
||||
|
wait = new PauseTransition(Duration.seconds(event.getShowDuration())); |
||||
|
wait.setOnFinished((e) -> { |
||||
|
if(statusLabel.getText().equals(event.getStatus())) { |
||||
|
getGUIThread().invokeLater(() -> statusLabel.setText("")); |
||||
|
} |
||||
|
}); |
||||
|
wait.play(); |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void timedEvent(TimedEvent event) { |
||||
|
if(event.getTimeMills() == 0) { |
||||
|
getGUIThread().invokeLater(() -> { |
||||
|
statusLabel.setText(""); |
||||
|
statusProgress.setVisible(false); |
||||
|
statusProgress.setValue(0); |
||||
|
}); |
||||
|
} else if(event.getTimeMills() < 0) { |
||||
|
getGUIThread().invokeLater(() -> { |
||||
|
statusLabel.setText(event.getStatus()); |
||||
|
statusProgress.setVisible(false); |
||||
|
}); |
||||
|
} else { |
||||
|
getGUIThread().invokeLater(() -> { |
||||
|
statusLabel.setText(event.getStatus()); |
||||
|
statusProgress.setVisible(true); |
||||
|
}); |
||||
|
statusTimeline = new Timeline( |
||||
|
new KeyFrame(Duration.ZERO, new KeyValue(progressProperty, 0)), |
||||
|
new KeyFrame(Duration.millis(event.getTimeMills()), e -> { |
||||
|
getGUIThread().invokeLater(() -> { |
||||
|
statusLabel.setText(""); |
||||
|
statusProgress.setVisible(false); |
||||
|
}); |
||||
|
}, new KeyValue(progressProperty, 1)) |
||||
|
); |
||||
|
statusTimeline.setCycleCount(1); |
||||
|
statusTimeline.play(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void walletHistoryStarted(WalletHistoryStartedEvent event) { |
||||
|
statusUpdated(new StatusEvent("Loading wallet history...")); |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void walletHistoryFinished(WalletHistoryFinishedEvent event) { |
||||
|
if(statusLabel.getText().equals("Loading wallet history...")) { |
||||
|
getGUIThread().invokeLater(() -> statusLabel.setText("")); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void walletHistoryFailed(WalletHistoryFailedEvent event) { |
||||
|
walletHistoryFinished(new WalletHistoryFinishedEvent(event.getWallet())); |
||||
|
statusUpdated(new StatusEvent("Error retrieving wallet history" + (Config.get().getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER ? ", trying another server..." : ""))); |
||||
|
} |
||||
|
} |
@ -0,0 +1,110 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal; |
||||
|
|
||||
|
import com.googlecode.lanterna.gui2.dialogs.MessageDialogBuilder; |
||||
|
import com.googlecode.lanterna.gui2.dialogs.MessageDialogButton; |
||||
|
import com.googlecode.lanterna.gui2.dialogs.TextInputDialogBuilder; |
||||
|
import com.sparrowwallet.drongo.wallet.Keystore; |
||||
|
import com.sparrowwallet.sparrow.InteractionServices; |
||||
|
import javafx.application.Platform; |
||||
|
import javafx.scene.Node; |
||||
|
import javafx.scene.control.Alert; |
||||
|
import javafx.scene.control.ButtonType; |
||||
|
|
||||
|
import java.text.BreakIterator; |
||||
|
import java.util.Arrays; |
||||
|
import java.util.Locale; |
||||
|
import java.util.Optional; |
||||
|
|
||||
|
public class TerminalInteractionServices implements InteractionServices { |
||||
|
private final Object alertShowing = new Object(); |
||||
|
private final Object passphraseShowing = new Object(); |
||||
|
|
||||
|
@Override |
||||
|
public Optional<ButtonType> showAlert(String title, String content, Alert.AlertType alertType, Node graphic, ButtonType... buttons) { |
||||
|
if(Platform.isFxApplicationThread()) { |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(() -> { |
||||
|
Optional<ButtonType> optButtonType = showMessageDialog(title, content, buttons); |
||||
|
Platform.runLater(() -> Platform.exitNestedEventLoop(alertShowing, optButtonType)); |
||||
|
}); |
||||
|
return (Optional<ButtonType>)Platform.enterNestedEventLoop(alertShowing); |
||||
|
} else { |
||||
|
return showMessageDialog(title, content, buttons); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private Optional<ButtonType> showMessageDialog(String title, String content, ButtonType[] buttons) { |
||||
|
String formattedContent = formatLines(content, 50); |
||||
|
|
||||
|
MessageDialogBuilder builder = new MessageDialogBuilder().setTitle(title).setText(formattedContent); |
||||
|
for(ButtonType buttonType : buttons) { |
||||
|
builder.addButton(getButton(buttonType)); |
||||
|
} |
||||
|
|
||||
|
MessageDialogButton button = builder.build().showDialog(SparrowTerminal.get().getGui()); |
||||
|
return Arrays.stream(buttons).filter(buttonType -> button.equals(getButton(buttonType))).findFirst(); |
||||
|
} |
||||
|
|
||||
|
private String formatLines(String input, int maxLength) { |
||||
|
StringBuilder builder = new StringBuilder(); |
||||
|
BreakIterator boundary = BreakIterator.getLineInstance(Locale.ROOT); |
||||
|
boundary.setText(input); |
||||
|
int start = boundary.first(); |
||||
|
int end = boundary.next(); |
||||
|
int lineLength = 0; |
||||
|
|
||||
|
while(end != BreakIterator.DONE) { |
||||
|
String word = input.substring(start,end); |
||||
|
lineLength = lineLength + word.length(); |
||||
|
if (lineLength >= maxLength) { |
||||
|
builder.append("\n"); |
||||
|
lineLength = word.length(); |
||||
|
} |
||||
|
builder.append(word); |
||||
|
start = end; |
||||
|
end = boundary.next(); |
||||
|
} |
||||
|
|
||||
|
return builder.toString(); |
||||
|
} |
||||
|
|
||||
|
private MessageDialogButton getButton(ButtonType buttonType) { |
||||
|
if(ButtonType.OK.equals(buttonType)) { |
||||
|
return MessageDialogButton.OK; |
||||
|
} |
||||
|
if(ButtonType.CANCEL.equals(buttonType)) { |
||||
|
return MessageDialogButton.Cancel; |
||||
|
} |
||||
|
if(ButtonType.YES.equals(buttonType)) { |
||||
|
return MessageDialogButton.Yes; |
||||
|
} |
||||
|
if(ButtonType.NO.equals(buttonType)) { |
||||
|
return MessageDialogButton.No; |
||||
|
} |
||||
|
if(ButtonType.CLOSE.equals(buttonType)) { |
||||
|
return MessageDialogButton.Close; |
||||
|
} |
||||
|
|
||||
|
throw new IllegalArgumentException("Cannot find button for " + buttonType); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Optional<String> requestPassphrase(String walletName, Keystore keystore) { |
||||
|
if(Platform.isFxApplicationThread()) { |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(() -> { |
||||
|
Optional<String> optPassphrase = showPassphraseDialog(walletName, keystore); |
||||
|
Platform.runLater(() -> Platform.exitNestedEventLoop(passphraseShowing, optPassphrase)); |
||||
|
}); |
||||
|
return (Optional<String>)Platform.enterNestedEventLoop(passphraseShowing); |
||||
|
} else { |
||||
|
return showPassphraseDialog(walletName, keystore); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private Optional<String> showPassphraseDialog(String walletName, Keystore keystore) { |
||||
|
TextInputDialogBuilder builder = new TextInputDialogBuilder().setTitle("Passphrase for " + walletName); |
||||
|
builder.setDescription("Enter the BIP39 passphrase for keystore:\n" + keystore.getLabel()); |
||||
|
builder.setPasswordInput(true); |
||||
|
String passphrase = builder.build().showDialog(SparrowTerminal.get().getGui()); |
||||
|
return passphrase == null ? Optional.empty() : Optional.of(passphrase); |
||||
|
} |
||||
|
} |
@ -0,0 +1,139 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.preferences; |
||||
|
|
||||
|
import com.googlecode.lanterna.TerminalSize; |
||||
|
import com.googlecode.lanterna.gui2.*; |
||||
|
import com.googlecode.lanterna.gui2.dialogs.DirectoryDialogBuilder; |
||||
|
import com.sparrowwallet.drongo.Network; |
||||
|
import com.sparrowwallet.sparrow.io.Config; |
||||
|
import com.sparrowwallet.sparrow.io.Server; |
||||
|
import com.sparrowwallet.sparrow.net.CoreAuthType; |
||||
|
import com.sparrowwallet.sparrow.net.Protocol; |
||||
|
import com.sparrowwallet.sparrow.terminal.SparrowTerminal; |
||||
|
|
||||
|
import java.io.File; |
||||
|
import java.util.List; |
||||
|
|
||||
|
public class BitcoinCoreDialog extends ServerUrlDialog { |
||||
|
private final ComboBox<String> authentication; |
||||
|
private final Label dataFolderLabel; |
||||
|
private final TextBox dataFolder; |
||||
|
private final Button selectDataFolder; |
||||
|
private final Label userPassLabel; |
||||
|
private final TextBox user; |
||||
|
private final TextBox pass; |
||||
|
|
||||
|
public BitcoinCoreDialog() { |
||||
|
super("Bitcoin Core"); |
||||
|
|
||||
|
setHints(List.of(Hint.CENTERED)); |
||||
|
Panel mainPanel = new Panel(new GridLayout(3).setHorizontalSpacing(2).setVerticalSpacing(0)); |
||||
|
|
||||
|
if(Config.get().getCoreServer() == null) { |
||||
|
Config.get().setCoreServer(new Server("http://127.0.0.1:" + Network.get().getDefaultPort())); |
||||
|
} |
||||
|
addUrlComponents(mainPanel, Config.get().getRecentCoreServers(), Config.get().getCoreServer()); |
||||
|
addLine(mainPanel); |
||||
|
|
||||
|
mainPanel.addComponent(new Label("Authentication")); |
||||
|
authentication = new ComboBox<>("Default", "User/Pass"); |
||||
|
authentication.setSelectedIndex(Config.get().getCoreAuthType() == CoreAuthType.USERPASS ? 0 : 1); |
||||
|
mainPanel.addComponent(authentication); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
dataFolderLabel = new Label("Data Folder"); |
||||
|
mainPanel.addComponent(dataFolderLabel); |
||||
|
dataFolder = new TextBox(new TerminalSize(30, 1), Config.get().getCoreDataDir() != null ? Config.get().getCoreDataDir().getAbsolutePath() : ""); |
||||
|
mainPanel.addComponent(dataFolder); |
||||
|
selectDataFolder = new Button("Select..."); |
||||
|
mainPanel.addComponent(selectDataFolder); |
||||
|
|
||||
|
userPassLabel = new Label("User/Pass"); |
||||
|
mainPanel.addComponent(userPassLabel); |
||||
|
user = new TextBox(new TerminalSize(16, 1)); |
||||
|
mainPanel.addComponent(user); |
||||
|
pass = new TextBox(new TerminalSize(16, 1)); |
||||
|
pass.setMask('*'); |
||||
|
mainPanel.addComponent(pass); |
||||
|
|
||||
|
if(Config.get().getCoreAuth() != null) { |
||||
|
String[] userPass = Config.get().getCoreAuth().split(":"); |
||||
|
if(userPass.length > 0) { |
||||
|
user.setText(userPass[0]); |
||||
|
} |
||||
|
if(userPass.length > 1) { |
||||
|
pass.setText(userPass[1]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
authentication.addListener((selectedIndex, previousSelection, changedByUserInteraction) -> { |
||||
|
dataFolderLabel.setVisible(selectedIndex == 0); |
||||
|
dataFolder.setVisible(selectedIndex == 0); |
||||
|
selectDataFolder.setVisible(selectedIndex == 0); |
||||
|
userPassLabel.setVisible(selectedIndex == 1); |
||||
|
user.setVisible(selectedIndex == 1); |
||||
|
pass.setVisible(selectedIndex == 1); |
||||
|
Config.get().setCoreAuthType(selectedIndex == 0 ? CoreAuthType.COOKIE : CoreAuthType.USERPASS); |
||||
|
}); |
||||
|
authentication.setSelectedIndex(Config.get().getCoreAuthType() == CoreAuthType.USERPASS ? 1 : 0); |
||||
|
|
||||
|
dataFolder.setTextChangeListener((newText, changedByUserInteraction) -> { |
||||
|
File dataDir = new File(newText); |
||||
|
if(dataDir.exists()) { |
||||
|
Config.get().setCoreDataDir(dataDir); |
||||
|
} |
||||
|
}); |
||||
|
selectDataFolder.addListener(button -> { |
||||
|
DirectoryDialogBuilder builder = new DirectoryDialogBuilder().setTitle("Select Bitcoin Core Data Folder").setActionLabel("Select"); |
||||
|
builder.setShowHiddenDirectories(true); |
||||
|
builder.setSelectedDirectory(Config.get().getCoreDataDir() == null ? new File(System.getProperty("user.home")) : Config.get().getCoreDataDir()); |
||||
|
File file = builder.build().showDialog(SparrowTerminal.get().getGui()); |
||||
|
if(file != null) { |
||||
|
dataFolder.setText(file.getAbsolutePath()); |
||||
|
} |
||||
|
}); |
||||
|
user.setTextChangeListener((newText, changedByUserInteraction) -> { |
||||
|
setCoreAuth(); |
||||
|
}); |
||||
|
pass.setTextChangeListener((newText, changedByUserInteraction) -> { |
||||
|
setCoreAuth(); |
||||
|
}); |
||||
|
|
||||
|
addLine(mainPanel); |
||||
|
addProxyComponents(mainPanel); |
||||
|
|
||||
|
Panel buttonPanel = new Panel(); |
||||
|
buttonPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(1)); |
||||
|
buttonPanel.addComponent(new Button("Test", this::onTest)); |
||||
|
buttonPanel.addComponent(new Button("Done", this::onDone)); |
||||
|
|
||||
|
addLine(mainPanel); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
buttonPanel.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER,false,false)).addTo(mainPanel); |
||||
|
setComponent(mainPanel); |
||||
|
} |
||||
|
|
||||
|
protected void setServerConfig() { |
||||
|
Server currentServer = getCurrentServer(); |
||||
|
if(currentServer != null) { |
||||
|
Config.get().setCoreServer(currentServer); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected void setServerAlias(Server server) { |
||||
|
Config.get().setCoreServerAlias(server); |
||||
|
} |
||||
|
|
||||
|
protected Protocol getProtocol() { |
||||
|
return Protocol.HTTP; |
||||
|
} |
||||
|
|
||||
|
protected void setProtocol(Protocol protocol) { |
||||
|
//empty
|
||||
|
} |
||||
|
|
||||
|
private void setCoreAuth() { |
||||
|
Config.get().setCoreAuth(user.getText() + ":" + pass.getText()); |
||||
|
} |
||||
|
} |
@ -0,0 +1,156 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.preferences; |
||||
|
|
||||
|
import com.googlecode.lanterna.TerminalSize; |
||||
|
import com.googlecode.lanterna.gui2.*; |
||||
|
import com.googlecode.lanterna.gui2.dialogs.DialogWindow; |
||||
|
import com.sparrowwallet.drongo.BitcoinUnit; |
||||
|
import com.sparrowwallet.sparrow.EventManager; |
||||
|
import com.sparrowwallet.sparrow.UnitFormat; |
||||
|
import com.sparrowwallet.sparrow.event.BitcoinUnitChangedEvent; |
||||
|
import com.sparrowwallet.sparrow.event.FiatCurrencySelectedEvent; |
||||
|
import com.sparrowwallet.sparrow.event.UnitFormatChangedEvent; |
||||
|
import com.sparrowwallet.sparrow.io.Config; |
||||
|
import com.sparrowwallet.sparrow.net.ExchangeSource; |
||||
|
import javafx.application.Platform; |
||||
|
import org.slf4j.Logger; |
||||
|
import org.slf4j.LoggerFactory; |
||||
|
|
||||
|
import java.util.Currency; |
||||
|
import java.util.List; |
||||
|
|
||||
|
public class GeneralDialog extends DialogWindow { |
||||
|
private static final Logger log = LoggerFactory.getLogger(GeneralDialog.class); |
||||
|
|
||||
|
private final ComboBox<BitcoinUnit> bitcoinUnit; |
||||
|
private final ComboBox<String> unitFormat; |
||||
|
|
||||
|
private final ComboBox<Currency> fiatCurrency; |
||||
|
private final ComboBox<ExchangeSource> exchangeSource; |
||||
|
|
||||
|
private final ComboBox.Listener fiatCurrencyListener = new ComboBox.Listener() { |
||||
|
@Override |
||||
|
public void onSelectionChanged(int selectedIndex, int previousSelection, boolean changedByUserInteraction) { |
||||
|
Currency newValue = fiatCurrency.getSelectedItem(); |
||||
|
if(newValue != null) { |
||||
|
Config.get().setFiatCurrency(newValue); |
||||
|
Platform.runLater(() -> { |
||||
|
EventManager.get().post(new FiatCurrencySelectedEvent(exchangeSource.getSelectedItem(), newValue)); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
public GeneralDialog() { |
||||
|
super("General Preferences"); |
||||
|
|
||||
|
setHints(List.of(Hint.CENTERED)); |
||||
|
|
||||
|
Panel mainPanel = new Panel(); |
||||
|
mainPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(5)); |
||||
|
|
||||
|
mainPanel.addComponent(new Label("Bitcoin Unit")); |
||||
|
bitcoinUnit = new ComboBox<>(BitcoinUnit.values()); |
||||
|
mainPanel.addComponent(bitcoinUnit); |
||||
|
|
||||
|
mainPanel.addComponent(new Label("Unit Format")); |
||||
|
unitFormat = new ComboBox<>("1,234.56", "1.235,56"); |
||||
|
mainPanel.addComponent(unitFormat); |
||||
|
|
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
mainPanel.addComponent(new Label("Currency")); |
||||
|
fiatCurrency = new ComboBox<>(); |
||||
|
mainPanel.addComponent(fiatCurrency); |
||||
|
|
||||
|
mainPanel.addComponent(new Label("Exchange rate source")); |
||||
|
exchangeSource = new ComboBox<>(ExchangeSource.values()); |
||||
|
mainPanel.addComponent(exchangeSource); |
||||
|
|
||||
|
bitcoinUnit.setSelectedItem(Config.get().getBitcoinUnit() == null ? BitcoinUnit.AUTO : Config.get().getBitcoinUnit()); |
||||
|
unitFormat.setSelectedIndex(Config.get().getUnitFormat() == UnitFormat.COMMA ? 1 : 0); |
||||
|
|
||||
|
if(Config.get().getExchangeSource() == null) { |
||||
|
Config.get().setExchangeSource(ExchangeSource.COINGECKO); |
||||
|
} |
||||
|
exchangeSource.setSelectedItem(Config.get().getExchangeSource()); |
||||
|
|
||||
|
bitcoinUnit.addListener((int selectedIndex, int previousSelection, boolean changedByUserInteraction) -> { |
||||
|
BitcoinUnit newValue = bitcoinUnit.getSelectedItem(); |
||||
|
Config.get().setBitcoinUnit(newValue); |
||||
|
Platform.runLater(() -> { |
||||
|
EventManager.get().post(new BitcoinUnitChangedEvent(newValue)); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
unitFormat.addListener((int selectedIndex, int previousSelection, boolean changedByUserInteraction) -> { |
||||
|
UnitFormat format = selectedIndex == 1 ? UnitFormat.COMMA : UnitFormat.DOT; |
||||
|
Config.get().setUnitFormat(format); |
||||
|
Platform.runLater(() -> { |
||||
|
EventManager.get().post(new UnitFormatChangedEvent(format)); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
exchangeSource.addListener((int selectedIndex, int previousSelection, boolean changedByUserInteraction) -> { |
||||
|
ExchangeSource source = exchangeSource.getSelectedItem(); |
||||
|
Config.get().setExchangeSource(source); |
||||
|
updateCurrencies(source); |
||||
|
}); |
||||
|
|
||||
|
updateCurrencies(exchangeSource.getSelectedItem()); |
||||
|
|
||||
|
Panel buttonPanel = new Panel(); |
||||
|
buttonPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(1)); |
||||
|
buttonPanel.addComponent(new Button("Done", this::onDone).setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.CENTER, GridLayout.Alignment.CENTER, true, false))); |
||||
|
|
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
buttonPanel.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER,false,false)).addTo(mainPanel); |
||||
|
setComponent(mainPanel); |
||||
|
} |
||||
|
|
||||
|
private void onDone() { |
||||
|
close(); |
||||
|
} |
||||
|
|
||||
|
private void updateCurrencies(ExchangeSource exchangeSource) { |
||||
|
Platform.runLater(() -> { |
||||
|
ExchangeSource.CurrenciesService currenciesService = new ExchangeSource.CurrenciesService(exchangeSource); |
||||
|
currenciesService.setOnSucceeded(event -> { |
||||
|
updateCurrencies(currenciesService.getValue()); |
||||
|
}); |
||||
|
currenciesService.setOnFailed(event -> { |
||||
|
log.error("Error retrieving currencies", event.getSource().getException()); |
||||
|
}); |
||||
|
currenciesService.start(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void updateCurrencies(List<Currency> currencies) { |
||||
|
fiatCurrency.removeListener(fiatCurrencyListener); |
||||
|
|
||||
|
fiatCurrency.clearItems(); |
||||
|
currencies.forEach(fiatCurrency::addItem); |
||||
|
|
||||
|
Currency configCurrency = Config.get().getFiatCurrency(); |
||||
|
if(configCurrency != null && currencies.contains(configCurrency)) { |
||||
|
fiatCurrency.setVisible(true); |
||||
|
fiatCurrency.setSelectedItem(configCurrency); |
||||
|
} else if(!currencies.isEmpty()) { |
||||
|
fiatCurrency.setVisible(true); |
||||
|
fiatCurrency.setSelectedIndex(0); |
||||
|
Config.get().setFiatCurrency(fiatCurrency.getSelectedItem()); |
||||
|
} else { |
||||
|
fiatCurrency.setVisible(false); |
||||
|
} |
||||
|
|
||||
|
//Always fire event regardless of previous selection to update rates
|
||||
|
Platform.runLater(() -> { |
||||
|
EventManager.get().post(new FiatCurrencySelectedEvent(exchangeSource.getSelectedItem(), fiatCurrency.getSelectedItem())); |
||||
|
}); |
||||
|
|
||||
|
fiatCurrency.addListener(fiatCurrencyListener); |
||||
|
} |
||||
|
} |
@ -0,0 +1,104 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.preferences; |
||||
|
|
||||
|
import com.googlecode.lanterna.TerminalSize; |
||||
|
import com.googlecode.lanterna.gui2.*; |
||||
|
import com.googlecode.lanterna.gui2.dialogs.FileDialogBuilder; |
||||
|
import com.sparrowwallet.sparrow.io.Config; |
||||
|
import com.sparrowwallet.sparrow.io.Server; |
||||
|
import com.sparrowwallet.sparrow.net.Protocol; |
||||
|
import com.sparrowwallet.sparrow.terminal.SparrowTerminal; |
||||
|
|
||||
|
import java.io.File; |
||||
|
import java.util.List; |
||||
|
|
||||
|
public class PrivateElectrumDialog extends ServerUrlDialog { |
||||
|
private final ComboBox<String> useSsl; |
||||
|
private final TextBox certificate; |
||||
|
private final Button selectCertificate; |
||||
|
|
||||
|
public PrivateElectrumDialog() { |
||||
|
super("Private Electrum"); |
||||
|
|
||||
|
setHints(List.of(Hint.CENTERED)); |
||||
|
Panel mainPanel = new Panel(new GridLayout(3).setHorizontalSpacing(2).setVerticalSpacing(0)); |
||||
|
|
||||
|
if(Config.get().getElectrumServer() == null) { |
||||
|
Config.get().setElectrumServer(new Server("tcp://127.0.0.1:50001")); |
||||
|
} |
||||
|
addUrlComponents(mainPanel, Config.get().getRecentElectrumServers(), Config.get().getElectrumServer()); |
||||
|
addLine(mainPanel); |
||||
|
|
||||
|
mainPanel.addComponent(new Label("Use SSL?")); |
||||
|
useSsl = new ComboBox<>("Yes", "No"); |
||||
|
useSsl.setSelectedIndex(1); |
||||
|
mainPanel.addComponent(useSsl); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
mainPanel.addComponent(new Label("Certificate")); |
||||
|
certificate = new TextBox(new TerminalSize(30, 1), Config.get().getElectrumServerCert() != null ? Config.get().getElectrumServerCert().getAbsolutePath() : ""); |
||||
|
mainPanel.addComponent(certificate); |
||||
|
selectCertificate = new Button("Select..."); |
||||
|
mainPanel.addComponent(selectCertificate); |
||||
|
|
||||
|
Server configuredServer = Config.get().getElectrumServer(); |
||||
|
if(configuredServer != null) { |
||||
|
Protocol protocol = configuredServer.getProtocol(); |
||||
|
boolean ssl = protocol.equals(Protocol.SSL); |
||||
|
useSsl.setSelectedIndex(ssl ? 0 : 1); |
||||
|
certificate.setEnabled(ssl); |
||||
|
selectCertificate.setEnabled(ssl); |
||||
|
} |
||||
|
|
||||
|
useSsl.addListener((selectedIndex, previousSelection, changedByUserInteraction) -> { |
||||
|
setServerConfig(); |
||||
|
certificate.setEnabled(selectedIndex == 0); |
||||
|
selectCertificate.setEnabled(selectedIndex == 0); |
||||
|
}); |
||||
|
certificate.setTextChangeListener((newText, changedByUserInteraction) -> { |
||||
|
File crtFile = getCertificate(newText); |
||||
|
Config.get().setElectrumServerCert(crtFile); |
||||
|
}); |
||||
|
selectCertificate.addListener(button -> { |
||||
|
FileDialogBuilder builder = new FileDialogBuilder().setTitle("Select SSL Certificate").setActionLabel("Select"); |
||||
|
builder.setShowHiddenDirectories(true); |
||||
|
File file = builder.build().showDialog(SparrowTerminal.get().getGui()); |
||||
|
if(file != null && getCertificate(file.getAbsolutePath()) != null) { |
||||
|
certificate.setText(file.getAbsolutePath()); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
addLine(mainPanel); |
||||
|
addProxyComponents(mainPanel); |
||||
|
|
||||
|
Panel buttonPanel = new Panel(); |
||||
|
buttonPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(1)); |
||||
|
buttonPanel.addComponent(new Button("Test", this::onTest)); |
||||
|
buttonPanel.addComponent(new Button("Done", this::onDone)); |
||||
|
|
||||
|
addLine(mainPanel); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
buttonPanel.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER,false,false)).addTo(mainPanel); |
||||
|
setComponent(mainPanel); |
||||
|
} |
||||
|
|
||||
|
protected void setServerConfig() { |
||||
|
Server currentServer = getCurrentServer(); |
||||
|
if(currentServer != null) { |
||||
|
Config.get().setElectrumServer(currentServer); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected void setServerAlias(Server server) { |
||||
|
Config.get().setElectrumServerAlias(server); |
||||
|
} |
||||
|
|
||||
|
protected Protocol getProtocol() { |
||||
|
return (useSsl.getSelectedIndex() == 0 ? Protocol.SSL : Protocol.TCP); |
||||
|
} |
||||
|
|
||||
|
protected void setProtocol(Protocol protocol) { |
||||
|
useSsl.setSelectedIndex(protocol == Protocol.SSL ? 0 : 1); |
||||
|
} |
||||
|
} |
@ -0,0 +1,66 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.preferences; |
||||
|
|
||||
|
import com.googlecode.lanterna.TerminalSize; |
||||
|
import com.googlecode.lanterna.gui2.*; |
||||
|
import com.sparrowwallet.sparrow.io.Config; |
||||
|
import com.sparrowwallet.sparrow.net.PublicElectrumServer; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
public class PublicElectrumDialog extends ServerProxyDialog { |
||||
|
private final ComboBox<PublicElectrumServer> url; |
||||
|
|
||||
|
public PublicElectrumDialog() { |
||||
|
super("Public Electrum"); |
||||
|
|
||||
|
setHints(List.of(Hint.CENTERED)); |
||||
|
|
||||
|
Panel mainPanel = new Panel(new GridLayout(3).setHorizontalSpacing(2).setVerticalSpacing(0)); |
||||
|
mainPanel.addComponent(new Label("Warning!")); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new Label("Using a public server means it can see your transactions"), |
||||
|
GridLayout.createLayoutData(GridLayout.Alignment.BEGINNING, GridLayout.Alignment.CENTER,true,false, 3, 1)); |
||||
|
|
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
mainPanel.addComponent(new Label("URL")); |
||||
|
url = new ComboBox<>(); |
||||
|
for(PublicElectrumServer server : PublicElectrumServer.getServers()) { |
||||
|
url.addItem(server); |
||||
|
} |
||||
|
if(Config.get().getPublicElectrumServer() == null) { |
||||
|
Config.get().changePublicServer(); |
||||
|
} |
||||
|
url.setSelectedItem(PublicElectrumServer.fromServer(Config.get().getPublicElectrumServer())); |
||||
|
url.addListener((selectedIndex, previousSelection, changedByUserInteraction) -> { |
||||
|
if(selectedIndex != previousSelection) { |
||||
|
Config.get().setPublicElectrumServer(PublicElectrumServer.values()[selectedIndex].getServer()); |
||||
|
} |
||||
|
}); |
||||
|
mainPanel.addComponent(url); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
addProxyComponents(mainPanel); |
||||
|
|
||||
|
Panel buttonPanel = new Panel(); |
||||
|
buttonPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(1)); |
||||
|
buttonPanel.addComponent(new Button("Test", this::onTest)); |
||||
|
buttonPanel.addComponent(new Button("Done", this::onDone)); |
||||
|
|
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
buttonPanel.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER,false,false)).addTo(mainPanel); |
||||
|
setComponent(mainPanel); |
||||
|
} |
||||
|
} |
@ -0,0 +1,129 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.preferences; |
||||
|
|
||||
|
import com.google.common.net.HostAndPort; |
||||
|
import com.googlecode.lanterna.TerminalSize; |
||||
|
import com.googlecode.lanterna.gui2.*; |
||||
|
import com.googlecode.lanterna.gui2.dialogs.DialogWindow; |
||||
|
import com.sparrowwallet.sparrow.AppServices; |
||||
|
import com.sparrowwallet.sparrow.EventManager; |
||||
|
import com.sparrowwallet.sparrow.Mode; |
||||
|
import com.sparrowwallet.sparrow.event.RequestConnectEvent; |
||||
|
import com.sparrowwallet.sparrow.io.Config; |
||||
|
import com.sparrowwallet.sparrow.terminal.SparrowTerminal; |
||||
|
import javafx.application.Platform; |
||||
|
|
||||
|
import java.io.File; |
||||
|
import java.io.FileInputStream; |
||||
|
import java.security.cert.CertificateFactory; |
||||
|
import java.util.regex.Pattern; |
||||
|
|
||||
|
public abstract class ServerProxyDialog extends DialogWindow { |
||||
|
private ComboBox<String> useProxy; |
||||
|
private TextBox proxyHost; |
||||
|
private TextBox proxyPort; |
||||
|
|
||||
|
public ServerProxyDialog(String title) { |
||||
|
super(title); |
||||
|
} |
||||
|
|
||||
|
protected void onDone() { |
||||
|
close(); |
||||
|
|
||||
|
Platform.runLater(() -> { |
||||
|
if(Config.get().getMode() == Mode.ONLINE && !(AppServices.isConnecting() || AppServices.isConnected())) { |
||||
|
EventManager.get().post(new RequestConnectEvent()); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected void onTest() { |
||||
|
close(); |
||||
|
|
||||
|
ServerTestDialog serverTestDialog = new ServerTestDialog(); |
||||
|
serverTestDialog.showDialog(SparrowTerminal.get().getGui()); |
||||
|
} |
||||
|
|
||||
|
protected void addProxyComponents(Panel mainPanel) { |
||||
|
mainPanel.addComponent(new Label("Use Proxy?")); |
||||
|
useProxy = new ComboBox<>("Yes", "No"); |
||||
|
useProxy.setSelectedIndex(Config.get().isUseProxy() ? 0 : 1); |
||||
|
useProxy.addListener((selectedIndex, previousSelection, changedByUserInteraction) -> { |
||||
|
Config.get().setUseProxy(selectedIndex == 0); |
||||
|
}); |
||||
|
mainPanel.addComponent(useProxy); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
mainPanel.addComponent(new Label("Proxy URL")); |
||||
|
proxyHost = new TextBox(new TerminalSize(30,1)).setValidationPattern(Pattern.compile("[a-zA-Z0-9.]+")); |
||||
|
mainPanel.addComponent(proxyHost); |
||||
|
|
||||
|
proxyPort = new TextBox(new TerminalSize(6,1)).setValidationPattern(Pattern.compile("[0-9]*")); |
||||
|
mainPanel.addComponent(proxyPort); |
||||
|
|
||||
|
String proxyServer = Config.get().getProxyServer(); |
||||
|
if(proxyServer != null) { |
||||
|
HostAndPort server = HostAndPort.fromString(proxyServer); |
||||
|
proxyHost.setText(server.getHost()); |
||||
|
if(server.hasPort()) { |
||||
|
proxyPort.setText(Integer.toString(server.getPort())); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
proxyHost.setTextChangeListener((newText, changedByUserInteraction) -> { |
||||
|
setProxyConfig(); |
||||
|
}); |
||||
|
proxyPort.setTextChangeListener((newText, changedByUserInteraction) -> { |
||||
|
setProxyConfig(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void setProxyConfig() { |
||||
|
String hostAsString = getHost(proxyHost.getText()); |
||||
|
Integer portAsInteger = getPort(proxyPort.getText()); |
||||
|
if(hostAsString != null && portAsInteger != null && isValidPort(portAsInteger)) { |
||||
|
Config.get().setProxyServer(HostAndPort.fromParts(hostAsString, portAsInteger).toString()); |
||||
|
} else if(hostAsString != null) { |
||||
|
Config.get().setProxyServer(HostAndPort.fromHost(hostAsString).toString()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected String getHost(String text) { |
||||
|
try { |
||||
|
return HostAndPort.fromHost(text).getHost(); |
||||
|
} catch(IllegalArgumentException e) { |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected Integer getPort(String text) { |
||||
|
try { |
||||
|
return Integer.parseInt(text); |
||||
|
} catch(NumberFormatException e) { |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected static boolean isValidPort(int port) { |
||||
|
return port >= 0 && port <= 65535; |
||||
|
} |
||||
|
|
||||
|
protected 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; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected void addLine(Panel mainPanel) { |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
} |
||||
|
} |
@ -0,0 +1,74 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.preferences; |
||||
|
|
||||
|
import com.googlecode.lanterna.TerminalSize; |
||||
|
import com.googlecode.lanterna.gui2.*; |
||||
|
import com.googlecode.lanterna.gui2.dialogs.DialogWindow; |
||||
|
import com.sparrowwallet.sparrow.EventManager; |
||||
|
import com.sparrowwallet.sparrow.Mode; |
||||
|
import com.sparrowwallet.sparrow.event.RequestConnectEvent; |
||||
|
import com.sparrowwallet.sparrow.event.RequestDisconnectEvent; |
||||
|
import com.sparrowwallet.sparrow.io.Config; |
||||
|
import com.sparrowwallet.sparrow.terminal.SparrowTerminal; |
||||
|
import javafx.application.Platform; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
public class ServerStatusDialog extends DialogWindow { |
||||
|
private final ComboBox<String> connect; |
||||
|
|
||||
|
public ServerStatusDialog() { |
||||
|
super("Server Preferences"); |
||||
|
|
||||
|
setHints(List.of(Hint.CENTERED)); |
||||
|
|
||||
|
Panel mainPanel = new Panel(); |
||||
|
mainPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(5)); |
||||
|
|
||||
|
mainPanel.addComponent(new Label("Connect?")); |
||||
|
connect = new ComboBox<>(); |
||||
|
connect.addItem("Yes"); |
||||
|
connect.addItem("No"); |
||||
|
connect.setSelectedIndex(Config.get().getMode() == Mode.ONLINE ? 0 : 1); |
||||
|
connect.addListener((selectedIndex, previousSelection, changedByUserInteraction) -> { |
||||
|
if(selectedIndex != previousSelection) { |
||||
|
Config.get().setMode(selectedIndex == 0 ? Mode.ONLINE : Mode.OFFLINE); |
||||
|
Platform.runLater(() -> { |
||||
|
EventManager.get().post(selectedIndex == 0 ? new RequestConnectEvent() : new RequestDisconnectEvent()); |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
mainPanel.addComponent(connect); |
||||
|
|
||||
|
mainPanel.addComponent(new Label("Server Type")); |
||||
|
mainPanel.addComponent(new Label(Config.get().getServerType().getName())); |
||||
|
|
||||
|
mainPanel.addComponent(new Label("Server")); |
||||
|
mainPanel.addComponent(new Label(Config.get().getServer().getDisplayName())); |
||||
|
|
||||
|
Panel buttonPanel = new Panel(); |
||||
|
buttonPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(1)); |
||||
|
buttonPanel.addComponent(new Button("Edit", this::onEdit).setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.CENTER, GridLayout.Alignment.CENTER, true, false))); |
||||
|
buttonPanel.addComponent(new Button("Cancel", this::onCancel)); |
||||
|
|
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
buttonPanel.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER,false,false)).addTo(mainPanel); |
||||
|
setComponent(mainPanel); |
||||
|
} |
||||
|
|
||||
|
private void onEdit() { |
||||
|
Platform.runLater(() -> { |
||||
|
EventManager.get().post(new RequestDisconnectEvent()); |
||||
|
}); |
||||
|
close(); |
||||
|
|
||||
|
ServerTypeDialog serverTypeDialog = new ServerTypeDialog(); |
||||
|
serverTypeDialog.showDialog(SparrowTerminal.get().getGui()); |
||||
|
} |
||||
|
|
||||
|
private void onCancel() { |
||||
|
close(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,228 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.preferences; |
||||
|
|
||||
|
import com.google.common.eventbus.Subscribe; |
||||
|
import com.googlecode.lanterna.TerminalSize; |
||||
|
import com.googlecode.lanterna.gui2.*; |
||||
|
import com.googlecode.lanterna.gui2.dialogs.DialogWindow; |
||||
|
import com.sparrowwallet.sparrow.AppServices; |
||||
|
import com.sparrowwallet.sparrow.EventManager; |
||||
|
import com.sparrowwallet.sparrow.Mode; |
||||
|
import com.sparrowwallet.sparrow.event.*; |
||||
|
import com.sparrowwallet.sparrow.io.Config; |
||||
|
import com.sparrowwallet.sparrow.io.Storage; |
||||
|
import com.sparrowwallet.sparrow.net.*; |
||||
|
import com.sparrowwallet.sparrow.terminal.SparrowTerminal; |
||||
|
import javafx.application.Platform; |
||||
|
import javafx.scene.control.ButtonType; |
||||
|
import javafx.util.Duration; |
||||
|
import org.berndpruenster.netlayer.tor.Tor; |
||||
|
|
||||
|
import java.io.File; |
||||
|
import java.text.DateFormat; |
||||
|
import java.text.SimpleDateFormat; |
||||
|
import java.util.List; |
||||
|
import java.util.Optional; |
||||
|
|
||||
|
public class ServerTestDialog extends DialogWindow { |
||||
|
private final Label testStatus; |
||||
|
private final TextBox testResults; |
||||
|
|
||||
|
private TorService torService; |
||||
|
private ElectrumServer.ConnectionService connectionService; |
||||
|
|
||||
|
public ServerTestDialog() { |
||||
|
super("Server Test"); |
||||
|
|
||||
|
setHints(List.of(Hint.CENTERED)); |
||||
|
|
||||
|
Panel mainPanel = new Panel(new GridLayout(1)); |
||||
|
|
||||
|
this.testStatus = new Label(""); |
||||
|
mainPanel.addComponent(testStatus); |
||||
|
|
||||
|
this.testResults = new TextBox(new TerminalSize(60, 10)); |
||||
|
testResults.setReadOnly(true); |
||||
|
mainPanel.addComponent(testResults); |
||||
|
|
||||
|
Panel buttonPanel = new Panel(); |
||||
|
buttonPanel.setLayoutManager(new GridLayout(3).setHorizontalSpacing(1)); |
||||
|
buttonPanel.addComponent(new Button("Back", this::onBack).setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.CENTER, GridLayout.Alignment.CENTER, false, false))); |
||||
|
buttonPanel.addComponent(new Button("Test", this::onTest).setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.CENTER, GridLayout.Alignment.CENTER, true, false))); |
||||
|
buttonPanel.addComponent(new Button("Done", this::onDone)); |
||||
|
|
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
buttonPanel.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER,false,false)).addTo(mainPanel); |
||||
|
setComponent(mainPanel); |
||||
|
|
||||
|
EventManager.get().register(this); |
||||
|
|
||||
|
onTest(); |
||||
|
} |
||||
|
|
||||
|
public void onBack() { |
||||
|
close(); |
||||
|
|
||||
|
if(Config.get().getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER) { |
||||
|
PublicElectrumDialog publicElectrumServer = new PublicElectrumDialog(); |
||||
|
publicElectrumServer.showDialog(SparrowTerminal.get().getGui()); |
||||
|
} else if(Config.get().getServerType() == ServerType.BITCOIN_CORE) { |
||||
|
BitcoinCoreDialog bitcoinCoreDialog = new BitcoinCoreDialog(); |
||||
|
bitcoinCoreDialog.showDialog(SparrowTerminal.get().getGui()); |
||||
|
} else if(Config.get().getServerType() == ServerType.ELECTRUM_SERVER) { |
||||
|
PrivateElectrumDialog privateElectrumDialog = new PrivateElectrumDialog(); |
||||
|
privateElectrumDialog.showDialog(SparrowTerminal.get().getGui()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void onTest() { |
||||
|
testResults.setText("Connecting " + (Config.get().hasServer() ? "to " + Config.get().getServer().getUrl() : "") + "..."); |
||||
|
|
||||
|
Platform.runLater(() -> { |
||||
|
if(Config.get().requiresInternalTor() && Tor.getDefault() == null) { |
||||
|
startTor(); |
||||
|
} else { |
||||
|
startElectrumConnection(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public void onDone() { |
||||
|
EventManager.get().unregister(this); |
||||
|
close(); |
||||
|
|
||||
|
Platform.runLater(() -> { |
||||
|
if(Config.get().getMode() == Mode.ONLINE && !(AppServices.isConnecting() || AppServices.isConnected())) { |
||||
|
EventManager.get().post(new RequestConnectEvent()); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void startTor() { |
||||
|
if(torService != null && torService.isRunning()) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
torService = new TorService(); |
||||
|
torService.setPeriod(Duration.hours(1000)); |
||||
|
torService.setRestartOnFailure(false); |
||||
|
|
||||
|
torService.setOnSucceeded(workerStateEvent -> { |
||||
|
Tor.setDefault(torService.getValue()); |
||||
|
torService.cancel(); |
||||
|
appendText("\nTor running, connecting to " + Config.get().getServer().getUrl() + "..."); |
||||
|
startElectrumConnection(); |
||||
|
}); |
||||
|
torService.setOnFailed(workerStateEvent -> { |
||||
|
torService.cancel(); |
||||
|
appendText("\nTor failed to start"); |
||||
|
showConnectionFailure(workerStateEvent.getSource().getException()); |
||||
|
}); |
||||
|
|
||||
|
torService.start(); |
||||
|
} |
||||
|
|
||||
|
private void startElectrumConnection() { |
||||
|
if(connectionService != null && connectionService.isRunning()) { |
||||
|
connectionService.cancel(); |
||||
|
} |
||||
|
|
||||
|
connectionService = new ElectrumServer.ConnectionService(false); |
||||
|
connectionService.setPeriod(Duration.hours(1)); |
||||
|
connectionService.setRestartOnFailure(false); |
||||
|
EventManager.get().register(connectionService); |
||||
|
|
||||
|
connectionService.setOnSucceeded(successEvent -> { |
||||
|
EventManager.get().unregister(connectionService); |
||||
|
ConnectionEvent connectionEvent = (ConnectionEvent)connectionService.getValue(); |
||||
|
showConnectionSuccess(connectionEvent.getServerVersion(), connectionEvent.getServerBanner()); |
||||
|
Config.get().setMode(Mode.ONLINE); |
||||
|
connectionService.cancel(); |
||||
|
Config.get().addRecentServer(); |
||||
|
}); |
||||
|
connectionService.setOnFailed(workerStateEvent -> { |
||||
|
EventManager.get().unregister(connectionService); |
||||
|
showConnectionFailure(workerStateEvent.getSource().getException()); |
||||
|
connectionService.cancel(); |
||||
|
}); |
||||
|
connectionService.start(); |
||||
|
} |
||||
|
|
||||
|
private void appendText(String text) { |
||||
|
testResults.setText(testResults.getText() + text); |
||||
|
} |
||||
|
|
||||
|
private void showConnectionSuccess(List<String> serverVersion, String serverBanner) { |
||||
|
testStatus.setText("Success"); |
||||
|
if(serverVersion != null) { |
||||
|
testResults.setText("Connected to " + serverVersion.get(0) + " on protocol version " + serverVersion.get(1)); |
||||
|
if(ElectrumServer.supportsBatching(serverVersion)) { |
||||
|
testResults.setText(testResults.getText() + "\nBatched RPC enabled."); |
||||
|
} |
||||
|
} |
||||
|
if(serverBanner != null) { |
||||
|
testResults.setText(testResults.getText() + "\nServer Banner: " + serverBanner); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void showConnectionFailure(Throwable exception) { |
||||
|
String reason = exception.getCause() != null ? exception.getCause().getMessage() : exception.getMessage(); |
||||
|
if(exception instanceof TlsServerException && exception.getCause() != null) { |
||||
|
TlsServerException tlsServerException = (TlsServerException)exception; |
||||
|
if(exception.getCause().getMessage().contains("PKIX path building failed")) { |
||||
|
File configCrtFile = Config.get().getElectrumServerCert(); |
||||
|
File savedCrtFile = Storage.getCertificateFile(tlsServerException.getServer().getHost()); |
||||
|
if(configCrtFile == null && savedCrtFile != null) { |
||||
|
Optional<ButtonType> optButton = AppServices.showErrorDialog("SSL Handshake Failed", "The certificate provided by the server at " + tlsServerException.getServer().getHost() + " appears to have changed." + |
||||
|
"\n\nThis may indicate a man-in-the-middle attack!" + |
||||
|
"\n\nDo you still want to proceed?", ButtonType.NO, ButtonType.YES); |
||||
|
if(optButton.isPresent() && optButton.get() == ButtonType.YES) { |
||||
|
if(savedCrtFile.delete()) { |
||||
|
Platform.runLater(this::startElectrumConnection); |
||||
|
return; |
||||
|
} else { |
||||
|
AppServices.showErrorDialog("Could not delete certificate", "The certificate file at " + savedCrtFile.getAbsolutePath() + " could not be deleted.\n\nPlease delete this file manually."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
reason = tlsServerException.getMessage() + "\n\n" + reason; |
||||
|
} else if(exception instanceof ProxyServerException) { |
||||
|
reason += ". Check if the proxy server is running."; |
||||
|
} else if(exception instanceof TorServerAlreadyBoundException) { |
||||
|
reason += "\nIs a Tor proxy already running on port " + TorService.PROXY_PORT + "?"; |
||||
|
} else if(reason != null && reason.contains("Check if Bitcoin Core is running")) { |
||||
|
reason += "\n\nSee https://sparrowwallet.com/docs/connect-node.html"; |
||||
|
} |
||||
|
|
||||
|
testStatus.setText("Failed"); |
||||
|
testResults.setText("Could not connect:\n\n" + reason); |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void bwtStatus(BwtStatusEvent event) { |
||||
|
if(!(event instanceof BwtSyncStatusEvent)) { |
||||
|
appendText("\n" + event.getStatus()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void bwtSyncStatus(BwtSyncStatusEvent event) { |
||||
|
if(connectionService != null && connectionService.isRunning() && event.getProgress() < 100) { |
||||
|
DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm"); |
||||
|
appendText("\nThe connection to the Bitcoin Core node was successful, but it is still syncing and cannot be used yet."); |
||||
|
appendText("\nCurrently " + event.getProgress() + "% completed to date " + dateFormat.format(event.getTip())); |
||||
|
connectionService.cancel(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void torStatus(TorStatusEvent event) { |
||||
|
Platform.runLater(() -> { |
||||
|
if(torService != null && torService.isRunning()) { |
||||
|
appendText("\n" + event.getStatus()); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
@ -0,0 +1,68 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.preferences; |
||||
|
|
||||
|
import com.googlecode.lanterna.TerminalSize; |
||||
|
import com.googlecode.lanterna.gui2.*; |
||||
|
import com.googlecode.lanterna.gui2.dialogs.DialogWindow; |
||||
|
import com.sparrowwallet.sparrow.io.Config; |
||||
|
import com.sparrowwallet.sparrow.net.ServerType; |
||||
|
import com.sparrowwallet.sparrow.terminal.SparrowTerminal; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
public class ServerTypeDialog extends DialogWindow { |
||||
|
private final RadioBoxList<String> type; |
||||
|
|
||||
|
public ServerTypeDialog() { |
||||
|
super("Server Type"); |
||||
|
|
||||
|
setHints(List.of(Hint.CENTERED)); |
||||
|
|
||||
|
Panel mainPanel = new Panel(); |
||||
|
mainPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(5)); |
||||
|
|
||||
|
ServerType[] serverTypes = new ServerType[] { ServerType.PUBLIC_ELECTRUM_SERVER, ServerType.BITCOIN_CORE, ServerType.ELECTRUM_SERVER }; |
||||
|
|
||||
|
mainPanel.addComponent(new Label("Connect using")); |
||||
|
type = new RadioBoxList<>(); |
||||
|
for(ServerType serverType : serverTypes) { |
||||
|
type.addItem(serverType.getName()); |
||||
|
} |
||||
|
|
||||
|
if(Config.get().getServerType() == null) { |
||||
|
Config.get().setServerType(ServerType.PUBLIC_ELECTRUM_SERVER); |
||||
|
} |
||||
|
type.setCheckedItem(Config.get().getServerType().getName()); |
||||
|
type.addListener((selectedIndex, previousSelection) -> { |
||||
|
if(selectedIndex != previousSelection) { |
||||
|
Config.get().setServerType(serverTypes[selectedIndex]); |
||||
|
} |
||||
|
}); |
||||
|
mainPanel.addComponent(type); |
||||
|
|
||||
|
Panel buttonPanel = new Panel(); |
||||
|
buttonPanel.setLayoutManager(new GridLayout(1).setHorizontalSpacing(1)); |
||||
|
buttonPanel.addComponent(new Button("Continue", this::onContinue)); |
||||
|
|
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
buttonPanel.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER,false,false)).addTo(mainPanel); |
||||
|
setComponent(mainPanel); |
||||
|
} |
||||
|
|
||||
|
private void onContinue() { |
||||
|
close(); |
||||
|
|
||||
|
if(Config.get().getServerType() == ServerType.PUBLIC_ELECTRUM_SERVER) { |
||||
|
PublicElectrumDialog publicElectrumServer = new PublicElectrumDialog(); |
||||
|
publicElectrumServer.showDialog(SparrowTerminal.get().getGui()); |
||||
|
} else if(Config.get().getServerType() == ServerType.BITCOIN_CORE) { |
||||
|
BitcoinCoreDialog bitcoinCoreDialog = new BitcoinCoreDialog(); |
||||
|
bitcoinCoreDialog.showDialog(SparrowTerminal.get().getGui()); |
||||
|
} else if(Config.get().getServerType() == ServerType.ELECTRUM_SERVER) { |
||||
|
PrivateElectrumDialog privateElectrumDialog = new PrivateElectrumDialog(); |
||||
|
privateElectrumDialog.showDialog(SparrowTerminal.get().getGui()); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,126 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.preferences; |
||||
|
|
||||
|
import com.google.common.net.HostAndPort; |
||||
|
import com.googlecode.lanterna.TerminalSize; |
||||
|
import com.googlecode.lanterna.gui2.*; |
||||
|
import com.sparrowwallet.sparrow.io.Server; |
||||
|
import com.sparrowwallet.sparrow.net.Protocol; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.Optional; |
||||
|
import java.util.regex.Pattern; |
||||
|
|
||||
|
public abstract class ServerUrlDialog extends ServerProxyDialog { |
||||
|
private ComboBox<ServerItem> host; |
||||
|
private TextBox port; |
||||
|
private TextBox alias; |
||||
|
|
||||
|
public ServerUrlDialog(String title) { |
||||
|
super(title); |
||||
|
} |
||||
|
|
||||
|
protected void addUrlComponents(Panel mainPanel, List<Server> recentServers, Server configuredServer) { |
||||
|
mainPanel.addComponent(new Label("URL")); |
||||
|
host = new ComboBox<>(); |
||||
|
host.setPreferredSize(new TerminalSize(30,1)); |
||||
|
host.setReadOnly(false); |
||||
|
mainPanel.addComponent(host, GridLayout.createLayoutData(GridLayout.Alignment.BEGINNING, GridLayout.Alignment.CENTER, true, false)); |
||||
|
port = new TextBox(new TerminalSize(6,1)).setValidationPattern(Pattern.compile("[0-9]*")); |
||||
|
mainPanel.addComponent(port); |
||||
|
|
||||
|
mainPanel.addComponent(new Label("Alias (optional)")); |
||||
|
alias = new TextBox(new TerminalSize(30,1)); |
||||
|
mainPanel.addComponent(alias); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
if(configuredServer != null) { |
||||
|
HostAndPort hostAndPort = configuredServer.getHostAndPort(); |
||||
|
recentServers.stream().map(ServerItem::new).forEach(host::addItem); |
||||
|
host.setSelectedItem(new ServerItem(configuredServer)); |
||||
|
if(host.getItemCount() == 0) { |
||||
|
host.addItem(new ServerItem(configuredServer)); |
||||
|
} |
||||
|
if(hostAndPort.hasPort()) { |
||||
|
port.setText(Integer.toString(hostAndPort.getPort())); |
||||
|
} |
||||
|
|
||||
|
if(configuredServer.getAlias() != null) { |
||||
|
alias.setText(configuredServer.getAlias()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
host.addListener((selectedIndex, previousSelection, changedByUserInteraction) -> { |
||||
|
Optional<Server> optServer = recentServers.stream().filter(server -> server.equals(host.getSelectedItem().getServer())).findFirst(); |
||||
|
if(optServer.isPresent()) { |
||||
|
Server server = optServer.get(); |
||||
|
port.setText(server.getHostAndPort().hasPort() ? Integer.toString(server.getHostAndPort().getPort()) : ""); |
||||
|
alias.setText(server.getAlias() == null ? "" : server.getAlias()); |
||||
|
setProtocol(server.getProtocol()); |
||||
|
} |
||||
|
setServerConfig(); |
||||
|
}); |
||||
|
port.setTextChangeListener((newText, changedByUserInteraction) -> { |
||||
|
setServerConfig(); |
||||
|
}); |
||||
|
alias.setTextChangeListener((newText, changedByUserInteraction) -> { |
||||
|
Server currentServer = getCurrentServer(); |
||||
|
if(currentServer != null && host.getSelectedItem() != null && currentServer.equals(host.getSelectedItem().getServer())) { |
||||
|
setServerAlias(currentServer); |
||||
|
} |
||||
|
setServerConfig(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void onDone() { |
||||
|
setServerConfig(); |
||||
|
super.onDone(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void onTest() { |
||||
|
setServerConfig(); |
||||
|
super.onTest(); |
||||
|
} |
||||
|
|
||||
|
protected abstract void setServerConfig(); |
||||
|
|
||||
|
protected abstract void setServerAlias(Server server); |
||||
|
|
||||
|
protected abstract Protocol getProtocol(); |
||||
|
|
||||
|
protected abstract void setProtocol(Protocol protocol); |
||||
|
|
||||
|
protected Server getCurrentServer() { |
||||
|
String hostAsString = getHost(host.getText()); |
||||
|
Integer portAsInteger = getPort(port.getText()); |
||||
|
if(hostAsString != null && portAsInteger != null && isValidPort(portAsInteger)) { |
||||
|
return new Server(getProtocol().toUrlString(hostAsString, portAsInteger), getAlias()); |
||||
|
} else if(hostAsString != null) { |
||||
|
return new Server(getProtocol().toUrlString(hostAsString), getAlias()); |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
private String getAlias() { |
||||
|
return alias.getText().isEmpty() ? null : alias.getText(); |
||||
|
} |
||||
|
|
||||
|
protected static class ServerItem { |
||||
|
private final Server server; |
||||
|
|
||||
|
public ServerItem(Server server) { |
||||
|
this.server = server; |
||||
|
} |
||||
|
|
||||
|
public Server getServer() { |
||||
|
return server; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String toString() { |
||||
|
return server.getHost(); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,116 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.wallet; |
||||
|
|
||||
|
import com.google.common.eventbus.Subscribe; |
||||
|
import com.googlecode.lanterna.TerminalSize; |
||||
|
import com.googlecode.lanterna.gui2.*; |
||||
|
import com.googlecode.lanterna.gui2.table.Table; |
||||
|
import com.googlecode.lanterna.gui2.table.TableModel; |
||||
|
import com.sparrowwallet.drongo.KeyPurpose; |
||||
|
import com.sparrowwallet.sparrow.event.UnitFormatChangedEvent; |
||||
|
import com.sparrowwallet.sparrow.event.WalletHistoryChangedEvent; |
||||
|
import com.sparrowwallet.sparrow.event.WalletNodesChangedEvent; |
||||
|
import com.sparrowwallet.sparrow.terminal.SparrowTerminal; |
||||
|
import com.sparrowwallet.sparrow.terminal.wallet.table.AddressTableCell; |
||||
|
import com.sparrowwallet.sparrow.terminal.wallet.table.CoinTableCell; |
||||
|
import com.sparrowwallet.sparrow.terminal.wallet.table.TableCell; |
||||
|
import com.sparrowwallet.sparrow.wallet.Entry; |
||||
|
import com.sparrowwallet.sparrow.wallet.Function; |
||||
|
import com.sparrowwallet.sparrow.wallet.NodeEntry; |
||||
|
import com.sparrowwallet.sparrow.wallet.WalletForm; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
public class AddressesDialog extends WalletDialog { |
||||
|
private final Table<TableCell> receiveTable; |
||||
|
private final Table<TableCell> changeTable; |
||||
|
|
||||
|
public AddressesDialog(WalletForm walletForm) { |
||||
|
super(walletForm.getWallet().getFullDisplayName() + " Addresses", walletForm); |
||||
|
|
||||
|
setHints(List.of(Hint.CENTERED, Hint.EXPANDED)); |
||||
|
|
||||
|
String[] tableColumns = getTableColumns(); |
||||
|
receiveTable = new Table<>(tableColumns); |
||||
|
receiveTable.setTableCellRenderer(new EntryTableCellRenderer()); |
||||
|
|
||||
|
changeTable = new Table<>(tableColumns); |
||||
|
changeTable.setTableCellRenderer(new EntryTableCellRenderer()); |
||||
|
|
||||
|
updateAddresses(); |
||||
|
|
||||
|
Panel buttonPanel = new Panel(new GridLayout(4).setHorizontalSpacing(2).setVerticalSpacing(0)); |
||||
|
buttonPanel.addComponent(new EmptySpace(new TerminalSize(15, 1))); |
||||
|
buttonPanel.addComponent(new EmptySpace(new TerminalSize(15, 1))); |
||||
|
buttonPanel.addComponent(new Button("Back", () -> onBack(Function.ADDRESSES))); |
||||
|
buttonPanel.addComponent(new Button("Refresh", this::onRefresh)); |
||||
|
|
||||
|
Panel mainPanel = new Panel(); |
||||
|
mainPanel.setLayoutManager(new LinearLayout(Direction.VERTICAL).setSpacing(1)); |
||||
|
mainPanel.addComponent(receiveTable.withBorder(new EmptyBorder("Receive"))); |
||||
|
mainPanel.addComponent(changeTable.withBorder(new EmptyBorder("Change"))); |
||||
|
mainPanel.addComponent(buttonPanel); |
||||
|
|
||||
|
setComponent(mainPanel); |
||||
|
|
||||
|
configureTable(receiveTable, KeyPurpose.RECEIVE); |
||||
|
configureTable(changeTable, KeyPurpose.CHANGE); |
||||
|
} |
||||
|
|
||||
|
private void configureTable(Table<TableCell> table, KeyPurpose keyPurpose) { |
||||
|
table.setSelectAction(() -> { |
||||
|
NodeEntry nodeEntry = (NodeEntry)getWalletForm().getNodeEntry(keyPurpose).getChildren().get(table.getSelectedRow()); |
||||
|
close(); |
||||
|
WalletData walletData = SparrowTerminal.get().getWalletData().get(getWalletForm().getWallet()); |
||||
|
ReceiveDialog receiveDialog = walletData.getReceiveDialog(); |
||||
|
receiveDialog.setNodeEntry(nodeEntry); |
||||
|
receiveDialog.showDialog(SparrowTerminal.get().getGui()); |
||||
|
}); |
||||
|
|
||||
|
Integer highestUsedReceiveIndex = getWalletForm().getNodeEntry(keyPurpose).getNode().getHighestUsedIndex(); |
||||
|
if(highestUsedReceiveIndex != null) { |
||||
|
table.setSelectedRow(highestUsedReceiveIndex + 1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private String[] getTableColumns() { |
||||
|
String address = getWalletForm().getNodeEntry(KeyPurpose.RECEIVE).getAddress().toString(); |
||||
|
return new String[] {centerPad("Address", address.length()), centerPad("Value", CoinTableCell.TRANSACTION_WIDTH)}; |
||||
|
} |
||||
|
|
||||
|
private void updateAddressesLater() { |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(this::updateAddresses); |
||||
|
} |
||||
|
|
||||
|
private void updateAddresses() { |
||||
|
receiveTable.setTableModel(getTableModel(getWalletForm().getNodeEntry(KeyPurpose.RECEIVE))); |
||||
|
changeTable.setTableModel(getTableModel(getWalletForm().getNodeEntry(KeyPurpose.CHANGE))); |
||||
|
} |
||||
|
|
||||
|
private TableModel<TableCell> getTableModel(NodeEntry nodeEntry) { |
||||
|
TableModel<TableCell> tableModel = new TableModel<>(getTableColumns()); |
||||
|
for(Entry addressEntry : nodeEntry.getChildren()) { |
||||
|
tableModel.addRow(new AddressTableCell(addressEntry), new CoinTableCell(addressEntry, false)); |
||||
|
} |
||||
|
|
||||
|
return tableModel; |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void walletNodesChanged(WalletNodesChangedEvent event) { |
||||
|
if(event.getWallet().equals(getWalletForm().getWallet())) { |
||||
|
updateAddressesLater(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void walletHistoryChanged(WalletHistoryChangedEvent event) { |
||||
|
if(event.getWallet().equals(getWalletForm().getWallet())) { |
||||
|
updateAddressesLater(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void unitFormatChanged(UnitFormatChangedEvent event) { |
||||
|
updateAddressesLater(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,118 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.wallet; |
||||
|
|
||||
|
import com.googlecode.lanterna.TerminalPosition; |
||||
|
import com.googlecode.lanterna.TerminalSize; |
||||
|
import com.googlecode.lanterna.TerminalTextUtils; |
||||
|
import com.googlecode.lanterna.graphics.ThemeDefinition; |
||||
|
import com.googlecode.lanterna.gui2.*; |
||||
|
|
||||
|
public class EmptyBorder extends AbstractBorder { |
||||
|
private final String title; |
||||
|
|
||||
|
protected EmptyBorder(String title) { |
||||
|
if (title == null) { |
||||
|
throw new IllegalArgumentException("Cannot create a border with null title"); |
||||
|
} |
||||
|
this.title = title; |
||||
|
} |
||||
|
|
||||
|
public String getTitle() { |
||||
|
return title; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String toString() { |
||||
|
return getClass().getSimpleName() + "{" + title + "}"; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected BorderRenderer createDefaultRenderer() { |
||||
|
return new EmptyBorderRenderer(); |
||||
|
} |
||||
|
|
||||
|
private static class EmptyBorderRenderer implements Border.BorderRenderer { |
||||
|
@Override |
||||
|
public TerminalSize getPreferredSize(Border component) { |
||||
|
EmptyBorder border = (EmptyBorder)component; |
||||
|
Component wrappedComponent = border.getComponent(); |
||||
|
TerminalSize preferredSize; |
||||
|
if (wrappedComponent == null) { |
||||
|
preferredSize = TerminalSize.ZERO; |
||||
|
} else { |
||||
|
preferredSize = wrappedComponent.getPreferredSize(); |
||||
|
} |
||||
|
preferredSize = preferredSize.withRelativeColumns(2).withRelativeRows(2); |
||||
|
String borderTitle = border.getTitle(); |
||||
|
return preferredSize.max(new TerminalSize((borderTitle.isEmpty() ? 2 : TerminalTextUtils.getColumnWidth(borderTitle) + 4), 2)); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public TerminalPosition getWrappedComponentTopLeftOffset() { |
||||
|
return TerminalPosition.OFFSET_1x1; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public TerminalSize getWrappedComponentSize(TerminalSize borderSize) { |
||||
|
return borderSize |
||||
|
.withRelativeColumns(-Math.min(2, borderSize.getColumns())) |
||||
|
.withRelativeRows(-Math.min(2, borderSize.getRows())); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void drawComponent(TextGUIGraphics graphics, Border component) { |
||||
|
EmptyBorder border = (EmptyBorder)component; |
||||
|
Component wrappedComponent = border.getComponent(); |
||||
|
if(wrappedComponent == null) { |
||||
|
return; |
||||
|
} |
||||
|
TerminalSize drawableArea = graphics.getSize(); |
||||
|
|
||||
|
char horizontalLine = ' '; |
||||
|
char verticalLine = ' '; |
||||
|
char bottomLeftCorner = ' '; |
||||
|
char topLeftCorner = ' '; |
||||
|
char bottomRightCorner = ' '; |
||||
|
char topRightCorner = ' '; |
||||
|
char titleLeft = ' '; |
||||
|
char titleRight = ' '; |
||||
|
|
||||
|
ThemeDefinition themeDefinition = component.getTheme().getDefinition(AbstractBorder.class); |
||||
|
graphics.applyThemeStyle(themeDefinition.getNormal()); |
||||
|
graphics.setCharacter(0, drawableArea.getRows() - 1, bottomLeftCorner); |
||||
|
if(drawableArea.getRows() > 2) { |
||||
|
graphics.drawLine(new TerminalPosition(0, drawableArea.getRows() - 2), new TerminalPosition(0, 1), verticalLine); |
||||
|
} |
||||
|
graphics.setCharacter(0, 0, topLeftCorner); |
||||
|
if(drawableArea.getColumns() > 2) { |
||||
|
graphics.drawLine(new TerminalPosition(1, 0), new TerminalPosition(drawableArea.getColumns() - 2, 0), horizontalLine); |
||||
|
} |
||||
|
|
||||
|
graphics.applyThemeStyle(themeDefinition.getNormal()); |
||||
|
graphics.setCharacter(drawableArea.getColumns() - 1, 0, topRightCorner); |
||||
|
if(drawableArea.getRows() > 2) { |
||||
|
graphics.drawLine(new TerminalPosition(drawableArea.getColumns() - 1, 1), |
||||
|
new TerminalPosition(drawableArea.getColumns() - 1, drawableArea.getRows() - 2), |
||||
|
verticalLine); |
||||
|
} |
||||
|
graphics.setCharacter(drawableArea.getColumns() - 1, drawableArea.getRows() - 1, bottomRightCorner); |
||||
|
if(drawableArea.getColumns() > 2) { |
||||
|
graphics.drawLine(new TerminalPosition(1, drawableArea.getRows() - 1), |
||||
|
new TerminalPosition(drawableArea.getColumns() - 2, drawableArea.getRows() - 1), |
||||
|
horizontalLine); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
if(border.getTitle() != null && !border.getTitle().isEmpty() && |
||||
|
drawableArea.getColumns() >= TerminalTextUtils.getColumnWidth(border.getTitle()) + 4) { |
||||
|
graphics.applyThemeStyle(themeDefinition.getActive()); |
||||
|
graphics.putString(2, 0, border.getTitle()); |
||||
|
|
||||
|
graphics.applyThemeStyle(themeDefinition.getNormal()); |
||||
|
graphics.setCharacter(1, 0, titleLeft); |
||||
|
graphics.setCharacter(2 + TerminalTextUtils.getColumnWidth(border.getTitle()), 0, titleRight); |
||||
|
} |
||||
|
|
||||
|
wrappedComponent.draw(graphics.newTextGraphics(getWrappedComponentTopLeftOffset(), getWrappedComponentSize(drawableArea))); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.wallet; |
||||
|
|
||||
|
import com.googlecode.lanterna.gui2.table.DefaultTableCellRenderer; |
||||
|
import com.sparrowwallet.sparrow.terminal.wallet.table.TableCell; |
||||
|
|
||||
|
public class EntryTableCellRenderer extends DefaultTableCellRenderer<TableCell> { |
||||
|
@Override |
||||
|
protected String[] getContent(TableCell cell) { |
||||
|
String[] lines; |
||||
|
if(cell == null) { |
||||
|
lines = new String[] { "" }; |
||||
|
} else { |
||||
|
lines = new String[] { cell.formatCell() }; |
||||
|
} |
||||
|
|
||||
|
return lines; |
||||
|
} |
||||
|
} |
@ -0,0 +1,190 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.wallet; |
||||
|
|
||||
|
import com.googlecode.lanterna.TerminalSize; |
||||
|
import com.googlecode.lanterna.gui2.*; |
||||
|
import com.googlecode.lanterna.gui2.dialogs.*; |
||||
|
import com.sparrowwallet.drongo.SecureString; |
||||
|
import com.sparrowwallet.drongo.crypto.InvalidPasswordException; |
||||
|
import com.sparrowwallet.drongo.wallet.Wallet; |
||||
|
import com.sparrowwallet.sparrow.EventManager; |
||||
|
import com.sparrowwallet.sparrow.MainApp; |
||||
|
import com.sparrowwallet.sparrow.TabData; |
||||
|
import com.sparrowwallet.sparrow.WalletTabData; |
||||
|
import com.sparrowwallet.sparrow.event.*; |
||||
|
import com.sparrowwallet.sparrow.io.Config; |
||||
|
import com.sparrowwallet.sparrow.io.Storage; |
||||
|
import com.sparrowwallet.sparrow.io.StorageException; |
||||
|
import com.sparrowwallet.sparrow.io.WalletAndKey; |
||||
|
import com.sparrowwallet.sparrow.terminal.SparrowTerminal; |
||||
|
import com.sparrowwallet.sparrow.wallet.WalletForm; |
||||
|
import javafx.application.Platform; |
||||
|
import javafx.scene.control.ButtonType; |
||||
|
import javafx.stage.Window; |
||||
|
import org.slf4j.Logger; |
||||
|
import org.slf4j.LoggerFactory; |
||||
|
|
||||
|
import java.io.File; |
||||
|
import java.io.IOException; |
||||
|
import java.util.*; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog; |
||||
|
import static com.sparrowwallet.sparrow.terminal.MasterActionListBox.MAX_RECENT_WALLETS; |
||||
|
|
||||
|
public class LoadWallet implements Runnable { |
||||
|
private static final Logger log = LoggerFactory.getLogger(LoadWallet.class); |
||||
|
private final Storage storage; |
||||
|
private final LoadingDialog loadingDialog; |
||||
|
|
||||
|
public LoadWallet(Storage storage) { |
||||
|
this.storage = storage; |
||||
|
this.loadingDialog = new LoadingDialog(storage); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void run() { |
||||
|
SparrowTerminal.get().getGui().addWindow(loadingDialog); |
||||
|
|
||||
|
try { |
||||
|
if(!storage.isEncrypted()) { |
||||
|
Platform.runLater(() -> { |
||||
|
Storage.LoadWalletService loadWalletService = new Storage.LoadWalletService(storage); |
||||
|
loadWalletService.setExecutor(Storage.LoadWalletService.getSingleThreadedExecutor()); |
||||
|
loadWalletService.setOnSucceeded(workerStateEvent -> { |
||||
|
WalletAndKey walletAndKey = loadWalletService.getValue(); |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(() -> openWallet(storage, walletAndKey)); |
||||
|
}); |
||||
|
loadWalletService.setOnFailed(workerStateEvent -> { |
||||
|
Throwable exception = workerStateEvent.getSource().getException(); |
||||
|
if(exception instanceof StorageException) { |
||||
|
showErrorDialog("Error Opening Wallet", exception.getMessage()); |
||||
|
} |
||||
|
}); |
||||
|
loadWalletService.start(); |
||||
|
}); |
||||
|
} else { |
||||
|
TextInputDialogBuilder builder = new TextInputDialogBuilder().setTitle("Wallet Password"); |
||||
|
builder.setDescription("Enter the password for\n" + storage.getWalletName(null)); |
||||
|
builder.setPasswordInput(true); |
||||
|
|
||||
|
String password = builder.build().showDialog(SparrowTerminal.get().getGui()); |
||||
|
if(password == null) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Platform.runLater(() -> { |
||||
|
Storage.LoadWalletService loadWalletService = new Storage.LoadWalletService(storage, new SecureString(password)); |
||||
|
loadWalletService.setOnSucceeded(workerStateEvent -> { |
||||
|
EventManager.get().post(new StorageEvent(storage.getWalletId(null), TimedEvent.Action.END, "Done")); |
||||
|
WalletAndKey walletAndKey = loadWalletService.getValue(); |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(() -> openWallet(storage, walletAndKey)); |
||||
|
}); |
||||
|
loadWalletService.setOnFailed(workerStateEvent -> { |
||||
|
EventManager.get().post(new StorageEvent(storage.getWalletId(null), TimedEvent.Action.END, "Failed")); |
||||
|
Throwable exception = loadWalletService.getException(); |
||||
|
if(exception instanceof InvalidPasswordException) { |
||||
|
Optional<ButtonType> optResponse = showErrorDialog("Invalid Password", "The wallet password was invalid. Try again?", ButtonType.CANCEL, ButtonType.OK); |
||||
|
if(optResponse.isPresent() && optResponse.get().equals(ButtonType.OK)) { |
||||
|
run(); |
||||
|
} |
||||
|
} else { |
||||
|
if(exception instanceof StorageException) { |
||||
|
showErrorDialog("Error Opening Wallet", exception.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
EventManager.get().post(new StorageEvent(storage.getWalletId(null), TimedEvent.Action.START, "Decrypting wallet...")); |
||||
|
loadWalletService.start(); |
||||
|
}); |
||||
|
} |
||||
|
} catch(Exception e) { |
||||
|
if(e instanceof IOException && e.getMessage().startsWith("The process cannot access the file because another process has locked")) { |
||||
|
showErrorDialog("Error Opening Wallet", "The wallet file is locked. Is another instance of " + MainApp.APP_NAME + " already running?"); |
||||
|
} else { |
||||
|
log.error("Error opening wallet", e); |
||||
|
showErrorDialog("Error Opening Wallet", e.getMessage() == null ? "Unsupported file format" : e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void openWallet(Storage storage, WalletAndKey walletAndKey) { |
||||
|
SparrowTerminal.get().getGui().removeWindow(loadingDialog); |
||||
|
|
||||
|
try { |
||||
|
storage.restorePublicKeysFromSeed(walletAndKey.getWallet(), walletAndKey.getKey()); |
||||
|
if(!walletAndKey.getWallet().isValid()) { |
||||
|
throw new IllegalStateException("Wallet file is not valid."); |
||||
|
} |
||||
|
addWallet(storage, walletAndKey.getWallet()); |
||||
|
for(Map.Entry<WalletAndKey, Storage> entry : walletAndKey.getChildWallets().entrySet()) { |
||||
|
openWallet(entry.getValue(), entry.getKey()); |
||||
|
} |
||||
|
if(walletAndKey.getWallet().isMasterWallet()) { |
||||
|
getOpeningDialog(walletAndKey.getWallet()).showDialog(SparrowTerminal.get().getGui()); |
||||
|
} |
||||
|
} catch(Exception e) { |
||||
|
log.error("Wallet Error", e); |
||||
|
showErrorDialog("Wallet Error", e.getMessage()); |
||||
|
} finally { |
||||
|
walletAndKey.clear(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void addWallet(Storage storage, Wallet wallet) { |
||||
|
Platform.runLater(() -> { |
||||
|
if(wallet.isNested()) { |
||||
|
WalletData walletData = SparrowTerminal.get().getWalletData().get(wallet.getMasterWallet()); |
||||
|
WalletForm walletForm = new WalletForm(storage, wallet); |
||||
|
EventManager.get().register(walletForm); |
||||
|
walletData.getWalletForm().getNestedWalletForms().add(walletForm); |
||||
|
} else { |
||||
|
EventManager.get().post(new WalletOpeningEvent(storage, wallet)); |
||||
|
|
||||
|
WalletForm walletForm = new WalletForm(storage, wallet); |
||||
|
EventManager.get().register(walletForm); |
||||
|
SparrowTerminal.get().getWalletData().put(wallet, new WalletData(walletForm)); |
||||
|
|
||||
|
List<WalletTabData> walletTabDataList = SparrowTerminal.get().getWalletData().values().stream() |
||||
|
.map(data -> new WalletTabData(TabData.TabType.WALLET, data.getWalletForm())).collect(Collectors.toList()); |
||||
|
EventManager.get().post(new OpenWalletsEvent(DEFAULT_WINDOW, walletTabDataList)); |
||||
|
|
||||
|
Set<File> walletFiles = new LinkedHashSet<>(); |
||||
|
walletFiles.add(storage.getWalletFile()); |
||||
|
walletFiles.addAll(Config.get().getRecentWalletFiles().stream().limit(MAX_RECENT_WALLETS - 1).collect(Collectors.toList())); |
||||
|
Config.get().setRecentWalletFiles(Config.get().isLoadRecentWallets() ? new ArrayList<>(walletFiles) : Collections.emptyList()); |
||||
|
} |
||||
|
|
||||
|
EventManager.get().post(new WalletOpenedEvent(storage, wallet)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static DialogWindow getOpeningDialog(Wallet masterWallet) { |
||||
|
if(masterWallet.getChildWallets().stream().anyMatch(childWallet -> !childWallet.isNested())) { |
||||
|
return new WalletAccountsDialog(masterWallet); |
||||
|
} else { |
||||
|
return new WalletActionsDialog(masterWallet); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static final javafx.stage.Window DEFAULT_WINDOW = new Window() { }; |
||||
|
|
||||
|
private static final class LoadingDialog extends DialogWindow { |
||||
|
public LoadingDialog(Storage storage) { |
||||
|
super(storage.getWalletName(null)); |
||||
|
|
||||
|
setHints(List.of(Hint.CENTERED)); |
||||
|
setFixedSize(new TerminalSize(30, 5)); |
||||
|
|
||||
|
Panel mainPanel = new Panel(); |
||||
|
mainPanel.setLayoutManager(new LinearLayout()); |
||||
|
mainPanel.addComponent(new EmptySpace(), LinearLayout.createLayoutData(LinearLayout.Alignment.Beginning, LinearLayout.GrowPolicy.CanGrow)); |
||||
|
|
||||
|
Label label = new Label("Loading..."); |
||||
|
mainPanel.addComponent(label, LinearLayout.createLayoutData(LinearLayout.Alignment.Center)); |
||||
|
|
||||
|
mainPanel.addComponent(new EmptySpace(), LinearLayout.createLayoutData(LinearLayout.Alignment.Beginning, LinearLayout.GrowPolicy.CanGrow)); |
||||
|
|
||||
|
setComponent(mainPanel); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,222 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.wallet; |
||||
|
|
||||
|
import com.googlecode.lanterna.TerminalSize; |
||||
|
import com.googlecode.lanterna.gui2.*; |
||||
|
import com.samourai.whirlpool.client.wallet.beans.IndexRange; |
||||
|
import com.sparrowwallet.drongo.protocol.ScriptType; |
||||
|
import com.sparrowwallet.drongo.wallet.MixConfig; |
||||
|
import com.sparrowwallet.drongo.wallet.Wallet; |
||||
|
import com.sparrowwallet.sparrow.AppServices; |
||||
|
import com.sparrowwallet.sparrow.wallet.WalletForm; |
||||
|
import com.sparrowwallet.sparrow.whirlpool.Whirlpool; |
||||
|
|
||||
|
import java.util.*; |
||||
|
import java.util.regex.Pattern; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
import static com.sparrowwallet.drongo.wallet.StandardAccount.WHIRLPOOL_BADBANK; |
||||
|
import static com.sparrowwallet.drongo.wallet.StandardAccount.WHIRLPOOL_PREMIX; |
||||
|
|
||||
|
public class MixToDialog extends WalletDialog { |
||||
|
private static final DisplayWallet NONE_DISPLAY_WALLET = new DisplayWallet(null); |
||||
|
|
||||
|
private MixConfig mixConfig; |
||||
|
|
||||
|
private final ComboBox<DisplayWallet> mixToWallet; |
||||
|
private final TextBox minimumMixes; |
||||
|
private final ComboBox<DisplayIndexRange> indexRange; |
||||
|
private final Button apply; |
||||
|
|
||||
|
public MixToDialog(WalletForm walletForm) { |
||||
|
super(walletForm.getWallet().getFullDisplayName() + " Mix To", walletForm); |
||||
|
|
||||
|
setHints(List.of(Hint.CENTERED)); |
||||
|
|
||||
|
Wallet wallet = getWalletForm().getWallet(); |
||||
|
this.mixConfig = wallet.getMasterMixConfig().copy(); |
||||
|
|
||||
|
Panel mainPanel = new Panel(); |
||||
|
mainPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(5).setVerticalSpacing(1)); |
||||
|
|
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
mainPanel.addComponent(new Label("Mix to wallet")); |
||||
|
mixToWallet = new ComboBox<>(); |
||||
|
mainPanel.addComponent(mixToWallet); |
||||
|
|
||||
|
mainPanel.addComponent(new Label("Minimum mixes")); |
||||
|
minimumMixes = new TextBox().setValidationPattern(Pattern.compile("[0-9]*")); |
||||
|
mainPanel.addComponent(minimumMixes); |
||||
|
|
||||
|
mainPanel.addComponent(new Label("Postmix index range")); |
||||
|
indexRange = new ComboBox<>(); |
||||
|
mainPanel.addComponent(indexRange); |
||||
|
|
||||
|
Panel buttonPanel = new Panel(); |
||||
|
buttonPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(1)); |
||||
|
buttonPanel.addComponent(new Button("Cancel", this::onCancel)); |
||||
|
apply = new Button("Apply", this::onApply).setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.CENTER, GridLayout.Alignment.CENTER, true, false)); |
||||
|
apply.setEnabled(false); |
||||
|
buttonPanel.addComponent(apply); |
||||
|
|
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
buttonPanel.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER,false,false)).addTo(mainPanel); |
||||
|
setComponent(mainPanel); |
||||
|
|
||||
|
List<DisplayWallet> allWallets = new ArrayList<>(); |
||||
|
allWallets.add(NONE_DISPLAY_WALLET); |
||||
|
List<Wallet> destinationWallets = AppServices.get().getOpenWallets().keySet().stream().filter(openWallet -> openWallet.isValid() |
||||
|
&& (openWallet.getScriptType() == ScriptType.P2WPKH || openWallet.getScriptType() == ScriptType.P2WSH) |
||||
|
&& openWallet != wallet && openWallet != wallet.getMasterWallet() |
||||
|
&& (openWallet.getStandardAccountType() == null || !List.of(WHIRLPOOL_PREMIX, WHIRLPOOL_BADBANK).contains(openWallet.getStandardAccountType()))).collect(Collectors.toList()); |
||||
|
allWallets.addAll(destinationWallets.stream().map(DisplayWallet::new).collect(Collectors.toList())); |
||||
|
allWallets.forEach(mixToWallet::addItem); |
||||
|
|
||||
|
String mixToWalletId = null; |
||||
|
try { |
||||
|
mixToWalletId = AppServices.getWhirlpoolServices().getWhirlpoolMixToWalletId(mixConfig); |
||||
|
} catch(NoSuchElementException e) { |
||||
|
//ignore, mix to wallet is not open
|
||||
|
} |
||||
|
|
||||
|
if(mixToWalletId != null) { |
||||
|
mixToWallet.setSelectedItem(new DisplayWallet(AppServices.get().getWallet(mixToWalletId))); |
||||
|
} else { |
||||
|
mixToWallet.setSelectedItem(NONE_DISPLAY_WALLET); |
||||
|
} |
||||
|
|
||||
|
int initialMinMixes = mixConfig.getMinMixes() == null ? Whirlpool.DEFAULT_MIXTO_MIN_MIXES : mixConfig.getMinMixes(); |
||||
|
minimumMixes.setText(Integer.toString(initialMinMixes)); |
||||
|
|
||||
|
List<DisplayIndexRange> indexRanges = Arrays.stream(IndexRange.values()).map(DisplayIndexRange::new).collect(Collectors.toList()); |
||||
|
indexRanges.forEach(indexRange::addItem); |
||||
|
|
||||
|
indexRange.setSelectedItem(new DisplayIndexRange(IndexRange.FULL)); |
||||
|
if(mixConfig.getIndexRange() != null) { |
||||
|
try { |
||||
|
indexRange.setSelectedItem(new DisplayIndexRange(IndexRange.valueOf(mixConfig.getIndexRange()))); |
||||
|
} catch(Exception e) { |
||||
|
//ignore
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
mixToWallet.addListener((selectedIndex, previousSelection, changedByUserInteraction) -> { |
||||
|
DisplayWallet newValue = mixToWallet.getSelectedItem(); |
||||
|
if(newValue == NONE_DISPLAY_WALLET) { |
||||
|
mixConfig.setMixToWalletName(null); |
||||
|
mixConfig.setMixToWalletFile(null); |
||||
|
} else { |
||||
|
mixConfig.setMixToWalletName(newValue.getWallet().getName()); |
||||
|
mixConfig.setMixToWalletFile(AppServices.get().getOpenWallets().get(newValue.getWallet()).getWalletFile()); |
||||
|
} |
||||
|
apply.setEnabled(apply.isEnabled() || selectedIndex != previousSelection); |
||||
|
}); |
||||
|
|
||||
|
minimumMixes.setTextChangeListener((newText, changedByUserInteraction) -> { |
||||
|
try { |
||||
|
int newValue = Integer.parseInt(newText); |
||||
|
if(newValue < 2 || newValue > 10000) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
mixConfig.setMinMixes(newValue); |
||||
|
apply.setEnabled(true); |
||||
|
} catch(NumberFormatException e) { |
||||
|
return; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
indexRange.addListener((selectedIndex, previousSelection, changedByUserInteraction) -> { |
||||
|
DisplayIndexRange newValue = indexRange.getSelectedItem(); |
||||
|
mixConfig.setIndexRange(newValue.getIndexRange().toString()); |
||||
|
apply.setEnabled(apply.isEnabled() || selectedIndex != previousSelection); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void onCancel() { |
||||
|
mixConfig = null; |
||||
|
close(); |
||||
|
} |
||||
|
|
||||
|
private void onApply() { |
||||
|
close(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Object showDialog(WindowBasedTextGUI textGUI) { |
||||
|
super.showDialog(textGUI); |
||||
|
return mixConfig; |
||||
|
} |
||||
|
|
||||
|
private static class DisplayWallet { |
||||
|
private final Wallet wallet; |
||||
|
|
||||
|
public DisplayWallet(Wallet wallet) { |
||||
|
this.wallet = wallet; |
||||
|
} |
||||
|
|
||||
|
public Wallet getWallet() { |
||||
|
return wallet; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String toString() { |
||||
|
return wallet == null ? "None" : wallet.getFullDisplayName(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean equals(Object o) { |
||||
|
if(this == o) { |
||||
|
return true; |
||||
|
} |
||||
|
if(o == null || getClass() != o.getClass()) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
DisplayWallet that = (DisplayWallet) o; |
||||
|
return Objects.equals(wallet, that.wallet); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int hashCode() { |
||||
|
return wallet != null ? wallet.hashCode() : 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static class DisplayIndexRange { |
||||
|
private final IndexRange indexRange; |
||||
|
|
||||
|
public DisplayIndexRange(IndexRange indexRange) { |
||||
|
this.indexRange = indexRange; |
||||
|
} |
||||
|
|
||||
|
public IndexRange getIndexRange() { |
||||
|
return indexRange; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String toString() { |
||||
|
return indexRange.toString().charAt(0) + indexRange.toString().substring(1).toLowerCase(Locale.ROOT); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean equals(Object o) { |
||||
|
if(this == o) { |
||||
|
return true; |
||||
|
} |
||||
|
if(o == null || getClass() != o.getClass()) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
DisplayIndexRange that = (DisplayIndexRange) o; |
||||
|
return indexRange == that.indexRange; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int hashCode() { |
||||
|
return indexRange.hashCode(); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,138 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.wallet; |
||||
|
|
||||
|
import com.google.common.eventbus.Subscribe; |
||||
|
import com.googlecode.lanterna.TerminalSize; |
||||
|
import com.googlecode.lanterna.gui2.*; |
||||
|
import com.sparrowwallet.drongo.KeyDerivation; |
||||
|
import com.sparrowwallet.drongo.KeyPurpose; |
||||
|
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex; |
||||
|
import com.sparrowwallet.drongo.wallet.Keystore; |
||||
|
import com.sparrowwallet.drongo.wallet.WalletNode; |
||||
|
import com.sparrowwallet.sparrow.AppServices; |
||||
|
import com.sparrowwallet.sparrow.event.*; |
||||
|
import com.sparrowwallet.sparrow.terminal.SparrowTerminal; |
||||
|
import com.sparrowwallet.sparrow.wallet.Function; |
||||
|
import com.sparrowwallet.sparrow.wallet.NodeEntry; |
||||
|
import com.sparrowwallet.sparrow.wallet.WalletForm; |
||||
|
|
||||
|
import java.text.DateFormat; |
||||
|
import java.text.SimpleDateFormat; |
||||
|
import java.util.List; |
||||
|
import java.util.Set; |
||||
|
|
||||
|
public class ReceiveDialog extends WalletDialog { |
||||
|
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm"); |
||||
|
|
||||
|
private final Label address; |
||||
|
private final Label derivation; |
||||
|
private final Label lastUsed; |
||||
|
|
||||
|
private NodeEntry currentEntry; |
||||
|
|
||||
|
public ReceiveDialog(WalletForm walletForm) { |
||||
|
super(walletForm.getWallet().getFullDisplayName() + " Receive", walletForm); |
||||
|
|
||||
|
setHints(List.of(Hint.CENTERED)); |
||||
|
|
||||
|
Panel mainPanel = new Panel(new GridLayout(2).setHorizontalSpacing(2).setVerticalSpacing(1).setTopMarginSize(1)); |
||||
|
|
||||
|
mainPanel.addComponent(new Label("Address")); |
||||
|
address = new Label("").addTo(mainPanel); |
||||
|
|
||||
|
mainPanel.addComponent(new Label("Derivation")); |
||||
|
derivation = new Label("").addTo(mainPanel); |
||||
|
|
||||
|
mainPanel.addComponent(new Label("Last Used")); |
||||
|
lastUsed = new Label("").addTo(mainPanel); |
||||
|
|
||||
|
Panel buttonPanel = new Panel(); |
||||
|
buttonPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(1)); |
||||
|
buttonPanel.addComponent(new Button("Back", () -> onBack(Function.RECEIVE))); |
||||
|
buttonPanel.addComponent(new Button("Get Fresh Address", this::refreshAddress).setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.CENTER, GridLayout.Alignment.CENTER, true, false))); |
||||
|
|
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
buttonPanel.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER,false,false)).addTo(mainPanel); |
||||
|
setComponent(mainPanel); |
||||
|
|
||||
|
refreshAddress(); |
||||
|
} |
||||
|
|
||||
|
public void refreshAddress() { |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(() -> { |
||||
|
NodeEntry freshEntry = getWalletForm().getFreshNodeEntry(KeyPurpose.RECEIVE, currentEntry); |
||||
|
setNodeEntry(freshEntry); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public void setNodeEntry(NodeEntry nodeEntry) { |
||||
|
this.currentEntry = nodeEntry; |
||||
|
address.setText(nodeEntry.getAddress().toString()); |
||||
|
derivation.setText(getDerivationPath(nodeEntry.getNode())); |
||||
|
updateLastUsed(); |
||||
|
} |
||||
|
|
||||
|
protected String getDerivationPath(WalletNode node) { |
||||
|
if(isSingleDerivationPath()) { |
||||
|
KeyDerivation firstDerivation = getWalletForm().getWallet().getKeystores().get(0).getKeyDerivation(); |
||||
|
return firstDerivation.extend(node.getDerivation()).getDerivationPath(); |
||||
|
} |
||||
|
|
||||
|
return node.getDerivationPath().replace("m", "multi"); |
||||
|
} |
||||
|
|
||||
|
protected boolean isSingleDerivationPath() { |
||||
|
KeyDerivation firstDerivation = getWalletForm().getWallet().getKeystores().get(0).getKeyDerivation(); |
||||
|
for(Keystore keystore : getWalletForm().getWallet().getKeystores()) { |
||||
|
if(!keystore.getKeyDerivation().getDerivationPath().equals(firstDerivation.getDerivationPath())) { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
private void updateLastUsed() { |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(() -> { |
||||
|
Set<BlockTransactionHashIndex> currentOutputs = currentEntry.getNode().getTransactionOutputs(); |
||||
|
if(AppServices.onlineProperty().get() && currentOutputs.isEmpty()) { |
||||
|
lastUsed.setText("Never"); |
||||
|
} else if(!currentOutputs.isEmpty()) { |
||||
|
long count = currentOutputs.size(); |
||||
|
BlockTransactionHashIndex lastUsedReference = currentOutputs.stream().skip(count - 1).findFirst().get(); |
||||
|
lastUsed.setText(lastUsedReference.getHeight() <= 0 ? "Unconfirmed Transaction" : (lastUsedReference.getDate() == null ? "Unknown" : DATE_FORMAT.format(lastUsedReference.getDate()))); |
||||
|
} else { |
||||
|
lastUsed.setText("Unknown"); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void walletNodesChanged(WalletNodesChangedEvent event) { |
||||
|
if(event.getWallet().equals(getWalletForm().getWallet())) { |
||||
|
if(currentEntry != null) { |
||||
|
currentEntry = null; |
||||
|
} |
||||
|
refreshAddress(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void walletHistoryChanged(WalletHistoryChangedEvent event) { |
||||
|
if(event.getWallet().equals(getWalletForm().getWallet())) { |
||||
|
if(currentEntry != null && event.getHistoryChangedNodes().contains(currentEntry.getNode())) { |
||||
|
refreshAddress(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void connection(ConnectionEvent event) { |
||||
|
updateLastUsed(); |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void disconnection(DisconnectionEvent event) { |
||||
|
updateLastUsed(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,155 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.wallet; |
||||
|
|
||||
|
import com.google.common.collect.Lists; |
||||
|
import com.google.common.eventbus.Subscribe; |
||||
|
import com.googlecode.lanterna.TerminalSize; |
||||
|
import com.googlecode.lanterna.gui2.*; |
||||
|
import com.googlecode.lanterna.gui2.table.Table; |
||||
|
import com.googlecode.lanterna.gui2.table.TableModel; |
||||
|
import com.sparrowwallet.drongo.protocol.Transaction; |
||||
|
import com.sparrowwallet.sparrow.AppServices; |
||||
|
import com.sparrowwallet.sparrow.event.*; |
||||
|
import com.sparrowwallet.sparrow.net.ExchangeSource; |
||||
|
import com.sparrowwallet.sparrow.terminal.SparrowTerminal; |
||||
|
import com.sparrowwallet.sparrow.terminal.wallet.table.CoinTableCell; |
||||
|
import com.sparrowwallet.sparrow.terminal.wallet.table.DateTableCell; |
||||
|
import com.sparrowwallet.sparrow.terminal.wallet.table.TableCell; |
||||
|
import com.sparrowwallet.sparrow.wallet.Entry; |
||||
|
import com.sparrowwallet.sparrow.wallet.Function; |
||||
|
import com.sparrowwallet.sparrow.wallet.WalletForm; |
||||
|
import com.sparrowwallet.sparrow.wallet.WalletTransactionsEntry; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
public class TransactionsDialog extends WalletDialog { |
||||
|
private final Label balance; |
||||
|
private final Label fiatBalance; |
||||
|
private final Label mempoolBalance; |
||||
|
private final Label fiatMempoolBalance; |
||||
|
private final Label transactionCount; |
||||
|
private final Table<TableCell> transactions; |
||||
|
|
||||
|
private final String[] tableColumns = {centerPad("Date", DateTableCell.TRANSACTION_WIDTH), centerPad("Value", CoinTableCell.TRANSACTION_WIDTH), centerPad("Balance", CoinTableCell.TRANSACTION_WIDTH)}; |
||||
|
|
||||
|
public TransactionsDialog(WalletForm walletForm) { |
||||
|
super(walletForm.getWallet().getFullDisplayName() + " Transactions", walletForm); |
||||
|
|
||||
|
setHints(List.of(Hint.CENTERED, Hint.EXPANDED)); |
||||
|
Panel labelPanel = new Panel(new GridLayout(3).setHorizontalSpacing(5).setVerticalSpacing(0)); |
||||
|
|
||||
|
WalletTransactionsEntry walletTransactionsEntry = getWalletForm().getWalletTransactionsEntry(); |
||||
|
|
||||
|
labelPanel.addComponent(new Label("Balance")); |
||||
|
balance = new Label("").addTo(labelPanel); |
||||
|
fiatBalance = new Label("").addTo(labelPanel); |
||||
|
|
||||
|
labelPanel.addComponent(new Label("Mempool")); |
||||
|
mempoolBalance = new Label("").addTo(labelPanel); |
||||
|
fiatMempoolBalance = new Label("").addTo(labelPanel); |
||||
|
|
||||
|
labelPanel.addComponent(new Label("Transactions")); |
||||
|
transactionCount = new Label("").addTo(labelPanel); |
||||
|
labelPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
transactions = new Table<>(tableColumns); |
||||
|
transactions.setTableCellRenderer(new EntryTableCellRenderer()); |
||||
|
|
||||
|
updateLabels(walletTransactionsEntry); |
||||
|
updateHistory(getWalletForm().getWalletTransactionsEntry()); |
||||
|
|
||||
|
Panel buttonPanel = new Panel(new GridLayout(4).setHorizontalSpacing(2).setVerticalSpacing(0)); |
||||
|
buttonPanel.addComponent(new EmptySpace(new TerminalSize(15, 1))); |
||||
|
buttonPanel.addComponent(new EmptySpace(new TerminalSize(15, 1))); |
||||
|
buttonPanel.addComponent(new Button("Back", () -> onBack(Function.TRANSACTIONS))); |
||||
|
buttonPanel.addComponent(new Button("Refresh", this::onRefresh)); |
||||
|
|
||||
|
Panel mainPanel = new Panel(); |
||||
|
mainPanel.setLayoutManager(new LinearLayout(Direction.VERTICAL).setSpacing(1)); |
||||
|
mainPanel.addComponent(labelPanel); |
||||
|
mainPanel.addComponent(transactions); |
||||
|
mainPanel.addComponent(buttonPanel); |
||||
|
|
||||
|
setComponent(mainPanel); |
||||
|
} |
||||
|
|
||||
|
private void updateHistory(WalletTransactionsEntry walletTransactionsEntry) { |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(() -> { |
||||
|
TableModel<TableCell> tableModel = getTableModel(walletTransactionsEntry); |
||||
|
transactions.setTableModel(tableModel); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private TableModel<TableCell> getTableModel(WalletTransactionsEntry walletTransactionsEntry) { |
||||
|
TableModel<TableCell> tableModel = new TableModel<>(tableColumns); |
||||
|
for(Entry entry : Lists.reverse(walletTransactionsEntry.getChildren())) { |
||||
|
tableModel.addRow(new DateTableCell(entry), new CoinTableCell(entry, false), new CoinTableCell(entry, true)); |
||||
|
} |
||||
|
|
||||
|
return tableModel; |
||||
|
} |
||||
|
|
||||
|
private void updateLabels(WalletTransactionsEntry walletTransactionsEntry) { |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(() -> { |
||||
|
balance.setText(formatBitcoinValue(walletTransactionsEntry.getBalance(), true)); |
||||
|
mempoolBalance.setText(formatBitcoinValue(walletTransactionsEntry.getMempoolBalance(), true)); |
||||
|
|
||||
|
if(AppServices.getFiatCurrencyExchangeRate() != null) { |
||||
|
fiatBalance.setText(formatFiatValue(getFiatValue(walletTransactionsEntry.getBalance(), AppServices.getFiatCurrencyExchangeRate().getBtcRate()))); |
||||
|
fiatMempoolBalance.setText(formatFiatValue(getFiatValue(walletTransactionsEntry.getMempoolBalance(), AppServices.getFiatCurrencyExchangeRate().getBtcRate()))); |
||||
|
} else { |
||||
|
fiatBalance.setText(""); |
||||
|
fiatMempoolBalance.setText(""); |
||||
|
} |
||||
|
|
||||
|
setTransactionCount(walletTransactionsEntry); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void setTransactionCount(WalletTransactionsEntry walletTransactionsEntry) { |
||||
|
transactionCount.setText(walletTransactionsEntry.getChildren() != null ? Integer.toString(walletTransactionsEntry.getChildren().size()) : "0"); |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void walletNodesChanged(WalletNodesChangedEvent event) { |
||||
|
if(event.getWallet().equals(getWalletForm().getWallet())) { |
||||
|
WalletTransactionsEntry walletTransactionsEntry = getWalletForm().getWalletTransactionsEntry(); |
||||
|
updateHistory(walletTransactionsEntry); |
||||
|
updateLabels(walletTransactionsEntry); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void walletHistoryChanged(WalletHistoryChangedEvent event) { |
||||
|
if(event.getWallet().equals(getWalletForm().getWallet())) { |
||||
|
WalletTransactionsEntry walletTransactionsEntry = getWalletForm().getWalletTransactionsEntry(); |
||||
|
walletTransactionsEntry.updateTransactions(); |
||||
|
updateHistory(walletTransactionsEntry); |
||||
|
updateLabels(walletTransactionsEntry); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void unitFormatChanged(UnitFormatChangedEvent event) { |
||||
|
updateHistory(getWalletForm().getWalletTransactionsEntry()); |
||||
|
updateLabels(getWalletForm().getWalletTransactionsEntry()); |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void fiatCurrencySelected(FiatCurrencySelectedEvent event) { |
||||
|
if(event.getExchangeSource() == ExchangeSource.NONE) { |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(() -> { |
||||
|
fiatBalance.setText(""); |
||||
|
fiatMempoolBalance.setText(""); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void exchangeRatesUpdated(ExchangeRatesUpdatedEvent event) { |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(() -> { |
||||
|
WalletTransactionsEntry walletTransactionsEntry = getWalletForm().getWalletTransactionsEntry(); |
||||
|
fiatBalance.setText(formatFiatValue(getFiatValue(walletTransactionsEntry.getBalance(), event.getBtcRate()))); |
||||
|
fiatMempoolBalance.setText(formatFiatValue(getFiatValue(walletTransactionsEntry.getMempoolBalance(), event.getBtcRate()))); |
||||
|
}); |
||||
|
} |
||||
|
} |
@ -0,0 +1,410 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.wallet; |
||||
|
|
||||
|
import com.google.common.eventbus.Subscribe; |
||||
|
import com.googlecode.lanterna.TerminalSize; |
||||
|
import com.googlecode.lanterna.gui2.*; |
||||
|
import com.googlecode.lanterna.gui2.table.Table; |
||||
|
import com.googlecode.lanterna.gui2.table.TableModel; |
||||
|
import com.samourai.whirlpool.client.wallet.beans.MixProgress; |
||||
|
import com.sparrowwallet.drongo.wallet.MixConfig; |
||||
|
import com.sparrowwallet.drongo.wallet.StandardAccount; |
||||
|
import com.sparrowwallet.sparrow.AppServices; |
||||
|
import com.sparrowwallet.sparrow.EventManager; |
||||
|
import com.sparrowwallet.sparrow.event.*; |
||||
|
import com.sparrowwallet.sparrow.net.ExchangeSource; |
||||
|
import com.sparrowwallet.sparrow.terminal.SparrowTerminal; |
||||
|
import com.sparrowwallet.sparrow.terminal.wallet.table.*; |
||||
|
import com.sparrowwallet.sparrow.wallet.*; |
||||
|
import com.sparrowwallet.sparrow.whirlpool.Whirlpool; |
||||
|
import javafx.application.Platform; |
||||
|
import javafx.beans.value.ChangeListener; |
||||
|
import javafx.beans.value.WeakChangeListener; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
import java.util.NoSuchElementException; |
||||
|
|
||||
|
public class UtxosDialog extends WalletDialog { |
||||
|
private final Label balance; |
||||
|
private final Label fiatBalance; |
||||
|
private final Label mempoolBalance; |
||||
|
private final Label fiatMempoolBalance; |
||||
|
private final Label utxoCount; |
||||
|
private final Table<TableCell> utxos; |
||||
|
|
||||
|
private Button startMix; |
||||
|
private Button mixTo; |
||||
|
|
||||
|
private final ChangeListener<Boolean> mixingOnlineListener = (observable, oldValue, newValue) -> { |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(() -> startMix.setEnabled(newValue)); |
||||
|
}; |
||||
|
|
||||
|
private final ChangeListener<Boolean> mixingStartingListener = (observable, oldValue, newValue) -> { |
||||
|
try { |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeAndWait(() -> { |
||||
|
startMix.setEnabled(!newValue && AppServices.onlineProperty().get()); |
||||
|
startMix.setLabel(newValue && AppServices.onlineProperty().get() ? "Starting Mixing..." : isMixing() ? "Stop Mixing" : "Start Mixing"); |
||||
|
mixTo.setEnabled(!newValue); |
||||
|
}); |
||||
|
} catch(InterruptedException e) { |
||||
|
//ignore
|
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
private final ChangeListener<Boolean> mixingStoppingListener = (observable, oldValue, newValue) -> { |
||||
|
try { |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeAndWait(() -> { |
||||
|
startMix.setEnabled(!newValue && AppServices.onlineProperty().get()); |
||||
|
startMix.setLabel(newValue ? "Stopping Mixing..." : isMixing() ? "Stop Mixing" : "Start Mixing"); |
||||
|
mixTo.setEnabled(!newValue); |
||||
|
}); |
||||
|
} catch(InterruptedException e) { |
||||
|
//ignore
|
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
private final ChangeListener<Boolean> mixingListener = (observable, oldValue, newValue) -> { |
||||
|
if(!newValue) { |
||||
|
WalletUtxosEntry walletUtxosEntry = getWalletForm().getWalletUtxosEntry(); |
||||
|
for(Entry entry : walletUtxosEntry.getChildren()) { |
||||
|
UtxoEntry utxoEntry = (UtxoEntry)entry; |
||||
|
if(utxoEntry.getMixStatus() != null && utxoEntry.getMixStatus().getMixProgress() != null |
||||
|
&& utxoEntry.getMixStatus().getMixProgress().getMixStep() != null |
||||
|
&& utxoEntry.getMixStatus().getMixProgress().getMixStep().isInterruptable()) { |
||||
|
whirlpoolMix(new WhirlpoolMixEvent(getWalletForm().getWallet(), utxoEntry.getHashIndex(), (MixProgress)null)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
public UtxosDialog(WalletForm walletForm) { |
||||
|
super(walletForm.getWallet().getFullDisplayName() + " UTXOs", walletForm); |
||||
|
|
||||
|
setHints(List.of(Hint.CENTERED, Hint.EXPANDED)); |
||||
|
Panel labelPanel = new Panel(new GridLayout(3).setHorizontalSpacing(5).setVerticalSpacing(0)); |
||||
|
|
||||
|
WalletUtxosEntry walletUtxosEntry = getWalletForm().getWalletUtxosEntry(); |
||||
|
|
||||
|
labelPanel.addComponent(new Label("Balance")); |
||||
|
balance = new Label("").addTo(labelPanel); |
||||
|
fiatBalance = new Label("").addTo(labelPanel); |
||||
|
|
||||
|
labelPanel.addComponent(new Label("Mempool")); |
||||
|
mempoolBalance = new Label("").addTo(labelPanel); |
||||
|
fiatMempoolBalance = new Label("").addTo(labelPanel); |
||||
|
|
||||
|
labelPanel.addComponent(new Label("UTXOs")); |
||||
|
utxoCount = new Label("").addTo(labelPanel); |
||||
|
labelPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
utxos = new Table<>(getTableColumns()); |
||||
|
utxos.setTableCellRenderer(new EntryTableCellRenderer()); |
||||
|
|
||||
|
updateLabels(walletUtxosEntry); |
||||
|
updateHistory(getWalletForm().getWalletUtxosEntry()); |
||||
|
|
||||
|
Panel buttonPanel = new Panel(new GridLayout(4).setHorizontalSpacing(2).setVerticalSpacing(0)); |
||||
|
if(getWalletForm().getWallet().isWhirlpoolMixWallet()) { |
||||
|
startMix = new Button("Start Mixing", this::toggleMixing).setSize(new TerminalSize(20, 1)).addTo(buttonPanel); |
||||
|
startMix.setEnabled(AppServices.onlineProperty().get()); |
||||
|
|
||||
|
mixTo = new Button("Mix to...", this::showMixToDialog).addTo(buttonPanel); |
||||
|
if(getWalletForm().getWallet().getStandardAccountType() == StandardAccount.WHIRLPOOL_POSTMIX) { |
||||
|
buttonPanel.addComponent(mixTo); |
||||
|
} else { |
||||
|
buttonPanel.addComponent(new EmptySpace(new TerminalSize(15, 1))); |
||||
|
} |
||||
|
|
||||
|
Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getWallet()); |
||||
|
if(whirlpool != null) { |
||||
|
startMix.setLabel(whirlpool.isMixing() ? "Stop Mixing" : "Start Mixing"); |
||||
|
if(whirlpool.startingProperty().getValue()) { |
||||
|
mixingStartingListener.changed(whirlpool.startingProperty(), null, whirlpool.startingProperty().getValue()); |
||||
|
} |
||||
|
whirlpool.startingProperty().addListener(new WeakChangeListener<>(mixingStartingListener)); |
||||
|
if(whirlpool.stoppingProperty().getValue()) { |
||||
|
mixingStoppingListener.changed(whirlpool.stoppingProperty(), null, whirlpool.stoppingProperty().getValue()); |
||||
|
} |
||||
|
whirlpool.stoppingProperty().addListener(new WeakChangeListener<>(mixingStoppingListener)); |
||||
|
whirlpool.mixingProperty().addListener(new WeakChangeListener<>(mixingListener)); |
||||
|
updateMixToButton(); |
||||
|
} |
||||
|
|
||||
|
AppServices.onlineProperty().addListener(new WeakChangeListener<>(mixingOnlineListener)); |
||||
|
|
||||
|
buttonPanel.addComponent(new Button("Back", () -> onBack(Function.UTXOS))); |
||||
|
buttonPanel.addComponent(new Button("Refresh", this::onRefresh)); |
||||
|
} else { |
||||
|
buttonPanel.addComponent(new EmptySpace(new TerminalSize(15, 1))); |
||||
|
buttonPanel.addComponent(new EmptySpace(new TerminalSize(15, 1))); |
||||
|
buttonPanel.addComponent(new Button("Back", () -> onBack(Function.UTXOS))); |
||||
|
buttonPanel.addComponent(new Button("Refresh", this::onRefresh)); |
||||
|
} |
||||
|
|
||||
|
Panel mainPanel = new Panel(); |
||||
|
mainPanel.setLayoutManager(new LinearLayout(Direction.VERTICAL).setSpacing(1)); |
||||
|
mainPanel.addComponent(labelPanel); |
||||
|
mainPanel.addComponent(utxos); |
||||
|
mainPanel.addComponent(buttonPanel); |
||||
|
|
||||
|
setComponent(mainPanel); |
||||
|
} |
||||
|
|
||||
|
private String[] getTableColumns() { |
||||
|
if(getWalletForm().getWallet().isWhirlpoolMixWallet()) { |
||||
|
return new String[] {centerPad("Date", DateTableCell.UTXO_WIDTH), centerPad("Output", OutputTableCell.WIDTH), |
||||
|
centerPad("Mixes", MixTableCell.WIDTH), centerPad("Value", CoinTableCell.UTXO_WIDTH)}; |
||||
|
} |
||||
|
|
||||
|
return new String[] {centerPad("Date", DateTableCell.UTXO_WIDTH), centerPad("Output", OutputTableCell.WIDTH), |
||||
|
centerPad("Address", AddressTableCell.UTXO_WIDTH), centerPad("Value", CoinTableCell.UTXO_WIDTH)}; |
||||
|
} |
||||
|
|
||||
|
private void updateHistory(WalletUtxosEntry walletUtxosEntry) { |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(() -> { |
||||
|
TableModel<TableCell> tableModel = getTableModel(walletUtxosEntry); |
||||
|
utxos.setTableModel(tableModel); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private TableModel<TableCell> getTableModel(WalletUtxosEntry walletUtxosEntry) { |
||||
|
TableModel<TableCell> tableModel = new TableModel<>(getTableColumns()); |
||||
|
List<Entry> utxoList = new ArrayList<>(walletUtxosEntry.getChildren()); |
||||
|
utxoList.sort((o1, o2) -> Long.compare(o2.getValue(), o1.getValue())); |
||||
|
|
||||
|
for(Entry entry : utxoList) { |
||||
|
if(walletUtxosEntry.getWallet().isWhirlpoolMixWallet()) { |
||||
|
tableModel.addRow(new DateTableCell(entry), new OutputTableCell(entry), new MixTableCell(entry), new CoinTableCell(entry, false)); |
||||
|
} else { |
||||
|
tableModel.addRow(new DateTableCell(entry), new OutputTableCell(entry), new AddressTableCell(entry), new CoinTableCell(entry, false)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return tableModel; |
||||
|
} |
||||
|
|
||||
|
private void updateLabels(WalletUtxosEntry walletUtxosEntry) { |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(() -> { |
||||
|
balance.setText(formatBitcoinValue(walletUtxosEntry.getBalance(), true)); |
||||
|
mempoolBalance.setText(formatBitcoinValue(walletUtxosEntry.getMempoolBalance(), true)); |
||||
|
|
||||
|
if(AppServices.getFiatCurrencyExchangeRate() != null) { |
||||
|
fiatBalance.setText(formatFiatValue(getFiatValue(walletUtxosEntry.getBalance(), AppServices.getFiatCurrencyExchangeRate().getBtcRate()))); |
||||
|
fiatMempoolBalance.setText(formatFiatValue(getFiatValue(walletUtxosEntry.getMempoolBalance(), AppServices.getFiatCurrencyExchangeRate().getBtcRate()))); |
||||
|
} else { |
||||
|
fiatBalance.setText(""); |
||||
|
fiatMempoolBalance.setText(""); |
||||
|
} |
||||
|
|
||||
|
setUtxoCount(walletUtxosEntry); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void setUtxoCount(WalletUtxosEntry walletUtxosEntry) { |
||||
|
utxoCount.setText(walletUtxosEntry.getChildren() != null ? Integer.toString(walletUtxosEntry.getChildren().size()) : "0"); |
||||
|
} |
||||
|
|
||||
|
private boolean isMixing() { |
||||
|
Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getWallet()); |
||||
|
return whirlpool != null && whirlpool.isMixing(); |
||||
|
} |
||||
|
|
||||
|
public void toggleMixing() { |
||||
|
if(isMixing()) { |
||||
|
stopMixing(); |
||||
|
} else { |
||||
|
startMixing(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void startMixing() { |
||||
|
startMix.setEnabled(false); |
||||
|
|
||||
|
Platform.runLater(() -> { |
||||
|
getWalletForm().getWallet().getMasterMixConfig().setMixOnStartup(Boolean.TRUE); |
||||
|
EventManager.get().post(new WalletMasterMixConfigChangedEvent(getWalletForm().getWallet())); |
||||
|
|
||||
|
Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getWallet()); |
||||
|
if(whirlpool != null && !whirlpool.isStarted() && AppServices.isConnected()) { |
||||
|
AppServices.getWhirlpoolServices().startWhirlpool(getWalletForm().getWallet(), whirlpool, true); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public void stopMixing() { |
||||
|
startMix.setEnabled(AppServices.onlineProperty().get()); |
||||
|
|
||||
|
Platform.runLater(() -> { |
||||
|
getWalletForm().getWallet().getMasterMixConfig().setMixOnStartup(Boolean.FALSE); |
||||
|
EventManager.get().post(new WalletMasterMixConfigChangedEvent(getWalletForm().getWallet())); |
||||
|
|
||||
|
Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getWallet()); |
||||
|
if(whirlpool.isStarted()) { |
||||
|
AppServices.getWhirlpoolServices().stopWhirlpool(whirlpool, true); |
||||
|
} else { |
||||
|
//Ensure http clients are shutdown
|
||||
|
whirlpool.shutdown(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public void showMixToDialog() { |
||||
|
MixToDialog mixToDialog = new MixToDialog(getWalletForm()); |
||||
|
MixConfig changedMixConfig = (MixConfig)mixToDialog.showDialog(SparrowTerminal.get().getGui()); |
||||
|
|
||||
|
if(changedMixConfig != null) { |
||||
|
MixConfig mixConfig = getWalletForm().getWallet().getMasterMixConfig(); |
||||
|
|
||||
|
mixConfig.setMixToWalletName(changedMixConfig.getMixToWalletName()); |
||||
|
mixConfig.setMixToWalletFile(changedMixConfig.getMixToWalletFile()); |
||||
|
mixConfig.setMinMixes(changedMixConfig.getMinMixes()); |
||||
|
mixConfig.setIndexRange(changedMixConfig.getIndexRange()); |
||||
|
|
||||
|
Platform.runLater(() -> { |
||||
|
EventManager.get().post(new WalletMasterMixConfigChangedEvent(getWalletForm().getWallet())); |
||||
|
|
||||
|
Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getWallet()); |
||||
|
whirlpool.setPostmixIndexRange(mixConfig.getIndexRange()); |
||||
|
try { |
||||
|
String mixToWalletId = AppServices.getWhirlpoolServices().getWhirlpoolMixToWalletId(mixConfig); |
||||
|
whirlpool.setMixToWallet(mixToWalletId, mixConfig.getMinMixes()); |
||||
|
} catch(NoSuchElementException e) { |
||||
|
mixConfig.setMixToWalletName(null); |
||||
|
mixConfig.setMixToWalletFile(null); |
||||
|
EventManager.get().post(new WalletMasterMixConfigChangedEvent(getWalletForm().getWallet())); |
||||
|
whirlpool.setMixToWallet(null, null); |
||||
|
} |
||||
|
|
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(this::updateMixToButton); |
||||
|
if(whirlpool.isStarted()) { |
||||
|
//Will automatically restart
|
||||
|
AppServices.getWhirlpoolServices().stopWhirlpool(whirlpool, false); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void updateMixToButton() { |
||||
|
if(mixTo == null) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
MixConfig mixConfig = getWalletForm().getWallet().getMasterMixConfig(); |
||||
|
if(mixConfig != null && mixConfig.getMixToWalletName() != null) { |
||||
|
mixTo.setLabel("Mixing to " + mixConfig.getMixToWalletName()); |
||||
|
try { |
||||
|
String mixToWalletId = AppServices.getWhirlpoolServices().getWhirlpoolMixToWalletId(mixConfig); |
||||
|
String mixToName = AppServices.get().getWallet(mixToWalletId).getFullDisplayName(); |
||||
|
mixTo.setLabel("Mixing to " + mixToName); |
||||
|
} catch(NoSuchElementException e) { |
||||
|
mixTo.setLabel("! Not Open"); |
||||
|
} |
||||
|
} else { |
||||
|
mixTo.setLabel("Mix to..."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void walletNodesChanged(WalletNodesChangedEvent event) { |
||||
|
if(event.getWallet().equals(getWalletForm().getWallet())) { |
||||
|
WalletUtxosEntry walletUtxosEntry = getWalletForm().getWalletUtxosEntry(); |
||||
|
updateHistory(walletUtxosEntry); |
||||
|
updateLabels(walletUtxosEntry); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void walletHistoryChanged(WalletHistoryChangedEvent event) { |
||||
|
if(event.getWallet().equals(getWalletForm().getWallet())) { |
||||
|
WalletUtxosEntry walletUtxosEntry = getWalletForm().getWalletUtxosEntry(); |
||||
|
walletUtxosEntry.updateUtxos(); |
||||
|
updateHistory(walletUtxosEntry); |
||||
|
updateLabels(walletUtxosEntry); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void whirlpoolMix(WhirlpoolMixEvent event) { |
||||
|
if(event.getWallet().equals(getWalletForm().getWallet())) { |
||||
|
WalletUtxosEntry walletUtxosEntry = getWalletForm().getWalletUtxosEntry(); |
||||
|
for(Entry entry : walletUtxosEntry.getChildren()) { |
||||
|
UtxoEntry utxoEntry = (UtxoEntry)entry; |
||||
|
if(utxoEntry.getHashIndex().equals(event.getUtxo())) { |
||||
|
if(event.getNextUtxo() != null) { |
||||
|
utxoEntry.setNextMixUtxo(event.getNextUtxo()); |
||||
|
} else if(event.getMixFailReason() != null) { |
||||
|
utxoEntry.setMixFailReason(event.getMixFailReason(), event.getMixError()); |
||||
|
} else { |
||||
|
utxoEntry.setMixProgress(event.getMixProgress()); |
||||
|
} |
||||
|
|
||||
|
TableModel<TableCell> tableModel = utxos.getTableModel(); |
||||
|
for(int row = 0; row < tableModel.getRowCount(); row++) { |
||||
|
UtxoEntry tableEntry = (UtxoEntry)tableModel.getRow(row).get(0).getEntry(); |
||||
|
if(tableEntry.getHashIndex().equals(event.getUtxo())) { |
||||
|
final int utxoRow = row; |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(() -> { |
||||
|
tableModel.setCell(2, utxoRow, new MixTableCell(utxoEntry)); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void newBlock(NewBlockEvent event) { |
||||
|
Whirlpool whirlpool = AppServices.getWhirlpoolServices().getWhirlpool(getWalletForm().getWallet()); |
||||
|
if(whirlpool != null) { |
||||
|
for(Entry entry : getWalletForm().getWalletUtxosEntry().getChildren()) { |
||||
|
UtxoEntry utxoEntry = (UtxoEntry)entry; |
||||
|
MixProgress mixProgress = whirlpool.getMixProgress(utxoEntry.getHashIndex()); |
||||
|
if(mixProgress != null || utxoEntry.getMixStatus() == null || (utxoEntry.getMixStatus().getMixFailReason() == null && utxoEntry.getMixStatus().getNextMixUtxo() == null)) { |
||||
|
whirlpoolMix(new WhirlpoolMixEvent(getWalletForm().getWallet(), utxoEntry.getHashIndex(), mixProgress)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void openWallets(OpenWalletsEvent event) { |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(this::updateMixToButton); |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void walletLabelChanged(WalletLabelChangedEvent event) { |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(this::updateMixToButton); |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void includeMempoolOutputsChangedEvent(IncludeMempoolOutputsChangedEvent event) { |
||||
|
updateHistory(getWalletForm().getWalletUtxosEntry()); |
||||
|
updateLabels(getWalletForm().getWalletUtxosEntry()); |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void unitFormatChanged(UnitFormatChangedEvent event) { |
||||
|
updateHistory(getWalletForm().getWalletUtxosEntry()); |
||||
|
updateLabels(getWalletForm().getWalletUtxosEntry()); |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void fiatCurrencySelected(FiatCurrencySelectedEvent event) { |
||||
|
if(event.getExchangeSource() == ExchangeSource.NONE) { |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(() -> { |
||||
|
fiatBalance.setText(""); |
||||
|
fiatMempoolBalance.setText(""); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Subscribe |
||||
|
public void exchangeRatesUpdated(ExchangeRatesUpdatedEvent event) { |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(() -> { |
||||
|
WalletUtxosEntry walletUtxosEntry = getWalletForm().getWalletUtxosEntry(); |
||||
|
fiatBalance.setText(formatFiatValue(getFiatValue(walletUtxosEntry.getBalance(), event.getBtcRate()))); |
||||
|
fiatMempoolBalance.setText(formatFiatValue(getFiatValue(walletUtxosEntry.getMempoolBalance(), event.getBtcRate()))); |
||||
|
}); |
||||
|
} |
||||
|
} |
@ -0,0 +1,55 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.wallet; |
||||
|
|
||||
|
import com.googlecode.lanterna.TerminalSize; |
||||
|
import com.googlecode.lanterna.gui2.*; |
||||
|
import com.googlecode.lanterna.gui2.dialogs.DialogWindow; |
||||
|
import com.sparrowwallet.drongo.wallet.Wallet; |
||||
|
import com.sparrowwallet.sparrow.terminal.SparrowTerminal; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
public class WalletAccountsDialog extends DialogWindow { |
||||
|
private final Wallet masterWallet; |
||||
|
private final ActionListBox actions; |
||||
|
|
||||
|
public WalletAccountsDialog(Wallet masterWallet) { |
||||
|
super(masterWallet.getFullDisplayName()); |
||||
|
|
||||
|
setHints(List.of(Hint.CENTERED)); |
||||
|
|
||||
|
this.masterWallet = masterWallet; |
||||
|
|
||||
|
actions = new ActionListBox(); |
||||
|
|
||||
|
for(Wallet wallet : masterWallet.getAllWallets()) { |
||||
|
actions.addItem(wallet.getDisplayName(), () -> { |
||||
|
close(); |
||||
|
SparrowTerminal.get().getGui().getGUIThread().invokeLater(() -> { |
||||
|
WalletActionsDialog walletActionsDialog = new WalletActionsDialog(wallet); |
||||
|
walletActionsDialog.showDialog(SparrowTerminal.get().getGui()); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
Panel mainPanel = new Panel(); |
||||
|
mainPanel.setLayoutManager(new GridLayout(1).setLeftMarginSize(1).setRightMarginSize(1)); |
||||
|
actions.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.FILL, GridLayout.Alignment.CENTER, true, false)).addTo(mainPanel); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
Panel buttonPanel = new Panel(); |
||||
|
buttonPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(1)); |
||||
|
buttonPanel.addComponent(new Button("Cancel", this::onCancel).setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.CENTER, GridLayout.Alignment.CENTER, true, false))); |
||||
|
buttonPanel.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER, false, false)).addTo(mainPanel); |
||||
|
|
||||
|
setComponent(mainPanel); |
||||
|
} |
||||
|
|
||||
|
private void onCancel() { |
||||
|
close(); |
||||
|
} |
||||
|
|
||||
|
public void setWalletAccount(Wallet wallet) { |
||||
|
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet(); |
||||
|
actions.setSelectedIndex(masterWallet.getAllWallets().indexOf(wallet)); |
||||
|
} |
||||
|
} |
@ -0,0 +1,96 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.wallet; |
||||
|
|
||||
|
import com.googlecode.lanterna.TerminalSize; |
||||
|
import com.googlecode.lanterna.gui2.*; |
||||
|
import com.googlecode.lanterna.gui2.dialogs.DialogWindow; |
||||
|
import com.sparrowwallet.drongo.wallet.Wallet; |
||||
|
import com.sparrowwallet.sparrow.terminal.SparrowTerminal; |
||||
|
import com.sparrowwallet.sparrow.wallet.Function; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
public class WalletActionsDialog extends DialogWindow { |
||||
|
private final Wallet wallet; |
||||
|
private final ActionListBox actions; |
||||
|
|
||||
|
public WalletActionsDialog(Wallet wallet) { |
||||
|
super(wallet.getFullDisplayName()); |
||||
|
|
||||
|
setHints(List.of(Hint.CENTERED)); |
||||
|
|
||||
|
this.wallet = wallet; |
||||
|
|
||||
|
actions = new ActionListBox(); |
||||
|
actions.addItem("Transactions", () -> { |
||||
|
close(); |
||||
|
TransactionsDialog transactionsDialog = getWalletData().getTransactionsDialog(); |
||||
|
transactionsDialog.showDialog(SparrowTerminal.get().getGui()); |
||||
|
}); |
||||
|
if(!getWalletData().getWalletForm().getWallet().isWhirlpoolChildWallet()) { |
||||
|
actions.addItem("Receive", () -> { |
||||
|
close(); |
||||
|
ReceiveDialog receiveDialog = getWalletData().getReceiveDialog(); |
||||
|
receiveDialog.showDialog(SparrowTerminal.get().getGui()); |
||||
|
}); |
||||
|
} |
||||
|
actions.addItem("Addresses", () -> { |
||||
|
close(); |
||||
|
AddressesDialog addressesDialog = getWalletData().getAddressesDialog(); |
||||
|
addressesDialog.showDialog(SparrowTerminal.get().getGui()); |
||||
|
}); |
||||
|
actions.addItem("UTXOs", () -> { |
||||
|
close(); |
||||
|
UtxosDialog utxosDialog = getWalletData().getUtxosDialog(); |
||||
|
utxosDialog.showDialog(SparrowTerminal.get().getGui()); |
||||
|
}); |
||||
|
|
||||
|
Panel mainPanel = new Panel(); |
||||
|
mainPanel.setLayoutManager(new GridLayout(1).setLeftMarginSize(1).setRightMarginSize(1)); |
||||
|
actions.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.FILL, GridLayout.Alignment.CENTER, true, false)).addTo(mainPanel); |
||||
|
mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); |
||||
|
|
||||
|
Panel buttonPanel = new Panel(); |
||||
|
buttonPanel.setLayoutManager(new GridLayout(2).setHorizontalSpacing(1)); |
||||
|
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet(); |
||||
|
if(masterWallet.getChildWallets().stream().anyMatch(childWallet -> !childWallet.isNested())) { |
||||
|
buttonPanel.addComponent(new Button("Accounts", this::onAccounts).setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.CENTER, GridLayout.Alignment.CENTER, true, false))); |
||||
|
} |
||||
|
buttonPanel.addComponent(new Button("Cancel", this::onCancel).setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.CENTER, GridLayout.Alignment.CENTER, true, false))); |
||||
|
buttonPanel.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER, false, false)).addTo(mainPanel); |
||||
|
|
||||
|
setComponent(mainPanel); |
||||
|
} |
||||
|
|
||||
|
public void setFunction(Function function) { |
||||
|
int isWhirlpoolWallet = getWalletData().getWalletForm().getWallet().isWhirlpoolChildWallet() ? 1 : 0; |
||||
|
if(function == Function.TRANSACTIONS) { |
||||
|
actions.setSelectedIndex(0); |
||||
|
} else if(function == Function.RECEIVE) { |
||||
|
actions.setSelectedIndex(1); |
||||
|
} else if(function == Function.ADDRESSES) { |
||||
|
actions.setSelectedIndex(2 - isWhirlpoolWallet); |
||||
|
} else if(function == Function.UTXOS) { |
||||
|
actions.setSelectedIndex(3 - isWhirlpoolWallet); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void onCancel() { |
||||
|
close(); |
||||
|
} |
||||
|
|
||||
|
private void onAccounts() { |
||||
|
close(); |
||||
|
WalletAccountsDialog walletAccountsDialog = new WalletAccountsDialog(wallet.isMasterWallet() ? wallet : wallet.getMasterWallet()); |
||||
|
walletAccountsDialog.setWalletAccount(wallet); |
||||
|
walletAccountsDialog.showDialog(SparrowTerminal.get().getGui()); |
||||
|
} |
||||
|
|
||||
|
private WalletData getWalletData() { |
||||
|
WalletData walletData = SparrowTerminal.get().getWalletData().get(wallet); |
||||
|
if(walletData == null) { |
||||
|
throw new IllegalStateException("Wallet data is null for " + wallet.getFullDisplayName()); |
||||
|
} |
||||
|
|
||||
|
return walletData; |
||||
|
} |
||||
|
} |
@ -0,0 +1,56 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.wallet; |
||||
|
|
||||
|
import com.sparrowwallet.sparrow.EventManager; |
||||
|
import com.sparrowwallet.sparrow.wallet.WalletForm; |
||||
|
|
||||
|
public class WalletData { |
||||
|
private final WalletForm walletForm; |
||||
|
private TransactionsDialog transactionsDialog; |
||||
|
private ReceiveDialog receiveDialog; |
||||
|
private AddressesDialog addressesDialog; |
||||
|
private UtxosDialog utxosDialog; |
||||
|
|
||||
|
public WalletData(WalletForm walletForm) { |
||||
|
this.walletForm = walletForm; |
||||
|
} |
||||
|
|
||||
|
public WalletForm getWalletForm() { |
||||
|
return walletForm; |
||||
|
} |
||||
|
|
||||
|
public TransactionsDialog getTransactionsDialog() { |
||||
|
if(transactionsDialog == null) { |
||||
|
transactionsDialog = new TransactionsDialog(walletForm); |
||||
|
EventManager.get().register(transactionsDialog); |
||||
|
} |
||||
|
|
||||
|
return transactionsDialog; |
||||
|
} |
||||
|
|
||||
|
public ReceiveDialog getReceiveDialog() { |
||||
|
if(receiveDialog == null) { |
||||
|
receiveDialog = new ReceiveDialog(walletForm); |
||||
|
EventManager.get().register(receiveDialog); |
||||
|
} |
||||
|
|
||||
|
return receiveDialog; |
||||
|
} |
||||
|
|
||||
|
public AddressesDialog getAddressesDialog() { |
||||
|
if(addressesDialog == null) { |
||||
|
addressesDialog = new AddressesDialog(walletForm); |
||||
|
EventManager.get().register(addressesDialog); |
||||
|
} |
||||
|
|
||||
|
return addressesDialog; |
||||
|
} |
||||
|
|
||||
|
public UtxosDialog getUtxosDialog() { |
||||
|
if(utxosDialog == null) { |
||||
|
utxosDialog = new UtxosDialog(walletForm); |
||||
|
EventManager.get().register(utxosDialog); |
||||
|
} |
||||
|
|
||||
|
return utxosDialog; |
||||
|
} |
||||
|
} |
@ -0,0 +1,99 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.wallet; |
||||
|
|
||||
|
import com.google.common.base.Strings; |
||||
|
import com.googlecode.lanterna.gui2.dialogs.DialogWindow; |
||||
|
import com.sparrowwallet.drongo.BitcoinUnit; |
||||
|
import com.sparrowwallet.drongo.protocol.Transaction; |
||||
|
import com.sparrowwallet.drongo.wallet.Wallet; |
||||
|
import com.sparrowwallet.sparrow.AppServices; |
||||
|
import com.sparrowwallet.sparrow.CurrencyRate; |
||||
|
import com.sparrowwallet.sparrow.EventManager; |
||||
|
import com.sparrowwallet.sparrow.UnitFormat; |
||||
|
import com.sparrowwallet.sparrow.event.WalletHistoryClearedEvent; |
||||
|
import com.sparrowwallet.sparrow.io.Config; |
||||
|
import com.sparrowwallet.sparrow.terminal.SparrowTerminal; |
||||
|
import com.sparrowwallet.sparrow.wallet.Function; |
||||
|
import com.sparrowwallet.sparrow.wallet.WalletForm; |
||||
|
import javafx.application.Platform; |
||||
|
|
||||
|
import java.util.Currency; |
||||
|
|
||||
|
public class WalletDialog extends DialogWindow { |
||||
|
private final WalletForm walletForm; |
||||
|
|
||||
|
public WalletDialog(String title, WalletForm walletForm) { |
||||
|
super(title); |
||||
|
this.walletForm = walletForm; |
||||
|
} |
||||
|
|
||||
|
public WalletForm getWalletForm() { |
||||
|
return walletForm; |
||||
|
} |
||||
|
|
||||
|
protected void onBack(Function function) { |
||||
|
close(); |
||||
|
WalletActionsDialog walletActionsDialog = new WalletActionsDialog(getWalletForm().getWallet()); |
||||
|
walletActionsDialog.setFunction(function); |
||||
|
walletActionsDialog.showDialog(SparrowTerminal.get().getGui()); |
||||
|
} |
||||
|
|
||||
|
protected void onRefresh() { |
||||
|
Wallet wallet = getWalletForm().getWallet(); |
||||
|
Wallet pastWallet = wallet.copy(); |
||||
|
wallet.clearHistory(); |
||||
|
AppServices.clearTransactionHistoryCache(wallet); |
||||
|
Platform.runLater(() -> EventManager.get().post(new WalletHistoryClearedEvent(wallet, pastWallet, getWalletForm().getWalletId()))); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void close() { |
||||
|
if(getTextGUI() != null) { |
||||
|
getTextGUI().removeWindow(this); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected String formatBitcoinValue(long value, boolean appendUnit) { |
||||
|
BitcoinUnit unit = Config.get().getBitcoinUnit(); |
||||
|
if(unit == null || unit.equals(BitcoinUnit.AUTO)) { |
||||
|
unit = (value >= BitcoinUnit.getAutoThreshold() ? BitcoinUnit.BTC : BitcoinUnit.SATOSHIS); |
||||
|
} |
||||
|
|
||||
|
UnitFormat format = Config.get().getUnitFormat(); |
||||
|
if(format == null) { |
||||
|
format = UnitFormat.DOT; |
||||
|
} |
||||
|
|
||||
|
return unit == BitcoinUnit.SATOSHIS ? format.formatSatsValue(value) + (appendUnit ? " sats" : "") : format.formatBtcValue(value) + (appendUnit ? " BTC" : ""); |
||||
|
} |
||||
|
|
||||
|
protected String formatFiatValue(Double value) { |
||||
|
UnitFormat format = Config.get().getUnitFormat(); |
||||
|
if(format == null) { |
||||
|
format = UnitFormat.DOT; |
||||
|
} |
||||
|
|
||||
|
CurrencyRate currencyRate = AppServices.getFiatCurrencyExchangeRate(); |
||||
|
if(currencyRate != null && currencyRate.isAvailable() && value > 0) { |
||||
|
Currency currency = currencyRate.getCurrency(); |
||||
|
return currency.getSymbol() + " " + format.formatCurrencyValue(value); |
||||
|
} else { |
||||
|
return ""; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected double getFiatValue(long satsValue, Double btcRate) { |
||||
|
return satsValue * btcRate / Transaction.SATOSHIS_PER_BITCOIN; |
||||
|
} |
||||
|
|
||||
|
protected static String centerPad(String text, int length) { |
||||
|
if(text.length() >= length) { |
||||
|
return text; |
||||
|
} |
||||
|
|
||||
|
int excess = length - text.length(); |
||||
|
int half = excess / 2; |
||||
|
int extra = excess % 2; |
||||
|
|
||||
|
return Strings.repeat(" ", half) + text + Strings.repeat(" ", half) + Strings.repeat(" ", extra); |
||||
|
} |
||||
|
} |
@ -0,0 +1,24 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.wallet.table; |
||||
|
|
||||
|
import com.sparrowwallet.sparrow.wallet.Entry; |
||||
|
import com.sparrowwallet.sparrow.wallet.NodeEntry; |
||||
|
import com.sparrowwallet.sparrow.wallet.UtxoEntry; |
||||
|
|
||||
|
public class AddressTableCell extends TableCell { |
||||
|
public static final int UTXO_WIDTH = 18; |
||||
|
|
||||
|
public AddressTableCell(Entry entry) { |
||||
|
super(entry); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String formatCell() { |
||||
|
if(entry instanceof NodeEntry nodeEntry) { |
||||
|
return nodeEntry.getAddress().toString(); |
||||
|
} else if(entry instanceof UtxoEntry utxoEntry) { |
||||
|
return utxoEntry.getNode().getAddress().toString().substring(0, 10) + ".."; |
||||
|
} |
||||
|
|
||||
|
return ""; |
||||
|
} |
||||
|
} |
@ -0,0 +1,51 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.wallet.table; |
||||
|
|
||||
|
import com.google.common.base.Strings; |
||||
|
import com.sparrowwallet.drongo.BitcoinUnit; |
||||
|
import com.sparrowwallet.sparrow.UnitFormat; |
||||
|
import com.sparrowwallet.sparrow.io.Config; |
||||
|
import com.sparrowwallet.sparrow.wallet.Entry; |
||||
|
import com.sparrowwallet.sparrow.wallet.TransactionEntry; |
||||
|
|
||||
|
public class CoinTableCell extends TableCell { |
||||
|
public static final int TRANSACTION_WIDTH = 20; |
||||
|
public static final int UTXO_WIDTH = 18; |
||||
|
|
||||
|
private final boolean balance; |
||||
|
|
||||
|
public CoinTableCell(Entry entry, boolean balance) { |
||||
|
super(entry); |
||||
|
this.balance = balance; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String formatCell() { |
||||
|
Long value = null; |
||||
|
if(balance && entry instanceof TransactionEntry transactionEntry) { |
||||
|
value = transactionEntry.getBalance(); |
||||
|
} else { |
||||
|
value = entry.getValue(); |
||||
|
} |
||||
|
|
||||
|
if(value == null) { |
||||
|
value = 0L; |
||||
|
} |
||||
|
|
||||
|
BitcoinUnit unit = Config.get().getBitcoinUnit(); |
||||
|
if(unit == null || unit.equals(BitcoinUnit.AUTO)) { |
||||
|
unit = (value >= BitcoinUnit.getAutoThreshold() ? BitcoinUnit.BTC : BitcoinUnit.SATOSHIS); |
||||
|
} |
||||
|
|
||||
|
UnitFormat format = Config.get().getUnitFormat(); |
||||
|
if(format == null) { |
||||
|
format = UnitFormat.DOT; |
||||
|
} |
||||
|
|
||||
|
String formattedValue = unit == BitcoinUnit.SATOSHIS ? format.formatSatsValue(value) : format.formatBtcValue(value); |
||||
|
return Strings.padStart(formattedValue, getWidth(entry), ' '); |
||||
|
} |
||||
|
|
||||
|
private int getWidth(Entry entry) { |
||||
|
return entry instanceof TransactionEntry ? TRANSACTION_WIDTH : UTXO_WIDTH; |
||||
|
} |
||||
|
} |
@ -0,0 +1,41 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.wallet.table; |
||||
|
|
||||
|
import com.sparrowwallet.sparrow.wallet.Entry; |
||||
|
import com.sparrowwallet.sparrow.wallet.TransactionEntry; |
||||
|
import com.sparrowwallet.sparrow.wallet.UtxoEntry; |
||||
|
|
||||
|
import java.text.DateFormat; |
||||
|
import java.text.SimpleDateFormat; |
||||
|
|
||||
|
public class DateTableCell extends TableCell { |
||||
|
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm"); |
||||
|
public static final int TRANSACTION_WIDTH = 20; |
||||
|
public static final int UTXO_WIDTH = 18; |
||||
|
|
||||
|
public DateTableCell(Entry entry) { |
||||
|
super(entry); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String formatCell() { |
||||
|
if(entry instanceof TransactionEntry transactionEntry && transactionEntry.getBlockTransaction() != null) { |
||||
|
if(transactionEntry.getBlockTransaction().getHeight() == -1) { |
||||
|
return "Unconfirmed Parent"; |
||||
|
} else if(transactionEntry.getBlockTransaction().getHeight() == 0) { |
||||
|
return "Unconfirmed"; |
||||
|
} else { |
||||
|
return DATE_FORMAT.format(transactionEntry.getBlockTransaction().getDate()); |
||||
|
} |
||||
|
} else if(entry instanceof UtxoEntry utxoEntry && utxoEntry.getBlockTransaction() != null) { |
||||
|
if(utxoEntry.getBlockTransaction().getHeight() == -1) { |
||||
|
return "Unconfirmed Parent"; |
||||
|
} else if(utxoEntry.getBlockTransaction().getHeight() == 0) { |
||||
|
return "Unconfirmed"; |
||||
|
} else { |
||||
|
return DATE_FORMAT.format(utxoEntry.getBlockTransaction().getDate()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return ""; |
||||
|
} |
||||
|
} |
@ -0,0 +1,64 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.wallet.table; |
||||
|
|
||||
|
import com.google.common.base.Strings; |
||||
|
import com.googlecode.lanterna.Symbols; |
||||
|
import com.samourai.whirlpool.client.mix.listener.MixFailReason; |
||||
|
import com.samourai.whirlpool.client.wallet.beans.MixProgress; |
||||
|
import com.sparrowwallet.sparrow.wallet.Entry; |
||||
|
import com.sparrowwallet.sparrow.wallet.UtxoEntry; |
||||
|
|
||||
|
public class MixTableCell extends TableCell { |
||||
|
public static final int WIDTH = 18; |
||||
|
|
||||
|
public MixTableCell(Entry entry) { |
||||
|
super(entry); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String formatCell() { |
||||
|
if(entry instanceof UtxoEntry utxoEntry) { |
||||
|
if(utxoEntry.getMixStatus() != null) { |
||||
|
UtxoEntry.MixStatus mixStatus = utxoEntry.getMixStatus(); |
||||
|
if(mixStatus.getNextMixUtxo() != null) { |
||||
|
return getMixSuccess(mixStatus); |
||||
|
} else if(mixStatus.getMixFailReason() != null) { |
||||
|
return getMixFail(mixStatus); |
||||
|
} else if(mixStatus.getMixProgress() != null) { |
||||
|
return getMixProgress(mixStatus, mixStatus.getMixProgress()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return getMixCountOnly(utxoEntry.getMixStatus()); |
||||
|
} |
||||
|
|
||||
|
return ""; |
||||
|
} |
||||
|
|
||||
|
private String getMixSuccess(UtxoEntry.MixStatus mixStatus) { |
||||
|
String msg = "Success!"; |
||||
|
String mixesDone = Strings.padStart(Integer.toString(mixStatus.getMixesDone()), WIDTH - msg.length(), ' '); |
||||
|
return msg + mixesDone; |
||||
|
} |
||||
|
|
||||
|
private String getMixFail(UtxoEntry.MixStatus mixStatus) { |
||||
|
if(mixStatus.getMixFailReason() == MixFailReason.CANCEL) { |
||||
|
return getMixCountOnly(mixStatus); |
||||
|
} |
||||
|
|
||||
|
String msg = mixStatus.getMixFailReason().getMessage(); |
||||
|
msg = msg.length() > 14 ? msg.substring(0, 14) : msg; |
||||
|
String mixesDone = Strings.padStart(Integer.toString(mixStatus.getMixesDone()), WIDTH - msg.length(), ' '); |
||||
|
return msg + mixesDone; |
||||
|
} |
||||
|
|
||||
|
private String getMixProgress(UtxoEntry.MixStatus mixStatus, MixProgress mixProgress) { |
||||
|
int progress = mixProgress.getMixStep().getProgressPercent(); |
||||
|
String progressBar = Strings.padEnd(Strings.repeat(Character.toString(Symbols.BLOCK_SOLID), progress / 10), 10, ' '); |
||||
|
String mixesDone = Strings.padStart(Integer.toString(mixStatus.getMixesDone()), WIDTH - 10, ' '); |
||||
|
return progressBar + mixesDone; |
||||
|
} |
||||
|
|
||||
|
private String getMixCountOnly(UtxoEntry.MixStatus mixStatus) { |
||||
|
return Strings.padStart(Integer.toString(mixStatus == null ? 0 : mixStatus.getMixesDone()), WIDTH, ' '); |
||||
|
} |
||||
|
} |
@ -0,0 +1,21 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.wallet.table; |
||||
|
|
||||
|
import com.sparrowwallet.sparrow.wallet.Entry; |
||||
|
import com.sparrowwallet.sparrow.wallet.UtxoEntry; |
||||
|
|
||||
|
public class OutputTableCell extends TableCell { |
||||
|
public static final int WIDTH = 16; |
||||
|
|
||||
|
public OutputTableCell(Entry entry) { |
||||
|
super(entry); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String formatCell() { |
||||
|
if(entry instanceof UtxoEntry utxoEntry) { |
||||
|
return utxoEntry.getDescription(); |
||||
|
} |
||||
|
|
||||
|
return ""; |
||||
|
} |
||||
|
} |
@ -0,0 +1,17 @@ |
|||||
|
package com.sparrowwallet.sparrow.terminal.wallet.table; |
||||
|
|
||||
|
import com.sparrowwallet.sparrow.wallet.Entry; |
||||
|
|
||||
|
public abstract class TableCell { |
||||
|
protected final Entry entry; |
||||
|
|
||||
|
public TableCell(Entry entry) { |
||||
|
this.entry = entry; |
||||
|
} |
||||
|
|
||||
|
public Entry getEntry() { |
||||
|
return entry; |
||||
|
} |
||||
|
|
||||
|
public abstract String formatCell(); |
||||
|
} |
Loading…
Reference in new issue