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)
171 qp = QtGui.QPainter()
175 qp.drawRect(0, 0, 198, 198)
179 k = self.qr.getModuleCount()
180 qp = QtGui.QPainter()
183 boxsize = min(r.width(), r.height())*0.8/k
185 left = (r.width() - size)/2
186 top = (r.height() - size)/2
190 if self.qr.isDark(r, c):
196 qp.drawRect(left+c*boxsize, top+r*boxsize, boxsize, boxsize)
201 class QR_Window(QWidget):
204 QWidget.__init__(self)
205 self.setWindowTitle('Electrum - Invoice')
206 self.setMinimumSize(800, 250)
210 self.setFocusPolicy(QtCore.Qt.NoFocus)
212 main_box = QHBoxLayout()
214 self.qrw = QRCodeWidget()
215 main_box.addWidget(self.qrw, 1)
218 main_box.addLayout(vbox)
220 self.address_label = QLabel("")
221 self.address_label.setFont(QFont(MONOSPACE_FONT))
222 vbox.addWidget(self.address_label)
224 self.label_label = QLabel("")
225 vbox.addWidget(self.label_label)
227 self.amount_label = QLabel("")
228 vbox.addWidget(self.amount_label)
231 self.setLayout(main_box)
234 def set_content(self, addr, label, amount):
236 address_text = "<span style='font-size: 18pt'>%s</span>" % addr if addr else ""
237 self.address_label.setText(address_text)
240 amount_text = "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % format_satoshis(amount) if amount else ""
241 self.amount_label.setText(amount_text)
244 label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
245 self.label_label.setText(label_text)
247 msg = 'bitcoin:'+self.address
248 if self.amount is not None:
249 msg += '?amount=%s'%(str( Decimal(self.amount) /100000000))
250 if self.label is not None:
251 msg += '&label=%s'%(self.label)
252 elif self.label is not None:
253 msg += '?label=%s'%(self.label)
255 self.qrw.set_addr( msg )
260 def waiting_dialog(f):
266 w.setWindowTitle('Electrum')
276 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
281 def ok_cancel_buttons(dialog):
284 b = QPushButton("OK")
286 b.clicked.connect(dialog.accept)
287 b = QPushButton("Cancel")
289 b.clicked.connect(dialog.reject)
293 class ElectrumWindow(QMainWindow):
295 def __init__(self, wallet, config):
296 QMainWindow.__init__(self)
299 self.wallet.interface.register_callback('updated', self.update_callback)
300 self.wallet.interface.register_callback('connected', self.update_callback)
301 self.wallet.interface.register_callback('disconnected', self.update_callback)
302 self.wallet.interface.register_callback('disconnecting', self.update_callback)
304 self.receive_tab_mode = config.get('qt_receive_tab_mode', 0)
305 self.merchant_name = config.get('merchant_name', 'Invoice')
307 self.qr_window = None
308 self.funds_error = False
309 self.completions = QStringListModel()
311 self.tabs = tabs = QTabWidget(self)
312 tabs.addTab(self.create_history_tab(), _('History') )
313 tabs.addTab(self.create_send_tab(), _('Send') )
314 tabs.addTab(self.create_receive_tab(), _('Receive') )
315 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
316 tabs.addTab(self.create_wall_tab(), _('Wall') )
317 tabs.setMinimumSize(600, 400)
318 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
319 self.setCentralWidget(tabs)
320 self.create_status_bar()
321 self.toggle_QR_window(self.receive_tab_mode == 2)
323 g = self.config.get("winpos-qt",[100, 100, 840, 400])
324 self.setGeometry(g[0], g[1], g[2], g[3])
325 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
326 if not self.wallet.seed: title += ' [seedless]'
327 self.setWindowTitle( title )
329 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
330 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
331 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
332 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
334 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
335 #self.connect(self, SIGNAL('editamount'), self.edit_amount)
336 self.history_list.setFocus(True)
338 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
339 if platform.system() == 'Windows':
340 n = 3 if self.wallet.seed else 2
341 tabs.setCurrentIndex (n)
342 tabs.setCurrentIndex (0)
345 QMainWindow.close(self)
347 self.qr_window.close()
348 self.qr_window = None
350 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.setForeground(3, QBrush(QColor("#BC1E1E")))
607 item.setToolTip(0, tx_hash)
609 item.setForeground(2, QBrush(QColor('grey')))
611 item.setIcon(0, icon)
612 self.history_list.insertTopLevelItem(0,item)
615 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
618 def create_send_tab(self):
623 grid.setColumnMinimumWidth(3,300)
624 grid.setColumnStretch(5,1)
626 self.payto_e = QLineEdit()
627 grid.addWidget(QLabel(_('Pay to')), 1, 0)
628 grid.addWidget(self.payto_e, 1, 1, 1, 3)
631 qrcode = qrscanner.scan_qr()
632 if 'address' in qrcode:
633 self.payto_e.setText(qrcode['address'])
634 if 'amount' in qrcode:
635 self.amount_e.setText(str(qrcode['amount']))
636 if 'label' in qrcode:
637 self.message_e.setText(qrcode['label'])
638 if 'message' in qrcode:
639 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
642 if qrscanner.is_available():
643 b = QPushButton(_("Scan QR code"))
644 b.clicked.connect(fill_from_qr)
645 grid.addWidget(b, 1, 5)
647 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)
649 completer = QCompleter()
650 completer.setCaseSensitivity(False)
651 self.payto_e.setCompleter(completer)
652 completer.setModel(self.completions)
654 self.message_e = QLineEdit()
655 grid.addWidget(QLabel(_('Description')), 2, 0)
656 grid.addWidget(self.message_e, 2, 1, 1, 3)
657 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)
659 self.amount_e = QLineEdit()
660 grid.addWidget(QLabel(_('Amount')), 3, 0)
661 grid.addWidget(self.amount_e, 3, 1, 1, 2)
662 grid.addWidget(HelpButton(
663 _('Amount to be sent.') + '\n\n' \
664 + _('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)
666 self.fee_e = QLineEdit()
667 grid.addWidget(QLabel(_('Fee')), 4, 0)
668 grid.addWidget(self.fee_e, 4, 1, 1, 2)
669 grid.addWidget(HelpButton(
670 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
671 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
672 + _('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)
674 b = EnterButton(_("Send"), self.do_send)
675 grid.addWidget(b, 6, 1)
677 b = EnterButton(_("Clear"),self.do_clear)
678 grid.addWidget(b, 6, 2)
680 self.payto_sig = QLabel('')
681 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
683 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
684 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
693 def entry_changed( is_fee ):
694 self.funds_error = False
695 amount = numbify(self.amount_e)
696 fee = numbify(self.fee_e)
697 if not is_fee: fee = None
700 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
702 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
705 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
708 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
709 self.funds_error = True
710 self.amount_e.setPalette(palette)
711 self.fee_e.setPalette(palette)
714 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
715 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
720 def update_completions(self):
722 for addr,label in self.wallet.labels.items():
723 if addr in self.wallet.addressbook:
724 l.append( label + ' <' + addr + '>')
725 l = l + self.wallet.aliases.keys()
727 self.completions.setStringList(l)
733 label = unicode( self.message_e.text() )
734 r = unicode( self.payto_e.text() )
738 m1 = re.match(ALIAS_REGEXP, r)
739 # label or alias, with address in brackets
740 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
743 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
747 to_address = m2.group(2)
751 if not self.wallet.is_valid(to_address):
752 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
756 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
758 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
761 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
763 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
766 if self.wallet.use_encryption:
767 password = self.password_dialog()
774 tx = self.wallet.mktx( [(to_address, amount)], label, password, fee)
775 except BaseException, e:
776 self.show_message(str(e))
780 h = self.wallet.send_tx(tx)
781 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
782 status, msg = self.wallet.receive_tx( h )
784 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
786 self.update_contacts_tab()
788 QMessageBox.warning(self, _('Error'), msg, _('OK'))
790 filename = 'unsigned_tx'
791 f = open(filename,'w')
794 QMessageBox.information(self, _('Unsigned transaction'), _("Unsigned transaction was saved to file:") + " " +filename, _('OK'))
797 def set_url(self, url):
798 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
799 self.tabs.setCurrentIndex(1)
800 label = self.wallet.labels.get(payto)
801 m_addr = label + ' <'+ payto+'>' if label else payto
802 self.payto_e.setText(m_addr)
804 self.message_e.setText(message)
805 self.amount_e.setText(amount)
807 self.set_frozen(self.payto_e,True)
808 self.set_frozen(self.amount_e,True)
809 self.set_frozen(self.message_e,True)
810 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
812 self.payto_sig.setVisible(False)
815 self.payto_sig.setVisible(False)
816 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
818 self.set_frozen(e,False)
820 def set_frozen(self,entry,frozen):
822 entry.setReadOnly(True)
823 entry.setFrame(False)
825 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
826 entry.setPalette(palette)
828 entry.setReadOnly(False)
831 palette.setColor(entry.backgroundRole(), QColor('white'))
832 entry.setPalette(palette)
835 def toggle_freeze(self,addr):
837 if addr in self.wallet.frozen_addresses:
838 self.wallet.unfreeze(addr)
840 self.wallet.freeze(addr)
841 self.update_receive_tab()
843 def toggle_priority(self,addr):
845 if addr in self.wallet.prioritized_addresses:
846 self.wallet.unprioritize(addr)
848 self.wallet.prioritize(addr)
849 self.update_receive_tab()
852 def create_list_tab(self, headers):
853 "generic tab creation method"
854 l = MyTreeWidget(self)
855 l.setColumnCount( len(headers) )
856 l.setHeaderLabels( headers )
866 vbox.addWidget(buttons)
871 buttons.setLayout(hbox)
876 def create_receive_tab(self):
877 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
878 l.setContextMenuPolicy(Qt.CustomContextMenu)
879 l.customContextMenuRequested.connect(self.create_receive_menu)
880 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
881 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
882 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
883 self.receive_list = l
884 self.receive_buttons_hbox = hbox
890 def receive_tab_set_mode(self, i):
891 self.receive_tab_mode = i
892 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
894 self.update_receive_tab()
895 self.toggle_QR_window(self.receive_tab_mode == 2)
898 def create_contacts_tab(self):
899 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
900 l.setContextMenuPolicy(Qt.CustomContextMenu)
901 l.customContextMenuRequested.connect(self.create_contact_menu)
902 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
903 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
904 self.contacts_list = l
905 self.contacts_buttons_hbox = hbox
906 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
911 def create_receive_menu(self, position):
912 # fixme: this function apparently has a side effect.
913 # if it is not called the menu pops up several times
914 #self.receive_list.selectedIndexes()
916 item = self.receive_list.itemAt(position)
918 addr = unicode(item.text(1))
920 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
921 if self.receive_tab_mode == 2:
922 menu.addAction(_("Request amount"), lambda: self.edit_amount())
923 menu.addAction(_("View QR"), lambda: ElectrumWindow.show_qrcode("Address","bitcoin:"+addr) )
924 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
925 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
927 if self.receive_tab_mode == 1:
928 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
929 menu.addAction(t, lambda: self.toggle_freeze(addr))
930 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
931 menu.addAction(t, lambda: self.toggle_priority(addr))
933 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
936 def payto(self, x, is_alias):
943 label = self.wallet.labels.get(addr)
944 m_addr = label + ' <' + addr + '>' if label else addr
945 self.tabs.setCurrentIndex(1)
946 self.payto_e.setText(m_addr)
947 self.amount_e.setFocus()
949 def delete_contact(self, x, is_alias):
950 if self.question("Do you want to remove %s from your list of contacts?"%x):
951 if not is_alias and x in self.wallet.addressbook:
952 self.wallet.addressbook.remove(x)
953 if x in self.wallet.labels.keys():
954 self.wallet.labels.pop(x)
955 elif is_alias and x in self.wallet.aliases:
956 self.wallet.aliases.pop(x)
957 self.update_history_tab()
958 self.update_contacts_tab()
959 self.update_completions()
961 def create_contact_menu(self, position):
962 # fixme: this function apparently has a side effect.
963 # if it is not called the menu pops up several times
964 #self.contacts_list.selectedIndexes()
966 item = self.contacts_list.itemAt(position)
968 addr = unicode(item.text(0))
969 label = unicode(item.text(1))
970 is_alias = label in self.wallet.aliases.keys()
971 x = label if is_alias else addr
973 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
974 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
975 menu.addAction(_("View QR code"),lambda: self.show_qrcode("Address","bitcoin:"+addr))
977 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
979 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
980 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
981 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
984 def update_receive_item(self, item):
985 address = str( item.data(1,0).toString() )
987 flags = self.wallet.get_address_flags(address)
988 item.setData(0,0,flags)
990 label = self.wallet.labels.get(address,'')
991 item.setData(2,0,label)
993 amount = self.wallet.requested_amounts.get(address,None)
994 amount_str = format_satoshis( amount, False, self.wallet.num_zeros ) if amount is not None else ""
995 item.setData(3,0,amount_str)
997 c, u = self.wallet.get_addr_balance(address)
998 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
999 item.setData(4,0,balance)
1001 if self.receive_tab_mode == 1:
1002 if address in self.wallet.frozen_addresses:
1003 item.setBackgroundColor(1, QColor('lightblue'))
1004 elif address in self.wallet.prioritized_addresses:
1005 item.setBackgroundColor(1, QColor('lightgreen'))
1008 def update_receive_tab(self):
1009 l = self.receive_list
1012 l.setColumnHidden(0, not self.receive_tab_mode == 1)
1013 l.setColumnHidden(3, not self.receive_tab_mode == 2)
1014 l.setColumnHidden(4, self.receive_tab_mode == 0)
1015 l.setColumnHidden(5, not self.receive_tab_mode == 1)
1016 l.setColumnWidth(0, 50)
1017 l.setColumnWidth(1, 310)
1018 l.setColumnWidth(2, 200)
1019 l.setColumnWidth(3, 130)
1020 l.setColumnWidth(4, 130)
1021 l.setColumnWidth(5, 10)
1025 for address in self.wallet.all_addresses():
1027 if self.wallet.is_change(address) and self.receive_tab_mode != 1:
1031 h = self.wallet.history.get(address,[])
1034 for tx_hash, tx_height in h:
1035 tx = self.wallet.transactions.get(tx_hash)
1043 if address in self.wallet.addresses:
1045 if gap > self.wallet.gap_limit:
1048 if address in self.wallet.addresses:
1051 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1052 item.setFont(0, QFont(MONOSPACE_FONT))
1053 item.setFont(1, QFont(MONOSPACE_FONT))
1054 item.setFont(3, QFont(MONOSPACE_FONT))
1055 self.update_receive_item(item)
1056 if is_red and address in self.wallet.addresses:
1057 item.setBackgroundColor(1, QColor('red'))
1058 l.addTopLevelItem(item)
1060 # we use column 1 because column 0 may be hidden
1061 l.setCurrentItem(l.topLevelItem(0),1)
1063 def show_contact_details(self, m):
1064 a = self.wallet.aliases.get(m)
1066 if a[0] in self.wallet.authorities.keys():
1067 s = self.wallet.authorities.get(a[0])
1070 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1071 QMessageBox.information(self, 'Alias', msg, 'OK')
1073 def update_contacts_tab(self):
1075 l = self.contacts_list
1077 l.setColumnWidth(0, 350)
1078 l.setColumnWidth(1, 330)
1079 l.setColumnWidth(2, 100)
1082 for alias, v in self.wallet.aliases.items():
1084 alias_targets.append(target)
1085 item = QTreeWidgetItem( [ target, alias, '-'] )
1086 item.setBackgroundColor(0, QColor('lightgray'))
1087 l.addTopLevelItem(item)
1089 for address in self.wallet.addressbook:
1090 if address in alias_targets: continue
1091 label = self.wallet.labels.get(address,'')
1093 for item in self.wallet.transactions.values():
1094 if address in item['outputs'] : n=n+1
1096 item = QTreeWidgetItem( [ address, label, tx] )
1097 item.setFont(0, QFont(MONOSPACE_FONT))
1098 l.addTopLevelItem(item)
1100 l.setCurrentItem(l.topLevelItem(0))
1102 def create_wall_tab(self):
1103 self.textbox = textbox = QTextEdit(self)
1104 textbox.setFont(QFont(MONOSPACE_FONT))
1105 textbox.setReadOnly(True)
1108 def create_status_bar(self):
1110 sb.setFixedHeight(35)
1111 if self.wallet.seed:
1112 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1113 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1114 if self.wallet.seed:
1115 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1116 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
1117 sb.addPermanentWidget( self.status_button )
1118 self.setStatusBar(sb)
1120 def new_contact_dialog(self):
1121 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1122 address = unicode(text)
1124 if self.wallet.is_valid(address):
1125 self.wallet.addressbook.append(address)
1127 self.update_contacts_tab()
1128 self.update_history_tab()
1129 self.update_completions()
1131 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1134 def show_seed_dialog(wallet, parent=None):
1136 QMessageBox.information(parent, _('Message'),
1137 _('No seed'), _('OK'))
1140 if wallet.use_encryption:
1141 password = parent.password_dialog()
1148 seed = wallet.pw_decode(wallet.seed, password)
1150 QMessageBox.warning(parent, _('Error'),
1151 _('Incorrect Password'), _('OK'))
1154 dialog = QDialog(None)
1156 dialog.setWindowTitle("Electrum")
1158 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1160 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1161 + _("Please write down or memorize these 12 words (order is important).") + " " \
1162 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1163 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1165 main_text = QLabel(msg)
1166 main_text.setWordWrap(True)
1169 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1176 copy_function = lambda: app.clipboard().setText(brainwallet)
1177 copy_button = QPushButton(_("Copy to Clipboard"))
1178 copy_button.clicked.connect(copy_function)
1180 show_qr_function = lambda: ElectrumWindow.show_qrcode(_("Seed"), seed)
1181 qr_button = QPushButton(_("View as QR Code"))
1182 qr_button.clicked.connect(show_qr_function)
1184 ok_button = QPushButton(_("OK"))
1185 ok_button.setDefault(True)
1186 ok_button.clicked.connect(dialog.accept)
1188 main_layout = QGridLayout()
1189 main_layout.addWidget(logo, 0, 0)
1190 main_layout.addWidget(main_text, 0, 1, 1, -1)
1191 main_layout.addWidget(copy_button, 1, 1)
1192 main_layout.addWidget(qr_button, 1, 2)
1193 main_layout.addWidget(ok_button, 1, 3)
1194 dialog.setLayout(main_layout)
1199 def show_qrcode(title, data):
1203 d.setWindowTitle(title)
1204 d.setMinimumSize(270, 300)
1205 vbox = QVBoxLayout()
1206 qrw = QRCodeWidget(data)
1207 vbox.addWidget(qrw, 1)
1208 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1209 hbox = QHBoxLayout()
1213 filename = "qrcode.bmp"
1214 bmp.save_qrcode(qrw.qr, filename)
1215 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1217 b = QPushButton(_("Print"))
1219 b.clicked.connect(print_qr)
1221 b = QPushButton(_("Close"))
1223 b.clicked.connect(d.accept)
1225 vbox.addLayout(hbox)
1229 def sign_message(self,address):
1230 if not address: return
1233 d.setWindowTitle('Sign Message')
1234 d.setMinimumSize(270, 350)
1236 tab_widget = QTabWidget()
1238 layout = QGridLayout(tab)
1240 sign_address = QLineEdit()
1241 sign_address.setText(address)
1242 layout.addWidget(QLabel(_('Address')), 1, 0)
1243 layout.addWidget(sign_address, 1, 1)
1245 sign_message = QTextEdit()
1246 layout.addWidget(QLabel(_('Message')), 2, 0)
1247 layout.addWidget(sign_message, 2, 1, 2, 1)
1249 sign_signature = QLineEdit()
1250 layout.addWidget(QLabel(_('Signature')), 3, 0)
1251 layout.addWidget(sign_signature, 3, 1)
1254 if self.wallet.use_encryption:
1255 password = self.password_dialog()
1262 signature = self.wallet.sign_message(sign_address.text(), str(sign_message.toPlainText()), password)
1263 sign_signature.setText(signature)
1264 except BaseException, e:
1265 self.show_message(str(e))
1268 hbox = QHBoxLayout()
1269 b = QPushButton(_("Sign"))
1271 b.clicked.connect(do_sign)
1272 b = QPushButton(_("Close"))
1273 b.clicked.connect(d.accept)
1275 layout.addLayout(hbox, 4, 1)
1276 tab_widget.addTab(tab, "Sign")
1280 layout = QGridLayout(tab)
1282 verify_address = QLineEdit()
1283 layout.addWidget(QLabel(_('Address')), 1, 0)
1284 layout.addWidget(verify_address, 1, 1)
1286 verify_message = QTextEdit()
1287 layout.addWidget(QLabel(_('Message')), 2, 0)
1288 layout.addWidget(verify_message, 2, 1, 2, 1)
1290 verify_signature = QLineEdit()
1291 layout.addWidget(QLabel(_('Signature')), 3, 0)
1292 layout.addWidget(verify_signature, 3, 1)
1296 self.wallet.verify_message(verify_address.text(), verify_signature.text(), str(verify_message.toPlainText()))
1297 self.show_message("Signature verified")
1298 except BaseException, e:
1299 self.show_message(str(e))
1302 hbox = QHBoxLayout()
1303 b = QPushButton(_("Verify"))
1304 b.clicked.connect(do_verify)
1306 b = QPushButton(_("Close"))
1307 b.clicked.connect(d.accept)
1309 layout.addLayout(hbox, 4, 1)
1310 tab_widget.addTab(tab, "Verify")
1312 vbox = QVBoxLayout()
1313 vbox.addWidget(tab_widget)
1318 def toggle_QR_window(self, show):
1319 if show and not self.qr_window:
1320 self.qr_window = QR_Window()
1321 self.qr_window.setVisible(True)
1322 self.qr_window_geometry = self.qr_window.geometry()
1323 item = self.receive_list.currentItem()
1325 address = str(item.text(1))
1326 label = self.wallet.labels.get(address)
1327 amount = self.wallet.requested_amounts.get(address)
1328 self.qr_window.set_content( address, label, amount )
1330 elif show and self.qr_window and not self.qr_window.isVisible():
1331 self.qr_window.setVisible(True)
1332 self.qr_window.setGeometry(self.qr_window_geometry)
1334 elif not show and self.qr_window and self.qr_window.isVisible():
1335 self.qr_window_geometry = self.qr_window.geometry()
1336 self.qr_window.setVisible(False)
1338 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1339 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1340 self.receive_list.setColumnWidth(2, 200)
1343 def question(self, msg):
1344 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1346 def show_message(self, msg):
1347 QMessageBox.information(self, _('Message'), msg, _('OK'))
1349 def password_dialog(self ):
1356 vbox = QVBoxLayout()
1357 msg = _('Please enter your password')
1358 vbox.addWidget(QLabel(msg))
1360 grid = QGridLayout()
1362 grid.addWidget(QLabel(_('Password')), 1, 0)
1363 grid.addWidget(pw, 1, 1)
1364 vbox.addLayout(grid)
1366 vbox.addLayout(ok_cancel_buttons(d))
1369 if not d.exec_(): return
1370 return unicode(pw.text())
1377 def change_password_dialog( wallet, parent=None ):
1380 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1388 new_pw = QLineEdit()
1389 new_pw.setEchoMode(2)
1390 conf_pw = QLineEdit()
1391 conf_pw.setEchoMode(2)
1393 vbox = QVBoxLayout()
1395 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1396 +_('To disable wallet encryption, enter an empty new password.')) \
1397 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1399 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1400 +_("Leave these fields empty if you want to disable encryption.")
1401 vbox.addWidget(QLabel(msg))
1403 grid = QGridLayout()
1406 if wallet.use_encryption:
1407 grid.addWidget(QLabel(_('Password')), 1, 0)
1408 grid.addWidget(pw, 1, 1)
1410 grid.addWidget(QLabel(_('New Password')), 2, 0)
1411 grid.addWidget(new_pw, 2, 1)
1413 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1414 grid.addWidget(conf_pw, 3, 1)
1415 vbox.addLayout(grid)
1417 vbox.addLayout(ok_cancel_buttons(d))
1420 if not d.exec_(): return
1422 password = unicode(pw.text()) if wallet.use_encryption else None
1423 new_password = unicode(new_pw.text())
1424 new_password2 = unicode(conf_pw.text())
1427 seed = wallet.pw_decode( wallet.seed, password)
1429 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1432 if new_password != new_password2:
1433 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1434 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1436 wallet.update_password(seed, password, new_password)
1439 def seed_dialog(wallet, parent=None):
1443 vbox = QVBoxLayout()
1444 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1445 vbox.addWidget(QLabel(msg))
1447 grid = QGridLayout()
1450 seed_e = QLineEdit()
1451 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1452 grid.addWidget(seed_e, 1, 1)
1456 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1457 grid.addWidget(gap_e, 2, 1)
1458 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1459 vbox.addLayout(grid)
1461 vbox.addLayout(ok_cancel_buttons(d))
1464 if not d.exec_(): return
1467 gap = int(unicode(gap_e.text()))
1469 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1473 seed = unicode(seed_e.text())
1476 print_error("Warning: Not hex, trying decode")
1478 seed = mnemonic.mn_decode( seed.split(' ') )
1480 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1483 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1486 wallet.seed = str(seed)
1487 #print repr(wallet.seed)
1488 wallet.gap_limit = gap
1493 def settings_dialog(self):
1495 d.setWindowTitle(_('Electrum Settings'))
1497 vbox = QVBoxLayout()
1499 tabs = QTabWidget(self)
1500 vbox.addWidget(tabs)
1503 grid_wallet = QGridLayout(tab)
1504 grid_wallet.setColumnStretch(0,1)
1505 tabs.addTab(tab, _('Wallet') )
1508 grid_ui = QGridLayout(tab2)
1509 grid_ui.setColumnStretch(0,1)
1510 tabs.addTab(tab2, _('Display') )
1512 fee_label = QLabel(_('Transaction fee'))
1513 grid_wallet.addWidget(fee_label, 2, 0)
1515 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1516 grid_wallet.addWidget(fee_e, 2, 1)
1517 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1518 + _('Recommended value') + ': 0.001'
1519 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1520 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1521 if not self.config.is_modifiable('fee'):
1522 for w in [fee_e, fee_label]: w.setEnabled(False)
1524 nz_label = QLabel(_('Display zeros'))
1525 grid_ui.addWidget(nz_label, 3, 0)
1527 nz_e.setText("%d"% self.wallet.num_zeros)
1528 grid_ui.addWidget(nz_e, 3, 1)
1529 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1530 grid_ui.addWidget(HelpButton(msg), 3, 2)
1531 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1532 if not self.config.is_modifiable('num_zeros'):
1533 for w in [nz_e, nz_label]: w.setEnabled(False)
1536 usechange_label = QLabel(_('Use change addresses'))
1537 grid_wallet.addWidget(usechange_label, 5, 0)
1538 usechange_combo = QComboBox()
1539 usechange_combo.addItems(['Yes', 'No'])
1540 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1541 grid_wallet.addWidget(usechange_combo, 5, 1)
1542 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1543 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1545 gap_label = QLabel(_('Gap limit'))
1546 grid_wallet.addWidget(gap_label, 6, 0)
1548 gap_e.setText("%d"% self.wallet.gap_limit)
1549 grid_wallet.addWidget(gap_e, 6, 1)
1550 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1551 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1552 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1553 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1554 + _('Warning') + ': ' \
1555 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1556 + _('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'
1557 grid_wallet.addWidget(HelpButton(msg), 6, 2)
1558 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1559 if not self.config.is_modifiable('gap_limit'):
1560 for w in [gap_e, gap_label]: w.setEnabled(False)
1562 gui_label=QLabel(_('Default GUI') + ':')
1563 grid_ui.addWidget(gui_label , 7, 0)
1564 gui_combo = QComboBox()
1565 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1566 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1567 if index==-1: index = 1
1568 gui_combo.setCurrentIndex(index)
1569 grid_ui.addWidget(gui_combo, 7, 1)
1570 grid_ui.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1571 if not self.config.is_modifiable('gui'):
1572 for w in [gui_combo, gui_label]: w.setEnabled(False)
1574 lang_label=QLabel(_('Language') + ':')
1575 grid_ui.addWidget(lang_label , 8, 0)
1576 lang_combo = QComboBox()
1577 languages = {'':_('Default'), 'br':_('Brasilian'), 'cs':_('Czech'), 'de':_('German'),
1578 'eo':_('Esperanto'), 'en':_('English'), 'es':_('Spanish'), 'fr':_('French'),
1579 'it':_('Italian'), 'lv':_('Latvian'), 'nl':_('Dutch'), 'ru':_('Russian'),
1580 'sl':_('Slovenian'), 'vi':_('Vietnamese'), 'zh':_('Chinese')
1582 lang_combo.addItems(languages.values())
1584 index = languages.keys().index(self.config.get("language",''))
1587 lang_combo.setCurrentIndex(index)
1588 grid_ui.addWidget(lang_combo, 8, 1)
1589 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart). ')), 8, 2)
1590 if not self.config.is_modifiable('language'):
1591 for w in [lang_combo, lang_label]: w.setEnabled(False)
1594 view_label=QLabel(_('Receive Tab') + ':')
1595 grid_ui.addWidget(view_label , 9, 0)
1596 view_combo = QComboBox()
1597 view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
1598 view_combo.setCurrentIndex(self.receive_tab_mode)
1599 grid_ui.addWidget(view_combo, 9, 1)
1600 hh = _('This selects the interaction mode of the "Receive" tab. ') + '\n\n' \
1601 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1602 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
1603 + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n'
1605 grid_ui.addWidget(HelpButton(hh), 9, 2)
1607 vbox.addLayout(ok_cancel_buttons(d))
1611 if not d.exec_(): return
1613 fee = unicode(fee_e.text())
1615 fee = int( 100000000 * Decimal(fee) )
1617 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1620 if self.wallet.fee != fee:
1621 self.wallet.fee = fee
1624 nz = unicode(nz_e.text())
1629 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1632 if self.wallet.num_zeros != nz:
1633 self.wallet.num_zeros = nz
1634 self.config.set_key('num_zeros', nz, True)
1635 self.update_history_tab()
1636 self.update_receive_tab()
1638 usechange_result = usechange_combo.currentIndex() == 0
1639 if self.wallet.use_change != usechange_result:
1640 self.wallet.use_change = usechange_result
1641 self.config.set_key('use_change', self.wallet.use_change, True)
1644 n = int(gap_e.text())
1646 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1649 if self.wallet.gap_limit != n:
1650 r = self.wallet.change_gap_limit(n)
1652 self.update_receive_tab()
1653 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1655 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1657 need_restart = False
1659 gui_request = str(gui_combo.currentText()).lower()
1660 if gui_request != self.config.get('gui'):
1661 self.config.set_key('gui', gui_request, True)
1664 lang_request = languages.keys()[lang_combo.currentIndex()]
1665 if lang_request != self.config.get('language'):
1666 self.config.set_key("language", lang_request, True)
1670 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
1672 self.receive_tab_set_mode(view_combo.currentIndex())
1676 def network_dialog(wallet, parent=None):
1677 interface = wallet.interface
1679 if interface.is_connected:
1680 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1682 status = _("Not connected")
1683 server = interface.server
1686 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1687 server = interface.server
1689 plist, servers_list = interface.get_servers_list()
1693 d.setWindowTitle(_('Server'))
1694 d.setMinimumSize(375, 20)
1696 vbox = QVBoxLayout()
1699 hbox = QHBoxLayout()
1701 l.setPixmap(QPixmap(":icons/network.png"))
1704 hbox.addWidget(QLabel(status))
1706 vbox.addLayout(hbox)
1710 grid = QGridLayout()
1712 vbox.addLayout(grid)
1715 server_protocol = QComboBox()
1716 server_host = QLineEdit()
1717 server_host.setFixedWidth(200)
1718 server_port = QLineEdit()
1719 server_port.setFixedWidth(60)
1721 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1722 protocol_letters = 'thsg'
1723 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1724 server_protocol.addItems(protocol_names)
1726 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1727 grid.addWidget(server_protocol, 0, 1)
1728 grid.addWidget(server_host, 0, 2)
1729 grid.addWidget(server_port, 0, 3)
1731 def change_protocol(p):
1732 protocol = protocol_letters[p]
1733 host = unicode(server_host.text())
1734 pp = plist.get(host,DEFAULT_PORTS)
1735 if protocol not in pp.keys():
1736 protocol = pp.keys()[0]
1738 server_host.setText( host )
1739 server_port.setText( port )
1741 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1743 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1744 servers_list_widget = QTreeWidget(parent)
1745 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1746 servers_list_widget.setMaximumHeight(150)
1747 servers_list_widget.setColumnWidth(0, 240)
1748 for _host in servers_list.keys():
1749 _type = 'P' if servers_list[_host].get('pruning') else 'F'
1750 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1752 def change_server(host, protocol=None):
1753 pp = plist.get(host,DEFAULT_PORTS)
1755 port = pp.get(protocol)
1756 if not port: protocol = None
1759 if 't' in pp.keys():
1761 port = pp.get(protocol)
1763 protocol = pp.keys()[0]
1764 port = pp.get(protocol)
1766 server_host.setText( host )
1767 server_port.setText( port )
1768 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1770 if not plist: return
1771 for p in protocol_letters:
1772 i = protocol_letters.index(p)
1773 j = server_protocol.model().index(i,0)
1774 if p not in pp.keys():
1775 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1777 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1781 host, port, protocol = server.split(':')
1782 change_server(host,protocol)
1784 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1785 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1787 if not wallet.config.is_modifiable('server'):
1788 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1791 autocycle_cb = QCheckBox('Try random servers if disconnected')
1792 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
1793 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
1794 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
1797 proxy_mode = QComboBox()
1798 proxy_host = QLineEdit()
1799 proxy_host.setFixedWidth(200)
1800 proxy_port = QLineEdit()
1801 proxy_port.setFixedWidth(60)
1802 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1804 def check_for_disable(index = False):
1805 if proxy_mode.currentText() != 'NONE':
1806 proxy_host.setEnabled(True)
1807 proxy_port.setEnabled(True)
1809 proxy_host.setEnabled(False)
1810 proxy_port.setEnabled(False)
1813 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1815 if not wallet.config.is_modifiable('proxy'):
1816 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1818 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1819 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1820 proxy_host.setText(proxy_config.get("host"))
1821 proxy_port.setText(proxy_config.get("port"))
1823 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1824 grid.addWidget(proxy_mode, 2, 1)
1825 grid.addWidget(proxy_host, 2, 2)
1826 grid.addWidget(proxy_port, 2, 3)
1829 vbox.addLayout(ok_cancel_buttons(d))
1832 if not d.exec_(): return
1834 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1835 if proxy_mode.currentText() != 'NONE':
1836 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1840 wallet.config.set_key("proxy", proxy, True)
1841 wallet.config.set_key("server", server, True)
1842 interface.set_server(server, proxy)
1843 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
1846 def closeEvent(self, event):
1848 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1854 def __init__(self, wallet, config, app=None):
1855 self.wallet = wallet
1856 self.config = config
1858 self.app = QApplication(sys.argv)
1861 def restore_or_create(self):
1862 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1863 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1864 if r==2: return None
1865 return 'restore' if r==1 else 'create'
1867 def seed_dialog(self):
1868 return ElectrumWindow.seed_dialog( self.wallet )
1870 def network_dialog(self):
1871 return ElectrumWindow.network_dialog( self.wallet, parent=None )
1874 def show_seed(self):
1875 ElectrumWindow.show_seed_dialog(self.wallet)
1878 def password_dialog(self):
1879 ElectrumWindow.change_password_dialog(self.wallet)
1882 def restore_wallet(self):
1883 wallet = self.wallet
1884 # wait until we are connected, because the user might have selected another server
1885 if not wallet.interface.is_connected:
1886 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1887 waiting_dialog(waiting)
1889 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1890 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1892 wallet.set_up_to_date(False)
1893 wallet.interface.poke('synchronizer')
1894 waiting_dialog(waiting)
1895 if wallet.is_found():
1896 print_error( "Recovery successful" )
1898 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1905 w = ElectrumWindow(self.wallet, self.config)
1906 if url: w.set_url(url)