diff --git a/drongo b/drongo index fa30f37e..0d566927 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit fa30f37e235d20e66fc5864a54f1100540ccbb51 +Subproject commit 0d56692784f5dfc50197533eb00ccf8e5c42e471 diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 88c21a5c..8c686865 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -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 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 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()); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/AddressTreeTable.java b/src/main/java/com/sparrowwallet/sparrow/control/AddressTreeTable.java index 313151f9..a26974cb 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/AddressTreeTable.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/AddressTreeTable.java @@ -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 { amountCol.setSortable(false); getColumns().add(amountCol); - TreeTableColumn actionCol = new TreeTableColumn<>("Actions"); - actionCol.setCellValueFactory((TreeTableColumn.CellDataFeatures 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 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 { private static class DataCell extends TreeTableCell { public DataCell() { super(); + setAlignment(Pos.CENTER_LEFT); + setContentDisplay(ContentDisplay.RIGHT); + getStyleClass().add("data-cell"); } @Override @@ -117,8 +131,21 @@ public class AddressTreeTable extends TreeTableView { 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 { 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 { 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 { public AmountCell() { super(); getStyleClass().add("amount-cell"); + setContentDisplay(ContentDisplay.RIGHT); } @Override @@ -263,6 +308,19 @@ public class AddressTreeTable extends TreeTableView { 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 { } } } - - private static class ActionCell extends TreeTableCell { - 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); - } - } - } } diff --git a/src/main/resources/com/sparrowwallet/sparrow/app.fxml b/src/main/resources/com/sparrowwallet/sparrow/app.fxml index d08eace5..24db6b6c 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/app.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/app.fxml @@ -17,8 +17,8 @@ - - + + diff --git a/src/main/resources/com/sparrowwallet/sparrow/wallet/addresses.css b/src/main/resources/com/sparrowwallet/sparrow/wallet/addresses.css index febb528a..cb9351bc 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/wallet/addresses.css +++ b/src/main/resources/com/sparrowwallet/sparrow/wallet/addresses.css @@ -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; -} \ No newline at end of file + -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; +}