3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re
24 print "could not import PyQt4"
25 print "on Linux systems, you may try 'sudo apt-get install python-qt4'"
29 from PyQt4.QtGui import *
30 from PyQt4.QtCore import *
31 import PyQt4.QtCore as QtCore
32 import PyQt4.QtGui as QtGui
33 from interface import DEFAULT_SERVERS
38 print "Could not import icons_rp.py"
39 print "Please generate it with: 'pyrcc4 icons.qrc -o icons_rc.py'"
42 from wallet import format_satoshis
43 from decimal import Decimal
46 MONOSPACE_FONT = 'Lucida Console' if platform.system() == 'Windows' else 'monospace'
49 def numbify(entry, is_int = False):
50 text = unicode(entry.text()).strip()
52 if not is_int: chars +='.'
53 s = ''.join([i for i in text if i in chars])
58 s = s[:p] + '.' + s[p:p+8]
60 amount = int( Decimal(s) * 100000000 )
72 class Timer(QtCore.QThread):
75 self.emit(QtCore.SIGNAL('timersignal'))
78 class EnterButton(QPushButton):
79 def __init__(self, text, func):
80 QPushButton.__init__(self, text)
82 self.clicked.connect(func)
84 def keyPressEvent(self, e):
85 if e.key() == QtCore.Qt.Key_Return:
88 class StatusBarButton(QPushButton):
89 def __init__(self, icon, tooltip, func):
90 QPushButton.__init__(self, icon, '')
91 self.setToolTip(tooltip)
93 self.setMaximumWidth(25)
94 self.clicked.connect(func)
97 def keyPressEvent(self, e):
98 if e.key() == QtCore.Qt.Key_Return:
102 class QRCodeWidget(QWidget):
104 def __init__(self, addr):
105 super(QRCodeWidget, self).__init__()
106 self.setGeometry(300, 300, 350, 350)
109 def set_addr(self, addr):
110 from electrum import pyqrnative
112 self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
113 self.qr.addData(addr)
116 def paintEvent(self, e):
117 qp = QtGui.QPainter()
120 size = self.qr.getModuleCount()*boxsize
121 k = self.qr.getModuleCount()
122 black = QColor(0, 0, 0, 255)
123 white = QColor(255, 255, 255, 255)
126 if self.qr.isDark(r, c):
132 qp.drawRect(c*boxsize, r*boxsize, boxsize, boxsize)
137 def ok_cancel_buttons(dialog):
140 b = QPushButton("OK")
142 b.clicked.connect(dialog.accept)
143 b = QPushButton("Cancel")
145 b.clicked.connect(dialog.reject)
149 class ElectrumWindow(QMainWindow):
151 def __init__(self, wallet):
152 QMainWindow.__init__(self)
154 self.wallet.gui_callback = self.update_callback
156 self.funds_error = False
158 self.tabs = tabs = QTabWidget(self)
159 tabs.addTab(self.create_history_tab(), 'History')
161 tabs.addTab(self.create_send_tab(), 'Send')
162 tabs.addTab(self.create_receive_tab(), 'Receive')
163 tabs.addTab(self.create_contacts_tab(),'Contacts')
164 tabs.addTab(self.create_wall_tab(), 'Wall')
165 tabs.setMinimumSize(600, 400)
166 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
167 self.setCentralWidget(tabs)
168 self.create_status_bar()
169 self.setGeometry(100,100,840,400)
170 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.path
171 if not self.wallet.seed: title += ' [seedless]'
172 self.setWindowTitle( title )
175 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
176 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
177 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
178 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
180 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
183 def connect_slots(self, sender):
185 self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient)
186 self.previous_payto_e=''
188 def check_recipient(self):
189 if self.payto_e.hasFocus():
191 r = unicode( self.payto_e.text() )
192 if r != self.previous_payto_e:
193 self.previous_payto_e = r
195 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
197 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
201 s = r + ' <' + to_address + '>'
202 self.payto_e.setText(s)
205 def update_callback(self):
206 self.emit(QtCore.SIGNAL('updatesignal'))
208 def update_wallet(self):
209 if self.wallet.interface and self.wallet.interface.is_connected:
210 if self.wallet.blocks == -1:
211 text = "Connecting..."
212 icon = QIcon(":icons/status_disconnected.png")
213 elif self.wallet.blocks == 0:
214 text = "Server not ready"
215 icon = QIcon(":icons/status_disconnected.png")
216 elif not self.wallet.up_to_date:
217 text = "Synchronizing..."
218 icon = QIcon(":icons/status_waiting.png")
220 c, u = self.wallet.get_balance()
221 text = "Balance: %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
222 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
223 icon = QIcon(":icons/status_connected.png")
225 text = "Not connected"
226 icon = QIcon(":icons/status_disconnected.png")
229 text = "Not enough funds"
231 self.statusBar().showMessage(text)
232 self.status_button.setIcon( icon )
234 if self.wallet.up_to_date:
235 self.textbox.setText( self.wallet.banner )
236 self.update_history_tab()
237 self.update_receive_tab()
238 self.update_contacts_tab()
241 def create_history_tab(self):
242 self.history_list = w = QTreeWidget(self)
243 #print w.getContentsMargins()
245 w.setColumnWidth(0, 40)
246 w.setColumnWidth(1, 140)
247 w.setColumnWidth(2, 350)
248 w.setColumnWidth(3, 140)
249 w.setColumnWidth(4, 140)
250 w.setHeaderLabels( [ '', 'Date', 'Description', 'Amount', 'Balance'] )
251 self.connect(w, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), self.tx_details)
252 self.connect(w, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
253 self.connect(w, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
256 def tx_details(self, item, column):
257 tx_hash = str(item.toolTip(0))
258 tx = self.wallet.tx_history.get(tx_hash)
261 conf = self.wallet.blocks - tx['height'] + 1
262 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
267 tx_details = "Transaction Details:\n\n" \
268 + "Transaction ID:\n" + tx_hash + "\n\n" \
269 + "Status: %d confirmations\n\n"%conf \
270 + "Date: %s\n\n"%time_str \
271 + "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" \
272 + "Outputs:\n-"+ '\n-'.join(tx['outputs'])
274 r = self.wallet.receipts.get(tx_hash)
276 tx_details += "\n_______________________________________" \
277 + '\n\nSigned URI: ' + r[2] \
278 + "\n\nSigned by: " + r[0] \
279 + '\n\nSignature: ' + r[1]
281 QMessageBox.information(self, 'Details', tx_details, 'OK')
284 def tx_label_clicked(self, item, column):
285 if column==2 and item.isSelected():
286 tx_hash = str(item.toolTip(0))
288 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
289 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
290 self.history_list.editItem( item, column )
291 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
294 def tx_label_changed(self, item, column):
298 tx_hash = str(item.toolTip(0))
299 tx = self.wallet.tx_history.get(tx_hash)
300 s = self.wallet.labels.get(tx_hash)
301 text = unicode( item.text(2) )
303 self.wallet.labels[tx_hash] = text
304 item.setForeground(2, QBrush(QColor('black')))
306 if s: self.wallet.labels.pop(tx_hash)
307 text = tx['default_label']
308 item.setText(2, text)
309 item.setForeground(2, QBrush(QColor('gray')))
312 def address_label_clicked(self, item, column, l):
313 if column==1 and item.isSelected():
314 addr = unicode( item.text(0) )
315 if addr in map(lambda x:x[1], self.wallet.aliases.values()):
317 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
318 l.editItem( item, column )
319 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
321 def address_label_changed(self, item, column, l):
322 addr = unicode( item.text(0) )
323 text = unicode( item.text(1) )
325 self.wallet.labels[addr] = text
327 s = self.wallet.labels.get(addr)
328 if s: self.wallet.labels.pop(addr)
329 self.update_history_tab()
331 def update_history_tab(self):
332 self.history_list.clear()
334 for tx in self.wallet.get_tx_history():
335 tx_hash = tx['tx_hash']
337 conf = self.wallet.blocks - tx['height'] + 1
338 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
339 icon = QIcon(":icons/confirmed.png")
343 icon = QIcon(":icons/unconfirmed.png")
346 label = self.wallet.labels.get(tx_hash)
347 is_default_label = (label == '') or (label is None)
348 if is_default_label: label = tx['default_label']
350 item = QTreeWidgetItem( [ '', time_str, label, format_satoshis(v,True,self.wallet.num_zeros), format_satoshis(balance,False,self.wallet.num_zeros)] )
351 item.setFont(2, QFont(MONOSPACE_FONT))
352 item.setFont(3, QFont(MONOSPACE_FONT))
353 item.setFont(4, QFont(MONOSPACE_FONT))
354 item.setToolTip(0, tx_hash)
356 item.setForeground(2, QBrush(QColor('grey')))
358 item.setIcon(0, icon)
359 self.history_list.insertTopLevelItem(0,item)
362 def create_send_tab(self):
367 grid.setColumnMinimumWidth(3,300)
368 grid.setColumnStretch(4,1)
370 self.payto_e = QLineEdit()
371 grid.addWidget(QLabel('Pay to'), 1, 0)
372 grid.addWidget(self.payto_e, 1, 1, 1, 3)
374 self.message_e = QLineEdit()
375 grid.addWidget(QLabel('Description'), 2, 0)
376 grid.addWidget(self.message_e, 2, 1, 1, 3)
378 self.amount_e = QLineEdit()
379 grid.addWidget(QLabel('Amount'), 3, 0)
380 grid.addWidget(self.amount_e, 3, 1, 1, 2)
382 self.fee_e = QLineEdit()
383 grid.addWidget(QLabel('Fee'), 4, 0)
384 grid.addWidget(self.fee_e, 4, 1, 1, 2)
386 b = EnterButton("Send", self.do_send)
387 grid.addWidget(b, 5, 1)
389 b = EnterButton("Clear",self.do_clear)
390 grid.addWidget(b, 5, 2)
392 self.payto_sig = QLabel('')
393 grid.addWidget(self.payto_sig, 6, 0, 1, 4)
404 def entry_changed( is_fee ):
405 self.funds_error = False
406 amount = numbify(self.amount_e)
407 fee = numbify(self.fee_e)
408 if not is_fee: fee = None
411 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
413 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
416 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
419 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
420 self.funds_error = True
421 self.amount_e.setPalette(palette)
422 self.fee_e.setPalette(palette)
424 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
425 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
431 label = unicode( self.message_e.text() )
432 r = unicode( self.payto_e.text() )
435 m1 = re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r)
436 m2 = re.match('(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+) \<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
439 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
443 to_address = m2.group(5)
447 if not self.wallet.is_valid(to_address):
448 QMessageBox.warning(self, 'Error', 'Invalid Bitcoin Address:\n'+to_address, 'OK')
452 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
454 QMessageBox.warning(self, 'Error', 'Invalid Amount', 'OK')
457 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
459 QMessageBox.warning(self, 'Error', 'Invalid Fee', 'OK')
462 if self.wallet.use_encryption:
463 password = self.password_dialog()
470 tx = self.wallet.mktx( to_address, amount, label, password, fee )
471 except BaseException, e:
472 self.show_message(e.message)
475 status, msg = self.wallet.sendtx( tx )
477 QMessageBox.information(self, '', 'Payment sent.\n'+msg, 'OK')
479 self.update_contacts_tab()
481 QMessageBox.warning(self, 'Error', msg, 'OK')
484 def set_url(self, url):
485 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
486 self.tabs.setCurrentIndex(1)
487 self.payto_e.setText(payto)
488 self.message_e.setText(message)
489 self.amount_e.setText(amount)
491 self.set_frozen(self.payto_e,True)
492 self.set_frozen(self.amount_e,True)
493 self.set_frozen(self.message_e,True)
494 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
496 self.payto_sig.setVisible(False)
499 self.payto_sig.setVisible(False)
500 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
502 self.set_frozen(e,False)
504 def set_frozen(self,entry,frozen):
506 entry.setReadOnly(True)
507 entry.setFrame(False)
509 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
510 entry.setPalette(palette)
512 entry.setReadOnly(False)
515 palette.setColor(entry.backgroundRole(), QColor('white'))
516 entry.setPalette(palette)
519 def get_current_addr(self, is_recv):
521 l = self.receive_list
523 l = self.contacts_list
526 return unicode( i.text(0) )
531 def add_receive_buttons(self):
533 l = self.receive_list
534 hbox = self.receive_buttons_hbox
536 hbox.addWidget(EnterButton("QR",lambda: self.show_address_qrcode(self.get_current_addr(True))))
537 hbox.addWidget(EnterButton("Copy to Clipboard", lambda: self.app.clipboard().setText(self.get_current_addr(True))))
540 addr = self.get_current_addr(True)
542 if addr in self.wallet.frozen_addresses:
543 self.wallet.frozen_addresses.remove(addr)
545 self.wallet.frozen_addresses.append(addr)
547 self.update_receive_tab()
549 self.freezeButton = b = EnterButton("Freeze", toggle_freeze)
554 def add_contacts_buttons(self):
555 l = self.contacts_list
556 hbox = self.contacts_buttons_hbox
558 hbox.addWidget(EnterButton("QR",lambda: self.show_address_qrcode(self.get_current_addr(False))))
559 hbox.addWidget(EnterButton("Copy to Clipboard", lambda: self.app.clipboard().setText(self.get_current_addr(False))))
561 addr = self.get_current_addr(False)
563 self.tabs.setCurrentIndex(1)
564 self.payto_e.setText(addr)
565 self.amount_e.setFocus()
566 hbox.addWidget(EnterButton('Pay to', lambda: payto()))
567 hbox.addWidget(EnterButton("New", self.newaddress_dialog))
570 def update_receive_buttons(self):
571 addr = self.get_current_addr(True)
572 t = "Unfreeze" if addr in self.wallet.frozen_addresses else "Freeze"
573 self.freezeButton.setText(t)
576 def create_receive_tab(self):
577 l = QTreeWidget(self)
579 l.setColumnWidth(0, 350)
580 l.setColumnWidth(1, 330)
581 l.setColumnWidth(2, 100)
582 l.setColumnWidth(3, 10)
583 l.setHeaderLabels( ['Address', 'Label','Balance','Tx'])
593 vbox.addWidget(buttons)
598 buttons.setLayout(hbox)
601 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l))
602 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l))
603 l.selectionModel().currentChanged.connect(self.update_receive_buttons)
605 self.receive_list = l
606 self.receive_buttons_hbox = hbox
607 self.add_receive_buttons()
611 def create_contacts_tab(self):
612 l = QTreeWidget(self)
614 l.setColumnWidth(0, 350)
615 l.setColumnWidth(1, 330)
616 l.setColumnWidth(2, 20)
617 l.setHeaderLabels( ['Address', 'Label','Tx'])
627 vbox.addWidget(buttons)
632 buttons.setLayout(hbox)
634 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l))
635 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l))
636 self.connect(l, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), self.show_contact_details)
637 self.contacts_list = l
638 self.contacts_buttons_hbox = hbox
639 self.add_contacts_buttons()
642 def update_receive_tab(self):
643 self.receive_list.clear()
644 for address in self.wallet.all_addresses():
645 if self.wallet.is_change(address):continue
646 label = self.wallet.labels.get(address,'')
648 h = self.wallet.history.get(address,[])
650 if not item['is_input'] : n=n+1
651 tx = "None" if n==0 else "%d"%n
653 c, u = self.wallet.get_addr_balance(address)
654 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
655 if address in self.wallet.frozen_addresses:
658 item = QTreeWidgetItem( [ address, label, balance, tx] )
659 item.setFont(0, QFont(MONOSPACE_FONT))
660 self.receive_list.addTopLevelItem(item)
662 def show_contact_details(self, item, column):
663 m = unicode(item.text(0))
664 a = self.wallet.aliases.get(m)
666 if a[0] in self.wallet.authorities.keys():
667 s = self.wallet.authorities.get(a[0])
670 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
671 QMessageBox.information(self, 'Alias', msg, 'OK')
673 def update_contacts_tab(self):
674 self.contacts_list.clear()
676 for alias, v in self.wallet.aliases.items():
678 item = QTreeWidgetItem( [ target, alias, '-'] )
679 self.contacts_list.addTopLevelItem(item)
681 for address in self.wallet.addressbook:
682 label = self.wallet.labels.get(address,'')
684 for item in self.wallet.tx_history.values():
685 if address in item['outputs'] : n=n+1
686 tx = "None" if n==0 else "%d"%n
687 item = QTreeWidgetItem( [ address, label, tx] )
688 item.setFont(0, QFont(MONOSPACE_FONT))
689 self.contacts_list.addTopLevelItem(item)
692 def create_wall_tab(self):
693 self.textbox = textbox = QTextEdit(self)
694 textbox.setFont(QFont(MONOSPACE_FONT))
695 textbox.setReadOnly(True)
698 def create_status_bar(self):
700 sb.setFixedHeight(35)
702 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
703 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
705 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
706 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
707 sb.addPermanentWidget( self.status_button )
708 self.setStatusBar(sb)
710 def newaddress_dialog(self):
711 text, ok = QInputDialog.getText(self, 'New Contact', 'Address:')
712 address = unicode(text)
714 if self.wallet.is_valid(address):
715 self.wallet.addressbook.append(address)
717 self.update_contacts_tab()
719 QMessageBox.warning(self, 'Error', 'Invalid Address', 'OK')
722 def show_seed_dialog(wallet, parent=None):
723 from electrum import mnemonic
726 QMessageBox.information(parent, 'Message', 'No seed', 'OK')
729 if wallet.use_encryption:
730 password = parent.password_dialog()
731 if not password: return
736 seed = wallet.pw_decode( wallet.seed, password)
738 QMessageBox.warning(parent, 'Error', 'Invalid Password', 'OK')
741 msg = "Your wallet generation seed is:\n\n" + seed \
742 + "\n\nPlease keep it in a safe place; if you lose it,\nyou will not be able to restore your wallet.\n\n" \
743 + "Equivalently, your wallet seed can be stored and\nrecovered with the following mnemonic code:\n\n\"" \
744 + ' '.join(mnemonic.mn_encode(seed)) + "\"\n\n\n"
748 d.setWindowTitle("Seed")
749 d.setMinimumSize(400, 270)
753 vbox2 = QVBoxLayout()
755 l.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
758 hbox.addLayout(vbox2)
759 hbox.addWidget(QLabel(msg))
771 b = QPushButton("Copy to Clipboard")
772 b.clicked.connect(lambda: app.clipboard().setText(' '.join(mnemonic.mn_encode(seed))))
774 b = QPushButton("View as QR Code")
775 b.clicked.connect(lambda: ElectrumWindow.show_seed_qrcode(seed))
778 b = QPushButton("OK")
779 b.clicked.connect(d.accept)
786 def show_seed_qrcode(seed):
790 d.setWindowTitle("Seed")
791 d.setMinimumSize(270, 300)
793 vbox.addWidget(QRCodeWidget(seed))
796 b = QPushButton("OK")
798 b.clicked.connect(d.accept)
804 def show_address_qrcode(self,address):
805 if not address: return
808 d.setWindowTitle(address)
809 d.setMinimumSize(270, 350)
811 qrw = QRCodeWidget(address)
815 amount_e = QLineEdit()
816 hbox.addWidget(QLabel('Amount'))
817 hbox.addWidget(amount_e)
820 #hbox = QHBoxLayout()
821 #label_e = QLineEdit()
822 #hbox.addWidget(QLabel('Label'))
823 #hbox.addWidget(label_e)
824 #vbox.addLayout(hbox)
826 def amount_changed():
827 amount = numbify(amount_e)
828 #label = str( label_e.getText() )
829 if amount is not None:
830 qrw.set_addr('bitcoin:%s?amount=%s'%(address,str( Decimal(amount) /100000000)))
832 qrw.set_addr( address )
836 from electrum import bmp
837 bmp.save_qrcode(qrw.qr, "qrcode.bmp")
838 self.show_message("QR code saved to file 'qrcode.bmp'")
840 amount_e.textChanged.connect( amount_changed )
844 b = QPushButton("Save")
845 b.clicked.connect(do_save)
847 b = QPushButton("Close")
849 b.clicked.connect(d.accept)
855 def question(self, msg):
856 return QMessageBox.question(self, 'Message', msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
858 def show_message(self, msg):
859 QMessageBox.information(self, 'Message', msg, 'OK')
861 def password_dialog(self ):
869 msg = 'Please enter your password'
870 vbox.addWidget(QLabel(msg))
874 grid.addWidget(QLabel('Password'), 1, 0)
875 grid.addWidget(pw, 1, 1)
878 vbox.addLayout(ok_cancel_buttons(d))
881 if not d.exec_(): return
882 return unicode(pw.text())
885 def change_password_dialog( wallet, parent=None ):
888 QMessageBox.information(parent, 'Message', 'No seed', 'OK')
897 new_pw.setEchoMode(2)
898 conf_pw = QLineEdit()
899 conf_pw.setEchoMode(2)
903 msg = 'Your wallet is encrypted. Use this dialog to change your password.\nTo disable wallet encryption, enter an empty new password.' if wallet.use_encryption else 'Your wallet keys are not encrypted'
905 msg = "Please choose a password to encrypt your wallet keys.\nLeave these fields empty if you want to disable encryption."
906 vbox.addWidget(QLabel(msg))
911 if wallet.use_encryption:
912 grid.addWidget(QLabel('Password'), 1, 0)
913 grid.addWidget(pw, 1, 1)
915 grid.addWidget(QLabel('New Password'), 2, 0)
916 grid.addWidget(new_pw, 2, 1)
918 grid.addWidget(QLabel('Confirm Password'), 3, 0)
919 grid.addWidget(conf_pw, 3, 1)
922 vbox.addLayout(ok_cancel_buttons(d))
925 if not d.exec_(): return
927 password = unicode(pw.text()) if wallet.use_encryption else None
928 new_password = unicode(new_pw.text())
929 new_password2 = unicode(conf_pw.text())
932 seed = wallet.pw_decode( wallet.seed, password)
934 QMessageBox.warning(parent, 'Error', 'Incorrect Password', 'OK')
937 if new_password != new_password2:
938 QMessageBox.warning(parent, 'Error', 'Passwords do not match', 'OK')
941 wallet.update_password(seed, new_password)
944 def seed_dialog(wallet, parent=None):
949 msg = "Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet."
950 vbox.addWidget(QLabel(msg))
956 grid.addWidget(QLabel('Seed or mnemonic'), 1, 0)
957 grid.addWidget(seed_e, 1, 1)
961 grid.addWidget(QLabel('Gap limit'), 2, 0)
962 grid.addWidget(gap_e, 2, 1)
963 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
966 vbox.addLayout(ok_cancel_buttons(d))
969 if not d.exec_(): return
972 gap = int(unicode(gap_e.text()))
974 QMessageBox.warning(None, 'Error', 'error', 'OK')
978 seed = unicode(seed_e.text())
981 from electrum import mnemonic
982 print "not hex, trying decode"
984 seed = mnemonic.mn_decode( seed.split(' ') )
986 QMessageBox.warning(None, 'Error', 'I cannot decode this', 'OK')
989 QMessageBox.warning(None, 'Error', 'no seed', 'OK')
992 wallet.seed = str(seed)
993 #print repr(wallet.seed)
994 wallet.gap_limit = gap
998 def settings_dialog(self):
1002 vbox = QVBoxLayout()
1004 msg = 'Here are the settings of your wallet.'
1005 vbox.addWidget(QLabel(msg))
1007 grid = QGridLayout()
1009 vbox.addLayout(grid)
1012 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1013 grid.addWidget(QLabel('Fee per tx. input'), 2, 0)
1014 grid.addWidget(fee_e, 2, 1)
1015 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1018 nz_e.setText("%d"% self.wallet.num_zeros)
1019 grid.addWidget(QLabel('Zeros displayed after decimal point'), 3, 0)
1020 grid.addWidget(nz_e, 3, 1)
1021 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1023 vbox.addLayout(ok_cancel_buttons(d))
1026 if not d.exec_(): return
1028 fee = unicode(fee_e.text())
1030 fee = int( 100000000 * Decimal(fee) )
1032 QMessageBox.warning(self, 'Error', 'Invalid value:%s'%fee, 'OK')
1035 if self.wallet.fee != fee:
1036 self.wallet.fee = fee
1039 nz = unicode(nz_e.text())
1044 QMessageBox.warning(self, 'Error', 'Invalid value:%s'%nz, 'OK')
1047 if self.wallet.num_zeros != nz:
1048 self.wallet.num_zeros = nz
1049 self.update_history_tab()
1050 self.update_receive_tab()
1054 def network_dialog(wallet, parent=None):
1055 interface = wallet.interface
1057 if interface.is_connected:
1058 status = "Connected to %s:%d\n%d blocks"%(interface.host, interface.port, wallet.blocks)
1060 status = "Not connected"
1061 server = wallet.server
1064 status = "Please choose a server."
1065 server = random.choice( DEFAULT_SERVERS )
1067 if not wallet.interface.servers:
1069 for x in DEFAULT_SERVERS:
1070 h,port,protocol = x.split(':')
1071 servers_list.append( (h,[(protocol,port)] ) )
1073 servers_list = wallet.interface.servers
1076 for item in servers_list:
1080 protocol, port = item2
1086 d.setWindowTitle('Server')
1087 d.setMinimumSize(375, 20)
1089 vbox = QVBoxLayout()
1092 hbox = QHBoxLayout()
1094 l.setPixmap(QPixmap(":icons/network.png"))
1096 hbox.addWidget(QLabel(status))
1098 vbox.addLayout(hbox)
1100 hbox = QHBoxLayout()
1101 host_line = QLineEdit()
1102 host_line.setText(server)
1103 hbox.addWidget(QLabel('Connect to:'))
1104 hbox.addWidget(host_line)
1105 vbox.addLayout(hbox)
1107 hbox = QHBoxLayout()
1109 buttonGroup = QGroupBox("protocol")
1110 radio1 = QRadioButton("tcp", buttonGroup)
1111 radio2 = QRadioButton("http", buttonGroup)
1114 return unicode(host_line.text()).split(':')
1116 def set_button(protocol):
1118 radio1.setChecked(1)
1119 elif protocol == 'h':
1120 radio2.setChecked(1)
1122 def set_protocol(protocol):
1123 host = current_line()[0]
1125 if protocol not in pp.keys():
1126 protocol = pp.keys()[0]
1127 set_button(protocol)
1129 host_line.setText( host + ':' + port + ':' + protocol)
1131 radio1.clicked.connect(lambda x: set_protocol('t') )
1132 radio2.clicked.connect(lambda x: set_protocol('h') )
1134 set_button(current_line()[2])
1136 hbox.addWidget(QLabel('Protocol:'))
1137 hbox.addWidget(radio1)
1138 hbox.addWidget(radio2)
1140 vbox.addLayout(hbox)
1142 if wallet.interface.servers:
1143 label = 'Active Servers'
1145 label = 'Default Servers'
1147 servers_list_widget = QTreeWidget(parent)
1148 servers_list_widget.setHeaderLabels( [ label ] )
1149 servers_list_widget.setMaximumHeight(150)
1150 for host in plist.keys():
1151 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ host ] ))
1154 host = unicode(x.text(0))
1156 if 't' in pp.keys():
1159 protocol = pp.keys()[0]
1161 host_line.setText( host + ':' + port + ':' + protocol)
1162 set_button(protocol)
1164 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), do_set_line)
1165 vbox.addWidget(servers_list_widget)
1167 vbox.addLayout(ok_cancel_buttons(d))
1170 if not d.exec_(): return
1171 server = unicode( host_line.text() )
1174 wallet.set_server(server)
1176 QMessageBox.information(None, 'Error', 'error', 'OK')
1186 class ElectrumGui():
1188 def __init__(self, wallet):
1189 self.wallet = wallet
1190 self.app = QApplication(sys.argv)
1192 def waiting_dialog(self):
1198 w.setWindowTitle('Electrum')
1200 vbox = QVBoxLayout()
1205 if self.wallet.up_to_date: w.close()
1207 l.setText("Please wait...\nGenerating addresses: %d"%len(self.wallet.all_addresses()))
1209 w.connect(s, QtCore.SIGNAL('timersignal'), f)
1210 self.wallet.interface.poke()
1215 def restore_or_create(self):
1217 msg = "Wallet file not found.\nDo you want to create a new wallet,\n or to restore an existing one?"
1218 r = QMessageBox.question(None, 'Message', msg, 'create', 'restore', 'cancel', 0, 2)
1219 if r==2: return False
1221 is_recovery = (r==1)
1222 wallet = self.wallet
1223 # ask for the server.
1224 if not ElectrumWindow.network_dialog( wallet, parent=None ): return False
1227 wallet.new_seed(None)
1228 wallet.init_mpk( wallet.seed )
1229 wallet.up_to_date_event.clear()
1230 wallet.up_to_date = False
1231 self.waiting_dialog()
1232 # run a dialog indicating the seed, ask the user to remember it
1233 ElectrumWindow.show_seed_dialog(wallet)
1235 ElectrumWindow.change_password_dialog(wallet)
1237 # ask for seed and gap.
1238 if not ElectrumWindow.seed_dialog( wallet ): return False
1239 wallet.init_mpk( wallet.seed )
1240 wallet.up_to_date_event.clear()
1241 wallet.up_to_date = False
1242 self.waiting_dialog()
1243 if wallet.is_found():
1244 # history and addressbook
1245 wallet.update_tx_history()
1246 wallet.fill_addressbook()
1247 print "recovery successful"
1250 QMessageBox.information(None, 'Message', "No transactions found for this seed", 'OK')
1258 w = ElectrumWindow(self.wallet)
1259 if url: w.set_url(url)