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
21 from util import print_error
26 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
28 from PyQt4.QtGui import *
29 from PyQt4.QtCore import *
30 import PyQt4.QtCore as QtCore
31 import PyQt4.QtGui as QtGui
32 from interface import DEFAULT_SERVERS
37 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o lib/icons_rc.py'")
39 from wallet import format_satoshis
40 import bmp, mnemonic, pyqrnative, qrscanner
42 from decimal import Decimal
46 if platform.system() == 'Windows':
47 MONOSPACE_FONT = 'Lucida Console'
48 elif platform.system() == 'Darwin':
49 MONOSPACE_FONT = 'Monaco'
51 MONOSPACE_FONT = 'monospace'
53 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
55 def numbify(entry, is_int = False):
56 text = unicode(entry.text()).strip()
57 pos = entry.cursorPosition()
59 if not is_int: chars +='.'
60 s = ''.join([i for i in text if i in chars])
65 s = s[:p] + '.' + s[p:p+8]
67 amount = int( Decimal(s) * 100000000 )
76 entry.setCursorPosition(pos)
80 class Timer(QtCore.QThread):
83 self.emit(QtCore.SIGNAL('timersignal'))
86 class HelpButton(QPushButton):
87 def __init__(self, text):
88 QPushButton.__init__(self, '?')
89 self.setFocusPolicy(Qt.NoFocus)
90 self.setFixedWidth(20)
91 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
94 class EnterButton(QPushButton):
95 def __init__(self, text, func):
96 QPushButton.__init__(self, text)
98 self.clicked.connect(func)
100 def keyPressEvent(self, e):
101 if e.key() == QtCore.Qt.Key_Return:
104 class MyTreeWidget(QTreeWidget):
105 def __init__(self, parent):
106 QTreeWidget.__init__(self, parent)
109 for i in range(0,self.viewport().height()/5):
110 if self.itemAt(QPoint(0,i*5)) == item:
114 for j in range(0,30):
115 if self.itemAt(QPoint(0,i*5 + j)) != item:
117 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
119 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
124 class StatusBarButton(QPushButton):
125 def __init__(self, icon, tooltip, func):
126 QPushButton.__init__(self, icon, '')
127 self.setToolTip(tooltip)
129 self.setMaximumWidth(25)
130 self.clicked.connect(func)
133 def keyPressEvent(self, e):
134 if e.key() == QtCore.Qt.Key_Return:
138 class QRCodeWidget(QWidget):
141 QWidget.__init__(self)
142 self.setMinimumSize(210, 210)
146 def set_addr(self, addr):
147 if self.addr != addr:
151 def paintEvent(self, e):
152 if not self.addr: return
154 self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
155 self.qr.addData(self.addr)
158 qp = QtGui.QPainter()
161 size = self.qr.getModuleCount()*boxsize
162 k = self.qr.getModuleCount()
163 black = QColor(0, 0, 0, 255)
164 white = QColor(255, 255, 255, 255)
167 if self.qr.isDark(r, c):
173 qp.drawRect(c*boxsize, r*boxsize, boxsize, boxsize)
178 class QR_Window(QWidget):
181 QWidget.__init__(self)
182 self.setWindowTitle('Electrum - Invoice')
183 self.setMinimumSize(800, 250)
187 self.setFocusPolicy(QtCore.Qt.NoFocus)
189 main_box = QHBoxLayout()
191 self.qrw = QRCodeWidget()
192 main_box.addWidget(self.qrw)
195 main_box.addLayout(vbox)
197 main_box.addStretch(1)
199 self.address_label = QLabel("")
200 self.address_label.setFont(QFont(MONOSPACE_FONT))
201 vbox.addWidget(self.address_label)
203 self.label_label = QLabel("")
204 vbox.addWidget(self.label_label)
206 self.amount_label = QLabel("")
207 vbox.addWidget(self.amount_label)
211 self.setLayout(main_box)
214 self.filename = "qrcode.bmp"
215 bmp.save_qrcode(self.qrw.qr, self.filename)
217 def set_content(self, addr, label, amount):
219 address_text = "<span style='font-size: 18pt'>%s</span>" % addr if addr else ""
220 self.address_label.setText(address_text)
223 amount_text = "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % format_satoshis(amount) if amount else ""
224 self.amount_label.setText(amount_text)
227 label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
228 self.label_label.setText(label_text)
230 msg = 'bitcoin:'+self.address
231 if self.amount is not None:
232 msg += '?amount=%s'%(str( Decimal(self.amount) /100000000))
233 if self.label is not None:
234 msg += '&label=%s'%(self.label)
235 elif self.label is not None:
236 msg += '?label=%s'%(self.label)
238 self.qrw.set_addr( msg )
243 def waiting_dialog(f):
249 w.setWindowTitle('Electrum')
259 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
264 def ok_cancel_buttons(dialog):
267 b = QPushButton("OK")
269 b.clicked.connect(dialog.accept)
270 b = QPushButton("Cancel")
272 b.clicked.connect(dialog.reject)
276 class ElectrumWindow(QMainWindow):
278 def __init__(self, wallet, config):
279 QMainWindow.__init__(self)
282 self.wallet.interface.register_callback('updated', self.update_callback)
283 self.wallet.interface.register_callback('connected', self.update_callback)
284 self.wallet.interface.register_callback('disconnected', self.update_callback)
285 self.wallet.interface.register_callback('disconnecting', self.update_callback)
287 self.receive_tab_mode = config.get('qt_receive_tab_mode', 0)
289 self.qr_window = None
290 self.funds_error = False
291 self.completions = QStringListModel()
293 self.tabs = tabs = QTabWidget(self)
294 tabs.addTab(self.create_history_tab(), _('History') )
296 tabs.addTab(self.create_send_tab(), _('Send') )
297 tabs.addTab(self.create_receive_tab(), _('Receive') )
298 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
299 tabs.addTab(self.create_wall_tab(), _('Wall') )
300 tabs.setMinimumSize(600, 400)
301 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
302 self.setCentralWidget(tabs)
303 self.create_status_bar()
304 self.toggle_QR_window(self.receive_tab_mode == 2)
306 g = self.config.get("winpos-qt",[100, 100, 840, 400])
307 self.setGeometry(g[0], g[1], g[2], g[3])
308 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
309 if not self.wallet.seed: title += ' [seedless]'
310 self.setWindowTitle( title )
312 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
313 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
314 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
315 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
317 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
318 #self.connect(self, SIGNAL('editamount'), self.edit_amount)
319 self.history_list.setFocus(True)
321 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
322 if platform.system() == 'Windows':
323 n = 3 if self.wallet.seed else 2
324 tabs.setCurrentIndex (n)
325 tabs.setCurrentIndex (0)
328 QMainWindow.close(self)
330 self.qr_window.close()
331 self.qr_window = None
333 def connect_slots(self, sender):
335 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
336 self.previous_payto_e=''
338 def timer_actions(self):
340 self.qr_window.qrw.update()
342 if self.payto_e.hasFocus():
344 r = unicode( self.payto_e.text() )
345 if r != self.previous_payto_e:
346 self.previous_payto_e = r
348 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
350 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
354 s = r + ' <' + to_address + '>'
355 self.payto_e.setText(s)
358 def update_callback(self):
359 self.emit(QtCore.SIGNAL('updatesignal'))
361 def update_wallet(self):
362 if self.wallet.interface and self.wallet.interface.is_connected:
363 if not self.wallet.up_to_date:
364 text = _( "Synchronizing..." )
365 icon = QIcon(":icons/status_waiting.png")
367 c, u = self.wallet.get_balance()
368 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
369 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
370 icon = QIcon(":icons/status_connected.png")
372 text = _( "Not connected" )
373 icon = QIcon(":icons/status_disconnected.png")
376 text = _( "Not enough funds" )
378 self.statusBar().showMessage(text)
379 self.status_button.setIcon( icon )
381 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
382 self.textbox.setText( self.wallet.banner )
383 self.update_history_tab()
384 self.update_receive_tab()
385 self.update_contacts_tab()
386 self.update_completions()
389 def create_history_tab(self):
390 self.history_list = l = MyTreeWidget(self)
392 l.setColumnWidth(0, 40)
393 l.setColumnWidth(1, 140)
394 l.setColumnWidth(2, 350)
395 l.setColumnWidth(3, 140)
396 l.setColumnWidth(4, 140)
397 l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
398 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
399 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
401 l.setContextMenuPolicy(Qt.CustomContextMenu)
402 l.customContextMenuRequested.connect(self.create_history_menu)
406 def create_history_menu(self, position):
407 self.history_list.selectedIndexes()
408 item = self.history_list.currentItem()
410 tx_hash = str(item.toolTip(0))
411 if not tx_hash: return
413 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
414 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
415 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
416 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
419 def tx_details(self, tx_hash):
420 tx_details = self.wallet.get_tx_details(tx_hash)
421 QMessageBox.information(self, 'Details', tx_details, 'OK')
424 def tx_label_clicked(self, item, column):
425 if column==2 and item.isSelected():
426 tx_hash = str(item.toolTip(0))
428 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
429 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
430 self.history_list.editItem( item, column )
431 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
434 def tx_label_changed(self, item, column):
438 tx_hash = str(item.toolTip(0))
439 tx = self.wallet.transactions.get(tx_hash)
440 s = self.wallet.labels.get(tx_hash)
441 text = unicode( item.text(2) )
443 self.wallet.labels[tx_hash] = text
444 item.setForeground(2, QBrush(QColor('black')))
446 if s: self.wallet.labels.pop(tx_hash)
447 text = self.wallet.get_default_label(tx_hash)
448 item.setText(2, text)
449 item.setForeground(2, QBrush(QColor('gray')))
453 def edit_label(self, is_recv):
454 l = self.receive_list if is_recv else self.contacts_list
455 c = 2 if is_recv else 1
456 item = l.currentItem()
457 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
458 l.editItem( item, c )
459 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
461 def edit_amount(self):
462 l = self.receive_list
463 item = l.currentItem()
464 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
465 l.editItem( item, 3 )
466 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
469 def address_label_clicked(self, item, column, l, column_addr, column_label):
470 if column == column_label and item.isSelected():
471 addr = unicode( item.text(column_addr) )
472 label = unicode( item.text(column_label) )
473 if label in self.wallet.aliases.keys():
475 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
476 l.editItem( item, column )
477 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
480 def address_label_changed(self, item, column, l, column_addr, column_label):
482 if column == column_label:
483 addr = unicode( item.text(column_addr) )
484 text = unicode( item.text(column_label) )
488 if text not in self.wallet.aliases.keys():
489 old_addr = self.wallet.labels.get(text)
491 self.wallet.labels[addr] = text
494 print_error("Error: This is one of your aliases")
495 label = self.wallet.labels.get(addr,'')
496 item.setText(column_label, QString(label))
498 s = self.wallet.labels.get(addr)
500 self.wallet.labels.pop(addr)
504 self.update_history_tab()
505 self.update_completions()
507 self.recv_changed(item)
510 address = unicode( item.text(column_addr) )
511 text = unicode( item.text(3) )
513 index = self.wallet.addresses.index(address)
518 amount = int( Decimal( unicode(text)) * 100000000 )
519 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
521 amount = self.wallet.requested_amounts.get(address)
523 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
528 self.wallet.requested_amounts[address] = amount
530 label = self.wallet.labels.get(address)
532 label = 'invoice %04d'%(index+1)
534 self.update_receive_item(self.receive_list.currentItem())
536 self.qr_window.set_content( address, label, amount )
539 def recv_changed(self, a):
540 "current item changed"
541 if a is not None and self.qr_window and self.qr_window.isVisible():
542 address = str(a.text(1))
543 label = self.wallet.labels.get(address)
544 amount = self.wallet.requested_amounts.get(address)
545 self.qr_window.set_content( address, label, amount )
548 def update_history_tab(self):
550 self.history_list.clear()
551 for item in self.wallet.get_tx_history():
552 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
555 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
561 icon = QIcon(":icons/unconfirmed.png")
563 icon = QIcon(":icons/clock%d.png"%conf)
565 icon = QIcon(":icons/confirmed.png")
568 icon = QIcon(":icons/unconfirmed.png")
570 if value is not None:
571 v_str = format_satoshis(value, True, self.wallet.num_zeros)
575 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
578 label, is_default_label = self.wallet.get_label(tx_hash)
580 label = _('Pruned transaction outputs')
581 is_default_label = False
583 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
584 item.setFont(2, QFont(MONOSPACE_FONT))
585 item.setFont(3, QFont(MONOSPACE_FONT))
586 item.setFont(4, QFont(MONOSPACE_FONT))
588 item.setToolTip(0, tx_hash)
590 item.setForeground(2, QBrush(QColor('grey')))
592 item.setIcon(0, icon)
593 self.history_list.insertTopLevelItem(0,item)
596 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
599 def create_send_tab(self):
604 grid.setColumnMinimumWidth(3,300)
605 grid.setColumnStretch(5,1)
607 self.payto_e = QLineEdit()
608 grid.addWidget(QLabel(_('Pay to')), 1, 0)
609 grid.addWidget(self.payto_e, 1, 1, 1, 3)
612 qrcode = qrscanner.scan_qr()
613 if 'address' in qrcode:
614 self.payto_e.setText(qrcode['address'])
615 if 'amount' in qrcode:
616 self.amount_e.setText(str(qrcode['amount']))
617 if 'label' in qrcode:
618 self.message_e.setText(qrcode['label'])
619 if 'message' in qrcode:
620 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
623 if qrscanner.is_available():
624 b = QPushButton(_("Scan QR code"))
625 b.clicked.connect(fill_from_qr)
626 grid.addWidget(b, 1, 5)
628 grid.addWidget(HelpButton(_('Recipient of the funds.') + '\n\n' + _('You may enter a Bitcoin address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)')), 1, 4)
630 completer = QCompleter()
631 completer.setCaseSensitivity(False)
632 self.payto_e.setCompleter(completer)
633 completer.setModel(self.completions)
635 self.message_e = QLineEdit()
636 grid.addWidget(QLabel(_('Description')), 2, 0)
637 grid.addWidget(self.message_e, 2, 1, 1, 3)
638 grid.addWidget(HelpButton(_('Description of the transaction (not mandatory).') + '\n\n' + _('The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.')), 2, 4)
640 self.amount_e = QLineEdit()
641 grid.addWidget(QLabel(_('Amount')), 3, 0)
642 grid.addWidget(self.amount_e, 3, 1, 1, 2)
643 grid.addWidget(HelpButton(
644 _('Amount to be sent.') + '\n\n' \
645 + _('The amount will be displayed in red if you do not have enough funds in your wallet. Note that if you have frozen some of your addresses, the available funds will be lower than your total balance.')), 3, 3)
647 self.fee_e = QLineEdit()
648 grid.addWidget(QLabel(_('Fee')), 4, 0)
649 grid.addWidget(self.fee_e, 4, 1, 1, 2)
650 grid.addWidget(HelpButton(
651 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
652 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
653 + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 4, 3)
655 b = EnterButton(_("Send"), self.do_send)
656 grid.addWidget(b, 6, 1)
658 b = EnterButton(_("Clear"),self.do_clear)
659 grid.addWidget(b, 6, 2)
661 self.payto_sig = QLabel('')
662 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
664 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
665 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
674 def entry_changed( is_fee ):
675 self.funds_error = False
676 amount = numbify(self.amount_e)
677 fee = numbify(self.fee_e)
678 if not is_fee: fee = None
681 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
683 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
686 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
689 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
690 self.funds_error = True
691 self.amount_e.setPalette(palette)
692 self.fee_e.setPalette(palette)
694 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
695 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
700 def update_completions(self):
702 for addr,label in self.wallet.labels.items():
703 if addr in self.wallet.addressbook:
704 l.append( label + ' <' + addr + '>')
705 l = l + self.wallet.aliases.keys()
707 self.completions.setStringList(l)
713 label = unicode( self.message_e.text() )
714 r = unicode( self.payto_e.text() )
718 m1 = re.match(ALIAS_REGEXP, r)
719 # label or alias, with address in brackets
720 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
723 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
727 to_address = m2.group(2)
731 if not self.wallet.is_valid(to_address):
732 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
736 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
738 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
741 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
743 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
746 if self.wallet.use_encryption:
747 password = self.password_dialog()
754 tx = self.wallet.mktx( to_address, amount, label, password, fee)
755 except BaseException, e:
756 self.show_message(str(e))
759 h = self.wallet.send_tx(tx)
760 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
761 status, msg = self.wallet.receive_tx( h )
764 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
766 self.update_contacts_tab()
768 QMessageBox.warning(self, _('Error'), msg, _('OK'))
771 def set_url(self, url):
772 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
773 self.tabs.setCurrentIndex(1)
774 label = self.wallet.labels.get(payto)
775 m_addr = label + ' <'+ payto+'>' if label else payto
776 self.payto_e.setText(m_addr)
778 self.message_e.setText(message)
779 self.amount_e.setText(amount)
781 self.set_frozen(self.payto_e,True)
782 self.set_frozen(self.amount_e,True)
783 self.set_frozen(self.message_e,True)
784 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
786 self.payto_sig.setVisible(False)
789 self.payto_sig.setVisible(False)
790 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
792 self.set_frozen(e,False)
794 def set_frozen(self,entry,frozen):
796 entry.setReadOnly(True)
797 entry.setFrame(False)
799 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
800 entry.setPalette(palette)
802 entry.setReadOnly(False)
805 palette.setColor(entry.backgroundRole(), QColor('white'))
806 entry.setPalette(palette)
809 def toggle_freeze(self,addr):
811 if addr in self.wallet.frozen_addresses:
812 self.wallet.unfreeze(addr)
814 self.wallet.freeze(addr)
815 self.update_receive_tab()
817 def toggle_priority(self,addr):
819 if addr in self.wallet.prioritized_addresses:
820 self.wallet.unprioritize(addr)
822 self.wallet.prioritize(addr)
823 self.update_receive_tab()
826 def create_list_tab(self, headers):
827 "generic tab creation method"
828 l = MyTreeWidget(self)
829 l.setColumnCount( len(headers) )
830 l.setHeaderLabels( headers )
840 vbox.addWidget(buttons)
845 buttons.setLayout(hbox)
850 def create_receive_tab(self):
851 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
852 l.setContextMenuPolicy(Qt.CustomContextMenu)
853 l.customContextMenuRequested.connect(self.create_receive_menu)
854 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
855 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
856 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
857 self.receive_list = l
858 self.receive_buttons_hbox = hbox
859 view_combo = QComboBox()
860 view_combo.addItems(['Simple View', 'Detailed View', 'Point of Sale'])
861 view_combo.setCurrentIndex(self.receive_tab_mode)
862 hbox.addWidget(view_combo)
863 view_combo.currentIndexChanged.connect(self.receive_tab_set_mode)
870 self.qr_window.do_save()
871 self.show_message(_("QR code saved to file") + " " + self.qr_window.filename)
874 def receive_tab_set_mode(self, i):
875 self.receive_tab_mode = i
876 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
878 self.update_receive_tab()
879 self.toggle_QR_window(self.receive_tab_mode == 2)
882 def create_contacts_tab(self):
883 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
884 l.setContextMenuPolicy(Qt.CustomContextMenu)
885 l.customContextMenuRequested.connect(self.create_contact_menu)
886 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
887 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
888 self.contacts_list = l
889 self.contacts_buttons_hbox = hbox
890 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
895 def create_receive_menu(self, position):
896 # fixme: this function apparently has a side effect.
897 # if it is not called the menu pops up several times
898 #self.receive_list.selectedIndexes()
900 item = self.receive_list.itemAt(position)
902 addr = unicode(item.text(1))
904 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
905 if self.receive_tab_mode == 2:
906 menu.addAction(_("Request amount"), lambda: self.edit_amount())
907 menu.addAction(_("Print QR"), self.print_qr)
908 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
909 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
911 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
912 menu.addAction(t, lambda: self.toggle_freeze(addr))
913 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
914 menu.addAction(t, lambda: self.toggle_priority(addr))
915 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
918 def payto(self, x, is_alias):
925 label = self.wallet.labels.get(addr)
926 m_addr = label + ' <' + addr + '>' if label else addr
927 self.tabs.setCurrentIndex(1)
928 self.payto_e.setText(m_addr)
929 self.amount_e.setFocus()
931 def delete_contact(self, x, is_alias):
932 if self.question("Do you want to remove %s from your list of contacts?"%x):
933 if not is_alias and x in self.wallet.addressbook:
934 self.wallet.addressbook.remove(x)
935 if x in self.wallet.labels.keys():
936 self.wallet.labels.pop(x)
937 elif is_alias and x in self.wallet.aliases:
938 self.wallet.aliases.pop(x)
939 self.update_history_tab()
940 self.update_contacts_tab()
941 self.update_completions()
943 def create_contact_menu(self, position):
944 # fixme: this function apparently has a side effect.
945 # if it is not called the menu pops up several times
946 #self.contacts_list.selectedIndexes()
948 item = self.contacts_list.itemAt(position)
950 addr = unicode(item.text(0))
951 label = unicode(item.text(1))
952 is_alias = label in self.wallet.aliases.keys()
953 x = label if is_alias else addr
955 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
956 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
957 menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
959 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
961 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
962 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
963 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
966 def update_receive_item(self, item):
967 address = str( item.data(1,0).toString() )
969 flags = self.wallet.get_address_flags(address)
970 item.setData(0,0,flags)
972 label = self.wallet.labels.get(address,'')
973 item.setData(2,0,label)
975 amount = self.wallet.requested_amounts.get(address,None)
976 amount_str = format_satoshis( amount, False, self.wallet.num_zeros ) if amount is not None else ""
977 item.setData(3,0,amount_str)
979 c, u = self.wallet.get_addr_balance(address)
980 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
981 item.setData(4,0,balance)
983 if address in self.wallet.frozen_addresses:
984 item.setBackgroundColor(1, QColor('lightblue'))
985 elif address in self.wallet.prioritized_addresses:
986 item.setBackgroundColor(1, QColor('lightgreen'))
989 def update_receive_tab(self):
990 l = self.receive_list
993 l.setColumnHidden(0, not self.receive_tab_mode == 1)
994 l.setColumnHidden(3, not self.receive_tab_mode == 2)
995 l.setColumnHidden(4, self.receive_tab_mode == 0)
996 l.setColumnHidden(5, not self.receive_tab_mode == 1)
997 l.setColumnWidth(0, 50)
998 l.setColumnWidth(1, 310)
999 l.setColumnWidth(2, 200)
1000 l.setColumnWidth(3, 130)
1001 l.setColumnWidth(4, 130)
1002 l.setColumnWidth(5, 10)
1006 for address in self.wallet.all_addresses():
1008 if self.wallet.is_change(address) and self.receive_tab_mode != 1:
1012 h = self.wallet.history.get(address,[])
1015 for tx_hash, tx_height in h:
1016 tx = self.wallet.transactions.get(tx_hash)
1024 if address in self.wallet.addresses:
1026 if gap > self.wallet.gap_limit:
1029 if address in self.wallet.addresses:
1032 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1033 item.setFont(0, QFont(MONOSPACE_FONT))
1034 item.setFont(1, QFont(MONOSPACE_FONT))
1035 item.setFont(3, QFont(MONOSPACE_FONT))
1036 self.update_receive_item(item)
1037 if is_red and address in self.wallet.addresses:
1038 item.setBackgroundColor(1, QColor('red'))
1039 l.addTopLevelItem(item)
1041 # we use column 1 because column 0 may be hidden
1042 l.setCurrentItem(l.topLevelItem(0),1)
1044 def show_contact_details(self, m):
1045 a = self.wallet.aliases.get(m)
1047 if a[0] in self.wallet.authorities.keys():
1048 s = self.wallet.authorities.get(a[0])
1051 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1052 QMessageBox.information(self, 'Alias', msg, 'OK')
1054 def update_contacts_tab(self):
1056 l = self.contacts_list
1058 l.setColumnWidth(0, 350)
1059 l.setColumnWidth(1, 330)
1060 l.setColumnWidth(2, 100)
1063 for alias, v in self.wallet.aliases.items():
1065 alias_targets.append(target)
1066 item = QTreeWidgetItem( [ target, alias, '-'] )
1067 item.setBackgroundColor(0, QColor('lightgray'))
1068 l.addTopLevelItem(item)
1070 for address in self.wallet.addressbook:
1071 if address in alias_targets: continue
1072 label = self.wallet.labels.get(address,'')
1074 for item in self.wallet.transactions.values():
1075 if address in item['outputs'] : n=n+1
1077 item = QTreeWidgetItem( [ address, label, tx] )
1078 item.setFont(0, QFont(MONOSPACE_FONT))
1079 l.addTopLevelItem(item)
1081 l.setCurrentItem(l.topLevelItem(0))
1083 def create_wall_tab(self):
1084 self.textbox = textbox = QTextEdit(self)
1085 textbox.setFont(QFont(MONOSPACE_FONT))
1086 textbox.setReadOnly(True)
1089 def create_status_bar(self):
1091 sb.setFixedHeight(35)
1092 if self.wallet.seed:
1093 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1094 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1095 if self.wallet.seed:
1096 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1097 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
1098 sb.addPermanentWidget( self.status_button )
1099 self.setStatusBar(sb)
1101 def new_contact_dialog(self):
1102 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1103 address = unicode(text)
1105 if self.wallet.is_valid(address):
1106 self.wallet.addressbook.append(address)
1108 self.update_contacts_tab()
1109 self.update_history_tab()
1110 self.update_completions()
1112 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1115 def show_seed_dialog(wallet, parent=None):
1117 QMessageBox.information(parent, _('Message'),
1118 _('No seed'), _('OK'))
1121 if wallet.use_encryption:
1122 password = parent.password_dialog()
1129 seed = wallet.pw_decode(wallet.seed, password)
1131 QMessageBox.warning(parent, _('Error'),
1132 _('Incorrect Password'), _('OK'))
1135 dialog = QDialog(None)
1137 dialog.setWindowTitle("Electrum")
1139 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1141 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1142 + _("Please write down or memorize these 12 words (order is important).") + " " \
1143 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1144 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1146 main_text = QLabel(msg)
1147 main_text.setWordWrap(True)
1150 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1157 copy_function = lambda: app.clipboard().setText(brainwallet)
1158 copy_button = QPushButton(_("Copy to Clipboard"))
1159 copy_button.clicked.connect(copy_function)
1161 show_qr_function = lambda: ElectrumWindow.show_seed_qrcode(seed)
1162 qr_button = QPushButton(_("View as QR Code"))
1163 qr_button.clicked.connect(show_qr_function)
1165 ok_button = QPushButton(_("OK"))
1166 ok_button.setDefault(True)
1167 ok_button.clicked.connect(dialog.accept)
1169 main_layout = QGridLayout()
1170 main_layout.addWidget(logo, 0, 0)
1171 main_layout.addWidget(main_text, 0, 1, 1, -1)
1172 main_layout.addWidget(copy_button, 1, 1)
1173 main_layout.addWidget(qr_button, 1, 2)
1174 main_layout.addWidget(ok_button, 1, 3)
1175 dialog.setLayout(main_layout)
1180 def show_seed_qrcode(seed):
1184 d.setWindowTitle(_("Seed"))
1185 d.setMinimumSize(270, 300)
1186 vbox = QVBoxLayout()
1187 vbox.addWidget(QRCodeWidget(seed))
1188 hbox = QHBoxLayout()
1190 b = QPushButton(_("OK"))
1192 b.clicked.connect(d.accept)
1194 vbox.addLayout(hbox)
1198 def sign_message(self,address):
1199 if not address: return
1202 d.setWindowTitle('Sign Message')
1203 d.setMinimumSize(270, 350)
1205 tab_widget = QTabWidget()
1207 layout = QGridLayout(tab)
1209 sign_address = QLineEdit()
1210 sign_address.setText(address)
1211 layout.addWidget(QLabel(_('Address')), 1, 0)
1212 layout.addWidget(sign_address, 1, 1)
1214 sign_message = QTextEdit()
1215 layout.addWidget(QLabel(_('Message')), 2, 0)
1216 layout.addWidget(sign_message, 2, 1, 2, 1)
1218 sign_signature = QLineEdit()
1219 layout.addWidget(QLabel(_('Signature')), 3, 0)
1220 layout.addWidget(sign_signature, 3, 1)
1223 if self.wallet.use_encryption:
1224 password = self.password_dialog()
1231 signature = self.wallet.sign_message(sign_address.text(), sign_message.toPlainText(), password)
1232 sign_signature.setText(signature)
1233 except BaseException, e:
1234 self.show_message(str(e))
1237 hbox = QHBoxLayout()
1238 b = QPushButton(_("Sign"))
1240 b.clicked.connect(do_sign)
1241 b = QPushButton(_("Close"))
1242 b.clicked.connect(d.accept)
1244 layout.addLayout(hbox, 4, 1)
1245 tab_widget.addTab(tab, "Sign")
1249 layout = QGridLayout(tab)
1251 verify_address = QLineEdit()
1252 layout.addWidget(QLabel(_('Address')), 1, 0)
1253 layout.addWidget(verify_address, 1, 1)
1255 verify_message = QTextEdit()
1256 layout.addWidget(QLabel(_('Message')), 2, 0)
1257 layout.addWidget(verify_message, 2, 1, 2, 1)
1259 verify_signature = QLineEdit()
1260 layout.addWidget(QLabel(_('Signature')), 3, 0)
1261 layout.addWidget(verify_signature, 3, 1)
1265 self.wallet.verify_message(verify_address.text(), verify_signature.text(), verify_message.toPlainText())
1266 self.show_message("Signature verified")
1267 except BaseException, e:
1268 self.show_message(str(e))
1271 hbox = QHBoxLayout()
1272 b = QPushButton(_("Verify"))
1273 b.clicked.connect(do_verify)
1275 b = QPushButton(_("Close"))
1276 b.clicked.connect(d.accept)
1278 layout.addLayout(hbox, 4, 1)
1279 tab_widget.addTab(tab, "Verify")
1281 vbox = QVBoxLayout()
1282 vbox.addWidget(tab_widget)
1287 def toggle_QR_window(self, show):
1288 if show and not self.qr_window:
1289 self.qr_window = QR_Window()
1290 self.qr_window.setVisible(True)
1291 self.qr_window_geometry = self.qr_window.geometry()
1292 item = self.receive_list.currentItem()
1294 address = str(item.text(1))
1295 label = self.wallet.labels.get(address)
1296 amount = self.wallet.requested_amounts.get(address)
1297 self.qr_window.set_content( address, label, amount )
1299 elif show and self.qr_window and not self.qr_window.isVisible():
1300 self.qr_window.setVisible(True)
1301 self.qr_window.setGeometry(self.qr_window_geometry)
1303 elif not show and self.qr_window and self.qr_window.isVisible():
1304 self.qr_window_geometry = self.qr_window.geometry()
1305 self.qr_window.setVisible(False)
1307 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1308 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1309 self.receive_list.setColumnWidth(2, 200)
1312 def question(self, msg):
1313 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1315 def show_message(self, msg):
1316 QMessageBox.information(self, _('Message'), msg, _('OK'))
1318 def password_dialog(self ):
1325 vbox = QVBoxLayout()
1326 msg = _('Please enter your password')
1327 vbox.addWidget(QLabel(msg))
1329 grid = QGridLayout()
1331 grid.addWidget(QLabel(_('Password')), 1, 0)
1332 grid.addWidget(pw, 1, 1)
1333 vbox.addLayout(grid)
1335 vbox.addLayout(ok_cancel_buttons(d))
1338 if not d.exec_(): return
1339 return unicode(pw.text())
1346 def change_password_dialog( wallet, parent=None ):
1349 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1357 new_pw = QLineEdit()
1358 new_pw.setEchoMode(2)
1359 conf_pw = QLineEdit()
1360 conf_pw.setEchoMode(2)
1362 vbox = QVBoxLayout()
1364 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1365 +_('To disable wallet encryption, enter an empty new password.')) \
1366 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1368 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1369 +_("Leave these fields empty if you want to disable encryption.")
1370 vbox.addWidget(QLabel(msg))
1372 grid = QGridLayout()
1375 if wallet.use_encryption:
1376 grid.addWidget(QLabel(_('Password')), 1, 0)
1377 grid.addWidget(pw, 1, 1)
1379 grid.addWidget(QLabel(_('New Password')), 2, 0)
1380 grid.addWidget(new_pw, 2, 1)
1382 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1383 grid.addWidget(conf_pw, 3, 1)
1384 vbox.addLayout(grid)
1386 vbox.addLayout(ok_cancel_buttons(d))
1389 if not d.exec_(): return
1391 password = unicode(pw.text()) if wallet.use_encryption else None
1392 new_password = unicode(new_pw.text())
1393 new_password2 = unicode(conf_pw.text())
1396 seed = wallet.pw_decode( wallet.seed, password)
1398 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1401 if new_password != new_password2:
1402 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1405 wallet.update_password(seed, password, new_password)
1408 def seed_dialog(wallet, parent=None):
1412 vbox = QVBoxLayout()
1413 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1414 vbox.addWidget(QLabel(msg))
1416 grid = QGridLayout()
1419 seed_e = QLineEdit()
1420 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1421 grid.addWidget(seed_e, 1, 1)
1425 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1426 grid.addWidget(gap_e, 2, 1)
1427 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1428 vbox.addLayout(grid)
1430 vbox.addLayout(ok_cancel_buttons(d))
1433 if not d.exec_(): return
1436 gap = int(unicode(gap_e.text()))
1438 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1442 seed = unicode(seed_e.text())
1445 print_error("Warning: Not hex, trying decode")
1447 seed = mnemonic.mn_decode( seed.split(' ') )
1449 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1452 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1455 wallet.seed = str(seed)
1456 #print repr(wallet.seed)
1457 wallet.gap_limit = gap
1462 def settings_dialog(self):
1465 vbox = QVBoxLayout()
1466 msg = _('Here are the settings of your wallet.') + '\n'\
1467 + _('For more explanations, click on the help buttons next to each field.')
1470 label.setFixedWidth(250)
1471 label.setWordWrap(True)
1472 label.setAlignment(Qt.AlignJustify)
1473 vbox.addWidget(label)
1475 grid = QGridLayout()
1477 vbox.addLayout(grid)
1479 fee_label = QLabel(_('Transaction fee'))
1480 grid.addWidget(fee_label, 2, 0)
1482 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1483 grid.addWidget(fee_e, 2, 1)
1484 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1485 + _('Recommended value') + ': 0.001'
1486 grid.addWidget(HelpButton(msg), 2, 2)
1487 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1488 if not self.config.is_modifiable('fee'):
1489 for w in [fee_e, fee_label]: w.setEnabled(False)
1491 nz_label = QLabel(_('Display zeros'))
1492 grid.addWidget(nz_label, 3, 0)
1494 nz_e.setText("%d"% self.wallet.num_zeros)
1495 grid.addWidget(nz_e, 3, 1)
1496 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1497 grid.addWidget(HelpButton(msg), 3, 2)
1498 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1499 if not self.config.is_modifiable('num_zeros'):
1500 for w in [nz_e, nz_label]: w.setEnabled(False)
1502 usechange_cb = QCheckBox(_('Use change addresses'))
1503 grid.addWidget(usechange_cb, 5, 0)
1504 usechange_cb.setChecked(self.wallet.use_change)
1505 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1506 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1508 gap_label = QLabel(_('Gap limit'))
1509 grid.addWidget(gap_label, 6, 0)
1511 gap_e.setText("%d"% self.wallet.gap_limit)
1512 grid.addWidget(gap_e, 6, 1)
1513 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1514 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1515 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1516 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1517 + _('Warning') + ': ' \
1518 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1519 + _('Do not modify it if you do not understand what you are doing, or if you expect to recover your wallet without knowing it!') + '\n\n'
1520 grid.addWidget(HelpButton(msg), 6, 2)
1521 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1522 if not self.config.is_modifiable('gap_limit'):
1523 for w in [gap_e, gap_label]: w.setEnabled(False)
1525 gui_label=QLabel(_('Default GUI') + ':')
1526 grid.addWidget(gui_label , 7, 0)
1527 gui_combo = QComboBox()
1528 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1529 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1530 if index==-1: index = 1
1531 gui_combo.setCurrentIndex(index)
1532 grid.addWidget(gui_combo, 7, 1)
1533 grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1534 if not self.config.is_modifiable('gui'):
1535 for w in [gui_combo, gui_label]: w.setEnabled(False)
1537 vbox.addLayout(ok_cancel_buttons(d))
1541 if not d.exec_(): return
1543 fee = unicode(fee_e.text())
1545 fee = int( 100000000 * Decimal(fee) )
1547 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1550 if self.wallet.fee != fee:
1551 self.wallet.fee = fee
1554 nz = unicode(nz_e.text())
1559 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1562 if self.wallet.num_zeros != nz:
1563 self.wallet.num_zeros = nz
1564 self.config.set_key('num_zeros', nz, True)
1565 self.update_history_tab()
1566 self.update_receive_tab()
1568 if self.wallet.use_change != usechange_cb.isChecked():
1569 self.wallet.use_change = usechange_cb.isChecked()
1570 self.config.set_key('use_change', self.wallet.use_change, True)
1573 n = int(gap_e.text())
1575 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1578 if self.wallet.gap_limit != n:
1579 r = self.wallet.change_gap_limit(n)
1581 self.update_receive_tab()
1582 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1584 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1586 self.config.set_key("gui", str(gui_combo.currentText()).lower(), True)
1591 def network_dialog(wallet, parent=None):
1592 interface = wallet.interface
1594 if interface.is_connected:
1595 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1597 status = _("Not connected")
1598 server = interface.server
1601 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1602 server = interface.server
1604 plist, servers_list = interface.get_servers_list()
1608 d.setWindowTitle(_('Server'))
1609 d.setMinimumSize(375, 20)
1611 vbox = QVBoxLayout()
1614 hbox = QHBoxLayout()
1616 l.setPixmap(QPixmap(":icons/network.png"))
1619 hbox.addWidget(QLabel(status))
1621 vbox.addLayout(hbox)
1625 grid = QGridLayout()
1627 vbox.addLayout(grid)
1630 server_protocol = QComboBox()
1631 server_host = QLineEdit()
1632 server_host.setFixedWidth(200)
1633 server_port = QLineEdit()
1634 server_port.setFixedWidth(60)
1636 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1637 protocol_letters = 'thsg'
1638 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1639 server_protocol.addItems(protocol_names)
1641 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1642 grid.addWidget(server_protocol, 0, 1)
1643 grid.addWidget(server_host, 0, 2)
1644 grid.addWidget(server_port, 0, 3)
1646 def change_protocol(p):
1647 protocol = protocol_letters[p]
1648 host = unicode(server_host.text())
1649 pp = plist.get(host,DEFAULT_PORTS)
1650 if protocol not in pp.keys():
1651 protocol = pp.keys()[0]
1653 server_host.setText( host )
1654 server_port.setText( port )
1656 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1658 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1659 servers_list_widget = QTreeWidget(parent)
1660 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1661 servers_list_widget.setMaximumHeight(150)
1662 servers_list_widget.setColumnWidth(0, 240)
1663 for _host in servers_list.keys():
1664 _type = 'pruning' if servers_list[_host].get('pruning') else 'full'
1665 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1667 def change_server(host, protocol=None):
1668 pp = plist.get(host,DEFAULT_PORTS)
1670 port = pp.get(protocol)
1671 if not port: protocol = None
1674 if 't' in pp.keys():
1676 port = pp.get(protocol)
1678 protocol = pp.keys()[0]
1679 port = pp.get(protocol)
1681 server_host.setText( host )
1682 server_port.setText( port )
1683 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1685 if not plist: return
1686 for p in protocol_letters:
1687 i = protocol_letters.index(p)
1688 j = server_protocol.model().index(i,0)
1689 if p not in pp.keys():
1690 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1692 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1696 host, port, protocol = server.split(':')
1697 change_server(host,protocol)
1699 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1700 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1702 if not wallet.config.is_modifiable('server'):
1703 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1706 proxy_mode = QComboBox()
1707 proxy_host = QLineEdit()
1708 proxy_host.setFixedWidth(200)
1709 proxy_port = QLineEdit()
1710 proxy_port.setFixedWidth(60)
1711 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1713 def check_for_disable(index = False):
1714 if proxy_mode.currentText() != 'NONE':
1715 proxy_host.setEnabled(True)
1716 proxy_port.setEnabled(True)
1718 proxy_host.setEnabled(False)
1719 proxy_port.setEnabled(False)
1722 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1724 if not wallet.config.is_modifiable('proxy'):
1725 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1727 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1728 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1729 proxy_host.setText(proxy_config.get("host"))
1730 proxy_port.setText(proxy_config.get("port"))
1732 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1733 grid.addWidget(proxy_mode, 2, 1)
1734 grid.addWidget(proxy_host, 2, 2)
1735 grid.addWidget(proxy_port, 2, 3)
1738 vbox.addLayout(ok_cancel_buttons(d))
1741 if not d.exec_(): return
1743 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1744 if proxy_mode.currentText() != 'NONE':
1745 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1749 wallet.config.set_key("proxy", proxy, True)
1750 wallet.config.set_key("server", server, True)
1751 interface.set_server(server, proxy)
1755 def closeEvent(self, event):
1757 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1763 def __init__(self, wallet, config, app=None):
1764 self.wallet = wallet
1765 self.config = config
1767 self.app = QApplication(sys.argv)
1770 def restore_or_create(self):
1771 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1772 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1773 if r==2: return None
1774 return 'restore' if r==1 else 'create'
1776 def seed_dialog(self):
1777 return ElectrumWindow.seed_dialog( self.wallet )
1779 def network_dialog(self):
1780 return ElectrumWindow.network_dialog( self.wallet, parent=None )
1783 def show_seed(self):
1784 ElectrumWindow.show_seed_dialog(self.wallet)
1787 def password_dialog(self):
1788 ElectrumWindow.change_password_dialog(self.wallet)
1791 def restore_wallet(self):
1792 wallet = self.wallet
1793 # wait until we are connected, because the user might have selected another server
1794 if not wallet.interface.is_connected:
1795 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1796 waiting_dialog(waiting)
1798 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1799 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1801 wallet.set_up_to_date(False)
1802 wallet.interface.poke('synchronizer')
1803 waiting_dialog(waiting)
1804 if wallet.is_found():
1805 print_error( "Recovery successful" )
1807 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1814 w = ElectrumWindow(self.wallet, self.config)
1815 if url: w.set_url(url)