address table improvements

This commit is contained in:
Craig Raw 2020-06-10 14:15:45 +02:00
parent 70c4eeaf5e
commit 936d42e7fd
5 changed files with 146 additions and 125 deletions

2
drongo

@ -1 +1 @@
Subproject commit fa30f37e235d20e66fc5864a54f1100540ccbb51 Subproject commit 0d56692784f5dfc50197533eb00ccf8e5c42e471

View file

@ -102,7 +102,7 @@ public class AppController implements Initializable {
boolean success = false; boolean success = false;
if(db.hasFiles()) { if(db.hasFiles()) {
for(File file : db.getFiles()) { for(File file : db.getFiles()) {
openFile(file); openTransactionFile(file);
} }
success = true; success = true;
} }
@ -160,6 +160,8 @@ public class AppController implements Initializable {
if(config.getMode() == Mode.ONLINE && config.getElectrumServer() != null && !config.getElectrumServer().isEmpty()) { if(config.getMode() == Mode.ONLINE && config.getElectrumServer() != null && !config.getElectrumServer().isEmpty()) {
connectionService.start(); connectionService.start();
} }
openWalletFile(new File("/Users/scy/.sparrow/wallets/sparta.json"));
} }
private ElectrumServer.ConnectionService createConnectionService() { 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(); Stage window = new Stage();
FileChooser fileChooser = new FileChooser(); FileChooser fileChooser = new FileChooser();
@ -215,11 +217,11 @@ public class AppController implements Initializable {
File file = fileChooser.showOpenDialog(window); File file = fileChooser.showOpenDialog(window);
if (file != null) { if (file != null) {
openFile(file); openTransactionFile(file);
} }
} }
private void openFile(File file) { private void openTransactionFile(File file) {
for(Tab tab : tabs.getTabs()) { for(Tab tab : tabs.getTabs()) {
TabData tabData = (TabData)tab.getUserData(); TabData tabData = (TabData)tab.getUserData();
if(file.equals(tabData.getFile())) { 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(); TextAreaDialog dialog = new TextAreaDialog();
dialog.setTitle("Open from text"); dialog.setTitle("Open from text");
dialog.getDialogPane().setHeaderText("Paste a transaction or PSBT:"); dialog.getDialogPane().setHeaderText("Paste a transaction or PSBT:");
@ -325,54 +327,58 @@ public class AppController implements Initializable {
File file = fileChooser.showOpenDialog(window); File file = fileChooser.showOpenDialog(window);
if(file != null) { if(file != null) {
try { openWalletFile(file);
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;
}
SecureString password = optionalPassword.get(); private void openWalletFile(File file) {
Storage.LoadWalletService loadWalletService = new Storage.LoadWalletService(storage, password); try {
loadWalletService.setOnSucceeded(workerStateEvent -> { Storage storage = new Storage(file);
EventManager.get().post(new StorageEvent(storage.getWalletFile(), TimedEvent.Action.END, "Done")); FileType fileType = IOUtils.getFileType(file);
Storage.WalletAndKey walletAndKey = loadWalletService.getValue(); if(FileType.JSON.equals(fileType)) {
try { Wallet wallet = storage.loadWallet();
restorePublicKeysFromSeed(walletAndKey.wallet, walletAndKey.key); restorePublicKeysFromSeed(wallet, null);
Tab tab = addWalletTab(storage, walletAndKey.wallet); Tab tab = addWalletTab(storage, wallet);
tabs.getSelectionModel().select(tab); tabs.getSelectionModel().select(tab);
} catch(MnemonicException e) { } else if(FileType.BINARY.equals(fileType)) {
showErrorDialog("Error Opening Wallet", e.getMessage()); WalletPasswordDialog dlg = new WalletPasswordDialog(WalletPasswordDialog.PasswordRequirement.LOAD);
} finally { Optional<SecureString> optionalPassword = dlg.showAndWait();
walletAndKey.key.clear(); if(optionalPassword.isEmpty()) {
} return;
});
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(); SecureString password = optionalPassword.get();
showErrorDialog("Error Opening Wallet", e.getMessage()); 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());
} }
} }

View file

@ -19,7 +19,8 @@ import javafx.scene.control.*;
import javafx.scene.control.cell.TextFieldTreeTableCell; import javafx.scene.control.cell.TextFieldTreeTableCell;
import javafx.scene.input.Clipboard; import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent; 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.scene.text.Font;
import javafx.util.converter.DefaultStringConverter; import javafx.util.converter.DefaultStringConverter;
import org.controlsfx.glyphfont.FontAwesome; import org.controlsfx.glyphfont.FontAwesome;
@ -67,16 +68,26 @@ public class AddressTreeTable extends TreeTableView<Entry> {
amountCol.setSortable(false); amountCol.setSortable(false);
getColumns().add(amountCol); 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); setEditable(true);
setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY); 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) { 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> { private static class DataCell extends TreeTableCell<Entry, Entry> {
public DataCell() { public DataCell() {
super(); super();
setAlignment(Pos.CENTER_LEFT);
setContentDisplay(ContentDisplay.RIGHT);
getStyleClass().add("data-cell");
} }
@Override @Override
@ -117,8 +131,21 @@ public class AddressTreeTable extends TreeTableView<Entry> {
NodeEntry nodeEntry = (NodeEntry)entry; NodeEntry nodeEntry = (NodeEntry)entry;
Address address = nodeEntry.getAddress(); Address address = nodeEntry.getAddress();
setText(address.toString()); 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"); 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) { } else if(entry instanceof HashIndexEntry) {
HashIndexEntry hashIndexEntry = (HashIndexEntry)entry; HashIndexEntry hashIndexEntry = (HashIndexEntry)entry;
setText(hashIndexEntry.getDescription()); setText(hashIndexEntry.getDescription());
@ -126,13 +153,22 @@ public class AddressTreeTable extends TreeTableView<Entry> {
Tooltip tooltip = new Tooltip(); Tooltip tooltip = new Tooltip();
tooltip.setText(hashIndexEntry.getHashIndex().toString()); tooltip.setText(hashIndexEntry.getHashIndex().toString());
setTooltip(tooltip); 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 { private static class AddressContextMenu extends ContextMenu {
public AddressContextMenu(Address address) { public AddressContextMenu(Address address, String outputDescriptor) {
MenuItem copyAddress = new MenuItem("Copy Address"); MenuItem copyAddress = new MenuItem("Copy Address");
copyAddress.setOnAction(AE -> { copyAddress.setOnAction(AE -> {
hide(); hide();
@ -149,7 +185,15 @@ public class AddressTreeTable extends TreeTableView<Entry> {
Clipboard.getSystemClipboard().setContent(content); 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() { public AmountCell() {
super(); super();
getStyleClass().add("amount-cell"); getStyleClass().add("amount-cell");
setContentDisplay(ContentDisplay.RIGHT);
} }
@Override @Override
@ -263,6 +308,19 @@ public class AddressTreeTable extends TreeTableView<Entry> {
String satsValue = String.format(Locale.ENGLISH, "%,d", amount); String satsValue = String.format(Locale.ENGLISH, "%,d", amount);
String btcValue = CoinLabel.getBTCFormat().format(amount.doubleValue() / Transaction.SATOSHIS_PER_BITCOIN) + " BTC"; 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 tooltip = new Tooltip();
tooltip.setText(btcValue); 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);
}
}
}
} }

View file

@ -17,8 +17,8 @@
<MenuItem mnemonicParsing="false" text="Open Wallet..." onAction="#openWallet"/> <MenuItem mnemonicParsing="false" text="Open Wallet..." onAction="#openWallet"/>
<Menu mnemonicParsing="false" text="Open Transaction"> <Menu mnemonicParsing="false" text="Open Transaction">
<items> <items>
<MenuItem text="File..." onAction="#openFromFile"/> <MenuItem text="File..." onAction="#openTransactionFromFile"/>
<MenuItem text="From Text..." onAction="#openFromText"/> <MenuItem text="From Text..." onAction="#openTransactionFromText"/>
<MenuItem text="Examples" onAction="#openExamples"/> <MenuItem text="Examples" onAction="#openExamples"/>
</items> </items>
</Menu> </Menu>

View file

@ -9,15 +9,15 @@
} }
.hashindex-row { .hashindex-row {
-fx-background-color: #fafafa; -fx-text-fill: #696c77;
} }
.hashindex-row .text { .hashindex-row.spent {
-fx-fill: #696c77; -fx-text-fill: #a0a1a7;
} }
.hashindex-row.spent .text { .tree-table-row-cell:selected .hashindex-row {
-fx-fill: #a0a1a7; -fx-text-fill: white;
} }
.label-cell .text-field { .label-cell .text-field {
@ -32,8 +32,18 @@
-fx-strikethrough: true; -fx-strikethrough: true;
} }
.action-cell .button { .data-cell .button {
-fx-padding: 0; -fx-padding: 0;
-fx-pref-height: 18; -fx-pref-height: 18;
-fx-pref-width: 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;
} }