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:
154 self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
155 self.qr.addData(self.addr)
159 def paintEvent(self, e):
164 black = QColor(0, 0, 0, 255)
165 white = QColor(255, 255, 255, 255)
169 qp = QtGui.QPainter()
173 qp.drawRect(0, 0, 198, 198)
177 size = self.qr.getModuleCount()*boxsize
178 k = self.qr.getModuleCount()
179 qp = QtGui.QPainter()
183 if self.qr.isDark(r, c):
189 qp.drawRect(c*boxsize, r*boxsize, boxsize, boxsize)
194 class QR_Window(QWidget):
197 QWidget.__init__(self)
198 self.setWindowTitle('Electrum - Invoice')
199 self.setMinimumSize(800, 250)
203 self.setFocusPolicy(QtCore.Qt.NoFocus)
205 main_box = QHBoxLayout()
207 self.qrw = QRCodeWidget()
208 main_box.addWidget(self.qrw)
211 main_box.addLayout(vbox)
213 main_box.addStretch(1)
215 self.address_label = QLabel("")
216 self.address_label.setFont(QFont(MONOSPACE_FONT))
217 vbox.addWidget(self.address_label)
219 self.label_label = QLabel("")
220 vbox.addWidget(self.label_label)
222 self.amount_label = QLabel("")
223 vbox.addWidget(self.amount_label)
227 self.setLayout(main_box)
230 self.filename = "qrcode.bmp"
231 bmp.save_qrcode(self.qrw.qr, self.filename)
233 def set_content(self, addr, label, amount):
235 address_text = "<span style='font-size: 18pt'>%s</span>" % addr if addr else ""
236 self.address_label.setText(address_text)
239 amount_text = "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % format_satoshis(amount) if amount else ""
240 self.amount_label.setText(amount_text)
243 label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
244 self.label_label.setText(label_text)
246 msg = 'bitcoin:'+self.address
247 if self.amount is not None:
248 msg += '?amount=%s'%(str( Decimal(self.amount) /100000000))
249 if self.label is not None:
250 msg += '&label=%s'%(self.label)
251 elif self.label is not None:
252 msg += '?label=%s'%(self.label)
254 self.qrw.set_addr( msg )
259 def waiting_dialog(f):
265 w.setWindowTitle('Electrum')
275 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
280 def ok_cancel_buttons(dialog):
283 b = QPushButton("OK")
285 b.clicked.connect(dialog.accept)
286 b = QPushButton("Cancel")
288 b.clicked.connect(dialog.reject)
292 class ElectrumWindow(QMainWindow):
294 def __init__(self, wallet, config):
295 QMainWindow.__init__(self)
298 self.wallet.interface.register_callback('updated', self.update_callback)
299 self.wallet.interface.register_callback('connected', self.update_callback)
300 self.wallet.interface.register_callback('disconnected', self.update_callback)
301 self.wallet.interface.register_callback('disconnecting', self.update_callback)
303 self.receive_tab_mode = config.get('qt_receive_tab_mode', 0)
305 self.qr_window = None
306 self.funds_error = False
307 self.completions = QStringListModel()
309 self.tabs = tabs = QTabWidget(self)
310 tabs.addTab(self.create_history_tab(), _('History') )
312 tabs.addTab(self.create_send_tab(), _('Send') )
313 tabs.addTab(self.create_receive_tab(), _('Receive') )
314 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
315 tabs.addTab(self.create_wall_tab(), _('Wall') )
316 tabs.setMinimumSize(600, 400)
317 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
318 self.setCentralWidget(tabs)
319 self.create_status_bar()
320 self.toggle_QR_window(self.receive_tab_mode == 2)
322 g = self.config.get("winpos-qt",[100, 100, 840, 400])
323 self.setGeometry(g[0], g[1], g[2], g[3])
324 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
325 if not self.wallet.seed: title += ' [seedless]'
326 self.setWindowTitle( title )
328 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
329 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
330 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
331 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
333 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
334 #self.connect(self, SIGNAL('editamount'), self.edit_amount)
335 self.history_list.setFocus(True)
337 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
338 if platform.system() == 'Windows':
339 n = 3 if self.wallet.seed else 2
340 tabs.setCurrentIndex (n)
341 tabs.setCurrentIndex (0)
344 QMainWindow.close(self)
346 self.qr_window.close()
347 self.qr_window = None
349 def connect_slots(self, sender):
351 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
352 self.previous_payto_e=''
354 def timer_actions(self):
356 self.qr_window.qrw.update_qr()
358 if self.payto_e.hasFocus():
360 r = unicode( self.payto_e.text() )
361 if r != self.previous_payto_e:
362 self.previous_payto_e = r
364 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
366 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
370 s = r + ' <' + to_address + '>'
371 self.payto_e.setText(s)
374 def update_callback(self):
375 self.emit(QtCore.SIGNAL('updatesignal'))
377 def update_wallet(self):
378 if self.wallet.interface and self.wallet.interface.is_connected:
379 if not self.wallet.up_to_date:
380 text = _( "Synchronizing..." )
381 icon = QIcon(":icons/status_waiting.png")
383 c, u = self.wallet.get_balance()
384 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
385 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
386 icon = QIcon(":icons/status_connected.png")
388 text = _( "Not connected" )
389 icon = QIcon(":icons/status_disconnected.png")
392 text = _( "Not enough funds" )
394 self.statusBar().showMessage(text)
395 self.status_button.setIcon( icon )
397 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
398 self.textbox.setText( self.wallet.banner )
399 self.update_history_tab()
400 self.update_receive_tab()
401 self.update_contacts_tab()
402 self.update_completions()
405 def create_history_tab(self):
406 self.history_list = l = MyTreeWidget(self)
408 l.setColumnWidth(0, 40)
409 l.setColumnWidth(1, 140)
410 l.setColumnWidth(2, 350)
411 l.setColumnWidth(3, 140)
412 l.setColumnWidth(4, 140)
413 l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
414 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
415 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
417 l.setContextMenuPolicy(Qt.CustomContextMenu)
418 l.customContextMenuRequested.connect(self.create_history_menu)
422 def create_history_menu(self, position):
423 self.history_list.selectedIndexes()
424 item = self.history_list.currentItem()
426 tx_hash = str(item.toolTip(0))
427 if not tx_hash: return
429 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
430 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
431 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
432 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
435 def tx_details(self, tx_hash):
436 tx_details = self.wallet.get_tx_details(tx_hash)
437 QMessageBox.information(self, 'Details', tx_details, 'OK')
440 def tx_label_clicked(self, item, column):
441 if column==2 and item.isSelected():
442 tx_hash = str(item.toolTip(0))
444 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
445 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
446 self.history_list.editItem( item, column )
447 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
450 def tx_label_changed(self, item, column):
454 tx_hash = str(item.toolTip(0))
455 tx = self.wallet.transactions.get(tx_hash)
456 s = self.wallet.labels.get(tx_hash)
457 text = unicode( item.text(2) )
459 self.wallet.labels[tx_hash] = text
460 item.setForeground(2, QBrush(QColor('black')))
462 if s: self.wallet.labels.pop(tx_hash)
463 text = self.wallet.get_default_label(tx_hash)
464 item.setText(2, text)
465 item.setForeground(2, QBrush(QColor('gray')))
469 def edit_label(self, is_recv):
470 l = self.receive_list if is_recv else self.contacts_list
471 c = 2 if is_recv else 1
472 item = l.currentItem()
473 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
474 l.editItem( item, c )
475 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
477 def edit_amount(self):
478 l = self.receive_list
479 item = l.currentItem()
480 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
481 l.editItem( item, 3 )
482 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
485 def address_label_clicked(self, item, column, l, column_addr, column_label):
486 if column == column_label and item.isSelected():
487 addr = unicode( item.text(column_addr) )
488 label = unicode( item.text(column_label) )
489 if label in self.wallet.aliases.keys():
491 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
492 l.editItem( item, column )
493 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
496 def address_label_changed(self, item, column, l, column_addr, column_label):
498 if column == column_label:
499 addr = unicode( item.text(column_addr) )
500 text = unicode( item.text(column_label) )
504 if text not in self.wallet.aliases.keys():
505 old_addr = self.wallet.labels.get(text)
507 self.wallet.labels[addr] = text
510 print_error("Error: This is one of your aliases")
511 label = self.wallet.labels.get(addr,'')
512 item.setText(column_label, QString(label))
514 s = self.wallet.labels.get(addr)
516 self.wallet.labels.pop(addr)
520 self.update_history_tab()
521 self.update_completions()
523 self.recv_changed(item)
526 address = unicode( item.text(column_addr) )
527 text = unicode( item.text(3) )
529 index = self.wallet.addresses.index(address)
534 amount = int( Decimal(text) * 100000000 )
535 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
537 amount = self.wallet.requested_amounts.get(address)
539 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
544 self.wallet.requested_amounts[address] = amount
546 label = self.wallet.labels.get(address)
548 label = 'invoice %04d'%(index+1)
549 self.wallet.labels[address] = label
551 self.update_receive_item(self.receive_list.currentItem())
553 self.qr_window.set_content( address, label, amount )
556 def recv_changed(self, a):
557 "current item changed"
558 if a is not None and self.qr_window and self.qr_window.isVisible():
559 address = str(a.text(1))
560 label = self.wallet.labels.get(address)
561 amount = self.wallet.requested_amounts.get(address)
562 self.qr_window.set_content( address, label, amount )
565 def update_history_tab(self):
567 self.history_list.clear()
568 for item in self.wallet.get_tx_history():
569 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
572 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
578 icon = QIcon(":icons/unconfirmed.png")
580 icon = QIcon(":icons/clock%d.png"%conf)
582 icon = QIcon(":icons/confirmed.png")
585 icon = QIcon(":icons/unconfirmed.png")
587 if value is not None:
588 v_str = format_satoshis(value, True, self.wallet.num_zeros)
592 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
595 label, is_default_label = self.wallet.get_label(tx_hash)
597 label = _('Pruned transaction outputs')
598 is_default_label = False
600 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
601 item.setFont(2, QFont(MONOSPACE_FONT))
602 item.setFont(3, QFont(MONOSPACE_FONT))
603 item.setFont(4, QFont(MONOSPACE_FONT))
605 item.setToolTip(0, tx_hash)
607 item.setForeground(2, QBrush(QColor('grey')))
609 item.setIcon(0, icon)
610 self.history_list.insertTopLevelItem(0,item)
613 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
616 def create_send_tab(self):
621 grid.setColumnMinimumWidth(3,300)
622 grid.setColumnStretch(5,1)
624 self.payto_e = QLineEdit()
625 grid.addWidget(QLabel(_('Pay to')), 1, 0)
626 grid.addWidget(self.payto_e, 1, 1, 1, 3)
629 qrcode = qrscanner.scan_qr()
630 if 'address' in qrcode:
631 self.payto_e.setText(qrcode['address'])
632 if 'amount' in qrcode:
633 self.amount_e.setText(str(qrcode['amount']))
634 if 'label' in qrcode:
635 self.message_e.setText(qrcode['label'])
636 if 'message' in qrcode:
637 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
640 if qrscanner.is_available():
641 b = QPushButton(_("Scan QR code"))
642 b.clicked.connect(fill_from_qr)
643 grid.addWidget(b, 1, 5)
645 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)
647 completer = QCompleter()
648 completer.setCaseSensitivity(False)
649 self.payto_e.setCompleter(completer)
650 completer.setModel(self.completions)
652 self.message_e = QLineEdit()
653 grid.addWidget(QLabel(_('Description')), 2, 0)
654 grid.addWidget(self.message_e, 2, 1, 1, 3)
655 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)
657 self.amount_e = QLineEdit()
658 grid.addWidget(QLabel(_('Amount')), 3, 0)
659 grid.addWidget(self.amount_e, 3, 1, 1, 2)
660 grid.addWidget(HelpButton(
661 _('Amount to be sent.') + '\n\n' \
662 + _('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)
664 self.fee_e = QLineEdit()
665 grid.addWidget(QLabel(_('Fee')), 4, 0)
666 grid.addWidget(self.fee_e, 4, 1, 1, 2)
667 grid.addWidget(HelpButton(
668 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
669 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
670 + _('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)
672 b = EnterButton(_("Send"), self.do_send)
673 grid.addWidget(b, 6, 1)
675 b = EnterButton(_("Clear"),self.do_clear)
676 grid.addWidget(b, 6, 2)
678 self.payto_sig = QLabel('')
679 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
681 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
682 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
691 def entry_changed( is_fee ):
692 self.funds_error = False
693 amount = numbify(self.amount_e)
694 fee = numbify(self.fee_e)
695 if not is_fee: fee = None
698 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
700 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
703 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
706 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
707 self.funds_error = True
708 self.amount_e.setPalette(palette)
709 self.fee_e.setPalette(palette)
711 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
712 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
717 def update_completions(self):
719 for addr,label in self.wallet.labels.items():
720 if addr in self.wallet.addressbook:
721 l.append( label + ' <' + addr + '>')
722 l = l + self.wallet.aliases.keys()
724 self.completions.setStringList(l)
730 label = unicode( self.message_e.text() )
731 r = unicode( self.payto_e.text() )
735 m1 = re.match(ALIAS_REGEXP, r)
736 # label or alias, with address in brackets
737 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
740 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
744 to_address = m2.group(2)
748 if not self.wallet.is_valid(to_address):
749 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
753 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
755 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
758 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
760 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
763 if self.wallet.use_encryption:
764 password = self.password_dialog()
771 tx = self.wallet.mktx( to_address, amount, label, password, fee)
772 except BaseException, e:
773 self.show_message(str(e))
776 h = self.wallet.send_tx(tx)
777 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
778 status, msg = self.wallet.receive_tx( h )
781 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
783 self.update_contacts_tab()
785 QMessageBox.warning(self, _('Error'), msg, _('OK'))
788 def set_url(self, url):
789 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
790 self.tabs.setCurrentIndex(1)
791 label = self.wallet.labels.get(payto)
792 m_addr = label + ' <'+ payto+'>' if label else payto
793 self.payto_e.setText(m_addr)
795 self.message_e.setText(message)
796 self.amount_e.setText(amount)
798 self.set_frozen(self.payto_e,True)
799 self.set_frozen(self.amount_e,True)
800 self.set_frozen(self.message_e,True)
801 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
803 self.payto_sig.setVisible(False)
806 self.payto_sig.setVisible(False)
807 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
809 self.set_frozen(e,False)
811 def set_frozen(self,entry,frozen):
813 entry.setReadOnly(True)
814 entry.setFrame(False)
816 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
817 entry.setPalette(palette)
819 entry.setReadOnly(False)
822 palette.setColor(entry.backgroundRole(), QColor('white'))
823 entry.setPalette(palette)
826 def toggle_freeze(self,addr):
828 if addr in self.wallet.frozen_addresses:
829 self.wallet.unfreeze(addr)
831 self.wallet.freeze(addr)
832 self.update_receive_tab()
834 def toggle_priority(self,addr):
836 if addr in self.wallet.prioritized_addresses:
837 self.wallet.unprioritize(addr)
839 self.wallet.prioritize(addr)
840 self.update_receive_tab()
843 def create_list_tab(self, headers):
844 "generic tab creation method"
845 l = MyTreeWidget(self)
846 l.setColumnCount( len(headers) )
847 l.setHeaderLabels( headers )
857 vbox.addWidget(buttons)
862 buttons.setLayout(hbox)
867 def create_receive_tab(self):
868 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
869 l.setContextMenuPolicy(Qt.CustomContextMenu)
870 l.customContextMenuRequested.connect(self.create_receive_menu)
871 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
872 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
873 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
874 self.receive_list = l
875 self.receive_buttons_hbox = hbox
876 view_combo = QComboBox()
877 view_combo.addItems(['Simple View', 'Detailed View', 'Point of Sale'])
878 view_combo.setCurrentIndex(self.receive_tab_mode)
879 hbox.addWidget(view_combo)
880 view_combo.currentIndexChanged.connect(self.receive_tab_set_mode)
887 self.qr_window.do_save()
888 self.show_message(_("QR code saved to file") + " " + self.qr_window.filename)
891 def receive_tab_set_mode(self, i):
892 self.receive_tab_mode = i
893 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
895 self.update_receive_tab()
896 self.toggle_QR_window(self.receive_tab_mode == 2)
899 def create_contacts_tab(self):
900 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
901 l.setContextMenuPolicy(Qt.CustomContextMenu)
902 l.customContextMenuRequested.connect(self.create_contact_menu)
903 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
904 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
905 self.contacts_list = l
906 self.contacts_buttons_hbox = hbox
907 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
912 def create_receive_menu(self, position):
913 # fixme: this function apparently has a side effect.
914 # if it is not called the menu pops up several times
915 #self.receive_list.selectedIndexes()
917 item = self.receive_list.itemAt(position)
919 addr = unicode(item.text(1))
921 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
922 if self.receive_tab_mode == 2:
923 menu.addAction(_("Request amount"), lambda: self.edit_amount())
924 menu.addAction(_("Print QR"), self.print_qr)
925 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
926 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
928 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
929 menu.addAction(t, lambda: self.toggle_freeze(addr))
930 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
931 menu.addAction(t, lambda: self.toggle_priority(addr))
932 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
935 def payto(self, x, is_alias):
942 label = self.wallet.labels.get(addr)
943 m_addr = label + ' <' + addr + '>' if label else addr
944 self.tabs.setCurrentIndex(1)
945 self.payto_e.setText(m_addr)
946 self.amount_e.setFocus()
948 def delete_contact(self, x, is_alias):
949 if self.question("Do you want to remove %s from your list of contacts?"%x):
950 if not is_alias and x in self.wallet.addressbook:
951 self.wallet.addressbook.remove(x)
952 if x in self.wallet.labels.keys():
953 self.wallet.labels.pop(x)
954 elif is_alias and x in self.wallet.aliases:
955 self.wallet.aliases.pop(x)
956 self.update_history_tab()
957 self.update_contacts_tab()
958 self.update_completions()
960 def create_contact_menu(self, position):
961 # fixme: this function apparently has a side effect.
962 # if it is not called the menu pops up several times
963 #self.contacts_list.selectedIndexes()
965 item = self.contacts_list.itemAt(position)
967 addr = unicode(item.text(0))
968 label = unicode(item.text(1))
969 is_alias = label in self.wallet.aliases.keys()
970 x = label if is_alias else addr
972 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
973 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
974 menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
976 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
978 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
979 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
980 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
983 def update_receive_item(self, item):
984 address = str( item.data(1,0).toString() )
986 flags = self.wallet.get_address_flags(address)
987 item.setData(0,0,flags)
989 label = self.wallet.labels.get(address,'')
990 item.setData(2,0,label)
992 amount = self.wallet.requested_amounts.get(address,None)
993 amount_str = format_satoshis( amount, False, self.wallet.num_zeros ) if amount is not None else ""
994 item.setData(3,0,amount_str)
996 c, u = self.wallet.get_addr_balance(address)
997 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
998 item.setData(4,0,balance)
1000 if address in self.wallet.frozen_addresses:
1001 item.setBackgroundColor(1, QColor('lightblue'))
1002 elif address in self.wallet.prioritized_addresses:
1003 item.setBackgroundColor(1, QColor('lightgreen'))
1006 def update_receive_tab(self):
1007 l = self.receive_list
1010 l.setColumnHidden(0, not self.receive_tab_mode == 1)
1011 l.setColumnHidden(3, not self.receive_tab_mode == 2)
1012 l.setColumnHidden(4, self.receive_tab_mode == 0)
1013 l.setColumnHidden(5, not self.receive_tab_mode == 1)
1014 l.setColumnWidth(0, 50)
1015 l.setColumnWidth(1, 310)
1016 l.setColumnWidth(2, 200)
1017 l.setColumnWidth(3, 130)
1018 l.setColumnWidth(4, 130)
1019 l.setColumnWidth(5, 10)
1023 for address in self.wallet.all_addresses():
1025 if self.wallet.is_change(address) and self.receive_tab_mode != 1:
1029 h = self.wallet.history.get(address,[])
1032 for tx_hash, tx_height in h:
1033 tx = self.wallet.transactions.get(tx_hash)
1041 if address in self.wallet.addresses:
1043 if gap > self.wallet.gap_limit:
1046 if address in self.wallet.addresses:
1049 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1050 item.setFont(0, QFont(MONOSPACE_FONT))
1051 item.setFont(1, QFont(MONOSPACE_FONT))
1052 item.setFont(3, QFont(MONOSPACE_FONT))
1053 self.update_receive_item(item)
1054 if is_red and address in self.wallet.addresses:
1055 item.setBackgroundColor(1, QColor('red'))
1056 l.addTopLevelItem(item)
1058 # we use column 1 because column 0 may be hidden
1059 l.setCurrentItem(l.topLevelItem(0),1)
1061 def show_contact_details(self, m):
1062 a = self.wallet.aliases.get(m)
1064 if a[0] in self.wallet.authorities.keys():
1065 s = self.wallet.authorities.get(a[0])
1068 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1069 QMessageBox.information(self, 'Alias', msg, 'OK')
1071 def update_contacts_tab(self):
1073 l = self.contacts_list
1075 l.setColumnWidth(0, 350)
1076 l.setColumnWidth(1, 330)
1077 l.setColumnWidth(2, 100)
1080 for alias, v in self.wallet.aliases.items():
1082 alias_targets.append(target)
1083 item = QTreeWidgetItem( [ target, alias, '-'] )
1084 item.setBackgroundColor(0, QColor('lightgray'))
1085 l.addTopLevelItem(item)
1087 for address in self.wallet.addressbook:
1088 if address in alias_targets: continue
1089 label = self.wallet.labels.get(address,'')
1091 for item in self.wallet.transactions.values():
1092 if address in item['outputs'] : n=n+1
1094 item = QTreeWidgetItem( [ address, label, tx] )
1095 item.setFont(0, QFont(MONOSPACE_FONT))
1096 l.addTopLevelItem(item)
1098 l.setCurrentItem(l.topLevelItem(0))
1100 def create_wall_tab(self):
1101 self.textbox = textbox = QTextEdit(self)
1102 textbox.setFont(QFont(MONOSPACE_FONT))
1103 textbox.setReadOnly(True)
1106 def create_status_bar(self):
1108 sb.setFixedHeight(35)
1109 if self.wallet.seed:
1110 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1111 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1112 if self.wallet.seed:
1113 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1114 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
1115 sb.addPermanentWidget( self.status_button )
1116 self.setStatusBar(sb)
1118 def new_contact_dialog(self):
1119 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1120 address = unicode(text)
1122 if self.wallet.is_valid(address):
1123 self.wallet.addressbook.append(address)
1125 self.update_contacts_tab()
1126 self.update_history_tab()
1127 self.update_completions()
1129 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1132 def show_seed_dialog(wallet, parent=None):
1134 QMessageBox.information(parent, _('Message'),
1135 _('No seed'), _('OK'))
1138 if wallet.use_encryption:
1139 password = parent.password_dialog()
1146 seed = wallet.pw_decode(wallet.seed, password)
1148 QMessageBox.warning(parent, _('Error'),
1149 _('Incorrect Password'), _('OK'))
1152 dialog = QDialog(None)
1154 dialog.setWindowTitle("Electrum")
1156 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1158 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1159 + _("Please write down or memorize these 12 words (order is important).") + " " \
1160 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1161 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1163 main_text = QLabel(msg)
1164 main_text.setWordWrap(True)
1167 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1174 copy_function = lambda: app.clipboard().setText(brainwallet)
1175 copy_button = QPushButton(_("Copy to Clipboard"))
1176 copy_button.clicked.connect(copy_function)
1178 show_qr_function = lambda: ElectrumWindow.show_seed_qrcode(seed)
1179 qr_button = QPushButton(_("View as QR Code"))
1180 qr_button.clicked.connect(show_qr_function)
1182 ok_button = QPushButton(_("OK"))
1183 ok_button.setDefault(True)
1184 ok_button.clicked.connect(dialog.accept)
1186 main_layout = QGridLayout()
1187 main_layout.addWidget(logo, 0, 0)
1188 main_layout.addWidget(main_text, 0, 1, 1, -1)
1189 main_layout.addWidget(copy_button, 1, 1)
1190 main_layout.addWidget(qr_button, 1, 2)
1191 main_layout.addWidget(ok_button, 1, 3)
1192 dialog.setLayout(main_layout)
1197 def show_seed_qrcode(seed):
1201 d.setWindowTitle(_("Seed"))
1202 d.setMinimumSize(270, 300)
1203 vbox = QVBoxLayout()
1204 vbox.addWidget(QRCodeWidget(seed))
1205 hbox = QHBoxLayout()
1207 b = QPushButton(_("OK"))
1209 b.clicked.connect(d.accept)
1211 vbox.addLayout(hbox)
1215 def sign_message(self,address):
1216 if not address: return
1219 d.setWindowTitle('Sign Message')
1220 d.setMinimumSize(270, 350)
1222 tab_widget = QTabWidget()
1224 layout = QGridLayout(tab)
1226 sign_address = QLineEdit()
1227 sign_address.setText(address)
1228 layout.addWidget(QLabel(_('Address')), 1, 0)
1229 layout.addWidget(sign_address, 1, 1)
1231 sign_message = QTextEdit()
1232 layout.addWidget(QLabel(_('Message')), 2, 0)
1233 layout.addWidget(sign_message, 2, 1, 2, 1)
1235 sign_signature = QLineEdit()
1236 layout.addWidget(QLabel(_('Signature')), 3, 0)
1237 layout.addWidget(sign_signature, 3, 1)
1240 if self.wallet.use_encryption:
1241 password = self.password_dialog()
1248 signature = self.wallet.sign_message(sign_address.text(), sign_message.toPlainText(), password)
1249 sign_signature.setText(signature)
1250 except BaseException, e:
1251 self.show_message(str(e))
1254 hbox = QHBoxLayout()
1255 b = QPushButton(_("Sign"))
1257 b.clicked.connect(do_sign)
1258 b = QPushButton(_("Close"))
1259 b.clicked.connect(d.accept)
1261 layout.addLayout(hbox, 4, 1)
1262 tab_widget.addTab(tab, "Sign")
1266 layout = QGridLayout(tab)
1268 verify_address = QLineEdit()
1269 layout.addWidget(QLabel(_('Address')), 1, 0)
1270 layout.addWidget(verify_address, 1, 1)
1272 verify_message = QTextEdit()
1273 layout.addWidget(QLabel(_('Message')), 2, 0)
1274 layout.addWidget(verify_message, 2, 1, 2, 1)
1276 verify_signature = QLineEdit()
1277 layout.addWidget(QLabel(_('Signature')), 3, 0)
1278 layout.addWidget(verify_signature, 3, 1)
1282 self.wallet.verify_message(verify_address.text(), verify_signature.text(), verify_message.toPlainText())
1283 self.show_message("Signature verified")
1284 except BaseException, e:
1285 self.show_message(str(e))
1288 hbox = QHBoxLayout()
1289 b = QPushButton(_("Verify"))
1290 b.clicked.connect(do_verify)
1292 b = QPushButton(_("Close"))
1293 b.clicked.connect(d.accept)
1295 layout.addLayout(hbox, 4, 1)
1296 tab_widget.addTab(tab, "Verify")
1298 vbox = QVBoxLayout()
1299 vbox.addWidget(tab_widget)
1304 def toggle_QR_window(self, show):
1305 if show and not self.qr_window:
1306 self.qr_window = QR_Window()
1307 self.qr_window.setVisible(True)
1308 self.qr_window_geometry = self.qr_window.geometry()
1309 item = self.receive_list.currentItem()
1311 address = str(item.text(1))
1312 label = self.wallet.labels.get(address)
1313 amount = self.wallet.requested_amounts.get(address)
1314 self.qr_window.set_content( address, label, amount )
1316 elif show and self.qr_window and not self.qr_window.isVisible():
1317 self.qr_window.setVisible(True)
1318 self.qr_window.setGeometry(self.qr_window_geometry)
1320 elif not show and self.qr_window and self.qr_window.isVisible():
1321 self.qr_window_geometry = self.qr_window.geometry()
1322 self.qr_window.setVisible(False)
1324 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1325 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1326 self.receive_list.setColumnWidth(2, 200)
1329 def question(self, msg):
1330 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1332 def show_message(self, msg):
1333 QMessageBox.information(self, _('Message'), msg, _('OK'))
1335 def password_dialog(self ):
1342 vbox = QVBoxLayout()
1343 msg = _('Please enter your password')
1344 vbox.addWidget(QLabel(msg))
1346 grid = QGridLayout()
1348 grid.addWidget(QLabel(_('Password')), 1, 0)
1349 grid.addWidget(pw, 1, 1)
1350 vbox.addLayout(grid)
1352 vbox.addLayout(ok_cancel_buttons(d))
1355 if not d.exec_(): return
1356 return unicode(pw.text())
1363 def change_password_dialog( wallet, parent=None ):
1366 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1374 new_pw = QLineEdit()
1375 new_pw.setEchoMode(2)
1376 conf_pw = QLineEdit()
1377 conf_pw.setEchoMode(2)
1379 vbox = QVBoxLayout()
1381 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1382 +_('To disable wallet encryption, enter an empty new password.')) \
1383 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1385 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1386 +_("Leave these fields empty if you want to disable encryption.")
1387 vbox.addWidget(QLabel(msg))
1389 grid = QGridLayout()
1392 if wallet.use_encryption:
1393 grid.addWidget(QLabel(_('Password')), 1, 0)
1394 grid.addWidget(pw, 1, 1)
1396 grid.addWidget(QLabel(_('New Password')), 2, 0)
1397 grid.addWidget(new_pw, 2, 1)
1399 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1400 grid.addWidget(conf_pw, 3, 1)
1401 vbox.addLayout(grid)
1403 vbox.addLayout(ok_cancel_buttons(d))
1406 if not d.exec_(): return
1408 password = unicode(pw.text()) if wallet.use_encryption else None
1409 new_password = unicode(new_pw.text())
1410 new_password2 = unicode(conf_pw.text())
1413 seed = wallet.pw_decode( wallet.seed, password)
1415 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1418 if new_password != new_password2:
1419 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1422 wallet.update_password(seed, password, new_password)
1425 def seed_dialog(wallet, parent=None):
1429 vbox = QVBoxLayout()
1430 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1431 vbox.addWidget(QLabel(msg))
1433 grid = QGridLayout()
1436 seed_e = QLineEdit()
1437 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1438 grid.addWidget(seed_e, 1, 1)
1442 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1443 grid.addWidget(gap_e, 2, 1)
1444 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1445 vbox.addLayout(grid)
1447 vbox.addLayout(ok_cancel_buttons(d))
1450 if not d.exec_(): return
1453 gap = int(unicode(gap_e.text()))
1455 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1459 seed = unicode(seed_e.text())
1462 print_error("Warning: Not hex, trying decode")
1464 seed = mnemonic.mn_decode( seed.split(' ') )
1466 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1469 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1472 wallet.seed = str(seed)
1473 #print repr(wallet.seed)
1474 wallet.gap_limit = gap
1479 def settings_dialog(self):
1482 vbox = QVBoxLayout()
1483 msg = _('Here are the settings of your wallet.') + '\n'\
1484 + _('For more explanations, click on the help buttons next to each field.')
1487 label.setFixedWidth(250)
1488 label.setWordWrap(True)
1489 label.setAlignment(Qt.AlignJustify)
1490 vbox.addWidget(label)
1492 grid = QGridLayout()
1494 vbox.addLayout(grid)
1496 fee_label = QLabel(_('Transaction fee'))
1497 grid.addWidget(fee_label, 2, 0)
1499 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1500 grid.addWidget(fee_e, 2, 1)
1501 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1502 + _('Recommended value') + ': 0.001'
1503 grid.addWidget(HelpButton(msg), 2, 2)
1504 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1505 if not self.config.is_modifiable('fee'):
1506 for w in [fee_e, fee_label]: w.setEnabled(False)
1508 nz_label = QLabel(_('Display zeros'))
1509 grid.addWidget(nz_label, 3, 0)
1511 nz_e.setText("%d"% self.wallet.num_zeros)
1512 grid.addWidget(nz_e, 3, 1)
1513 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1514 grid.addWidget(HelpButton(msg), 3, 2)
1515 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1516 if not self.config.is_modifiable('num_zeros'):
1517 for w in [nz_e, nz_label]: w.setEnabled(False)
1519 usechange_cb = QCheckBox(_('Use change addresses'))
1520 grid.addWidget(usechange_cb, 5, 0)
1521 usechange_cb.setChecked(self.wallet.use_change)
1522 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1523 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1525 gap_label = QLabel(_('Gap limit'))
1526 grid.addWidget(gap_label, 6, 0)
1528 gap_e.setText("%d"% self.wallet.gap_limit)
1529 grid.addWidget(gap_e, 6, 1)
1530 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1531 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1532 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1533 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1534 + _('Warning') + ': ' \
1535 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1536 + _('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'
1537 grid.addWidget(HelpButton(msg), 6, 2)
1538 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1539 if not self.config.is_modifiable('gap_limit'):
1540 for w in [gap_e, gap_label]: w.setEnabled(False)
1542 gui_label=QLabel(_('Default GUI') + ':')
1543 grid.addWidget(gui_label , 7, 0)
1544 gui_combo = QComboBox()
1545 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1546 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1547 if index==-1: index = 1
1548 gui_combo.setCurrentIndex(index)
1549 grid.addWidget(gui_combo, 7, 1)
1550 grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1551 if not self.config.is_modifiable('gui'):
1552 for w in [gui_combo, gui_label]: w.setEnabled(False)
1554 vbox.addLayout(ok_cancel_buttons(d))
1558 if not d.exec_(): return
1560 fee = unicode(fee_e.text())
1562 fee = int( 100000000 * Decimal(fee) )
1564 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1567 if self.wallet.fee != fee:
1568 self.wallet.fee = fee
1571 nz = unicode(nz_e.text())
1576 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1579 if self.wallet.num_zeros != nz:
1580 self.wallet.num_zeros = nz
1581 self.config.set_key('num_zeros', nz, True)
1582 self.update_history_tab()
1583 self.update_receive_tab()
1585 if self.wallet.use_change != usechange_cb.isChecked():
1586 self.wallet.use_change = usechange_cb.isChecked()
1587 self.config.set_key('use_change', self.wallet.use_change, True)
1590 n = int(gap_e.text())
1592 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1595 if self.wallet.gap_limit != n:
1596 r = self.wallet.change_gap_limit(n)
1598 self.update_receive_tab()
1599 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1601 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1603 self.config.set_key("gui", str(gui_combo.currentText()).lower(), True)
1608 def network_dialog(wallet, parent=None):
1609 interface = wallet.interface
1611 if interface.is_connected:
1612 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1614 status = _("Not connected")
1615 server = interface.server
1618 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1619 server = interface.server
1621 plist, servers_list = interface.get_servers_list()
1625 d.setWindowTitle(_('Server'))
1626 d.setMinimumSize(375, 20)
1628 vbox = QVBoxLayout()
1631 hbox = QHBoxLayout()
1633 l.setPixmap(QPixmap(":icons/network.png"))
1636 hbox.addWidget(QLabel(status))
1638 vbox.addLayout(hbox)
1642 grid = QGridLayout()
1644 vbox.addLayout(grid)
1647 server_protocol = QComboBox()
1648 server_host = QLineEdit()
1649 server_host.setFixedWidth(200)
1650 server_port = QLineEdit()
1651 server_port.setFixedWidth(60)
1653 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1654 protocol_letters = 'thsg'
1655 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1656 server_protocol.addItems(protocol_names)
1658 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1659 grid.addWidget(server_protocol, 0, 1)
1660 grid.addWidget(server_host, 0, 2)
1661 grid.addWidget(server_port, 0, 3)
1663 def change_protocol(p):
1664 protocol = protocol_letters[p]
1665 host = unicode(server_host.text())
1666 pp = plist.get(host,DEFAULT_PORTS)
1667 if protocol not in pp.keys():
1668 protocol = pp.keys()[0]
1670 server_host.setText( host )
1671 server_port.setText( port )
1673 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1675 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1676 servers_list_widget = QTreeWidget(parent)
1677 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1678 servers_list_widget.setMaximumHeight(150)
1679 servers_list_widget.setColumnWidth(0, 240)
1680 for _host in servers_list.keys():
1681 _type = 'pruning' if servers_list[_host].get('pruning') else 'full'
1682 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1684 def change_server(host, protocol=None):
1685 pp = plist.get(host,DEFAULT_PORTS)
1687 port = pp.get(protocol)
1688 if not port: protocol = None
1691 if 't' in pp.keys():
1693 port = pp.get(protocol)
1695 protocol = pp.keys()[0]
1696 port = pp.get(protocol)
1698 server_host.setText( host )
1699 server_port.setText( port )
1700 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1702 if not plist: return
1703 for p in protocol_letters:
1704 i = protocol_letters.index(p)
1705 j = server_protocol.model().index(i,0)
1706 if p not in pp.keys():
1707 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1709 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1713 host, port, protocol = server.split(':')
1714 change_server(host,protocol)
1716 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1717 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1719 if not wallet.config.is_modifiable('server'):
1720 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1723 proxy_mode = QComboBox()
1724 proxy_host = QLineEdit()
1725 proxy_host.setFixedWidth(200)
1726 proxy_port = QLineEdit()
1727 proxy_port.setFixedWidth(60)
1728 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1730 def check_for_disable(index = False):
1731 if proxy_mode.currentText() != 'NONE':
1732 proxy_host.setEnabled(True)
1733 proxy_port.setEnabled(True)
1735 proxy_host.setEnabled(False)
1736 proxy_port.setEnabled(False)
1739 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1741 if not wallet.config.is_modifiable('proxy'):
1742 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1744 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1745 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1746 proxy_host.setText(proxy_config.get("host"))
1747 proxy_port.setText(proxy_config.get("port"))
1749 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1750 grid.addWidget(proxy_mode, 2, 1)
1751 grid.addWidget(proxy_host, 2, 2)
1752 grid.addWidget(proxy_port, 2, 3)
1755 vbox.addLayout(ok_cancel_buttons(d))
1758 if not d.exec_(): return
1760 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1761 if proxy_mode.currentText() != 'NONE':
1762 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1766 wallet.config.set_key("proxy", proxy, True)
1767 wallet.config.set_key("server", server, True)
1768 interface.set_server(server, proxy)
1772 def closeEvent(self, event):
1774 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1780 def __init__(self, wallet, config, app=None):
1781 self.wallet = wallet
1782 self.config = config
1784 self.app = QApplication(sys.argv)
1787 def restore_or_create(self):
1788 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1789 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1790 if r==2: return None
1791 return 'restore' if r==1 else 'create'
1793 def seed_dialog(self):
1794 return ElectrumWindow.seed_dialog( self.wallet )
1796 def network_dialog(self):
1797 return ElectrumWindow.network_dialog( self.wallet, parent=None )
1800 def show_seed(self):
1801 ElectrumWindow.show_seed_dialog(self.wallet)
1804 def password_dialog(self):
1805 ElectrumWindow.change_password_dialog(self.wallet)
1808 def restore_wallet(self):
1809 wallet = self.wallet
1810 # wait until we are connected, because the user might have selected another server
1811 if not wallet.interface.is_connected:
1812 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1813 waiting_dialog(waiting)
1815 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1816 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1818 wallet.set_up_to_date(False)
1819 wallet.interface.poke('synchronizer')
1820 waiting_dialog(waiting)
1821 if wallet.is_found():
1822 print_error( "Recovery successful" )
1824 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1831 w = ElectrumWindow(self.wallet, self.config)
1832 if url: w.set_url(url)