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;
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());
}
}

View file

@ -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);
}
}
}
}

View file

@ -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>

View file

@ -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;
}