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,840,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 )%tabs.count() )) QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() )) 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) #print w.getContentsMargins() w.setColumnCount(5) w.setColumnWidth(0, 40) w.setColumnWidth(1, 140) w.setColumnWidth(2, 350) w.setColumnWidth(3, 140) w.setColumnWidth(4, 140) 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.setColumnMinimumWidth(3,300) grid.setColumnStretch(4,1) grid.addWidget(QLabel('Pay to'), 1, 0) grid.addWidget(paytoEdit, 1, 1, 1, 3) grid.addWidget(QLabel('Description'), 2, 0) grid.addWidget(descriptionEdit, 2, 1, 1, 3) grid.addWidget(QLabel('Amount'), 3, 0) grid.addWidget(amountEdit, 3, 1, 1, 2) grid.addWidget(QLabel('Fee'), 4, 0) grid.addWidget(feeEdit, 4, 1, 1, 2) sendButton = QPushButton("Send") grid.addWidget(sendButton, 5, 1) clearButton = QPushButton("Clear") grid.addWidget(clearButton, 5, 2) 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, 350) 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) b.clicked.connect(self.change_password_dialog) 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') def change_password_dialog(self): d = QDialog(self) d.setModal(1) pw = QLineEdit() pw.setEchoMode(2) new_pw = QLineEdit() new_pw.setEchoMode(2) conf_pw = QLineEdit() conf_pw.setEchoMode(2) grid = QGridLayout() grid.setSpacing(8) msg = 'Your wallet is encrypted. Use this dialog to change the password.\n To disable wallet encryption, enter an empty new password.' if self.wallet.use_encryption else 'Your wallet keys are not encrypted' grid.addWidget(QLabel(msg), 0, 0, 1, 2) if self.wallet.use_encryption: grid.addWidget(QLabel('Password'), 1, 0) grid.addWidget(pw, 1, 1) grid.addWidget(QLabel('New Password'), 2, 0) grid.addWidget(new_pw, 2, 1) grid.addWidget(QLabel('Confirm Password'), 3, 0) grid.addWidget(conf_pw, 3, 1) b = QPushButton("Cancel") grid.addWidget(b, 5, 1) b.clicked.connect(d.reject) b = QPushButton("OK") grid.addWidget(b, 5, 2) b.clicked.connect(d.accept) d.setLayout(grid) if not d.exec_(): return password = str(pw.text()) if self.wallet.use_encryption else None new_password = str(new_pw.text()) new_password2 = str(conf_pw.text()) try: seed = self.wallet.pw_decode( self.wallet.seed, password) except: QMessageBox.warning(self, 'Error', 'Incorrect Password', 'OK') return if new_password != new_password2: QMessageBox.warning(self, 'Error', 'Passwords do not match', 'OK') return self.wallet.update_password(seed, new_password) 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_()