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):
140 def __init__(self, data = None):
141 QWidget.__init__(self)
142 self.setMinimumSize(210, 210)
149 def set_addr(self, addr):
150 if self.addr != addr:
156 if self.addr and not self.qr:
157 self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
158 self.qr.addData(self.addr)
162 def paintEvent(self, e):
167 black = QColor(0, 0, 0, 255)
168 white = QColor(255, 255, 255, 255)
172 qp = QtGui.QPainter()
176 qp.drawRect(0, 0, 198, 198)
180 size = self.qr.getModuleCount()*boxsize
181 k = self.qr.getModuleCount()
182 qp = QtGui.QPainter()
186 if self.qr.isDark(r, c):
192 qp.drawRect(c*boxsize, r*boxsize, boxsize, boxsize)
197 class QR_Window(QWidget):
200 QWidget.__init__(self)
201 self.setWindowTitle('Electrum - Invoice')
202 self.setMinimumSize(800, 250)
206 self.setFocusPolicy(QtCore.Qt.NoFocus)
208 main_box = QHBoxLayout()
210 self.qrw = QRCodeWidget()
211 main_box.addWidget(self.qrw)
214 main_box.addLayout(vbox)
216 main_box.addStretch(1)
218 self.address_label = QLabel("")
219 self.address_label.setFont(QFont(MONOSPACE_FONT))
220 vbox.addWidget(self.address_label)
222 self.label_label = QLabel("")
223 vbox.addWidget(self.label_label)
225 self.amount_label = QLabel("")
226 vbox.addWidget(self.amount_label)
229 self.setLayout(main_box)
232 def set_content(self, addr, label, amount):
234 address_text = "<span style='font-size: 18pt'>%s</span>" % addr if addr else ""
235 self.address_label.setText(address_text)
238 amount_text = "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % format_satoshis(amount) if amount else ""
239 self.amount_label.setText(amount_text)
242 label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
243 self.label_label.setText(label_text)
245 msg = 'bitcoin:'+self.address
246 if self.amount is not None:
247 msg += '?amount=%s'%(str( Decimal(self.amount) /100000000))
248 if self.label is not None:
249 msg += '&label=%s'%(self.label)
250 elif self.label is not None:
251 msg += '?label=%s'%(self.label)
253 self.qrw.set_addr( msg )
258 def waiting_dialog(f):
264 w.setWindowTitle('Electrum')
274 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
279 def ok_cancel_buttons(dialog):
282 b = QPushButton("OK")
284 b.clicked.connect(dialog.accept)
285 b = QPushButton("Cancel")
287 b.clicked.connect(dialog.reject)
291 class ElectrumWindow(QMainWindow):
293 def __init__(self, wallet, config):
294 QMainWindow.__init__(self)
297 self.wallet.interface.register_callback('updated', self.update_callback)
298 self.wallet.interface.register_callback('connected', self.update_callback)
299 self.wallet.interface.register_callback('disconnected', self.update_callback)
300 self.wallet.interface.register_callback('disconnecting', self.update_callback)
302 self.receive_tab_mode = config.get('qt_receive_tab_mode', 0)
303 self.merchant_name = config.get('merchant_name', 'Invoice')
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 = self.merchant_name + ' - %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)
886 def receive_tab_set_mode(self, i):
887 self.receive_tab_mode = i
888 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
890 self.update_receive_tab()
891 self.toggle_QR_window(self.receive_tab_mode == 2)
894 def create_contacts_tab(self):
895 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
896 l.setContextMenuPolicy(Qt.CustomContextMenu)
897 l.customContextMenuRequested.connect(self.create_contact_menu)
898 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
899 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
900 self.contacts_list = l
901 self.contacts_buttons_hbox = hbox
902 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
907 def create_receive_menu(self, position):
908 # fixme: this function apparently has a side effect.
909 # if it is not called the menu pops up several times
910 #self.receive_list.selectedIndexes()
912 item = self.receive_list.itemAt(position)
914 addr = unicode(item.text(1))
916 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
917 if self.receive_tab_mode == 2:
918 menu.addAction(_("Request amount"), lambda: self.edit_amount())
919 menu.addAction(_("View QR"), lambda: ElectrumWindow.show_qrcode("Address","bitcoin:"+addr) )
920 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
921 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
923 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
924 menu.addAction(t, lambda: self.toggle_freeze(addr))
925 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
926 menu.addAction(t, lambda: self.toggle_priority(addr))
927 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
930 def payto(self, x, is_alias):
937 label = self.wallet.labels.get(addr)
938 m_addr = label + ' <' + addr + '>' if label else addr
939 self.tabs.setCurrentIndex(1)
940 self.payto_e.setText(m_addr)
941 self.amount_e.setFocus()
943 def delete_contact(self, x, is_alias):
944 if self.question("Do you want to remove %s from your list of contacts?"%x):
945 if not is_alias and x in self.wallet.addressbook:
946 self.wallet.addressbook.remove(x)
947 if x in self.wallet.labels.keys():
948 self.wallet.labels.pop(x)
949 elif is_alias and x in self.wallet.aliases:
950 self.wallet.aliases.pop(x)
951 self.update_history_tab()
952 self.update_contacts_tab()
953 self.update_completions()
955 def create_contact_menu(self, position):
956 # fixme: this function apparently has a side effect.
957 # if it is not called the menu pops up several times
958 #self.contacts_list.selectedIndexes()
960 item = self.contacts_list.itemAt(position)
962 addr = unicode(item.text(0))
963 label = unicode(item.text(1))
964 is_alias = label in self.wallet.aliases.keys()
965 x = label if is_alias else addr
967 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
968 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
969 menu.addAction(_("View QR code"),lambda: self.show_qrcode("Address","bitcoin:"+addr))
971 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
973 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
974 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
975 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
978 def update_receive_item(self, item):
979 address = str( item.data(1,0).toString() )
981 flags = self.wallet.get_address_flags(address)
982 item.setData(0,0,flags)
984 label = self.wallet.labels.get(address,'')
985 item.setData(2,0,label)
987 amount = self.wallet.requested_amounts.get(address,None)
988 amount_str = format_satoshis( amount, False, self.wallet.num_zeros ) if amount is not None else ""
989 item.setData(3,0,amount_str)
991 c, u = self.wallet.get_addr_balance(address)
992 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
993 item.setData(4,0,balance)
995 if address in self.wallet.frozen_addresses:
996 item.setBackgroundColor(1, QColor('lightblue'))
997 elif address in self.wallet.prioritized_addresses:
998 item.setBackgroundColor(1, QColor('lightgreen'))
1001 def update_receive_tab(self):
1002 l = self.receive_list
1005 l.setColumnHidden(0, not self.receive_tab_mode == 1)
1006 l.setColumnHidden(3, not self.receive_tab_mode == 2)
1007 l.setColumnHidden(4, self.receive_tab_mode == 0)
1008 l.setColumnHidden(5, not self.receive_tab_mode == 1)
1009 l.setColumnWidth(0, 50)
1010 l.setColumnWidth(1, 310)
1011 l.setColumnWidth(2, 200)
1012 l.setColumnWidth(3, 130)
1013 l.setColumnWidth(4, 130)
1014 l.setColumnWidth(5, 10)
1018 for address in self.wallet.all_addresses():
1020 if self.wallet.is_change(address) and self.receive_tab_mode != 1:
1024 h = self.wallet.history.get(address,[])
1027 for tx_hash, tx_height in h:
1028 tx = self.wallet.transactions.get(tx_hash)
1036 if address in self.wallet.addresses:
1038 if gap > self.wallet.gap_limit:
1041 if address in self.wallet.addresses:
1044 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1045 item.setFont(0, QFont(MONOSPACE_FONT))
1046 item.setFont(1, QFont(MONOSPACE_FONT))
1047 item.setFont(3, QFont(MONOSPACE_FONT))
1048 self.update_receive_item(item)
1049 if is_red and address in self.wallet.addresses:
1050 item.setBackgroundColor(1, QColor('red'))
1051 l.addTopLevelItem(item)
1053 # we use column 1 because column 0 may be hidden
1054 l.setCurrentItem(l.topLevelItem(0),1)
1056 def show_contact_details(self, m):
1057 a = self.wallet.aliases.get(m)
1059 if a[0] in self.wallet.authorities.keys():
1060 s = self.wallet.authorities.get(a[0])
1063 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1064 QMessageBox.information(self, 'Alias', msg, 'OK')
1066 def update_contacts_tab(self):
1068 l = self.contacts_list
1070 l.setColumnWidth(0, 350)
1071 l.setColumnWidth(1, 330)
1072 l.setColumnWidth(2, 100)
1075 for alias, v in self.wallet.aliases.items():
1077 alias_targets.append(target)
1078 item = QTreeWidgetItem( [ target, alias, '-'] )
1079 item.setBackgroundColor(0, QColor('lightgray'))
1080 l.addTopLevelItem(item)
1082 for address in self.wallet.addressbook:
1083 if address in alias_targets: continue
1084 label = self.wallet.labels.get(address,'')
1086 for item in self.wallet.transactions.values():
1087 if address in item['outputs'] : n=n+1
1089 item = QTreeWidgetItem( [ address, label, tx] )
1090 item.setFont(0, QFont(MONOSPACE_FONT))
1091 l.addTopLevelItem(item)
1093 l.setCurrentItem(l.topLevelItem(0))
1095 def create_wall_tab(self):
1096 self.textbox = textbox = QTextEdit(self)
1097 textbox.setFont(QFont(MONOSPACE_FONT))
1098 textbox.setReadOnly(True)
1101 def create_status_bar(self):
1103 sb.setFixedHeight(35)
1104 if self.wallet.seed:
1105 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1106 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1107 if self.wallet.seed:
1108 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1109 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
1110 sb.addPermanentWidget( self.status_button )
1111 self.setStatusBar(sb)
1113 def new_contact_dialog(self):
1114 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1115 address = unicode(text)
1117 if self.wallet.is_valid(address):
1118 self.wallet.addressbook.append(address)
1120 self.update_contacts_tab()
1121 self.update_history_tab()
1122 self.update_completions()
1124 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1127 def show_seed_dialog(wallet, parent=None):
1129 QMessageBox.information(parent, _('Message'),
1130 _('No seed'), _('OK'))
1133 if wallet.use_encryption:
1134 password = parent.password_dialog()
1141 seed = wallet.pw_decode(wallet.seed, password)
1143 QMessageBox.warning(parent, _('Error'),
1144 _('Incorrect Password'), _('OK'))
1147 dialog = QDialog(None)
1149 dialog.setWindowTitle("Electrum")
1151 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1153 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1154 + _("Please write down or memorize these 12 words (order is important).") + " " \
1155 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1156 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1158 main_text = QLabel(msg)
1159 main_text.setWordWrap(True)
1162 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1169 copy_function = lambda: app.clipboard().setText(brainwallet)
1170 copy_button = QPushButton(_("Copy to Clipboard"))
1171 copy_button.clicked.connect(copy_function)
1173 show_qr_function = lambda: ElectrumWindow.show_qrcode(_("Seed"), seed)
1174 qr_button = QPushButton(_("View as QR Code"))
1175 qr_button.clicked.connect(show_qr_function)
1177 ok_button = QPushButton(_("OK"))
1178 ok_button.setDefault(True)
1179 ok_button.clicked.connect(dialog.accept)
1181 main_layout = QGridLayout()
1182 main_layout.addWidget(logo, 0, 0)
1183 main_layout.addWidget(main_text, 0, 1, 1, -1)
1184 main_layout.addWidget(copy_button, 1, 1)
1185 main_layout.addWidget(qr_button, 1, 2)
1186 main_layout.addWidget(ok_button, 1, 3)
1187 dialog.setLayout(main_layout)
1192 def show_qrcode(title, data):
1196 d.setWindowTitle(title)
1197 d.setMinimumSize(270, 300)
1198 vbox = QVBoxLayout()
1199 qrw = QRCodeWidget(data)
1201 vbox.addWidget(QLabel(data))
1202 hbox = QHBoxLayout()
1206 filename = "qrcode.bmp"
1207 bmp.save_qrcode(qrw.qr, filename)
1208 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1210 b = QPushButton(_("Print"))
1212 b.clicked.connect(print_qr)
1214 b = QPushButton(_("Close"))
1216 b.clicked.connect(d.accept)
1218 vbox.addLayout(hbox)
1222 def sign_message(self,address):
1223 if not address: return
1226 d.setWindowTitle('Sign Message')
1227 d.setMinimumSize(270, 350)
1229 tab_widget = QTabWidget()
1231 layout = QGridLayout(tab)
1233 sign_address = QLineEdit()
1234 sign_address.setText(address)
1235 layout.addWidget(QLabel(_('Address')), 1, 0)
1236 layout.addWidget(sign_address, 1, 1)
1238 sign_message = QTextEdit()
1239 layout.addWidget(QLabel(_('Message')), 2, 0)
1240 layout.addWidget(sign_message, 2, 1, 2, 1)
1242 sign_signature = QLineEdit()
1243 layout.addWidget(QLabel(_('Signature')), 3, 0)
1244 layout.addWidget(sign_signature, 3, 1)
1247 if self.wallet.use_encryption:
1248 password = self.password_dialog()
1255 signature = self.wallet.sign_message(sign_address.text(), sign_message.toPlainText(), password)
1256 sign_signature.setText(signature)
1257 except BaseException, e:
1258 self.show_message(str(e))
1261 hbox = QHBoxLayout()
1262 b = QPushButton(_("Sign"))
1264 b.clicked.connect(do_sign)
1265 b = QPushButton(_("Close"))
1266 b.clicked.connect(d.accept)
1268 layout.addLayout(hbox, 4, 1)
1269 tab_widget.addTab(tab, "Sign")
1273 layout = QGridLayout(tab)
1275 verify_address = QLineEdit()
1276 layout.addWidget(QLabel(_('Address')), 1, 0)
1277 layout.addWidget(verify_address, 1, 1)
1279 verify_message = QTextEdit()
1280 layout.addWidget(QLabel(_('Message')), 2, 0)
1281 layout.addWidget(verify_message, 2, 1, 2, 1)
1283 verify_signature = QLineEdit()
1284 layout.addWidget(QLabel(_('Signature')), 3, 0)
1285 layout.addWidget(verify_signature, 3, 1)
1289 self.wallet.verify_message(verify_address.text(), verify_signature.text(), verify_message.toPlainText())
1290 self.show_message("Signature verified")
1291 except BaseException, e:
1292 self.show_message(str(e))
1295 hbox = QHBoxLayout()
1296 b = QPushButton(_("Verify"))
1297 b.clicked.connect(do_verify)
1299 b = QPushButton(_("Close"))
1300 b.clicked.connect(d.accept)
1302 layout.addLayout(hbox, 4, 1)
1303 tab_widget.addTab(tab, "Verify")
1305 vbox = QVBoxLayout()
1306 vbox.addWidget(tab_widget)
1311 def toggle_QR_window(self, show):
1312 if show and not self.qr_window:
1313 self.qr_window = QR_Window()
1314 self.qr_window.setVisible(True)
1315 self.qr_window_geometry = self.qr_window.geometry()
1316 item = self.receive_list.currentItem()
1318 address = str(item.text(1))
1319 label = self.wallet.labels.get(address)
1320 amount = self.wallet.requested_amounts.get(address)
1321 self.qr_window.set_content( address, label, amount )
1323 elif show and self.qr_window and not self.qr_window.isVisible():
1324 self.qr_window.setVisible(True)
1325 self.qr_window.setGeometry(self.qr_window_geometry)
1327 elif not show and self.qr_window and self.qr_window.isVisible():
1328 self.qr_window_geometry = self.qr_window.geometry()
1329 self.qr_window.setVisible(False)
1331 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1332 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1333 self.receive_list.setColumnWidth(2, 200)
1336 def question(self, msg):
1337 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1339 def show_message(self, msg):
1340 QMessageBox.information(self, _('Message'), msg, _('OK'))
1342 def password_dialog(self ):
1349 vbox = QVBoxLayout()
1350 msg = _('Please enter your password')
1351 vbox.addWidget(QLabel(msg))
1353 grid = QGridLayout()
1355 grid.addWidget(QLabel(_('Password')), 1, 0)
1356 grid.addWidget(pw, 1, 1)
1357 vbox.addLayout(grid)
1359 vbox.addLayout(ok_cancel_buttons(d))
1362 if not d.exec_(): return
1363 return unicode(pw.text())
1370 def change_password_dialog( wallet, parent=None ):
1373 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1381 new_pw = QLineEdit()
1382 new_pw.setEchoMode(2)
1383 conf_pw = QLineEdit()
1384 conf_pw.setEchoMode(2)
1386 vbox = QVBoxLayout()
1388 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1389 +_('To disable wallet encryption, enter an empty new password.')) \
1390 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1392 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1393 +_("Leave these fields empty if you want to disable encryption.")
1394 vbox.addWidget(QLabel(msg))
1396 grid = QGridLayout()
1399 if wallet.use_encryption:
1400 grid.addWidget(QLabel(_('Password')), 1, 0)
1401 grid.addWidget(pw, 1, 1)
1403 grid.addWidget(QLabel(_('New Password')), 2, 0)
1404 grid.addWidget(new_pw, 2, 1)
1406 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1407 grid.addWidget(conf_pw, 3, 1)
1408 vbox.addLayout(grid)
1410 vbox.addLayout(ok_cancel_buttons(d))
1413 if not d.exec_(): return
1415 password = unicode(pw.text()) if wallet.use_encryption else None
1416 new_password = unicode(new_pw.text())
1417 new_password2 = unicode(conf_pw.text())
1420 seed = wallet.pw_decode( wallet.seed, password)
1422 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1425 if new_password != new_password2:
1426 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1427 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1429 wallet.update_password(seed, password, new_password)
1432 def seed_dialog(wallet, parent=None):
1436 vbox = QVBoxLayout()
1437 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1438 vbox.addWidget(QLabel(msg))
1440 grid = QGridLayout()
1443 seed_e = QLineEdit()
1444 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1445 grid.addWidget(seed_e, 1, 1)
1449 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1450 grid.addWidget(gap_e, 2, 1)
1451 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1452 vbox.addLayout(grid)
1454 vbox.addLayout(ok_cancel_buttons(d))
1457 if not d.exec_(): return
1460 gap = int(unicode(gap_e.text()))
1462 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1466 seed = unicode(seed_e.text())
1469 print_error("Warning: Not hex, trying decode")
1471 seed = mnemonic.mn_decode( seed.split(' ') )
1473 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1476 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1479 wallet.seed = str(seed)
1480 #print repr(wallet.seed)
1481 wallet.gap_limit = gap
1486 def settings_dialog(self):
1489 vbox = QVBoxLayout()
1490 msg = _('Here are the settings of your wallet.') + '\n'\
1491 + _('For more explanations, click on the help buttons next to each field.')
1494 label.setFixedWidth(250)
1495 label.setWordWrap(True)
1496 label.setAlignment(Qt.AlignJustify)
1497 vbox.addWidget(label)
1499 grid = QGridLayout()
1501 vbox.addLayout(grid)
1503 fee_label = QLabel(_('Transaction fee'))
1504 grid.addWidget(fee_label, 2, 0)
1506 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1507 grid.addWidget(fee_e, 2, 1)
1508 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1509 + _('Recommended value') + ': 0.001'
1510 grid.addWidget(HelpButton(msg), 2, 2)
1511 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1512 if not self.config.is_modifiable('fee'):
1513 for w in [fee_e, fee_label]: w.setEnabled(False)
1515 nz_label = QLabel(_('Display zeros'))
1516 grid.addWidget(nz_label, 3, 0)
1518 nz_e.setText("%d"% self.wallet.num_zeros)
1519 grid.addWidget(nz_e, 3, 1)
1520 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1521 grid.addWidget(HelpButton(msg), 3, 2)
1522 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1523 if not self.config.is_modifiable('num_zeros'):
1524 for w in [nz_e, nz_label]: w.setEnabled(False)
1526 usechange_cb = QCheckBox(_('Use change addresses'))
1527 grid.addWidget(usechange_cb, 5, 0)
1528 usechange_cb.setChecked(self.wallet.use_change)
1529 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1530 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1532 gap_label = QLabel(_('Gap limit'))
1533 grid.addWidget(gap_label, 6, 0)
1535 gap_e.setText("%d"% self.wallet.gap_limit)
1536 grid.addWidget(gap_e, 6, 1)
1537 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1538 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1539 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1540 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1541 + _('Warning') + ': ' \
1542 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1543 + _('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'
1544 grid.addWidget(HelpButton(msg), 6, 2)
1545 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1546 if not self.config.is_modifiable('gap_limit'):
1547 for w in [gap_e, gap_label]: w.setEnabled(False)
1549 gui_label=QLabel(_('Default GUI') + ':')
1550 grid.addWidget(gui_label , 7, 0)
1551 gui_combo = QComboBox()
1552 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1553 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1554 if index==-1: index = 1
1555 gui_combo.setCurrentIndex(index)
1556 grid.addWidget(gui_combo, 7, 1)
1557 grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1558 if not self.config.is_modifiable('gui'):
1559 for w in [gui_combo, gui_label]: w.setEnabled(False)
1561 vbox.addLayout(ok_cancel_buttons(d))
1565 if not d.exec_(): return
1567 fee = unicode(fee_e.text())
1569 fee = int( 100000000 * Decimal(fee) )
1571 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1574 if self.wallet.fee != fee:
1575 self.wallet.fee = fee
1578 nz = unicode(nz_e.text())
1583 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1586 if self.wallet.num_zeros != nz:
1587 self.wallet.num_zeros = nz
1588 self.config.set_key('num_zeros', nz, True)
1589 self.update_history_tab()
1590 self.update_receive_tab()
1592 if self.wallet.use_change != usechange_cb.isChecked():
1593 self.wallet.use_change = usechange_cb.isChecked()
1594 self.config.set_key('use_change', self.wallet.use_change, True)
1597 n = int(gap_e.text())
1599 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1602 if self.wallet.gap_limit != n:
1603 r = self.wallet.change_gap_limit(n)
1605 self.update_receive_tab()
1606 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1608 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1610 self.config.set_key("gui", str(gui_combo.currentText()).lower(), True)
1615 def network_dialog(wallet, parent=None):
1616 interface = wallet.interface
1618 if interface.is_connected:
1619 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1621 status = _("Not connected")
1622 server = interface.server
1625 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1626 server = interface.server
1628 plist, servers_list = interface.get_servers_list()
1632 d.setWindowTitle(_('Server'))
1633 d.setMinimumSize(375, 20)
1635 vbox = QVBoxLayout()
1638 hbox = QHBoxLayout()
1640 l.setPixmap(QPixmap(":icons/network.png"))
1643 hbox.addWidget(QLabel(status))
1645 vbox.addLayout(hbox)
1649 grid = QGridLayout()
1651 vbox.addLayout(grid)
1654 server_protocol = QComboBox()
1655 server_host = QLineEdit()
1656 server_host.setFixedWidth(200)
1657 server_port = QLineEdit()
1658 server_port.setFixedWidth(60)
1660 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1661 protocol_letters = 'thsg'
1662 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1663 server_protocol.addItems(protocol_names)
1665 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1666 grid.addWidget(server_protocol, 0, 1)
1667 grid.addWidget(server_host, 0, 2)
1668 grid.addWidget(server_port, 0, 3)
1670 def change_protocol(p):
1671 protocol = protocol_letters[p]
1672 host = unicode(server_host.text())
1673 pp = plist.get(host,DEFAULT_PORTS)
1674 if protocol not in pp.keys():
1675 protocol = pp.keys()[0]
1677 server_host.setText( host )
1678 server_port.setText( port )
1680 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1682 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1683 servers_list_widget = QTreeWidget(parent)
1684 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1685 servers_list_widget.setMaximumHeight(150)
1686 servers_list_widget.setColumnWidth(0, 240)
1687 for _host in servers_list.keys():
1688 _type = 'pruning' if servers_list[_host].get('pruning') else 'full'
1689 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1691 def change_server(host, protocol=None):
1692 pp = plist.get(host,DEFAULT_PORTS)
1694 port = pp.get(protocol)
1695 if not port: protocol = None
1698 if 't' in pp.keys():
1700 port = pp.get(protocol)
1702 protocol = pp.keys()[0]
1703 port = pp.get(protocol)
1705 server_host.setText( host )
1706 server_port.setText( port )
1707 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1709 if not plist: return
1710 for p in protocol_letters:
1711 i = protocol_letters.index(p)
1712 j = server_protocol.model().index(i,0)
1713 if p not in pp.keys():
1714 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1716 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1720 host, port, protocol = server.split(':')
1721 change_server(host,protocol)
1723 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1724 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1726 if not wallet.config.is_modifiable('server'):
1727 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1730 proxy_mode = QComboBox()
1731 proxy_host = QLineEdit()
1732 proxy_host.setFixedWidth(200)
1733 proxy_port = QLineEdit()
1734 proxy_port.setFixedWidth(60)
1735 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1737 def check_for_disable(index = False):
1738 if proxy_mode.currentText() != 'NONE':
1739 proxy_host.setEnabled(True)
1740 proxy_port.setEnabled(True)
1742 proxy_host.setEnabled(False)
1743 proxy_port.setEnabled(False)
1746 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1748 if not wallet.config.is_modifiable('proxy'):
1749 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1751 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1752 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1753 proxy_host.setText(proxy_config.get("host"))
1754 proxy_port.setText(proxy_config.get("port"))
1756 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1757 grid.addWidget(proxy_mode, 2, 1)
1758 grid.addWidget(proxy_host, 2, 2)
1759 grid.addWidget(proxy_port, 2, 3)
1762 vbox.addLayout(ok_cancel_buttons(d))
1765 if not d.exec_(): return
1767 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1768 if proxy_mode.currentText() != 'NONE':
1769 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1773 wallet.config.set_key("proxy", proxy, True)
1774 wallet.config.set_key("server", server, True)
1775 interface.set_server(server, proxy)
1779 def closeEvent(self, event):
1781 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1787 def __init__(self, wallet, config, app=None):
1788 self.wallet = wallet
1789 self.config = config
1791 self.app = QApplication(sys.argv)
1794 def restore_or_create(self):
1795 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1796 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1797 if r==2: return None
1798 return 'restore' if r==1 else 'create'
1800 def seed_dialog(self):
1801 return ElectrumWindow.seed_dialog( self.wallet )
1803 def network_dialog(self):
1804 return ElectrumWindow.network_dialog( self.wallet, parent=None )
1807 def show_seed(self):
1808 ElectrumWindow.show_seed_dialog(self.wallet)
1811 def password_dialog(self):
1812 ElectrumWindow.change_password_dialog(self.wallet)
1815 def restore_wallet(self):
1816 wallet = self.wallet
1817 # wait until we are connected, because the user might have selected another server
1818 if not wallet.interface.is_connected:
1819 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1820 waiting_dialog(waiting)
1822 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1823 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1825 wallet.set_up_to_date(False)
1826 wallet.interface.poke('synchronizer')
1827 waiting_dialog(waiting)
1828 if wallet.is_found():
1829 print_error( "Recovery successful" )
1831 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1838 w = ElectrumWindow(self.wallet, self.config)
1839 if url: w.set_url(url)