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') )
311 tabs.addTab(self.create_send_tab(), _('Send') )
312 tabs.addTab(self.create_receive_tab(), _('Receive') )
313 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
314 tabs.addTab(self.create_wall_tab(), _('Wall') )
315 tabs.setMinimumSize(600, 400)
316 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
317 self.setCentralWidget(tabs)
318 self.create_status_bar()
319 self.toggle_QR_window(self.receive_tab_mode == 2)
321 g = self.config.get("winpos-qt",[100, 100, 840, 400])
322 self.setGeometry(g[0], g[1], g[2], g[3])
323 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
324 if not self.wallet.seed: title += ' [seedless]'
325 self.setWindowTitle( title )
327 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
328 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
329 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
330 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
332 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
333 #self.connect(self, SIGNAL('editamount'), self.edit_amount)
334 self.history_list.setFocus(True)
336 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
337 if platform.system() == 'Windows':
338 n = 3 if self.wallet.seed else 2
339 tabs.setCurrentIndex (n)
340 tabs.setCurrentIndex (0)
343 QMainWindow.close(self)
345 self.qr_window.close()
346 self.qr_window = None
348 def connect_slots(self, sender):
349 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
350 self.previous_payto_e=''
352 def timer_actions(self):
354 self.qr_window.qrw.update_qr()
356 if self.payto_e.hasFocus():
358 r = unicode( self.payto_e.text() )
359 if r != self.previous_payto_e:
360 self.previous_payto_e = r
362 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
364 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
368 s = r + ' <' + to_address + '>'
369 self.payto_e.setText(s)
372 def update_callback(self):
373 self.emit(QtCore.SIGNAL('updatesignal'))
375 def update_wallet(self):
376 if self.wallet.interface and self.wallet.interface.is_connected:
377 if not self.wallet.up_to_date:
378 text = _( "Synchronizing..." )
379 icon = QIcon(":icons/status_waiting.png")
381 c, u = self.wallet.get_balance()
382 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
383 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
384 icon = QIcon(":icons/status_connected.png")
386 text = _( "Not connected" )
387 icon = QIcon(":icons/status_disconnected.png")
390 text = _( "Not enough funds" )
392 self.statusBar().showMessage(text)
393 self.status_button.setIcon( icon )
395 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
396 self.textbox.setText( self.wallet.banner )
397 self.update_history_tab()
398 self.update_receive_tab()
399 self.update_contacts_tab()
400 self.update_completions()
403 def create_history_tab(self):
404 self.history_list = l = MyTreeWidget(self)
406 l.setColumnWidth(0, 40)
407 l.setColumnWidth(1, 140)
408 l.setColumnWidth(2, 350)
409 l.setColumnWidth(3, 140)
410 l.setColumnWidth(4, 140)
411 l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
412 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
413 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
415 l.setContextMenuPolicy(Qt.CustomContextMenu)
416 l.customContextMenuRequested.connect(self.create_history_menu)
420 def create_history_menu(self, position):
421 self.history_list.selectedIndexes()
422 item = self.history_list.currentItem()
424 tx_hash = str(item.toolTip(0))
425 if not tx_hash: return
427 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
428 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
429 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
430 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
433 def tx_details(self, tx_hash):
434 tx_details = self.wallet.get_tx_details(tx_hash)
435 QMessageBox.information(self, 'Details', tx_details, 'OK')
438 def tx_label_clicked(self, item, column):
439 if column==2 and item.isSelected():
440 tx_hash = str(item.toolTip(0))
442 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
443 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
444 self.history_list.editItem( item, column )
445 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
448 def tx_label_changed(self, item, column):
452 tx_hash = str(item.toolTip(0))
453 tx = self.wallet.transactions.get(tx_hash)
454 s = self.wallet.labels.get(tx_hash)
455 text = unicode( item.text(2) )
457 self.wallet.labels[tx_hash] = text
458 item.setForeground(2, QBrush(QColor('black')))
460 if s: self.wallet.labels.pop(tx_hash)
461 text = self.wallet.get_default_label(tx_hash)
462 item.setText(2, text)
463 item.setForeground(2, QBrush(QColor('gray')))
467 def edit_label(self, is_recv):
468 l = self.receive_list if is_recv else self.contacts_list
469 c = 2 if is_recv else 1
470 item = l.currentItem()
471 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
472 l.editItem( item, c )
473 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
475 def edit_amount(self):
476 l = self.receive_list
477 item = l.currentItem()
478 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
479 l.editItem( item, 3 )
480 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
483 def address_label_clicked(self, item, column, l, column_addr, column_label):
484 if column == column_label and item.isSelected():
485 addr = unicode( item.text(column_addr) )
486 label = unicode( item.text(column_label) )
487 if label in self.wallet.aliases.keys():
489 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
490 l.editItem( item, column )
491 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
494 def address_label_changed(self, item, column, l, column_addr, column_label):
496 if column == column_label:
497 addr = unicode( item.text(column_addr) )
498 text = unicode( item.text(column_label) )
502 if text not in self.wallet.aliases.keys():
503 old_addr = self.wallet.labels.get(text)
505 self.wallet.labels[addr] = text
508 print_error("Error: This is one of your aliases")
509 label = self.wallet.labels.get(addr,'')
510 item.setText(column_label, QString(label))
512 s = self.wallet.labels.get(addr)
514 self.wallet.labels.pop(addr)
518 self.update_history_tab()
519 self.update_completions()
521 self.recv_changed(item)
524 address = unicode( item.text(column_addr) )
525 text = unicode( item.text(3) )
527 index = self.wallet.addresses.index(address)
532 amount = int( Decimal(text) * 100000000 )
533 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
535 amount = self.wallet.requested_amounts.get(address)
537 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
542 self.wallet.requested_amounts[address] = amount
544 label = self.wallet.labels.get(address)
546 label = self.merchant_name + ' - %04d'%(index+1)
547 self.wallet.labels[address] = label
549 self.update_receive_item(self.receive_list.currentItem())
551 self.qr_window.set_content( address, label, amount )
554 def recv_changed(self, a):
555 "current item changed"
556 if a is not None and self.qr_window and self.qr_window.isVisible():
557 address = str(a.text(1))
558 label = self.wallet.labels.get(address)
559 amount = self.wallet.requested_amounts.get(address)
560 self.qr_window.set_content( address, label, amount )
563 def update_history_tab(self):
565 self.history_list.clear()
566 for item in self.wallet.get_tx_history():
567 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
570 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
576 icon = QIcon(":icons/unconfirmed.png")
578 icon = QIcon(":icons/clock%d.png"%conf)
580 icon = QIcon(":icons/confirmed.png")
583 icon = QIcon(":icons/unconfirmed.png")
585 if value is not None:
586 v_str = format_satoshis(value, True, self.wallet.num_zeros)
590 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
593 label, is_default_label = self.wallet.get_label(tx_hash)
595 label = _('Pruned transaction outputs')
596 is_default_label = False
598 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
599 item.setFont(2, QFont(MONOSPACE_FONT))
600 item.setFont(3, QFont(MONOSPACE_FONT))
601 item.setFont(4, QFont(MONOSPACE_FONT))
603 item.setToolTip(0, tx_hash)
605 item.setForeground(2, QBrush(QColor('grey')))
607 item.setIcon(0, icon)
608 self.history_list.insertTopLevelItem(0,item)
611 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
614 def create_send_tab(self):
619 grid.setColumnMinimumWidth(3,300)
620 grid.setColumnStretch(5,1)
622 self.payto_e = QLineEdit()
623 grid.addWidget(QLabel(_('Pay to')), 1, 0)
624 grid.addWidget(self.payto_e, 1, 1, 1, 3)
627 qrcode = qrscanner.scan_qr()
628 if 'address' in qrcode:
629 self.payto_e.setText(qrcode['address'])
630 if 'amount' in qrcode:
631 self.amount_e.setText(str(qrcode['amount']))
632 if 'label' in qrcode:
633 self.message_e.setText(qrcode['label'])
634 if 'message' in qrcode:
635 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
638 if qrscanner.is_available():
639 b = QPushButton(_("Scan QR code"))
640 b.clicked.connect(fill_from_qr)
641 grid.addWidget(b, 1, 5)
643 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)
645 completer = QCompleter()
646 completer.setCaseSensitivity(False)
647 self.payto_e.setCompleter(completer)
648 completer.setModel(self.completions)
650 self.message_e = QLineEdit()
651 grid.addWidget(QLabel(_('Description')), 2, 0)
652 grid.addWidget(self.message_e, 2, 1, 1, 3)
653 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)
655 self.amount_e = QLineEdit()
656 grid.addWidget(QLabel(_('Amount')), 3, 0)
657 grid.addWidget(self.amount_e, 3, 1, 1, 2)
658 grid.addWidget(HelpButton(
659 _('Amount to be sent.') + '\n\n' \
660 + _('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)
662 self.fee_e = QLineEdit()
663 grid.addWidget(QLabel(_('Fee')), 4, 0)
664 grid.addWidget(self.fee_e, 4, 1, 1, 2)
665 grid.addWidget(HelpButton(
666 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
667 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
668 + _('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)
670 b = EnterButton(_("Send"), self.do_send)
671 grid.addWidget(b, 6, 1)
673 b = EnterButton(_("Clear"),self.do_clear)
674 grid.addWidget(b, 6, 2)
676 self.payto_sig = QLabel('')
677 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
679 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
680 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
689 def entry_changed( is_fee ):
690 self.funds_error = False
691 amount = numbify(self.amount_e)
692 fee = numbify(self.fee_e)
693 if not is_fee: fee = None
696 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
698 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
701 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
704 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
705 self.funds_error = True
706 self.amount_e.setPalette(palette)
707 self.fee_e.setPalette(palette)
709 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
710 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
715 def update_completions(self):
717 for addr,label in self.wallet.labels.items():
718 if addr in self.wallet.addressbook:
719 l.append( label + ' <' + addr + '>')
720 l = l + self.wallet.aliases.keys()
722 self.completions.setStringList(l)
728 label = unicode( self.message_e.text() )
729 r = unicode( self.payto_e.text() )
733 m1 = re.match(ALIAS_REGEXP, r)
734 # label or alias, with address in brackets
735 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
738 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
742 to_address = m2.group(2)
746 if not self.wallet.is_valid(to_address):
747 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
751 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
753 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
756 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
758 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
761 if self.wallet.use_encryption:
762 password = self.password_dialog()
769 tx = self.wallet.mktx( [(to_address, amount)], label, password, fee)
770 except BaseException, e:
771 self.show_message(str(e))
775 h = self.wallet.send_tx(tx)
776 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
777 status, msg = self.wallet.receive_tx( h )
779 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
781 self.update_contacts_tab()
783 QMessageBox.warning(self, _('Error'), msg, _('OK'))
785 filename = 'unsigned_tx'
786 f = open(filename,'w')
789 QMessageBox.information(self, _('Unsigned transaction'), _("Unsigned transaction was saved to file:") + " " +filename, _('OK'))
792 def set_url(self, url):
793 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
794 self.tabs.setCurrentIndex(1)
795 label = self.wallet.labels.get(payto)
796 m_addr = label + ' <'+ payto+'>' if label else payto
797 self.payto_e.setText(m_addr)
799 self.message_e.setText(message)
800 self.amount_e.setText(amount)
802 self.set_frozen(self.payto_e,True)
803 self.set_frozen(self.amount_e,True)
804 self.set_frozen(self.message_e,True)
805 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
807 self.payto_sig.setVisible(False)
810 self.payto_sig.setVisible(False)
811 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
813 self.set_frozen(e,False)
815 def set_frozen(self,entry,frozen):
817 entry.setReadOnly(True)
818 entry.setFrame(False)
820 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
821 entry.setPalette(palette)
823 entry.setReadOnly(False)
826 palette.setColor(entry.backgroundRole(), QColor('white'))
827 entry.setPalette(palette)
830 def toggle_freeze(self,addr):
832 if addr in self.wallet.frozen_addresses:
833 self.wallet.unfreeze(addr)
835 self.wallet.freeze(addr)
836 self.update_receive_tab()
838 def toggle_priority(self,addr):
840 if addr in self.wallet.prioritized_addresses:
841 self.wallet.unprioritize(addr)
843 self.wallet.prioritize(addr)
844 self.update_receive_tab()
847 def create_list_tab(self, headers):
848 "generic tab creation method"
849 l = MyTreeWidget(self)
850 l.setColumnCount( len(headers) )
851 l.setHeaderLabels( headers )
861 vbox.addWidget(buttons)
866 buttons.setLayout(hbox)
871 def create_receive_tab(self):
872 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
873 l.setContextMenuPolicy(Qt.CustomContextMenu)
874 l.customContextMenuRequested.connect(self.create_receive_menu)
875 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
876 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
877 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
878 self.receive_list = l
879 self.receive_buttons_hbox = hbox
885 def receive_tab_set_mode(self, i):
886 self.receive_tab_mode = i
887 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
889 self.update_receive_tab()
890 self.toggle_QR_window(self.receive_tab_mode == 2)
893 def create_contacts_tab(self):
894 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
895 l.setContextMenuPolicy(Qt.CustomContextMenu)
896 l.customContextMenuRequested.connect(self.create_contact_menu)
897 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
898 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
899 self.contacts_list = l
900 self.contacts_buttons_hbox = hbox
901 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
906 def create_receive_menu(self, position):
907 # fixme: this function apparently has a side effect.
908 # if it is not called the menu pops up several times
909 #self.receive_list.selectedIndexes()
911 item = self.receive_list.itemAt(position)
913 addr = unicode(item.text(1))
915 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
916 if self.receive_tab_mode == 2:
917 menu.addAction(_("Request amount"), lambda: self.edit_amount())
918 menu.addAction(_("View QR"), lambda: ElectrumWindow.show_qrcode("Address","bitcoin:"+addr) )
919 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
920 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
922 if self.receive_tab_mode == 1:
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))
928 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
931 def payto(self, x, is_alias):
938 label = self.wallet.labels.get(addr)
939 m_addr = label + ' <' + addr + '>' if label else addr
940 self.tabs.setCurrentIndex(1)
941 self.payto_e.setText(m_addr)
942 self.amount_e.setFocus()
944 def delete_contact(self, x, is_alias):
945 if self.question("Do you want to remove %s from your list of contacts?"%x):
946 if not is_alias and x in self.wallet.addressbook:
947 self.wallet.addressbook.remove(x)
948 if x in self.wallet.labels.keys():
949 self.wallet.labels.pop(x)
950 elif is_alias and x in self.wallet.aliases:
951 self.wallet.aliases.pop(x)
952 self.update_history_tab()
953 self.update_contacts_tab()
954 self.update_completions()
956 def create_contact_menu(self, position):
957 # fixme: this function apparently has a side effect.
958 # if it is not called the menu pops up several times
959 #self.contacts_list.selectedIndexes()
961 item = self.contacts_list.itemAt(position)
963 addr = unicode(item.text(0))
964 label = unicode(item.text(1))
965 is_alias = label in self.wallet.aliases.keys()
966 x = label if is_alias else addr
968 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
969 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
970 menu.addAction(_("View QR code"),lambda: self.show_qrcode("Address","bitcoin:"+addr))
972 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
974 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
975 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
976 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
979 def update_receive_item(self, item):
980 address = str( item.data(1,0).toString() )
982 flags = self.wallet.get_address_flags(address)
983 item.setData(0,0,flags)
985 label = self.wallet.labels.get(address,'')
986 item.setData(2,0,label)
988 amount = self.wallet.requested_amounts.get(address,None)
989 amount_str = format_satoshis( amount, False, self.wallet.num_zeros ) if amount is not None else ""
990 item.setData(3,0,amount_str)
992 c, u = self.wallet.get_addr_balance(address)
993 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
994 item.setData(4,0,balance)
996 if self.receive_tab_mode == 1:
997 if address in self.wallet.frozen_addresses:
998 item.setBackgroundColor(1, QColor('lightblue'))
999 elif address in self.wallet.prioritized_addresses:
1000 item.setBackgroundColor(1, QColor('lightgreen'))
1003 def update_receive_tab(self):
1004 l = self.receive_list
1007 l.setColumnHidden(0, not self.receive_tab_mode == 1)
1008 l.setColumnHidden(3, not self.receive_tab_mode == 2)
1009 l.setColumnHidden(4, self.receive_tab_mode == 0)
1010 l.setColumnHidden(5, not self.receive_tab_mode == 1)
1011 l.setColumnWidth(0, 50)
1012 l.setColumnWidth(1, 310)
1013 l.setColumnWidth(2, 200)
1014 l.setColumnWidth(3, 130)
1015 l.setColumnWidth(4, 130)
1016 l.setColumnWidth(5, 10)
1020 for address in self.wallet.all_addresses():
1022 if self.wallet.is_change(address) and self.receive_tab_mode != 1:
1026 h = self.wallet.history.get(address,[])
1029 for tx_hash, tx_height in h:
1030 tx = self.wallet.transactions.get(tx_hash)
1038 if address in self.wallet.addresses:
1040 if gap > self.wallet.gap_limit:
1043 if address in self.wallet.addresses:
1046 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1047 item.setFont(0, QFont(MONOSPACE_FONT))
1048 item.setFont(1, QFont(MONOSPACE_FONT))
1049 item.setFont(3, QFont(MONOSPACE_FONT))
1050 self.update_receive_item(item)
1051 if is_red and address in self.wallet.addresses:
1052 item.setBackgroundColor(1, QColor('red'))
1053 l.addTopLevelItem(item)
1055 # we use column 1 because column 0 may be hidden
1056 l.setCurrentItem(l.topLevelItem(0),1)
1058 def show_contact_details(self, m):
1059 a = self.wallet.aliases.get(m)
1061 if a[0] in self.wallet.authorities.keys():
1062 s = self.wallet.authorities.get(a[0])
1065 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1066 QMessageBox.information(self, 'Alias', msg, 'OK')
1068 def update_contacts_tab(self):
1070 l = self.contacts_list
1072 l.setColumnWidth(0, 350)
1073 l.setColumnWidth(1, 330)
1074 l.setColumnWidth(2, 100)
1077 for alias, v in self.wallet.aliases.items():
1079 alias_targets.append(target)
1080 item = QTreeWidgetItem( [ target, alias, '-'] )
1081 item.setBackgroundColor(0, QColor('lightgray'))
1082 l.addTopLevelItem(item)
1084 for address in self.wallet.addressbook:
1085 if address in alias_targets: continue
1086 label = self.wallet.labels.get(address,'')
1088 for item in self.wallet.transactions.values():
1089 if address in item['outputs'] : n=n+1
1091 item = QTreeWidgetItem( [ address, label, tx] )
1092 item.setFont(0, QFont(MONOSPACE_FONT))
1093 l.addTopLevelItem(item)
1095 l.setCurrentItem(l.topLevelItem(0))
1097 def create_wall_tab(self):
1098 self.textbox = textbox = QTextEdit(self)
1099 textbox.setFont(QFont(MONOSPACE_FONT))
1100 textbox.setReadOnly(True)
1103 def create_status_bar(self):
1105 sb.setFixedHeight(35)
1106 if self.wallet.seed:
1107 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1108 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1109 if self.wallet.seed:
1110 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1111 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
1112 sb.addPermanentWidget( self.status_button )
1113 self.setStatusBar(sb)
1115 def new_contact_dialog(self):
1116 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1117 address = unicode(text)
1119 if self.wallet.is_valid(address):
1120 self.wallet.addressbook.append(address)
1122 self.update_contacts_tab()
1123 self.update_history_tab()
1124 self.update_completions()
1126 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1129 def show_seed_dialog(wallet, parent=None):
1131 QMessageBox.information(parent, _('Message'),
1132 _('No seed'), _('OK'))
1135 if wallet.use_encryption:
1136 password = parent.password_dialog()
1143 seed = wallet.pw_decode(wallet.seed, password)
1145 QMessageBox.warning(parent, _('Error'),
1146 _('Incorrect Password'), _('OK'))
1149 dialog = QDialog(None)
1151 dialog.setWindowTitle("Electrum")
1153 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1155 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1156 + _("Please write down or memorize these 12 words (order is important).") + " " \
1157 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1158 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1160 main_text = QLabel(msg)
1161 main_text.setWordWrap(True)
1164 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1171 copy_function = lambda: app.clipboard().setText(brainwallet)
1172 copy_button = QPushButton(_("Copy to Clipboard"))
1173 copy_button.clicked.connect(copy_function)
1175 show_qr_function = lambda: ElectrumWindow.show_qrcode(_("Seed"), seed)
1176 qr_button = QPushButton(_("View as QR Code"))
1177 qr_button.clicked.connect(show_qr_function)
1179 ok_button = QPushButton(_("OK"))
1180 ok_button.setDefault(True)
1181 ok_button.clicked.connect(dialog.accept)
1183 main_layout = QGridLayout()
1184 main_layout.addWidget(logo, 0, 0)
1185 main_layout.addWidget(main_text, 0, 1, 1, -1)
1186 main_layout.addWidget(copy_button, 1, 1)
1187 main_layout.addWidget(qr_button, 1, 2)
1188 main_layout.addWidget(ok_button, 1, 3)
1189 dialog.setLayout(main_layout)
1194 def show_qrcode(title, data):
1198 d.setWindowTitle(title)
1199 d.setMinimumSize(270, 300)
1200 vbox = QVBoxLayout()
1201 qrw = QRCodeWidget(data)
1203 vbox.addWidget(QLabel(data))
1204 hbox = QHBoxLayout()
1208 filename = "qrcode.bmp"
1209 bmp.save_qrcode(qrw.qr, filename)
1210 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1212 b = QPushButton(_("Print"))
1214 b.clicked.connect(print_qr)
1216 b = QPushButton(_("Close"))
1218 b.clicked.connect(d.accept)
1220 vbox.addLayout(hbox)
1224 def sign_message(self,address):
1225 if not address: return
1228 d.setWindowTitle('Sign Message')
1229 d.setMinimumSize(270, 350)
1231 tab_widget = QTabWidget()
1233 layout = QGridLayout(tab)
1235 sign_address = QLineEdit()
1236 sign_address.setText(address)
1237 layout.addWidget(QLabel(_('Address')), 1, 0)
1238 layout.addWidget(sign_address, 1, 1)
1240 sign_message = QTextEdit()
1241 layout.addWidget(QLabel(_('Message')), 2, 0)
1242 layout.addWidget(sign_message, 2, 1, 2, 1)
1244 sign_signature = QLineEdit()
1245 layout.addWidget(QLabel(_('Signature')), 3, 0)
1246 layout.addWidget(sign_signature, 3, 1)
1249 if self.wallet.use_encryption:
1250 password = self.password_dialog()
1257 signature = self.wallet.sign_message(sign_address.text(), str(sign_message.toPlainText()), password)
1258 sign_signature.setText(signature)
1259 except BaseException, e:
1260 self.show_message(str(e))
1263 hbox = QHBoxLayout()
1264 b = QPushButton(_("Sign"))
1266 b.clicked.connect(do_sign)
1267 b = QPushButton(_("Close"))
1268 b.clicked.connect(d.accept)
1270 layout.addLayout(hbox, 4, 1)
1271 tab_widget.addTab(tab, "Sign")
1275 layout = QGridLayout(tab)
1277 verify_address = QLineEdit()
1278 layout.addWidget(QLabel(_('Address')), 1, 0)
1279 layout.addWidget(verify_address, 1, 1)
1281 verify_message = QTextEdit()
1282 layout.addWidget(QLabel(_('Message')), 2, 0)
1283 layout.addWidget(verify_message, 2, 1, 2, 1)
1285 verify_signature = QLineEdit()
1286 layout.addWidget(QLabel(_('Signature')), 3, 0)
1287 layout.addWidget(verify_signature, 3, 1)
1291 self.wallet.verify_message(verify_address.text(), verify_signature.text(), str(verify_message.toPlainText()))
1292 self.show_message("Signature verified")
1293 except BaseException, e:
1294 self.show_message(str(e))
1297 hbox = QHBoxLayout()
1298 b = QPushButton(_("Verify"))
1299 b.clicked.connect(do_verify)
1301 b = QPushButton(_("Close"))
1302 b.clicked.connect(d.accept)
1304 layout.addLayout(hbox, 4, 1)
1305 tab_widget.addTab(tab, "Verify")
1307 vbox = QVBoxLayout()
1308 vbox.addWidget(tab_widget)
1313 def toggle_QR_window(self, show):
1314 if show and not self.qr_window:
1315 self.qr_window = QR_Window()
1316 self.qr_window.setVisible(True)
1317 self.qr_window_geometry = self.qr_window.geometry()
1318 item = self.receive_list.currentItem()
1320 address = str(item.text(1))
1321 label = self.wallet.labels.get(address)
1322 amount = self.wallet.requested_amounts.get(address)
1323 self.qr_window.set_content( address, label, amount )
1325 elif show and self.qr_window and not self.qr_window.isVisible():
1326 self.qr_window.setVisible(True)
1327 self.qr_window.setGeometry(self.qr_window_geometry)
1329 elif not show and self.qr_window and self.qr_window.isVisible():
1330 self.qr_window_geometry = self.qr_window.geometry()
1331 self.qr_window.setVisible(False)
1333 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1334 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1335 self.receive_list.setColumnWidth(2, 200)
1338 def question(self, msg):
1339 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1341 def show_message(self, msg):
1342 QMessageBox.information(self, _('Message'), msg, _('OK'))
1344 def password_dialog(self ):
1351 vbox = QVBoxLayout()
1352 msg = _('Please enter your password')
1353 vbox.addWidget(QLabel(msg))
1355 grid = QGridLayout()
1357 grid.addWidget(QLabel(_('Password')), 1, 0)
1358 grid.addWidget(pw, 1, 1)
1359 vbox.addLayout(grid)
1361 vbox.addLayout(ok_cancel_buttons(d))
1364 if not d.exec_(): return
1365 return unicode(pw.text())
1372 def change_password_dialog( wallet, parent=None ):
1375 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1383 new_pw = QLineEdit()
1384 new_pw.setEchoMode(2)
1385 conf_pw = QLineEdit()
1386 conf_pw.setEchoMode(2)
1388 vbox = QVBoxLayout()
1390 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1391 +_('To disable wallet encryption, enter an empty new password.')) \
1392 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1394 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1395 +_("Leave these fields empty if you want to disable encryption.")
1396 vbox.addWidget(QLabel(msg))
1398 grid = QGridLayout()
1401 if wallet.use_encryption:
1402 grid.addWidget(QLabel(_('Password')), 1, 0)
1403 grid.addWidget(pw, 1, 1)
1405 grid.addWidget(QLabel(_('New Password')), 2, 0)
1406 grid.addWidget(new_pw, 2, 1)
1408 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1409 grid.addWidget(conf_pw, 3, 1)
1410 vbox.addLayout(grid)
1412 vbox.addLayout(ok_cancel_buttons(d))
1415 if not d.exec_(): return
1417 password = unicode(pw.text()) if wallet.use_encryption else None
1418 new_password = unicode(new_pw.text())
1419 new_password2 = unicode(conf_pw.text())
1422 seed = wallet.pw_decode( wallet.seed, password)
1424 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1427 if new_password != new_password2:
1428 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1429 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1431 wallet.update_password(seed, password, new_password)
1434 def seed_dialog(wallet, parent=None):
1438 vbox = QVBoxLayout()
1439 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1440 vbox.addWidget(QLabel(msg))
1442 grid = QGridLayout()
1445 seed_e = QLineEdit()
1446 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1447 grid.addWidget(seed_e, 1, 1)
1451 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1452 grid.addWidget(gap_e, 2, 1)
1453 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1454 vbox.addLayout(grid)
1456 vbox.addLayout(ok_cancel_buttons(d))
1459 if not d.exec_(): return
1462 gap = int(unicode(gap_e.text()))
1464 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1468 seed = unicode(seed_e.text())
1471 print_error("Warning: Not hex, trying decode")
1473 seed = mnemonic.mn_decode( seed.split(' ') )
1475 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1478 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1481 wallet.seed = str(seed)
1482 #print repr(wallet.seed)
1483 wallet.gap_limit = gap
1488 def settings_dialog(self):
1490 d.setWindowTitle(_('Electrum Settings'))
1492 vbox = QVBoxLayout()
1494 tabs = QTabWidget(self)
1495 vbox.addWidget(tabs)
1498 grid_wallet = QGridLayout(tab)
1499 grid_wallet.setColumnStretch(0,1)
1500 tabs.addTab(tab, _('Wallet') )
1503 grid_ui = QGridLayout(tab2)
1504 grid_ui.setColumnStretch(0,1)
1505 tabs.addTab(tab2, _('Display') )
1507 fee_label = QLabel(_('Transaction fee'))
1508 grid_wallet.addWidget(fee_label, 2, 0)
1510 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1511 grid_wallet.addWidget(fee_e, 2, 1)
1512 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1513 + _('Recommended value') + ': 0.001'
1514 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1515 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1516 if not self.config.is_modifiable('fee'):
1517 for w in [fee_e, fee_label]: w.setEnabled(False)
1519 nz_label = QLabel(_('Display zeros'))
1520 grid_ui.addWidget(nz_label, 3, 0)
1522 nz_e.setText("%d"% self.wallet.num_zeros)
1523 grid_ui.addWidget(nz_e, 3, 1)
1524 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1525 grid_ui.addWidget(HelpButton(msg), 3, 2)
1526 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1527 if not self.config.is_modifiable('num_zeros'):
1528 for w in [nz_e, nz_label]: w.setEnabled(False)
1531 usechange_label = QLabel(_('Use change addresses'))
1532 grid_wallet.addWidget(usechange_label, 5, 0)
1533 usechange_combo = QComboBox()
1534 usechange_combo.addItems(['Yes', 'No'])
1535 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1536 grid_wallet.addWidget(usechange_combo, 5, 1)
1537 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1538 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1540 gap_label = QLabel(_('Gap limit'))
1541 grid_wallet.addWidget(gap_label, 6, 0)
1543 gap_e.setText("%d"% self.wallet.gap_limit)
1544 grid_wallet.addWidget(gap_e, 6, 1)
1545 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1546 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1547 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1548 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1549 + _('Warning') + ': ' \
1550 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1551 + _('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'
1552 grid_wallet.addWidget(HelpButton(msg), 6, 2)
1553 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1554 if not self.config.is_modifiable('gap_limit'):
1555 for w in [gap_e, gap_label]: w.setEnabled(False)
1557 gui_label=QLabel(_('Default GUI') + ':')
1558 grid_ui.addWidget(gui_label , 7, 0)
1559 gui_combo = QComboBox()
1560 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1561 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1562 if index==-1: index = 1
1563 gui_combo.setCurrentIndex(index)
1564 grid_ui.addWidget(gui_combo, 7, 1)
1565 grid_ui.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1566 if not self.config.is_modifiable('gui'):
1567 for w in [gui_combo, gui_label]: w.setEnabled(False)
1569 lang_label=QLabel(_('Language') + ':')
1570 grid_ui.addWidget(lang_label , 8, 0)
1571 lang_combo = QComboBox()
1572 languages = {'':_('Default'), 'br':_('Brasilian'), 'cs':_('Czech'), 'de':_('German'),
1573 'eo':_('Esperanto'), 'en':_('English'), 'es':_('Spanish'), 'fr':_('French'),
1574 'it':_('Italian'), 'lv':_('Latvian'), 'nl':_('Dutch'), 'ru':_('Russian'),
1575 'sl':_('Slovenian'), 'vi':_('Vietnamese'), 'zh':_('Chinese')
1577 lang_combo.addItems(languages.values())
1579 index = languages.keys().index(self.config.get("language",''))
1582 lang_combo.setCurrentIndex(index)
1583 grid_ui.addWidget(lang_combo, 8, 1)
1584 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart). ')), 8, 2)
1585 if not self.config.is_modifiable('language'):
1586 for w in [lang_combo, lang_label]: w.setEnabled(False)
1589 view_label=QLabel(_('Receive Tab') + ':')
1590 grid_ui.addWidget(view_label , 9, 0)
1591 view_combo = QComboBox()
1592 view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
1593 view_combo.setCurrentIndex(self.receive_tab_mode)
1594 grid_ui.addWidget(view_combo, 9, 1)
1595 hh = _('This selects the interaction mode of the "Receive" tab. ') + '\n\n' \
1596 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1597 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
1598 + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n'
1600 grid_ui.addWidget(HelpButton(hh), 9, 2)
1602 vbox.addLayout(ok_cancel_buttons(d))
1606 if not d.exec_(): return
1608 fee = unicode(fee_e.text())
1610 fee = int( 100000000 * Decimal(fee) )
1612 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1615 if self.wallet.fee != fee:
1616 self.wallet.fee = fee
1619 nz = unicode(nz_e.text())
1624 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1627 if self.wallet.num_zeros != nz:
1628 self.wallet.num_zeros = nz
1629 self.config.set_key('num_zeros', nz, True)
1630 self.update_history_tab()
1631 self.update_receive_tab()
1633 usechange_result = usechange_combo.currentIndex() == 0
1634 if self.wallet.use_change != usechange_result:
1635 self.wallet.use_change = usechange_result
1636 self.config.set_key('use_change', self.wallet.use_change, True)
1639 n = int(gap_e.text())
1641 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1644 if self.wallet.gap_limit != n:
1645 r = self.wallet.change_gap_limit(n)
1647 self.update_receive_tab()
1648 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1650 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1652 need_restart = False
1654 gui_request = str(gui_combo.currentText()).lower()
1655 if gui_request != self.config.get('gui'):
1656 self.config.set_key('gui', gui_request, True)
1659 lang_request = languages.keys()[lang_combo.currentIndex()]
1660 if lang_request != self.config.get('language'):
1661 self.config.set_key("language", lang_request, True)
1665 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
1667 self.receive_tab_set_mode(view_combo.currentIndex())
1671 def network_dialog(wallet, parent=None):
1672 interface = wallet.interface
1674 if interface.is_connected:
1675 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1677 status = _("Not connected")
1678 server = interface.server
1681 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1682 server = interface.server
1684 plist, servers_list = interface.get_servers_list()
1688 d.setWindowTitle(_('Server'))
1689 d.setMinimumSize(375, 20)
1691 vbox = QVBoxLayout()
1694 hbox = QHBoxLayout()
1696 l.setPixmap(QPixmap(":icons/network.png"))
1699 hbox.addWidget(QLabel(status))
1701 vbox.addLayout(hbox)
1705 grid = QGridLayout()
1707 vbox.addLayout(grid)
1710 server_protocol = QComboBox()
1711 server_host = QLineEdit()
1712 server_host.setFixedWidth(200)
1713 server_port = QLineEdit()
1714 server_port.setFixedWidth(60)
1716 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1717 protocol_letters = 'thsg'
1718 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1719 server_protocol.addItems(protocol_names)
1721 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1722 grid.addWidget(server_protocol, 0, 1)
1723 grid.addWidget(server_host, 0, 2)
1724 grid.addWidget(server_port, 0, 3)
1726 def change_protocol(p):
1727 protocol = protocol_letters[p]
1728 host = unicode(server_host.text())
1729 pp = plist.get(host,DEFAULT_PORTS)
1730 if protocol not in pp.keys():
1731 protocol = pp.keys()[0]
1733 server_host.setText( host )
1734 server_port.setText( port )
1736 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1738 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1739 servers_list_widget = QTreeWidget(parent)
1740 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1741 servers_list_widget.setMaximumHeight(150)
1742 servers_list_widget.setColumnWidth(0, 240)
1743 for _host in servers_list.keys():
1744 _type = 'P' if servers_list[_host].get('pruning') else 'F'
1745 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1747 def change_server(host, protocol=None):
1748 pp = plist.get(host,DEFAULT_PORTS)
1750 port = pp.get(protocol)
1751 if not port: protocol = None
1754 if 't' in pp.keys():
1756 port = pp.get(protocol)
1758 protocol = pp.keys()[0]
1759 port = pp.get(protocol)
1761 server_host.setText( host )
1762 server_port.setText( port )
1763 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1765 if not plist: return
1766 for p in protocol_letters:
1767 i = protocol_letters.index(p)
1768 j = server_protocol.model().index(i,0)
1769 if p not in pp.keys():
1770 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1772 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1776 host, port, protocol = server.split(':')
1777 change_server(host,protocol)
1779 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1780 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1782 if not wallet.config.is_modifiable('server'):
1783 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1786 autocycle_cb = QCheckBox('Try random servers if disconnected')
1787 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
1788 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
1789 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
1792 proxy_mode = QComboBox()
1793 proxy_host = QLineEdit()
1794 proxy_host.setFixedWidth(200)
1795 proxy_port = QLineEdit()
1796 proxy_port.setFixedWidth(60)
1797 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1799 def check_for_disable(index = False):
1800 if proxy_mode.currentText() != 'NONE':
1801 proxy_host.setEnabled(True)
1802 proxy_port.setEnabled(True)
1804 proxy_host.setEnabled(False)
1805 proxy_port.setEnabled(False)
1808 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1810 if not wallet.config.is_modifiable('proxy'):
1811 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1813 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1814 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1815 proxy_host.setText(proxy_config.get("host"))
1816 proxy_port.setText(proxy_config.get("port"))
1818 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1819 grid.addWidget(proxy_mode, 2, 1)
1820 grid.addWidget(proxy_host, 2, 2)
1821 grid.addWidget(proxy_port, 2, 3)
1824 vbox.addLayout(ok_cancel_buttons(d))
1827 if not d.exec_(): return
1829 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1830 if proxy_mode.currentText() != 'NONE':
1831 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1835 wallet.config.set_key("proxy", proxy, True)
1836 wallet.config.set_key("server", server, True)
1837 interface.set_server(server, proxy)
1838 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
1841 def closeEvent(self, event):
1843 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1849 def __init__(self, wallet, config, app=None):
1850 self.wallet = wallet
1851 self.config = config
1853 self.app = QApplication(sys.argv)
1856 def restore_or_create(self):
1857 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1858 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1859 if r==2: return None
1860 return 'restore' if r==1 else 'create'
1862 def seed_dialog(self):
1863 return ElectrumWindow.seed_dialog( self.wallet )
1865 def network_dialog(self):
1866 return ElectrumWindow.network_dialog( self.wallet, parent=None )
1869 def show_seed(self):
1870 ElectrumWindow.show_seed_dialog(self.wallet)
1873 def password_dialog(self):
1874 ElectrumWindow.change_password_dialog(self.wallet)
1877 def restore_wallet(self):
1878 wallet = self.wallet
1879 # wait until we are connected, because the user might have selected another server
1880 if not wallet.interface.is_connected:
1881 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1882 waiting_dialog(waiting)
1884 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1885 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1887 wallet.set_up_to_date(False)
1888 wallet.interface.poke('synchronizer')
1889 waiting_dialog(waiting)
1890 if wallet.is_found():
1891 print_error( "Recovery successful" )
1893 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1900 w = ElectrumWindow(self.wallet, self.config)
1901 if url: w.set_url(url)