mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-24 12:46:45 +00:00
address table improvements
This commit is contained in:
parent
70c4eeaf5e
commit
936d42e7fd
5 changed files with 146 additions and 125 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
|||
Subproject commit fa30f37e235d20e66fc5864a54f1100540ccbb51
|
||||
Subproject commit 0d56692784f5dfc50197533eb00ccf8e5c42e471
|
|
@ -102,7 +102,7 @@ public class AppController implements Initializable {
|
|||
boolean success = false;
|
||||
if(db.hasFiles()) {
|
||||
for(File file : db.getFiles()) {
|
||||
openFile(file);
|
||||
openTransactionFile(file);
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
|
@ -160,6 +160,8 @@ public class AppController implements Initializable {
|
|||
if(config.getMode() == Mode.ONLINE && config.getElectrumServer() != null && !config.getElectrumServer().isEmpty()) {
|
||||
connectionService.start();
|
||||
}
|
||||
|
||||
openWalletFile(new File("/Users/scy/.sparrow/wallets/sparta.json"));
|
||||
}
|
||||
|
||||
private ElectrumServer.ConnectionService createConnectionService() {
|
||||
|
@ -202,7 +204,7 @@ public class AppController implements Initializable {
|
|||
}
|
||||
}
|
||||
|
||||
public void openFromFile(ActionEvent event) {
|
||||
public void openTransactionFromFile(ActionEvent event) {
|
||||
Stage window = new Stage();
|
||||
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
|
@ -215,11 +217,11 @@ public class AppController implements Initializable {
|
|||
|
||||
File file = fileChooser.showOpenDialog(window);
|
||||
if (file != null) {
|
||||
openFile(file);
|
||||
openTransactionFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
private void openFile(File file) {
|
||||
private void openTransactionFile(File file) {
|
||||
for(Tab tab : tabs.getTabs()) {
|
||||
TabData tabData = (TabData)tab.getUserData();
|
||||
if(file.equals(tabData.getFile())) {
|
||||
|
@ -266,7 +268,7 @@ public class AppController implements Initializable {
|
|||
}
|
||||
}
|
||||
|
||||
public void openFromText(ActionEvent event) {
|
||||
public void openTransactionFromText(ActionEvent event) {
|
||||
TextAreaDialog dialog = new TextAreaDialog();
|
||||
dialog.setTitle("Open from text");
|
||||
dialog.getDialogPane().setHeaderText("Paste a transaction or PSBT:");
|
||||
|
@ -325,54 +327,58 @@ public class AppController implements Initializable {
|
|||
|
||||
File file = fileChooser.showOpenDialog(window);
|
||||
if(file != null) {
|
||||
try {
|
||||
Storage storage = new Storage(file);
|
||||
FileType fileType = IOUtils.getFileType(file);
|
||||
if(FileType.JSON.equals(fileType)) {
|
||||
Wallet wallet = storage.loadWallet();
|
||||
restorePublicKeysFromSeed(wallet, null);
|
||||
Tab tab = addWalletTab(storage, wallet);
|
||||
tabs.getSelectionModel().select(tab);
|
||||
} else if(FileType.BINARY.equals(fileType)) {
|
||||
WalletPasswordDialog dlg = new WalletPasswordDialog(WalletPasswordDialog.PasswordRequirement.LOAD);
|
||||
Optional<SecureString> optionalPassword = dlg.showAndWait();
|
||||
if(optionalPassword.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
openWalletFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
SecureString password = optionalPassword.get();
|
||||
Storage.LoadWalletService loadWalletService = new Storage.LoadWalletService(storage, password);
|
||||
loadWalletService.setOnSucceeded(workerStateEvent -> {
|
||||
EventManager.get().post(new StorageEvent(storage.getWalletFile(), TimedEvent.Action.END, "Done"));
|
||||
Storage.WalletAndKey walletAndKey = loadWalletService.getValue();
|
||||
try {
|
||||
restorePublicKeysFromSeed(walletAndKey.wallet, walletAndKey.key);
|
||||
Tab tab = addWalletTab(storage, walletAndKey.wallet);
|
||||
tabs.getSelectionModel().select(tab);
|
||||
} catch(MnemonicException e) {
|
||||
showErrorDialog("Error Opening Wallet", e.getMessage());
|
||||
} finally {
|
||||
walletAndKey.key.clear();
|
||||
}
|
||||
});
|
||||
loadWalletService.setOnFailed(workerStateEvent -> {
|
||||
EventManager.get().post(new StorageEvent(storage.getWalletFile(), TimedEvent.Action.END, "Failed"));
|
||||
Throwable exception = loadWalletService.getException();
|
||||
if(exception instanceof InvalidPasswordException) {
|
||||
showErrorDialog("Invalid Password", "The wallet password was invalid.");
|
||||
} else {
|
||||
showErrorDialog("Error Opening Wallet", exception.getMessage());
|
||||
}
|
||||
});
|
||||
EventManager.get().post(new StorageEvent(storage.getWalletFile(), TimedEvent.Action.START, "Decrypting wallet..."));
|
||||
loadWalletService.start();
|
||||
} else {
|
||||
throw new IOException("Unsupported file type");
|
||||
private void openWalletFile(File file) {
|
||||
try {
|
||||
Storage storage = new Storage(file);
|
||||
FileType fileType = IOUtils.getFileType(file);
|
||||
if(FileType.JSON.equals(fileType)) {
|
||||
Wallet wallet = storage.loadWallet();
|
||||
restorePublicKeysFromSeed(wallet, null);
|
||||
Tab tab = addWalletTab(storage, wallet);
|
||||
tabs.getSelectionModel().select(tab);
|
||||
} else if(FileType.BINARY.equals(fileType)) {
|
||||
WalletPasswordDialog dlg = new WalletPasswordDialog(WalletPasswordDialog.PasswordRequirement.LOAD);
|
||||
Optional<SecureString> optionalPassword = dlg.showAndWait();
|
||||
if(optionalPassword.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
showErrorDialog("Error Opening Wallet", e.getMessage());
|
||||
|
||||
SecureString password = optionalPassword.get();
|
||||
Storage.LoadWalletService loadWalletService = new Storage.LoadWalletService(storage, password);
|
||||
loadWalletService.setOnSucceeded(workerStateEvent -> {
|
||||
EventManager.get().post(new StorageEvent(storage.getWalletFile(), TimedEvent.Action.END, "Done"));
|
||||
Storage.WalletAndKey walletAndKey = loadWalletService.getValue();
|
||||
try {
|
||||
restorePublicKeysFromSeed(walletAndKey.wallet, walletAndKey.key);
|
||||
Tab tab = addWalletTab(storage, walletAndKey.wallet);
|
||||
tabs.getSelectionModel().select(tab);
|
||||
} catch(MnemonicException e) {
|
||||
showErrorDialog("Error Opening Wallet", e.getMessage());
|
||||
} finally {
|
||||
walletAndKey.key.clear();
|
||||
}
|
||||
});
|
||||
loadWalletService.setOnFailed(workerStateEvent -> {
|
||||
EventManager.get().post(new StorageEvent(storage.getWalletFile(), TimedEvent.Action.END, "Failed"));
|
||||
Throwable exception = loadWalletService.getException();
|
||||
if(exception instanceof InvalidPasswordException) {
|
||||
showErrorDialog("Invalid Password", "The wallet password was invalid.");
|
||||
} else {
|
||||
showErrorDialog("Error Opening Wallet", exception.getMessage());
|
||||
}
|
||||
});
|
||||
EventManager.get().post(new StorageEvent(storage.getWalletFile(), TimedEvent.Action.START, "Decrypting wallet..."));
|
||||
loadWalletService.start();
|
||||
} else {
|
||||
throw new IOException("Unsupported file type");
|
||||
}
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
showErrorDialog("Error Opening Wallet", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,8 @@ import javafx.scene.control.*;
|
|||
import javafx.scene.control.cell.TextFieldTreeTableCell;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.util.converter.DefaultStringConverter;
|
||||
import org.controlsfx.glyphfont.FontAwesome;
|
||||
|
@ -67,16 +68,26 @@ public class AddressTreeTable extends TreeTableView<Entry> {
|
|||
amountCol.setSortable(false);
|
||||
getColumns().add(amountCol);
|
||||
|
||||
TreeTableColumn<Entry, Entry> actionCol = new TreeTableColumn<>("Actions");
|
||||
actionCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Entry> param) -> {
|
||||
return new ReadOnlyObjectWrapper<>(param.getValue().getValue());
|
||||
});
|
||||
actionCol.setCellFactory(p -> new ActionCell());
|
||||
actionCol.setSortable(false);
|
||||
getColumns().add(actionCol);
|
||||
|
||||
setEditable(true);
|
||||
setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
|
||||
|
||||
scrollTo(rootEntry.getNode().getHighestUsedIndex());
|
||||
|
||||
setOnMouseClicked(mouseEvent -> {
|
||||
if(mouseEvent.getButton().equals(MouseButton.PRIMARY)){
|
||||
if(mouseEvent.getClickCount() == 2) {
|
||||
TreeItem<Entry> treeItem = getSelectionModel().getSelectedItem();
|
||||
if(treeItem != null && treeItem.getChildren().isEmpty()) {
|
||||
Entry entry = getSelectionModel().getSelectedItem().getValue();
|
||||
if(entry instanceof NodeEntry) {
|
||||
NodeEntry nodeEntry = (NodeEntry)entry;
|
||||
EventManager.get().post(new ReceiveActionEvent(nodeEntry));
|
||||
Platform.runLater(() -> EventManager.get().post(new ReceiveToEvent(nodeEntry)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void applyRowStyles(TreeTableCell<?, ?> cell, Entry entry) {
|
||||
|
@ -100,6 +111,9 @@ public class AddressTreeTable extends TreeTableView<Entry> {
|
|||
private static class DataCell extends TreeTableCell<Entry, Entry> {
|
||||
public DataCell() {
|
||||
super();
|
||||
setAlignment(Pos.CENTER_LEFT);
|
||||
setContentDisplay(ContentDisplay.RIGHT);
|
||||
getStyleClass().add("data-cell");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -117,8 +131,21 @@ public class AddressTreeTable extends TreeTableView<Entry> {
|
|||
NodeEntry nodeEntry = (NodeEntry)entry;
|
||||
Address address = nodeEntry.getAddress();
|
||||
setText(address.toString());
|
||||
setContextMenu(new AddressContextMenu(address));
|
||||
setContextMenu(new AddressContextMenu(address, nodeEntry.getOutputDescriptor()));
|
||||
Tooltip tooltip = new Tooltip();
|
||||
tooltip.setText(nodeEntry.getNode().getDerivationPath());
|
||||
setTooltip(tooltip);
|
||||
getStyleClass().add("address-cell");
|
||||
|
||||
Button receiveButton = new Button("");
|
||||
Glyph receiveGlyph = new Glyph("FontAwesome", FontAwesome.Glyph.ARROW_DOWN);
|
||||
receiveGlyph.setFontSize(12);
|
||||
receiveButton.setGraphic(receiveGlyph);
|
||||
receiveButton.setOnAction(event -> {
|
||||
EventManager.get().post(new ReceiveActionEvent(nodeEntry));
|
||||
Platform.runLater(() -> EventManager.get().post(new ReceiveToEvent(nodeEntry)));
|
||||
});
|
||||
setGraphic(receiveButton);
|
||||
} else if(entry instanceof HashIndexEntry) {
|
||||
HashIndexEntry hashIndexEntry = (HashIndexEntry)entry;
|
||||
setText(hashIndexEntry.getDescription());
|
||||
|
@ -126,13 +153,22 @@ public class AddressTreeTable extends TreeTableView<Entry> {
|
|||
Tooltip tooltip = new Tooltip();
|
||||
tooltip.setText(hashIndexEntry.getHashIndex().toString());
|
||||
setTooltip(tooltip);
|
||||
|
||||
Button viewTransactionButton = new Button("");
|
||||
Glyph searchGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.SEARCH);
|
||||
searchGlyph.setFontSize(12);
|
||||
viewTransactionButton.setGraphic(searchGlyph);
|
||||
viewTransactionButton.setOnAction(event -> {
|
||||
EventManager.get().post(new TransactionViewEvent(hashIndexEntry.getBlockTransaction(), hashIndexEntry));
|
||||
});
|
||||
setGraphic(viewTransactionButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class AddressContextMenu extends ContextMenu {
|
||||
public AddressContextMenu(Address address) {
|
||||
public AddressContextMenu(Address address, String outputDescriptor) {
|
||||
MenuItem copyAddress = new MenuItem("Copy Address");
|
||||
copyAddress.setOnAction(AE -> {
|
||||
hide();
|
||||
|
@ -149,7 +185,15 @@ public class AddressTreeTable extends TreeTableView<Entry> {
|
|||
Clipboard.getSystemClipboard().setContent(content);
|
||||
});
|
||||
|
||||
getItems().addAll(copyAddress, copyHex);
|
||||
MenuItem copyOutputDescriptor = new MenuItem("Copy Output Descriptor");
|
||||
copyOutputDescriptor.setOnAction(AE -> {
|
||||
hide();
|
||||
ClipboardContent content = new ClipboardContent();
|
||||
content.putString(outputDescriptor);
|
||||
Clipboard.getSystemClipboard().setContent(content);
|
||||
});
|
||||
|
||||
getItems().addAll(copyAddress, copyHex, copyOutputDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -248,6 +292,7 @@ public class AddressTreeTable extends TreeTableView<Entry> {
|
|||
public AmountCell() {
|
||||
super();
|
||||
getStyleClass().add("amount-cell");
|
||||
setContentDisplay(ContentDisplay.RIGHT);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -263,6 +308,19 @@ public class AddressTreeTable extends TreeTableView<Entry> {
|
|||
String satsValue = String.format(Locale.ENGLISH, "%,d", amount);
|
||||
String btcValue = CoinLabel.getBTCFormat().format(amount.doubleValue() / Transaction.SATOSHIS_PER_BITCOIN) + " BTC";
|
||||
|
||||
Entry entry = getTreeTableView().getTreeItem(getIndex()).getValue();
|
||||
if(entry instanceof HashIndexEntry) {
|
||||
Region node = new Region();
|
||||
node.setPrefWidth(10);
|
||||
setGraphic(node);
|
||||
|
||||
if(((HashIndexEntry) entry).getType() == HashIndexEntry.Type.INPUT) {
|
||||
satsValue = "-" + satsValue;
|
||||
}
|
||||
} else {
|
||||
setGraphic(null);
|
||||
}
|
||||
|
||||
Tooltip tooltip = new Tooltip();
|
||||
tooltip.setText(btcValue);
|
||||
|
||||
|
@ -271,57 +329,4 @@ public class AddressTreeTable extends TreeTableView<Entry> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ActionCell extends TreeTableCell<Entry, Entry> {
|
||||
private final HBox actionBox;
|
||||
private final Button receiveButton;
|
||||
private final Button viewTransactionButton;
|
||||
|
||||
public ActionCell() {
|
||||
super();
|
||||
getStyleClass().add("action-cell");
|
||||
|
||||
actionBox = new HBox();
|
||||
actionBox.setSpacing(8);
|
||||
actionBox.setAlignment(Pos.CENTER);
|
||||
|
||||
receiveButton = new Button("");
|
||||
Glyph receiveGlyph = new Glyph("FontAwesome", FontAwesome.Glyph.ARROW_DOWN);
|
||||
receiveGlyph.setFontSize(12);
|
||||
receiveButton.setGraphic(receiveGlyph);
|
||||
receiveButton.setOnAction(event -> {
|
||||
NodeEntry nodeEntry = (NodeEntry)getTreeTableView().getTreeItem(getIndex()).getValue();
|
||||
EventManager.get().post(new ReceiveActionEvent(nodeEntry));
|
||||
Platform.runLater(() -> EventManager.get().post(new ReceiveToEvent(nodeEntry)));
|
||||
});
|
||||
|
||||
viewTransactionButton = new Button("");
|
||||
Glyph searchGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.SEARCH);
|
||||
searchGlyph.setFontSize(12);
|
||||
viewTransactionButton.setGraphic(searchGlyph);
|
||||
viewTransactionButton.setOnAction(event -> {
|
||||
HashIndexEntry hashIndexEntry = (HashIndexEntry)getTreeTableView().getTreeItem(getIndex()).getValue();
|
||||
EventManager.get().post(new TransactionViewEvent(hashIndexEntry.getBlockTransaction(), hashIndexEntry));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(Entry entry, boolean empty) {
|
||||
super.updateItem(entry, empty);
|
||||
if (empty) {
|
||||
setGraphic(null);
|
||||
} else {
|
||||
applyRowStyles(this, getTreeTableView().getTreeItem(getIndex()).getValue());
|
||||
|
||||
actionBox.getChildren().remove(0, actionBox.getChildren().size());
|
||||
if(entry instanceof NodeEntry) {
|
||||
actionBox.getChildren().add(receiveButton);
|
||||
} else if(entry instanceof HashIndexEntry) {
|
||||
actionBox.getChildren().add(viewTransactionButton);
|
||||
}
|
||||
|
||||
setGraphic(actionBox);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
<MenuItem mnemonicParsing="false" text="Open Wallet..." onAction="#openWallet"/>
|
||||
<Menu mnemonicParsing="false" text="Open Transaction">
|
||||
<items>
|
||||
<MenuItem text="File..." onAction="#openFromFile"/>
|
||||
<MenuItem text="From Text..." onAction="#openFromText"/>
|
||||
<MenuItem text="File..." onAction="#openTransactionFromFile"/>
|
||||
<MenuItem text="From Text..." onAction="#openTransactionFromText"/>
|
||||
<MenuItem text="Examples" onAction="#openExamples"/>
|
||||
</items>
|
||||
</Menu>
|
||||
|
|
|
@ -9,15 +9,15 @@
|
|||
}
|
||||
|
||||
.hashindex-row {
|
||||
-fx-background-color: #fafafa;
|
||||
-fx-text-fill: #696c77;
|
||||
}
|
||||
|
||||
.hashindex-row .text {
|
||||
-fx-fill: #696c77;
|
||||
.hashindex-row.spent {
|
||||
-fx-text-fill: #a0a1a7;
|
||||
}
|
||||
|
||||
.hashindex-row.spent .text {
|
||||
-fx-fill: #a0a1a7;
|
||||
.tree-table-row-cell:selected .hashindex-row {
|
||||
-fx-text-fill: white;
|
||||
}
|
||||
|
||||
.label-cell .text-field {
|
||||
|
@ -32,8 +32,18 @@
|
|||
-fx-strikethrough: true;
|
||||
}
|
||||
|
||||
.action-cell .button {
|
||||
.data-cell .button {
|
||||
-fx-padding: 0;
|
||||
-fx-pref-height: 18;
|
||||
-fx-pref-width: 18;
|
||||
-fx-border-width: 0;
|
||||
-fx-background-color: -fx-background;
|
||||
}
|
||||
|
||||
.data-cell .button .label .text {
|
||||
-fx-fill: -fx-background;
|
||||
}
|
||||
|
||||
.data-cell:hover .button .label .text {
|
||||
-fx-fill: -fx-text-base-color;
|
||||
}
|
Loading…
Reference in a new issue