Craig Raw
5 years ago
22 changed files with 557 additions and 193 deletions
@ -1 +1 @@ |
|||
Subproject commit eabcf4e8f48ae18ff8d21436a2ab5e5153719944 |
|||
Subproject commit 7871413573e67ed7539cf03d6deadd1a2c4abafa |
@ -0,0 +1,113 @@ |
|||
package com.sparrowwallet.sparrow; |
|||
|
|||
import com.sparrowwallet.drongo.protocol.Script; |
|||
import com.sparrowwallet.drongo.protocol.ScriptChunk; |
|||
import com.sparrowwallet.sparrow.transaction.ScriptContextMenu; |
|||
import javafx.geometry.Point2D; |
|||
import javafx.scene.control.Label; |
|||
import javafx.stage.Popup; |
|||
import org.fxmisc.richtext.CodeArea; |
|||
import org.fxmisc.richtext.event.MouseOverTextEvent; |
|||
import org.fxmisc.richtext.model.TwoDimensional; |
|||
|
|||
import java.time.Duration; |
|||
|
|||
import static com.sparrowwallet.drongo.protocol.ScriptType.*; |
|||
import static com.sparrowwallet.drongo.protocol.ScriptType.P2WSH; |
|||
import static org.fxmisc.richtext.model.TwoDimensional.Bias.Backward; |
|||
|
|||
public abstract class BaseController { |
|||
protected void appendScript(CodeArea codeArea, Script script) { |
|||
appendScript(codeArea, script, null, null); |
|||
} |
|||
|
|||
protected void appendScript(CodeArea codeArea, Script script, Script redeemScript, Script witnessScript) { |
|||
if(P2PKH.isScriptType(script)) { |
|||
codeArea.append(script.getChunks().get(0).toString(), "script-opcode"); |
|||
codeArea.append(" ", ""); |
|||
codeArea.append(script.getChunks().get(1).toString(), "script-opcode"); |
|||
codeArea.append(" ", ""); |
|||
codeArea.append("<pkh>", "script-hash"); |
|||
codeArea.append(" ", ""); |
|||
codeArea.append(script.getChunks().get(3).toString(), "script-opcode"); |
|||
codeArea.append(" ", ""); |
|||
codeArea.append(script.getChunks().get(4).toString(), "script-opcode"); |
|||
} else if(P2SH.isScriptType(script)) { |
|||
codeArea.append(script.getChunks().get(0).toString(), "script-opcode"); |
|||
codeArea.append(" ", ""); |
|||
codeArea.append("<sh>", "script-hash"); |
|||
codeArea.append(" ", ""); |
|||
codeArea.append(script.getChunks().get(2).toString(), "script-opcode"); |
|||
} else if(P2WPKH.isScriptType(script)) { |
|||
codeArea.append(script.getChunks().get(0).toString(), "script-opcode"); |
|||
codeArea.append(" ", ""); |
|||
codeArea.append("<wpkh>", "script-hash"); |
|||
} else if(P2WSH.isScriptType(script)) { |
|||
codeArea.append(script.getChunks().get(0).toString(), "script-opcode"); |
|||
codeArea.append(" ", ""); |
|||
codeArea.append("<wsh>", "script-hash"); |
|||
} else { |
|||
int signatureCount = 1; |
|||
int pubKeyCount = 1; |
|||
for (int i = 0; i < script.getChunks().size(); i++) { |
|||
ScriptChunk chunk = script.getChunks().get(i); |
|||
if(chunk.isOpCode()) { |
|||
codeArea.append(chunk.toString(), "script-opcode"); |
|||
} else if(chunk.isSignature()) { |
|||
codeArea.append("<signature" + signatureCount++ + ">", "script-signature"); |
|||
} else if(chunk.isPubKey()) { |
|||
codeArea.append("<pubkey" + pubKeyCount++ + ">", "script-pubkey"); |
|||
} else if(chunk.isScript()) { |
|||
Script nestedScript = chunk.getScript(); |
|||
if (nestedScript.equals(redeemScript)) { |
|||
codeArea.append("<RedeemScript>", "script-redeem"); |
|||
} else if (nestedScript.equals(witnessScript)) { |
|||
codeArea.append("<WitnessScript>", "script-redeem"); |
|||
} else { |
|||
codeArea.append("(", "script-nest"); |
|||
appendScript(codeArea, nestedScript); |
|||
codeArea.append(")", "script-nest"); |
|||
} |
|||
} else { |
|||
codeArea.append(chunk.toString(), "script-other"); |
|||
} |
|||
|
|||
if(i < script.getChunks().size() - 1) { |
|||
codeArea.append(" ", ""); |
|||
} |
|||
} |
|||
} |
|||
|
|||
addScriptPopup(codeArea, script); |
|||
} |
|||
|
|||
protected void addScriptPopup(CodeArea area, Script script) { |
|||
ScriptContextMenu contextMenu = new ScriptContextMenu(area, script); |
|||
area.setContextMenu(contextMenu); |
|||
|
|||
Popup popup = new Popup(); |
|||
Label popupMsg = new Label(); |
|||
popupMsg.getStyleClass().add("tooltip"); |
|||
popup.getContent().add(popupMsg); |
|||
|
|||
area.setMouseOverTextDelay(Duration.ofMillis(150)); |
|||
area.addEventHandler(MouseOverTextEvent.MOUSE_OVER_TEXT_BEGIN, e -> { |
|||
TwoDimensional.Position position = area.getParagraph(0).getStyleSpans().offsetToPosition(e.getCharacterIndex(), Backward); |
|||
if(position.getMajor() % 2 == 0) { |
|||
ScriptChunk hoverChunk = script.getChunks().get(position.getMajor()/2); |
|||
if(!hoverChunk.isOpCode()) { |
|||
Point2D pos = e.getScreenPosition(); |
|||
popupMsg.setText(describeScriptChunk(hoverChunk)); |
|||
popup.show(area, pos.getX(), pos.getY() + 10); |
|||
} |
|||
} |
|||
}); |
|||
area.addEventHandler(MouseOverTextEvent.MOUSE_OVER_TEXT_END, e -> { |
|||
popup.hide(); |
|||
}); |
|||
} |
|||
|
|||
protected String describeScriptChunk(ScriptChunk chunk) { |
|||
return chunk.toString(); |
|||
} |
|||
} |
@ -0,0 +1,64 @@ |
|||
package com.sparrowwallet.sparrow.control; |
|||
|
|||
import javafx.animation.FadeTransition; |
|||
import javafx.beans.InvalidationListener; |
|||
import javafx.beans.Observable; |
|||
import javafx.beans.property.ObjectProperty; |
|||
import javafx.scene.Cursor; |
|||
import javafx.scene.Node; |
|||
import javafx.scene.input.Clipboard; |
|||
import javafx.scene.input.ClipboardContent; |
|||
import javafx.scene.layout.Region; |
|||
import javafx.scene.layout.StackPane; |
|||
import javafx.util.Duration; |
|||
import org.controlsfx.control.textfield.CustomTextField; |
|||
|
|||
public class CopyableTextField extends CustomTextField { |
|||
private static final Duration FADE_DURATION = Duration.millis(350); |
|||
|
|||
public CopyableTextField() { |
|||
super(); |
|||
getStyleClass().add("copyable-text-field"); |
|||
setupCopyButtonField(super.rightProperty()); |
|||
} |
|||
|
|||
private void setupCopyButtonField(ObjectProperty<Node> rightProperty) { |
|||
Region copyButton = new Region(); |
|||
copyButton.getStyleClass().addAll("graphic"); //$NON-NLS-1$
|
|||
StackPane copyButtonPane = new StackPane(copyButton); |
|||
copyButtonPane.getStyleClass().addAll("copy-button"); //$NON-NLS-1$
|
|||
copyButtonPane.setOpacity(0.0); |
|||
copyButtonPane.setCursor(Cursor.DEFAULT); |
|||
copyButtonPane.setOnMouseReleased(e -> { |
|||
ClipboardContent content = new ClipboardContent(); |
|||
content.putString(getText()); |
|||
Clipboard.getSystemClipboard().setContent(content); |
|||
}); |
|||
|
|||
rightProperty.set(copyButtonPane); |
|||
|
|||
final FadeTransition fader = new FadeTransition(FADE_DURATION, copyButtonPane); |
|||
fader.setCycleCount(1); |
|||
|
|||
textProperty().addListener(new InvalidationListener() { |
|||
@Override |
|||
public void invalidated(Observable arg0) { |
|||
String text = getText(); |
|||
boolean isTextEmpty = text == null || text.isEmpty(); |
|||
boolean isButtonVisible = fader.getNode().getOpacity() > 0; |
|||
|
|||
if (isTextEmpty && isButtonVisible) { |
|||
setButtonVisible(false); |
|||
} else if (!isTextEmpty && !isButtonVisible) { |
|||
setButtonVisible(true); |
|||
} |
|||
} |
|||
|
|||
private void setButtonVisible( boolean visible ) { |
|||
fader.setFromValue(visible? 0.0: 1.0); |
|||
fader.setToValue(visible? 1.0: 0.0); |
|||
fader.play(); |
|||
} |
|||
}); |
|||
} |
|||
} |
@ -1,15 +1,15 @@ |
|||
package com.sparrowwallet.sparrow.event; |
|||
|
|||
import com.sparrowwallet.drongo.wallet.Wallet; |
|||
import com.sparrowwallet.sparrow.wallet.NodeEntry; |
|||
|
|||
public class ReceiveActionEvent { |
|||
private Wallet.Node receiveNode; |
|||
private NodeEntry receiveEntry; |
|||
|
|||
public ReceiveActionEvent(Wallet.Node receiveNode) { |
|||
this.receiveNode = receiveNode; |
|||
public ReceiveActionEvent(NodeEntry receiveEntry) { |
|||
this.receiveEntry = receiveEntry; |
|||
} |
|||
|
|||
public Wallet.Node getReceiveNode() { |
|||
return receiveNode; |
|||
public NodeEntry getReceiveEntry() { |
|||
return receiveEntry; |
|||
} |
|||
} |
|||
|
@ -0,0 +1,33 @@ |
|||
package com.sparrowwallet.sparrow.wallet; |
|||
|
|||
import javafx.beans.property.SimpleStringProperty; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
public abstract class Entry { |
|||
private final SimpleStringProperty labelProperty; |
|||
private final List<Entry> children = new ArrayList<>(); |
|||
|
|||
public Entry(String label) { |
|||
this.labelProperty = new SimpleStringProperty(label); |
|||
} |
|||
|
|||
public Entry(SimpleStringProperty labelProperty) { |
|||
this.labelProperty = labelProperty; |
|||
} |
|||
|
|||
public String getLabel() { |
|||
return labelProperty.get(); |
|||
} |
|||
|
|||
public SimpleStringProperty labelProperty() { |
|||
return labelProperty; |
|||
} |
|||
|
|||
public List<Entry> getChildren() { |
|||
return children; |
|||
} |
|||
|
|||
public abstract Long getAmount(); |
|||
} |
@ -0,0 +1,25 @@ |
|||
package com.sparrowwallet.sparrow.wallet; |
|||
|
|||
import com.sparrowwallet.drongo.wallet.Wallet; |
|||
|
|||
public class NodeEntry extends Entry { |
|||
private final Wallet.Node node; |
|||
|
|||
public NodeEntry(Wallet.Node node) { |
|||
super(node.getLabel()); |
|||
this.node = node; |
|||
|
|||
labelProperty().addListener((observable, oldValue, newValue) -> node.setLabel(newValue)); |
|||
} |
|||
|
|||
@Override |
|||
public Long getAmount() { |
|||
//TODO: Iterate through TransactionEntries to calculate amount
|
|||
|
|||
return null; |
|||
} |
|||
|
|||
public Wallet.Node getNode() { |
|||
return node; |
|||
} |
|||
} |
@ -0,0 +1,104 @@ |
|||
package com.sparrowwallet.sparrow.wallet; |
|||
|
|||
import com.google.zxing.BarcodeFormat; |
|||
import com.google.zxing.client.j2se.MatrixToImageConfig; |
|||
import com.google.zxing.client.j2se.MatrixToImageWriter; |
|||
import com.google.zxing.common.BitMatrix; |
|||
import com.google.zxing.qrcode.QRCodeWriter; |
|||
import com.sparrowwallet.drongo.KeyPurpose; |
|||
import com.sparrowwallet.sparrow.EventManager; |
|||
import com.sparrowwallet.sparrow.control.CopyableLabel; |
|||
import com.sparrowwallet.sparrow.control.CopyableTextField; |
|||
import javafx.beans.value.ChangeListener; |
|||
import javafx.beans.value.ObservableValue; |
|||
import javafx.event.ActionEvent; |
|||
import javafx.fxml.FXML; |
|||
import javafx.fxml.Initializable; |
|||
import javafx.scene.control.TextField; |
|||
import javafx.scene.image.Image; |
|||
import javafx.scene.image.ImageView; |
|||
import org.fxmisc.richtext.CodeArea; |
|||
|
|||
import java.io.ByteArrayInputStream; |
|||
import java.io.ByteArrayOutputStream; |
|||
import java.net.URL; |
|||
import java.util.ResourceBundle; |
|||
|
|||
public class ReceiveController extends WalletFormController implements Initializable { |
|||
@FXML |
|||
private CopyableTextField address; |
|||
|
|||
@FXML |
|||
private TextField label; |
|||
|
|||
@FXML |
|||
private CopyableLabel derivationPath; |
|||
|
|||
@FXML |
|||
private CopyableLabel lastUsed; |
|||
|
|||
@FXML |
|||
private ImageView qrCode; |
|||
|
|||
@FXML |
|||
private CodeArea scriptPubKeyArea; |
|||
|
|||
private NodeEntry currentEntry; |
|||
|
|||
@Override |
|||
public void initialize(URL location, ResourceBundle resources) { |
|||
EventManager.get().register(this); |
|||
} |
|||
|
|||
@Override |
|||
public void initializeView() { |
|||
|
|||
} |
|||
|
|||
public void setNodeEntry(NodeEntry nodeEntry) { |
|||
if(currentEntry != null) { |
|||
label.textProperty().unbindBidirectional(currentEntry.labelProperty()); |
|||
} |
|||
|
|||
this.currentEntry = nodeEntry; |
|||
|
|||
address.setText(nodeEntry.getNode().getAddress().toString()); |
|||
|
|||
label.textProperty().bindBidirectional(nodeEntry.labelProperty()); |
|||
|
|||
derivationPath.setText(nodeEntry.getNode().getDerivationPath()); |
|||
|
|||
//TODO: Find last used block height if available (red flag?)
|
|||
lastUsed.setText("Unknown"); |
|||
|
|||
Image qrImage = getQrCode(nodeEntry.getNode().getAddress().toString()); |
|||
if(qrImage != null) { |
|||
qrCode.setImage(qrImage); |
|||
} |
|||
|
|||
scriptPubKeyArea.clear(); |
|||
appendScript(scriptPubKeyArea, nodeEntry.getNode().getOutputScript(), null, null); |
|||
} |
|||
|
|||
private Image getQrCode(String address) { |
|||
try { |
|||
QRCodeWriter qrCodeWriter = new QRCodeWriter(); |
|||
BitMatrix qrMatrix = qrCodeWriter.encode(address, BarcodeFormat.QR_CODE, 150, 150); |
|||
|
|||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
|||
MatrixToImageWriter.writeToStream(qrMatrix, "PNG", baos, new MatrixToImageConfig()); |
|||
|
|||
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); |
|||
return new Image(bais); |
|||
} catch(Exception e) { |
|||
e.printStackTrace(); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public void getNewAddress(ActionEvent event) { |
|||
NodeEntry freshEntry = getWalletForm().getFreshNodeEntry(KeyPurpose.RECEIVE, currentEntry); |
|||
setNodeEntry(freshEntry); |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
.address-text-field { |
|||
-fx-font-family: Courier; |
|||
} |
|||
|
|||
.qr-code { |
|||
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 10, 0, 0, 0); |
|||
-fx-padding: 20; |
|||
} |
@ -0,0 +1,83 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
|
|||
<?import java.lang.*?> |
|||
<?import java.util.*?> |
|||
<?import javafx.scene.*?> |
|||
<?import javafx.scene.control.*?> |
|||
<?import javafx.scene.layout.*?> |
|||
|
|||
<?import javafx.geometry.Insets?> |
|||
<?import tornadofx.control.Form?> |
|||
<?import tornadofx.control.Fieldset?> |
|||
<?import tornadofx.control.Field?> |
|||
<?import javafx.scene.image.ImageView?> |
|||
<?import org.fxmisc.flowless.VirtualizedScrollPane?> |
|||
<?import org.fxmisc.richtext.CodeArea?> |
|||
<?import com.sparrowwallet.sparrow.control.CopyableLabel?> |
|||
<?import org.controlsfx.glyphfont.Glyph?> |
|||
<?import com.sparrowwallet.sparrow.control.CopyableTextField?> |
|||
|
|||
<BorderPane stylesheets="@receive.css, @wallet.css, @../script.css, @../general.css" styleClass="wallet-pane" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.wallet.ReceiveController"> |
|||
<center> |
|||
|
|||
<GridPane hgap="10.0" vgap="10.0"> |
|||
<padding> |
|||
<Insets left="25.0" right="25.0" top="25.0" /> |
|||
</padding> |
|||
<columnConstraints> |
|||
<ColumnConstraints percentWidth="70" /> |
|||
<ColumnConstraints percentWidth="30" /> |
|||
</columnConstraints> |
|||
<rowConstraints> |
|||
<RowConstraints /> |
|||
</rowConstraints> |
|||
<Form GridPane.columnIndex="0" GridPane.rowIndex="0"> |
|||
<Fieldset inputGrow="SOMETIMES" text="Receive"> |
|||
<Field text="Address:"> |
|||
<CopyableTextField fx:id="address" styleClass="address-text-field" editable="false" prefWidth="350"/> |
|||
</Field> |
|||
<Field text="Label:"> |
|||
<TextField fx:id="label" /> |
|||
</Field> |
|||
<Field text="Derivation:"> |
|||
<CopyableLabel fx:id="derivationPath" /> |
|||
</Field> |
|||
<Field text="Last Used:"> |
|||
<CopyableLabel fx:id="lastUsed" /> |
|||
</Field> |
|||
</Fieldset> |
|||
</Form> |
|||
|
|||
<AnchorPane GridPane.columnIndex="1" GridPane.rowIndex="0"> |
|||
<ImageView fx:id="qrCode" styleClass="qr-code" AnchorPane.rightAnchor="5"/> |
|||
</AnchorPane> |
|||
|
|||
<Separator styleClass="form-separator" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="1" /> |
|||
|
|||
<Form GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="2"> |
|||
<Fieldset inputGrow="SOMETIMES" text="Required Script"> |
|||
<Field text="ScriptPubKey:"> |
|||
<VirtualizedScrollPane> |
|||
<content> |
|||
<CodeArea fx:id="scriptPubKeyArea" editable="false" wrapText="true" prefHeight="42" maxHeight="42" styleClass="uneditable-codearea" /> |
|||
</content> |
|||
</VirtualizedScrollPane> |
|||
</Field> |
|||
</Fieldset> |
|||
</Form> |
|||
|
|||
</GridPane> |
|||
</center> |
|||
<bottom> |
|||
<AnchorPane> |
|||
<padding> |
|||
<Insets left="25.0" right="25.0" bottom="25.0" /> |
|||
</padding> |
|||
<Button fx:id="nextAddress" text="Get Next Address" defaultButton="true" AnchorPane.rightAnchor="10" onAction="#getNewAddress"> |
|||
<graphic> |
|||
<Glyph fontFamily="FontAwesome" icon="ARROW_DOWN" fontSize="12" /> |
|||
</graphic> |
|||
</Button> |
|||
</AnchorPane> |
|||
</bottom> |
|||
</BorderPane> |
Loading…
Reference in new issue