From 7bd236db232e3069ef02fcfa23974c268ced62da Mon Sep 17 00:00:00 2001 From: romanman Date: Fri, 23 May 2014 18:31:37 +0300 Subject: [PATCH] Nice looking dialog for pay transaction introduced --- .../main/java/org/ethereum/gui/GUIUtils.java | 31 ++++ .../java/org/ethereum/gui/PayOutDialog.java | 149 +++++++++++++----- .../main/java/org/ethereum/gui/ToolBar.java | 1 + .../java/org/ethereum/manager/MainData.java | 15 +- .../EthereumPeerTasterHandler.java | 2 +- .../src/main/resources/buttons/approve.png | Bin 0 -> 4196 bytes .../src/main/resources/buttons/reject.png | Bin 0 -> 4238 bytes 7 files changed, 159 insertions(+), 39 deletions(-) create mode 100644 ethereumj-core/src/main/java/org/ethereum/gui/GUIUtils.java create mode 100644 ethereumj-core/src/main/resources/buttons/approve.png create mode 100644 ethereumj-core/src/main/resources/buttons/reject.png diff --git a/ethereumj-core/src/main/java/org/ethereum/gui/GUIUtils.java b/ethereumj-core/src/main/java/org/ethereum/gui/GUIUtils.java new file mode 100644 index 00000000..8d5b46aa --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/gui/GUIUtils.java @@ -0,0 +1,31 @@ +package org.ethereum.gui; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import javax.swing.border.TitledBorder; +import java.awt.*; + +/** + * www.ethereumJ.com + * User: Roman Mandeleil + * Created on: 23/05/2014 13:51 + */ + +public class GUIUtils { + + + public static void addStyle(JTextField textField, String labelName){ + textField.setHorizontalAlignment(SwingConstants.RIGHT); + Border line = BorderFactory.createLineBorder(Color.LIGHT_GRAY); + TitledBorder titled = BorderFactory.createTitledBorder(line, labelName); + titled.setTitleFont(new Font("Verdana", 0, 13)); + titled.setTitleColor(new Color(213, 225, 185)); + Border empty = new EmptyBorder(5, 8, 5, 8); + CompoundBorder border = new CompoundBorder(titled, empty); + textField.setBorder(border); + textField.setForeground(new Color(143, 170, 220)); + textField.setFont(new Font("Monospaced", 0, 13)); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/gui/PayOutDialog.java b/ethereumj-core/src/main/java/org/ethereum/gui/PayOutDialog.java index f343e797..126c0591 100644 --- a/ethereumj-core/src/main/java/org/ethereum/gui/PayOutDialog.java +++ b/ethereumj-core/src/main/java/org/ethereum/gui/PayOutDialog.java @@ -11,9 +11,15 @@ import org.spongycastle.util.encoders.Hex; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.math.BigInteger; +import java.net.URL; import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; /** * www.ethereumJ.com @@ -22,61 +28,105 @@ import javax.swing.*; */ class PayOutDialog extends JDialog { + PayOutDialog dialog; + AddressState addressState = null; + JLabel statusMsg = null; public PayOutDialog(Frame parent, final AddressState addressState) { super(parent, "Payout details: ", false); + dialog = this; this.addressState = addressState; - JLabel receiver = new JLabel("receiver: "); final JTextField receiverInput = new JTextField(18); - receiverInput.setHorizontalAlignment(SwingConstants.RIGHT); + GUIUtils.addStyle(receiverInput, "Pay to:"); - final JLabel amount = new JLabel("amount: "); final JTextField amountInput = new JTextField(18); - amountInput.setHorizontalAlignment(SwingConstants.RIGHT); - amountInput.setText(addressState.getBalance().toString()); + GUIUtils.addStyle(amountInput, "Amount: "); + + final JTextField feeInput = new JTextField(5); + GUIUtils.addStyle(feeInput, "Fee: "); this.getContentPane().setBackground(Color.WHITE); - this.getContentPane().setLayout(new GridLayout(0, 1, 0, 0)); + this.getContentPane().setLayout(null); - JPanel row1 = new JPanel(); - row1.setBackground(Color.WHITE); - row1.add(receiver); - row1.add(receiverInput); - this.getContentPane().add(row1); + receiverInput.setBounds(70, 30, 350, 45); + this.getContentPane().add(receiverInput); - JPanel row2 = new JPanel(); - row2.setBackground(Color.WHITE); - row2.add(amount); - row2.add(amountInput); - this.getContentPane().add(row2); + amountInput.setBounds(70, 80, 250, 45); + this.getContentPane().add(amountInput); - JPanel row3 = new JPanel(); - row3.setBackground(Color.WHITE); + feeInput.setBounds(330, 80, 90, 45); + this.getContentPane().add(feeInput); - JButton sendButton = new JButton("Send"); - sendButton.addActionListener( - new ActionListener() { + URL rejectIconURL = ClassLoader.getSystemResource("buttons/reject.png"); + ImageIcon rejectIcon = new ImageIcon(rejectIconURL); + JLabel rejectLabel = new JLabel(rejectIcon); + rejectLabel.setToolTipText("Cancel"); + rejectLabel.setCursor(new Cursor(Cursor.HAND_CURSOR)); + + JLabel statusMessage = new JLabel(""); + statusMessage.setBounds(50, 180, 400, 50); + statusMessage.setHorizontalAlignment(SwingConstants.CENTER); + this.statusMsg = statusMessage; + this.getContentPane().add(statusMessage); + + rejectLabel.setBounds(260, 145, 45, 45); + this.getContentPane().add(rejectLabel); + rejectLabel.setVisible(true); + rejectLabel.addMouseListener( + new MouseAdapter() { @Override - public void actionPerformed(ActionEvent e) { + public void mouseClicked(MouseEvent e) { + + dialog.dispose(); + }} + ); + + + URL approveIconURL = ClassLoader.getSystemResource("buttons/approve.png"); + ImageIcon approveIcon = new ImageIcon(approveIconURL); + JLabel approveLabel = new JLabel(approveIcon); + approveLabel.setToolTipText("Submit the transaction"); + approveLabel.setCursor(new Cursor(Cursor.HAND_CURSOR)); + + approveLabel.setBounds(200, 145, 45, 45); + this.getContentPane().add(approveLabel); + approveLabel.setVisible(true); + + + approveLabel.addMouseListener( + new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + + + BigInteger fee = new BigInteger(feeInput.getText()); + BigInteger value = new BigInteger(amountInput.getText()); + byte[] address = Hex.decode( receiverInput.getText()); + // Client - ClientPeer peer = MainData.instance.getActivePeer(); + ClientPeer peer = MainData.instance.getActivePeer(); - BigInteger value = new BigInteger(amountInput.getText()); - byte[] address = Hex.decode(receiverInput.getText()); + if (peer == null){ + dialog.alertStatusMsg("Not connected to any peer"); + return; + } byte[] senderPrivKey = addressState.getEcKey().getPrivKeyBytes(); - byte[] nonce = addressState.getNonce() == BigInteger.ZERO ? - null : addressState.getNonce().toByteArray(); - byte[] gasPrice= Hex.decode("09184e72a000"); - byte[] gas = Hex.decode("4255"); + byte[] nonce = addressState.getNonce() == BigInteger.ZERO ? + null : addressState.getNonce().toByteArray(); - Transaction tx = new Transaction(nonce, gasPrice, gas, - address, BigIntegers.asUnsignedByteArray(value), null); + // todo: in the future it should be retrieved from the block + byte[] gasPrice = new BigInteger("10000000000000").toByteArray(); + + Transaction tx = new Transaction(nonce, gasPrice, + BigIntegers.asUnsignedByteArray(fee), + address, + BigIntegers.asUnsignedByteArray(value), null); try { tx.sign(senderPrivKey); @@ -87,15 +137,18 @@ class PayOutDialog extends JDialog { } peer.sendTransaction(tx); + dialog.infoStatusMsg("Transaction sent to the network, waiting for approve"); } } ); - row3.add(sendButton); - row3.add(new JButton("Cancel")); - row3.setAlignmentY(Component.TOP_ALIGNMENT); - this.getContentPane().add(row3); + feeInput.setText("1000"); + amountInput.setText("0"); + + this.getContentPane().revalidate(); + this.getContentPane().repaint(); + this.setResizable(false); } protected JRootPane createRootPane() { @@ -112,17 +165,39 @@ class PayOutDialog extends JDialog { KeyStroke stroke = KeyStroke.getKeyStroke("ESCAPE"); Action actionListener = new AbstractAction() { public void actionPerformed(ActionEvent actionEvent) { - setVisible(false); + dispose(); } }; InputMap inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); inputMap.put(stroke, "ESCAPE"); rootPane.getActionMap().put("ESCAPE", actionListener); - this.setSize(350, 140); + this.setSize(500, 255); this.setVisible(true); + return rootPane; } + + public void infoStatusMsg(String text){ + this.statusMsg.setForeground(Color.GREEN.darker().darker()); + this.statusMsg.setText(text); + } + + public void alertStatusMsg(String text){ + this.statusMsg.setForeground(Color.RED); + this.statusMsg.setText(text); + } + + + public static void main(String args[]) { + + AddressState as = new AddressState(); + + PayOutDialog pod = new PayOutDialog(null, as); + pod.setVisible(true); + + + } } diff --git a/ethereumj-core/src/main/java/org/ethereum/gui/ToolBar.java b/ethereumj-core/src/main/java/org/ethereum/gui/ToolBar.java index 6847c174..f186723b 100644 --- a/ethereumj-core/src/main/java/org/ethereum/gui/ToolBar.java +++ b/ethereumj-core/src/main/java/org/ethereum/gui/ToolBar.java @@ -35,6 +35,7 @@ public class ToolBar extends JFrame { introLogger.info(""); introLogger.info("♢ EthereumJ [v0.5.1] "); introLogger.info("♢ Code by Roman Mandeleil, (c) 2014."); + introLogger.info("♢ Contribution: Nick Savers "); introLogger.info("♢ Based on a design by Vitaly Buterin."); introLogger.info(""); introLogger.info("java.version: " + System.getProperty("java.version")); diff --git a/ethereumj-core/src/main/java/org/ethereum/manager/MainData.java b/ethereumj-core/src/main/java/org/ethereum/manager/MainData.java index 4e3b4c90..922c80a8 100644 --- a/ethereumj-core/src/main/java/org/ethereum/manager/MainData.java +++ b/ethereumj-core/src/main/java/org/ethereum/manager/MainData.java @@ -113,7 +113,20 @@ public class MainData { return activePeer; } - public void addTransactions(List transactions) {} + + /* todo: here will be all the pending transaction approve + * 1) the dialog put a pending transaction her + * 2) the dialog send the transaction to a net + * 3) wherever the transaction got for the wire in will change to approve state + * 4) only after the approve a) Wallet state changes + * + * 5) After the block is received with that tx the pending been clean up + */ + public void addTransactions(List transactions) { + + + + } public List getPeers() { return peers; diff --git a/ethereumj-core/src/main/java/org/ethereum/net/peerdiscovery/EthereumPeerTasterHandler.java b/ethereumj-core/src/main/java/org/ethereum/net/peerdiscovery/EthereumPeerTasterHandler.java index df94d30c..3d3eadd1 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/peerdiscovery/EthereumPeerTasterHandler.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/peerdiscovery/EthereumPeerTasterHandler.java @@ -56,7 +56,7 @@ public class EthereumPeerTasterHandler extends ChannelInboundHandlerAdapter { byte[] peerIdBytes = HashUtil.randomPeerId(); HelloMessage helloMessage = new HelloMessage((byte)0x11, (byte)0x00, "EthereumJ [v0.5.1] by RomanJ ", - (byte)0b00000111, (short)30303, peerIdBytes); + (byte)0b00000001, (short)30303, peerIdBytes); byte[] helloLength =ByteUtil.calcPacketLength(helloMessage.getPayload()); final ByteBuf buffer = ctx.alloc().buffer(helloMessage.getPayload().length + 8); diff --git a/ethereumj-core/src/main/resources/buttons/approve.png b/ethereumj-core/src/main/resources/buttons/approve.png new file mode 100644 index 0000000000000000000000000000000000000000..c7455cc16dc71540b521a7bc2d9d09f7b35bafd5 GIT binary patch literal 4196 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