You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
417 lines
15 KiB
417 lines
15 KiB
import sys, time, datetime |
|
|
|
# todo: see PySide |
|
|
|
from PyQt4.QtGui import * |
|
from PyQt4.QtCore import * |
|
import PyQt4.QtCore as QtCore |
|
import PyQt4.QtGui as QtGui |
|
|
|
from wallet import format_satoshis |
|
|
|
def restore_create_dialog(wallet): |
|
pass |
|
|
|
class Sender(QtCore.QThread): |
|
def run(self): |
|
while True: |
|
self.emit(QtCore.SIGNAL('testsignal')) |
|
time.sleep(0.5) |
|
|
|
|
|
class ElectrumWindow(QMainWindow): |
|
|
|
def __init__(self, wallet): |
|
QMainWindow.__init__(self) |
|
self.wallet = wallet |
|
|
|
tabs = QTabWidget(self) |
|
tabs.addTab(self.create_history_tab(), 'History') |
|
tabs.addTab(self.create_send_tab(), 'Send') |
|
tabs.addTab(self.create_receive_tab(), 'Receive') |
|
tabs.addTab(self.create_contacts_tab(),'Contacts') |
|
tabs.addTab(self.create_wall_tab(), 'Wall') |
|
tabs.setMinimumSize(600, 400) |
|
tabs.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) |
|
self.setCentralWidget(tabs) |
|
self.create_status_bar() |
|
self.setGeometry(100,100,800,400) |
|
self.setWindowTitle( 'Electrum ' + self.wallet.electrum_version + ' - Qt') |
|
self.show() |
|
|
|
QShortcut(QKeySequence("Ctrl+W"), self, self.close) |
|
QShortcut(QKeySequence("Ctrl+Q"), self, self.close) |
|
QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( tabs.currentIndex() - 1)) |
|
QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( tabs.currentIndex() + 1)) |
|
|
|
|
|
def connect_slots(self, sender): |
|
self.connect(sender, QtCore.SIGNAL('testsignal'), self.update_wallet) |
|
|
|
|
|
def update_wallet(self): |
|
if self.wallet.interface.is_connected: |
|
if self.wallet.interface.blocks == 0: |
|
text = "Server not ready" |
|
icon = QIcon("icons/status_disconnected.svg") |
|
elif not self.wallet.interface.was_polled: |
|
text = "Synchronizing..." |
|
icon = QIcon("icons/status_waiting.svg") |
|
else: |
|
c, u = self.wallet.get_balance() |
|
text = "Balance: %s "%( format_satoshis(c) ) |
|
if u: text += "[%s unconfirmed]"%( format_satoshis(u,True) ) |
|
icon = QIcon("icons/status_connected.png") |
|
else: |
|
text = "Not connected" |
|
icon = QIcon("icons/status_disconnected.svg") |
|
|
|
self.statusBar().showMessage(text) |
|
self.status_button.setIcon( icon ) |
|
|
|
if self.wallet.interface.was_updated: |
|
self.wallet.interface.was_updated = False |
|
self.textbox.setText( self.wallet.interface.message ) |
|
self.update_history_tab() |
|
self.update_receive_tab() |
|
self.update_contacts_tab() |
|
|
|
|
|
def create_history_tab(self): |
|
self.history_list = w = QTreeWidget(self) |
|
w.setColumnCount(5) |
|
w.setColumnWidth(0, 40) |
|
w.setColumnWidth(1, 140) |
|
w.setColumnWidth(2, 340) |
|
w.setColumnWidth(3, 120) |
|
w.setColumnWidth(4, 120) |
|
w.setHeaderLabels( [ '', 'Date', 'Description', 'Amount', 'Balance'] ) |
|
self.connect(w, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), self.tx_details) |
|
self.connect(w, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked) |
|
self.connect(w, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed) |
|
return w |
|
|
|
def tx_details(self, item, column): |
|
tx_hash = str(item.toolTip(0)) |
|
tx = self.wallet.tx_history.get(tx_hash) |
|
|
|
if tx['height']: |
|
conf = self.wallet.interface.blocks - tx['height'] + 1 |
|
time_str = datetime.datetime.fromtimestamp( tx['nTime']).isoformat(' ')[:-3] |
|
else: |
|
conf = 0 |
|
time_str = 'pending' |
|
|
|
tx_details = "Transaction Details:\n\n" \ |
|
+ "Transaction ID:\n" + tx_hash + "\n\n" \ |
|
+ "Status: %d confirmations\n\n"%conf \ |
|
+ "Date: %s\n\n"%time_str \ |
|
+ "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" \ |
|
+ "Outputs:\n-"+ '\n-'.join(tx['outputs']) |
|
|
|
r = self.wallet.receipts.get(tx_hash) |
|
if r: |
|
tx_details += "\n_______________________________________" \ |
|
+ '\n\nSigned URI: ' + r[2] \ |
|
+ "\n\nSigned by: " + r[0] \ |
|
+ '\n\nSignature: ' + r[1] |
|
|
|
QMessageBox.information(self, 'Details', tx_details, 'OK') |
|
|
|
|
|
def tx_label_clicked(self, item, column): |
|
if column==2 and item.isSelected(): |
|
tx_hash = str(item.toolTip(0)) |
|
self.is_edit=True |
|
#if not self.wallet.labels.get(tx_hash): item.setText(2,'') |
|
item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled) |
|
self.history_list.editItem( item, column ) |
|
item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled) |
|
self.is_edit=False |
|
|
|
def tx_label_changed(self, item, column): |
|
if self.is_edit: |
|
return |
|
self.is_edit=True |
|
tx_hash = str(item.toolTip(0)) |
|
tx = self.wallet.tx_history.get(tx_hash) |
|
s = self.wallet.labels.get(tx_hash) |
|
text = str( item.text(2) ) |
|
if text: |
|
self.wallet.labels[tx_hash] = text |
|
item.setForeground(2, QBrush(QColor('black'))) |
|
else: |
|
if s: self.wallet.labels.pop(tx_hash) |
|
text = tx['default_label'] |
|
item.setText(2, text) |
|
item.setForeground(2, QBrush(QColor('gray'))) |
|
self.is_edit=False |
|
|
|
def address_label_clicked(self, item, column, l): |
|
if column==1 and item.isSelected(): |
|
item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled) |
|
l.editItem( item, column ) |
|
item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled) |
|
|
|
def address_label_changed(self, item, column, l): |
|
addr = str(item.text(0)) |
|
text = str( item.text(1) ) |
|
if text: |
|
self.wallet.labels[addr] = text |
|
else: |
|
s = self.wallet.labels.get(addr) |
|
if s: self.wallet.labels.pop(addr) |
|
self.update_history_tab() |
|
|
|
def update_history_tab(self): |
|
self.history_list.clear() |
|
balance = 0 |
|
for tx in self.wallet.get_tx_history(): |
|
tx_hash = tx['tx_hash'] |
|
if tx['height']: |
|
conf = self.wallet.interface.blocks - tx['height'] + 1 |
|
time_str = datetime.datetime.fromtimestamp( tx['nTime']).isoformat(' ')[:-3] |
|
icon = QIcon("icons/confirmed.png") |
|
else: |
|
conf = 0 |
|
time_str = 'pending' |
|
icon = QIcon("icons/unconfirmed.svg") |
|
v = tx['value'] |
|
balance += v |
|
label = self.wallet.labels.get(tx_hash) |
|
is_default_label = (label == '') or (label is None) |
|
if is_default_label: label = tx['default_label'] |
|
|
|
item = QTreeWidgetItem( [ '', time_str, label, format_satoshis(v,True), format_satoshis(balance)] ) |
|
item.setFont(2, QFont('monospace')) |
|
item.setFont(3, QFont('monospace')) |
|
item.setFont(4, QFont('monospace')) |
|
item.setToolTip(0, tx_hash) |
|
if is_default_label: |
|
item.setForeground(2, QBrush(QColor('grey'))) |
|
|
|
item.setIcon(0, icon) |
|
self.history_list.insertTopLevelItem(0,item) |
|
|
|
|
|
def create_send_tab(self): |
|
w = QWidget() |
|
|
|
paytoEdit = QtGui.QLineEdit() |
|
descriptionEdit = QtGui.QLineEdit() |
|
amountEdit = QtGui.QLineEdit() |
|
feeEdit = QtGui.QLineEdit() |
|
|
|
grid = QtGui.QGridLayout() |
|
grid.setSpacing(8) |
|
|
|
grid.addWidget(QLabel('Pay to'), 1, 0) |
|
grid.addWidget(paytoEdit, 1, 1) |
|
|
|
grid.addWidget(QLabel('Description'), 2, 0) |
|
grid.addWidget(descriptionEdit, 2, 1) |
|
|
|
grid.addWidget(QLabel('Amount'), 3, 0) |
|
grid.addWidget(amountEdit, 3, 1) |
|
|
|
grid.addWidget(QLabel('Fee'), 4, 0) |
|
grid.addWidget(feeEdit, 4, 1) |
|
|
|
sendButton = QPushButton("Send") |
|
clearButton = QPushButton("Clear") |
|
grid.addWidget(sendButton, 5, 1) |
|
grid.addWidget(clearButton, 5, 1) |
|
|
|
w.setLayout(grid) |
|
w.show() |
|
|
|
w2 = QWidget() |
|
vbox = QtGui.QVBoxLayout() |
|
vbox.addWidget(w) |
|
vbox.addStretch(1) |
|
w2.setLayout(vbox) |
|
|
|
return w2 |
|
|
|
def make_address_list(self, is_recv): |
|
|
|
l = QTreeWidget(self) |
|
l.setColumnCount(3) |
|
l.setColumnWidth(0, 330) |
|
l.setColumnWidth(1, 330) |
|
l.setColumnWidth(2, 20) |
|
l.setHeaderLabels( ['Address', 'Label','Tx']) |
|
|
|
vbox = QtGui.QVBoxLayout() |
|
vbox.setMargin(0) |
|
vbox.setSpacing(0) |
|
vbox.addWidget(l) |
|
|
|
hbox = QtGui.QHBoxLayout() |
|
hbox.setMargin(0) |
|
hbox.setSpacing(0) |
|
qrButton = QtGui.QPushButton("QR") |
|
copyButton = QtGui.QPushButton("Copy to Clipboard") |
|
hbox.addWidget(qrButton) |
|
hbox.addWidget(copyButton) |
|
if not is_recv: |
|
addButton = QtGui.QPushButton("New") |
|
addButton.clicked.connect(self.newaddress_dialog) |
|
paytoButton = QtGui.QPushButton("Pay to") |
|
hbox.addWidget(addButton) |
|
hbox.addWidget(paytoButton) |
|
hbox.addStretch(1) |
|
buttons = QWidget() |
|
buttons.setLayout(hbox) |
|
vbox.addWidget(buttons) |
|
|
|
w = QWidget() |
|
w.setLayout(vbox) |
|
return w, l |
|
|
|
def create_receive_tab(self): |
|
w, l = self.make_address_list(True) |
|
self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l)) |
|
self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l)) |
|
self.receive_list = l |
|
return w |
|
|
|
def create_contacts_tab(self): |
|
w, l = self.make_address_list(False) |
|
self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l)) |
|
self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l)) |
|
self.connect(l, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), self.show_contact_details) |
|
self.contacts_list = l |
|
return w |
|
|
|
def update_receive_tab(self): |
|
self.receive_list.clear() |
|
for address in self.wallet.all_addresses(): |
|
if self.wallet.is_change(address):continue |
|
label = self.wallet.labels.get(address,'') |
|
n = 0 |
|
h = self.wallet.history.get(address,[]) |
|
for item in h: |
|
if not item['is_in'] : n=n+1 |
|
tx = "None" if n==0 else "%d"%n |
|
item = QTreeWidgetItem( [ address, label, tx] ) |
|
item.setFont(0, QFont('monospace')) |
|
self.receive_list.addTopLevelItem(item) |
|
|
|
def show_contact_details(self, item, column): |
|
m = str(item.text(0)) |
|
a = self.wallet.aliases.get(m) |
|
if a: |
|
if a[0] in self.wallet.authorities.keys(): |
|
s = self.wallet.authorities.get(a[0]) |
|
else: |
|
s = "self-signed" |
|
msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0] |
|
QMessageBox.information(self, 'Alias', msg, 'OK') |
|
|
|
def update_contacts_tab(self): |
|
self.contacts_list.clear() |
|
for alias, v in self.wallet.aliases.items(): |
|
s, target = v |
|
label = self.wallet.labels.get(alias,'') |
|
item = QTreeWidgetItem( [ alias, label, '-'] ) |
|
self.contacts_list.addTopLevelItem(item) |
|
|
|
for address in self.wallet.addressbook: |
|
label = self.wallet.labels.get(address,'') |
|
n = 0 |
|
for item in self.wallet.tx_history.values(): |
|
if address in item['outputs'] : n=n+1 |
|
tx = "None" if n==0 else "%d"%n |
|
item = QTreeWidgetItem( [ address, label, tx] ) |
|
item.setFont(0, QFont('monospace')) |
|
self.contacts_list.addTopLevelItem(item) |
|
|
|
|
|
def create_wall_tab(self): |
|
self.textbox = textbox = QTextEdit(self) |
|
textbox.setReadOnly(True) |
|
return textbox |
|
|
|
def create_status_bar(self): |
|
sb = QStatusBar() |
|
sb.setFixedHeight(35) |
|
|
|
hbox = QtGui.QHBoxLayout() |
|
hbox.setMargin(0) |
|
buttons = QWidget() |
|
buttons.setLayout(hbox) |
|
|
|
icon = QIcon("icons/lock.svg") |
|
b = QPushButton( icon, '' ) |
|
b.setToolTip("Password") |
|
b.setFlat(True) |
|
b.setMaximumWidth(25) |
|
hbox.addWidget(b) |
|
|
|
icon = QIcon("icons/preferences.svg") |
|
b = QPushButton( icon, '' ) |
|
b.setToolTip("Preferences") |
|
b.setFlat(True) |
|
b.setMaximumWidth(25) |
|
hbox.addWidget(b) |
|
|
|
icon = QIcon("icons/seed.png") |
|
b = QPushButton( icon, '' ) |
|
b.setToolTip("Seed") |
|
b.setFlat(True) |
|
b.setMaximumWidth(20) |
|
b.clicked.connect(self.show_seed_dialog) |
|
hbox.addWidget(b) |
|
|
|
icon = QIcon("icons/status_disconnected.svg") |
|
self.status_button = b = QPushButton( icon, '' ) |
|
b.setToolTip("Network") |
|
b.setFlat(True) |
|
b.setMaximumWidth(25) |
|
hbox.addWidget(b) |
|
|
|
sb.addPermanentWidget(buttons) |
|
self.setStatusBar(sb) |
|
|
|
def newaddress_dialog(self): |
|
|
|
text, ok = QtGui.QInputDialog.getText(self, 'New Contact', 'Address:') |
|
address = str(text) |
|
if ok: |
|
if self.wallet.is_valid(address): |
|
self.wallet.addressbook.append(address) |
|
self.wallet.save() |
|
self.update_contacts_tab() |
|
else: |
|
QMessageBox.warning(self, 'Error', 'Invalid Address', 'OK') |
|
|
|
def show_seed_dialog(self): |
|
import mnemonic |
|
pw, ok = QtGui.QInputDialog.getText(self, 'Password', 'Password:') |
|
if ok: |
|
try: |
|
seed = self.wallet.pw_decode( self.wallet.seed, str(pw)) |
|
except: |
|
QMessageBox.warning(self, 'Error', 'Invalid Password', 'OK') |
|
return |
|
|
|
msg = "Your wallet generation seed is:\n\n" + seed \ |
|
+ "\n\nPlease keep it in a safe place; if you lose it, you will not be able to restore your wallet.\n\n" \ |
|
+ "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:\n\n\"" \ |
|
+ ' '.join(mnemonic.mn_encode(seed)) + "\"" |
|
QMessageBox.information(self, 'Seed', msg, 'OK') |
|
|
|
|
|
class BitcoinGUI(): |
|
|
|
def __init__(self, wallet): |
|
self.wallet = wallet |
|
|
|
def main(self): |
|
s = Sender() |
|
s.start() |
|
app = QApplication(sys.argv) |
|
w = ElectrumWindow(self.wallet) |
|
w.connect_slots(s) |
|
app.exec_()
|
|
|