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)
300 self.wallet.interface.register_callback('updated', self.update_callback)
301 self.wallet.interface.register_callback('connected', self.update_callback)
302 self.wallet.interface.register_callback('disconnected', self.update_callback)
303 self.wallet.interface.register_callback('disconnecting', self.update_callback)
305 self.receive_tab_mode = config.get('qt_receive_tab_mode', 0)
306 self.merchant_name = config.get('merchant_name', 'Invoice')
308 self.qr_window = None
309 self.funds_error = False
310 self.completions = QStringListModel()
312 self.tabs = tabs = QTabWidget(self)
313 tabs.addTab(self.create_history_tab(), _('History') )
314 tabs.addTab(self.create_send_tab(), _('Send') )
315 tabs.addTab(self.create_receive_tab(), _('Receive') )
316 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
317 tabs.addTab(self.create_wall_tab(), _('Wall') )
318 tabs.setMinimumSize(600, 400)
319 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
320 self.setCentralWidget(tabs)
321 self.create_status_bar()
322 self.toggle_QR_window(self.receive_tab_mode == 2)
324 g = self.config.get("winpos-qt",[100, 100, 840, 400])
325 self.setGeometry(g[0], g[1], g[2], g[3])
326 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
327 if not self.wallet.seed: title += ' [seedless]'
328 self.setWindowTitle( title )
330 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
331 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
332 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
333 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
335 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
336 #self.connect(self, SIGNAL('editamount'), self.edit_amount)
337 self.history_list.setFocus(True)
339 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
340 if platform.system() == 'Windows':
341 n = 3 if self.wallet.seed else 2
342 tabs.setCurrentIndex (n)
343 tabs.setCurrentIndex (0)
346 QMainWindow.close(self)
348 self.qr_window.close()
349 self.qr_window = None
351 def connect_slots(self, sender):
352 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
353 self.previous_payto_e=''
355 def timer_actions(self):
357 self.qr_window.qrw.update_qr()
359 if self.payto_e.hasFocus():
361 r = unicode( self.payto_e.text() )
362 if r != self.previous_payto_e:
363 self.previous_payto_e = r
365 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
367 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
371 s = r + ' <' + to_address + '>'
372 self.payto_e.setText(s)
375 def update_callback(self):
376 self.emit(QtCore.SIGNAL('updatesignal'))
378 def update_wallet(self):
379 if self.wallet.interface and self.wallet.interface.is_connected:
380 if not self.wallet.up_to_date:
381 text = _( "Synchronizing..." )
382 icon = QIcon(":icons/status_waiting.png")
384 c, u = self.wallet.get_balance()
385 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
386 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
387 icon = QIcon(":icons/status_connected.png")
389 text = _( "Not connected" )
390 icon = QIcon(":icons/status_disconnected.png")
393 text = _( "Not enough funds" )
395 self.statusBar().showMessage(text)
396 self.status_button.setIcon( icon )
398 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
399 self.textbox.setText( self.wallet.banner )
400 self.update_history_tab()
401 self.update_receive_tab()
402 self.update_contacts_tab()
403 self.update_completions()
406 def create_history_tab(self):
407 self.history_list = l = MyTreeWidget(self)
409 l.setColumnWidth(0, 40)
410 l.setColumnWidth(1, 140)
411 l.setColumnWidth(2, 350)
412 l.setColumnWidth(3, 140)
413 l.setColumnWidth(4, 140)
414 l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
415 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
416 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
418 l.setContextMenuPolicy(Qt.CustomContextMenu)
419 l.customContextMenuRequested.connect(self.create_history_menu)
423 def create_history_menu(self, position):
424 self.history_list.selectedIndexes()
425 item = self.history_list.currentItem()
427 tx_hash = str(item.toolTip(0))
428 if not tx_hash: return
430 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
431 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
432 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
433 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
436 def tx_details(self, tx_hash):
437 tx_details = self.wallet.get_tx_details(tx_hash)
438 QMessageBox.information(self, 'Details', tx_details, 'OK')
441 def tx_label_clicked(self, item, column):
442 if column==2 and item.isSelected():
443 tx_hash = str(item.toolTip(0))
445 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
446 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
447 self.history_list.editItem( item, column )
448 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
451 def tx_label_changed(self, item, column):
455 tx_hash = str(item.toolTip(0))
456 tx = self.wallet.transactions.get(tx_hash)
457 s = self.wallet.labels.get(tx_hash)
458 text = unicode( item.text(2) )
460 self.wallet.labels[tx_hash] = text
461 item.setForeground(2, QBrush(QColor('black')))
463 if s: self.wallet.labels.pop(tx_hash)
464 text = self.wallet.get_default_label(tx_hash)
465 item.setText(2, text)
466 item.setForeground(2, QBrush(QColor('gray')))
470 def edit_label(self, is_recv):
471 l = self.receive_list if is_recv else self.contacts_list
472 c = 2 if is_recv else 1
473 item = l.currentItem()
474 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
475 l.editItem( item, c )
476 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
478 def edit_amount(self):
479 l = self.receive_list
480 item = l.currentItem()
481 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
482 l.editItem( item, 3 )
483 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
486 def address_label_clicked(self, item, column, l, column_addr, column_label):
487 if column == column_label and item.isSelected():
488 addr = unicode( item.text(column_addr) )
489 label = unicode( item.text(column_label) )
490 if label in self.wallet.aliases.keys():
492 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
493 l.editItem( item, column )
494 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
497 def address_label_changed(self, item, column, l, column_addr, column_label):
499 if column == column_label:
500 addr = unicode( item.text(column_addr) )
501 text = unicode( item.text(column_label) )
505 if text not in self.wallet.aliases.keys():
506 old_addr = self.wallet.labels.get(text)
508 self.wallet.labels[addr] = text
511 print_error("Error: This is one of your aliases")
512 label = self.wallet.labels.get(addr,'')
513 item.setText(column_label, QString(label))
515 s = self.wallet.labels.get(addr)
517 self.wallet.labels.pop(addr)
521 self.update_history_tab()
522 self.update_completions()
524 self.recv_changed(item)
527 address = unicode( item.text(column_addr) )
528 text = unicode( item.text(3) )
530 index = self.wallet.addresses.index(address)
535 amount = int( Decimal(text) * 100000000 )
536 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
538 amount = self.wallet.requested_amounts.get(address)
540 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
545 self.wallet.requested_amounts[address] = amount
547 label = self.wallet.labels.get(address)
549 label = self.merchant_name + ' - %04d'%(index+1)
550 self.wallet.labels[address] = label
552 self.update_receive_item(self.receive_list.currentItem())
554 self.qr_window.set_content( address, label, amount )
557 def recv_changed(self, a):
558 "current item changed"
559 if a is not None and self.qr_window and self.qr_window.isVisible():
560 address = str(a.text(1))
561 label = self.wallet.labels.get(address)
562 amount = self.wallet.requested_amounts.get(address)
563 self.qr_window.set_content( address, label, amount )
566 def update_history_tab(self):
568 self.history_list.clear()
569 for item in self.wallet.get_tx_history():
570 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
573 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
579 icon = QIcon(":icons/unconfirmed.png")
581 icon = QIcon(":icons/clock%d.png"%conf)
583 icon = QIcon(":icons/confirmed.png")
586 icon = QIcon(":icons/unconfirmed.png")
588 if value is not None:
589 v_str = format_satoshis(value, True, self.wallet.num_zeros)
593 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
596 label, is_default_label = self.wallet.get_label(tx_hash)
598 label = _('Pruned transaction outputs')
599 is_default_label = False
601 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
602 item.setFont(2, QFont(MONOSPACE_FONT))
603 item.setFont(3, QFont(MONOSPACE_FONT))
604 item.setFont(4, QFont(MONOSPACE_FONT))
606 item.setForeground(3, QBrush(QColor("#BC1E1E")))
608 item.setToolTip(0, tx_hash)
610 item.setForeground(2, QBrush(QColor('grey')))
612 item.setIcon(0, icon)
613 self.history_list.insertTopLevelItem(0,item)
616 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
619 def create_send_tab(self):
624 grid.setColumnMinimumWidth(3,300)
625 grid.setColumnStretch(5,1)
627 self.payto_e = QLineEdit()
628 grid.addWidget(QLabel(_('Pay to')), 1, 0)
629 grid.addWidget(self.payto_e, 1, 1, 1, 3)
632 qrcode = qrscanner.scan_qr()
633 if 'address' in qrcode:
634 self.payto_e.setText(qrcode['address'])
635 if 'amount' in qrcode:
636 self.amount_e.setText(str(qrcode['amount']))
637 if 'label' in qrcode:
638 self.message_e.setText(qrcode['label'])
639 if 'message' in qrcode:
640 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
643 if qrscanner.is_available():
644 b = QPushButton(_("Scan QR code"))
645 b.clicked.connect(fill_from_qr)
646 grid.addWidget(b, 1, 5)
648 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)
650 completer = QCompleter()
651 completer.setCaseSensitivity(False)
652 self.payto_e.setCompleter(completer)
653 completer.setModel(self.completions)
655 self.message_e = QLineEdit()
656 grid.addWidget(QLabel(_('Description')), 2, 0)
657 grid.addWidget(self.message_e, 2, 1, 1, 3)
658 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)
660 self.amount_e = QLineEdit()
661 grid.addWidget(QLabel(_('Amount')), 3, 0)
662 grid.addWidget(self.amount_e, 3, 1, 1, 2)
663 grid.addWidget(HelpButton(
664 _('Amount to be sent.') + '\n\n' \
665 + _('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)
667 self.fee_e = QLineEdit()
668 grid.addWidget(QLabel(_('Fee')), 4, 0)
669 grid.addWidget(self.fee_e, 4, 1, 1, 2)
670 grid.addWidget(HelpButton(
671 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
672 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
673 + _('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)
675 b = EnterButton(_("Send"), self.do_send)
676 grid.addWidget(b, 6, 1)
678 b = EnterButton(_("Clear"),self.do_clear)
679 grid.addWidget(b, 6, 2)
681 self.payto_sig = QLabel('')
682 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
684 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
685 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
694 def entry_changed( is_fee ):
695 self.funds_error = False
696 amount = numbify(self.amount_e)
697 fee = numbify(self.fee_e)
698 if not is_fee: fee = None
701 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
703 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
706 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
709 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
710 self.funds_error = True
711 self.amount_e.setPalette(palette)
712 self.fee_e.setPalette(palette)
715 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
716 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
721 def update_completions(self):
723 for addr,label in self.wallet.labels.items():
724 if addr in self.wallet.addressbook:
725 l.append( label + ' <' + addr + '>')
726 l = l + self.wallet.aliases.keys()
728 self.completions.setStringList(l)
734 label = unicode( self.message_e.text() )
735 r = unicode( self.payto_e.text() )
739 m1 = re.match(ALIAS_REGEXP, r)
740 # label or alias, with address in brackets
741 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
744 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
748 to_address = m2.group(2)
752 if not self.wallet.is_valid(to_address):
753 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
757 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
759 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
762 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
764 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
767 if self.wallet.use_encryption:
768 password = self.password_dialog()
775 tx = self.wallet.mktx( [(to_address, amount)], label, password, fee)
776 except BaseException, e:
777 self.show_message(str(e))
781 h = self.wallet.send_tx(tx)
782 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
783 status, msg = self.wallet.receive_tx( h )
785 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
787 self.update_contacts_tab()
789 QMessageBox.warning(self, _('Error'), msg, _('OK'))
791 filename = 'unsigned_tx'
792 f = open(filename,'w')
795 QMessageBox.information(self, _('Unsigned transaction'), _("Unsigned transaction was saved to file:") + " " +filename, _('OK'))
798 def set_url(self, url):
799 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
800 self.tabs.setCurrentIndex(1)
801 label = self.wallet.labels.get(payto)
802 m_addr = label + ' <'+ payto+'>' if label else payto
803 self.payto_e.setText(m_addr)
805 self.message_e.setText(message)
806 self.amount_e.setText(amount)
808 self.set_frozen(self.payto_e,True)
809 self.set_frozen(self.amount_e,True)
810 self.set_frozen(self.message_e,True)
811 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
813 self.payto_sig.setVisible(False)
816 self.payto_sig.setVisible(False)
817 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
819 self.set_frozen(e,False)
821 def set_frozen(self,entry,frozen):
823 entry.setReadOnly(True)
824 entry.setFrame(False)
826 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
827 entry.setPalette(palette)
829 entry.setReadOnly(False)
832 palette.setColor(entry.backgroundRole(), QColor('white'))
833 entry.setPalette(palette)
836 def toggle_freeze(self,addr):
838 if addr in self.wallet.frozen_addresses:
839 self.wallet.unfreeze(addr)
841 self.wallet.freeze(addr)
842 self.update_receive_tab()
844 def toggle_priority(self,addr):
846 if addr in self.wallet.prioritized_addresses:
847 self.wallet.unprioritize(addr)
849 self.wallet.prioritize(addr)
850 self.update_receive_tab()
853 def create_list_tab(self, headers):
854 "generic tab creation method"
855 l = MyTreeWidget(self)
856 l.setColumnCount( len(headers) )
857 l.setHeaderLabels( headers )
867 vbox.addWidget(buttons)
872 buttons.setLayout(hbox)
877 def create_receive_tab(self):
878 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
879 l.setContextMenuPolicy(Qt.CustomContextMenu)
880 l.customContextMenuRequested.connect(self.create_receive_menu)
881 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
882 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
883 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
884 self.receive_list = l
885 self.receive_buttons_hbox = hbox
891 def receive_tab_set_mode(self, i):
892 self.receive_tab_mode = i
893 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
895 self.update_receive_tab()
896 self.toggle_QR_window(self.receive_tab_mode == 2)
899 def create_contacts_tab(self):
900 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
901 l.setContextMenuPolicy(Qt.CustomContextMenu)
902 l.customContextMenuRequested.connect(self.create_contact_menu)
903 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
904 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
905 self.contacts_list = l
906 self.contacts_buttons_hbox = hbox
907 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
912 def create_receive_menu(self, position):
913 # fixme: this function apparently has a side effect.
914 # if it is not called the menu pops up several times
915 #self.receive_list.selectedIndexes()
917 item = self.receive_list.itemAt(position)
919 addr = unicode(item.text(1))
921 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
922 if self.receive_tab_mode == 2:
923 menu.addAction(_("Request amount"), lambda: self.edit_amount())
924 menu.addAction(_("View QR"), lambda: ElectrumWindow.show_qrcode("Address","bitcoin:"+addr) )
925 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
926 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
928 if self.receive_tab_mode == 1:
929 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
930 menu.addAction(t, lambda: self.toggle_freeze(addr))
931 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
932 menu.addAction(t, lambda: self.toggle_priority(addr))
934 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
937 def payto(self, x, is_alias):
944 label = self.wallet.labels.get(addr)
945 m_addr = label + ' <' + addr + '>' if label else addr
946 self.tabs.setCurrentIndex(1)
947 self.payto_e.setText(m_addr)
948 self.amount_e.setFocus()
950 def delete_contact(self, x, is_alias):
951 if self.question("Do you want to remove %s from your list of contacts?"%x):
952 if not is_alias and x in self.wallet.addressbook:
953 self.wallet.addressbook.remove(x)
954 if x in self.wallet.labels.keys():
955 self.wallet.labels.pop(x)
956 elif is_alias and x in self.wallet.aliases:
957 self.wallet.aliases.pop(x)
958 self.update_history_tab()
959 self.update_contacts_tab()
960 self.update_completions()
962 def create_contact_menu(self, position):
963 # fixme: this function apparently has a side effect.
964 # if it is not called the menu pops up several times
965 #self.contacts_list.selectedIndexes()
967 item = self.contacts_list.itemAt(position)
969 addr = unicode(item.text(0))
970 label = unicode(item.text(1))
971 is_alias = label in self.wallet.aliases.keys()
972 x = label if is_alias else addr
974 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
975 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
976 menu.addAction(_("View QR code"),lambda: self.show_qrcode("Address","bitcoin:"+addr))
978 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
980 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
981 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
982 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
985 def update_receive_item(self, item):
986 address = str( item.data(1,0).toString() )
988 flags = self.wallet.get_address_flags(address)
989 item.setData(0,0,flags)
991 label = self.wallet.labels.get(address,'')
992 item.setData(2,0,label)
994 amount = self.wallet.requested_amounts.get(address,None)
995 amount_str = format_satoshis( amount, False, self.wallet.num_zeros ) if amount is not None else ""
996 item.setData(3,0,amount_str)
998 c, u = self.wallet.get_addr_balance(address)
999 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1000 item.setData(4,0,balance)
1002 if self.receive_tab_mode == 1:
1003 if address in self.wallet.frozen_addresses:
1004 item.setBackgroundColor(1, QColor('lightblue'))
1005 elif address in self.wallet.prioritized_addresses:
1006 item.setBackgroundColor(1, QColor('lightgreen'))
1009 def update_receive_tab(self):
1010 l = self.receive_list
1013 l.setColumnHidden(0, not self.receive_tab_mode == 1)
1014 l.setColumnHidden(3, not self.receive_tab_mode == 2)
1015 l.setColumnHidden(4, self.receive_tab_mode == 0)
1016 l.setColumnHidden(5, not self.receive_tab_mode == 1)
1017 l.setColumnWidth(0, 50)
1018 l.setColumnWidth(1, 310)
1019 l.setColumnWidth(2, 200)
1020 l.setColumnWidth(3, 130)
1021 l.setColumnWidth(4, 130)
1022 l.setColumnWidth(5, 10)
1026 for address in self.wallet.all_addresses():
1028 if self.wallet.is_change(address) and self.receive_tab_mode != 1:
1032 h = self.wallet.history.get(address,[])
1035 for tx_hash, tx_height in h:
1036 tx = self.wallet.transactions.get(tx_hash)
1044 if address in self.wallet.addresses:
1046 if gap > self.wallet.gap_limit:
1049 if address in self.wallet.addresses:
1052 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1053 item.setFont(0, QFont(MONOSPACE_FONT))
1054 item.setFont(1, QFont(MONOSPACE_FONT))
1055 item.setFont(3, QFont(MONOSPACE_FONT))
1056 self.update_receive_item(item)
1057 if is_red and address in self.wallet.addresses:
1058 item.setBackgroundColor(1, QColor('red'))
1059 l.addTopLevelItem(item)
1061 # we use column 1 because column 0 may be hidden
1062 l.setCurrentItem(l.topLevelItem(0),1)
1064 def show_contact_details(self, m):
1065 a = self.wallet.aliases.get(m)
1067 if a[0] in self.wallet.authorities.keys():
1068 s = self.wallet.authorities.get(a[0])
1071 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1072 QMessageBox.information(self, 'Alias', msg, 'OK')
1074 def update_contacts_tab(self):
1076 l = self.contacts_list
1078 l.setColumnWidth(0, 350)
1079 l.setColumnWidth(1, 330)
1080 l.setColumnWidth(2, 100)
1083 for alias, v in self.wallet.aliases.items():
1085 alias_targets.append(target)
1086 item = QTreeWidgetItem( [ target, alias, '-'] )
1087 item.setBackgroundColor(0, QColor('lightgray'))
1088 l.addTopLevelItem(item)
1090 for address in self.wallet.addressbook:
1091 if address in alias_targets: continue
1092 label = self.wallet.labels.get(address,'')
1094 for item in self.wallet.transactions.values():
1095 if address in item['outputs'] : n=n+1
1097 item = QTreeWidgetItem( [ address, label, tx] )
1098 item.setFont(0, QFont(MONOSPACE_FONT))
1099 l.addTopLevelItem(item)
1101 l.setCurrentItem(l.topLevelItem(0))
1103 def create_wall_tab(self):
1104 self.textbox = textbox = QTextEdit(self)
1105 textbox.setFont(QFont(MONOSPACE_FONT))
1106 textbox.setReadOnly(True)
1109 def create_status_bar(self):
1111 sb.setFixedHeight(35)
1112 qtVersion = qVersion()
1113 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1114 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), "Switch to Lite Mode", self.go_lite ) )
1115 if self.wallet.seed:
1116 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1117 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1118 if self.wallet.seed:
1119 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1120 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
1121 sb.addPermanentWidget( self.status_button )
1122 self.setStatusBar(sb)
1128 self.lite.mini.show()
1130 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1131 self.lite.main(None)
1133 def new_contact_dialog(self):
1134 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1135 address = unicode(text)
1137 if self.wallet.is_valid(address):
1138 self.wallet.addressbook.append(address)
1140 self.update_contacts_tab()
1141 self.update_history_tab()
1142 self.update_completions()
1144 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1147 def show_seed_dialog(wallet, parent=None):
1149 QMessageBox.information(parent, _('Message'),
1150 _('No seed'), _('OK'))
1153 if wallet.use_encryption:
1154 password = parent.password_dialog()
1161 seed = wallet.pw_decode(wallet.seed, password)
1163 QMessageBox.warning(parent, _('Error'),
1164 _('Incorrect Password'), _('OK'))
1167 dialog = QDialog(None)
1169 dialog.setWindowTitle("Electrum")
1171 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1173 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1174 + _("Please write down or memorize these 12 words (order is important).") + " " \
1175 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1176 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1178 main_text = QLabel(msg)
1179 main_text.setWordWrap(True)
1182 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1189 copy_function = lambda: app.clipboard().setText(brainwallet)
1190 copy_button = QPushButton(_("Copy to Clipboard"))
1191 copy_button.clicked.connect(copy_function)
1193 show_qr_function = lambda: ElectrumWindow.show_qrcode(_("Seed"), seed)
1194 qr_button = QPushButton(_("View as QR Code"))
1195 qr_button.clicked.connect(show_qr_function)
1197 ok_button = QPushButton(_("OK"))
1198 ok_button.setDefault(True)
1199 ok_button.clicked.connect(dialog.accept)
1201 main_layout = QGridLayout()
1202 main_layout.addWidget(logo, 0, 0)
1203 main_layout.addWidget(main_text, 0, 1, 1, -1)
1204 main_layout.addWidget(copy_button, 1, 1)
1205 main_layout.addWidget(qr_button, 1, 2)
1206 main_layout.addWidget(ok_button, 1, 3)
1207 dialog.setLayout(main_layout)
1212 def show_qrcode(title, data):
1216 d.setWindowTitle(title)
1217 d.setMinimumSize(270, 300)
1218 vbox = QVBoxLayout()
1219 qrw = QRCodeWidget(data)
1220 vbox.addWidget(qrw, 1)
1221 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1222 hbox = QHBoxLayout()
1226 filename = "qrcode.bmp"
1227 bmp.save_qrcode(qrw.qr, filename)
1228 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1230 b = QPushButton(_("Print"))
1232 b.clicked.connect(print_qr)
1234 b = QPushButton(_("Close"))
1236 b.clicked.connect(d.accept)
1238 vbox.addLayout(hbox)
1242 def sign_message(self,address):
1243 if not address: return
1246 d.setWindowTitle('Sign Message')
1247 d.setMinimumSize(270, 350)
1249 tab_widget = QTabWidget()
1251 layout = QGridLayout(tab)
1253 sign_address = QLineEdit()
1254 sign_address.setText(address)
1255 layout.addWidget(QLabel(_('Address')), 1, 0)
1256 layout.addWidget(sign_address, 1, 1)
1258 sign_message = QTextEdit()
1259 layout.addWidget(QLabel(_('Message')), 2, 0)
1260 layout.addWidget(sign_message, 2, 1, 2, 1)
1262 sign_signature = QLineEdit()
1263 layout.addWidget(QLabel(_('Signature')), 3, 0)
1264 layout.addWidget(sign_signature, 3, 1)
1267 if self.wallet.use_encryption:
1268 password = self.password_dialog()
1275 signature = self.wallet.sign_message(sign_address.text(), str(sign_message.toPlainText()), password)
1276 sign_signature.setText(signature)
1277 except BaseException, e:
1278 self.show_message(str(e))
1281 hbox = QHBoxLayout()
1282 b = QPushButton(_("Sign"))
1284 b.clicked.connect(do_sign)
1285 b = QPushButton(_("Close"))
1286 b.clicked.connect(d.accept)
1288 layout.addLayout(hbox, 4, 1)
1289 tab_widget.addTab(tab, "Sign")
1293 layout = QGridLayout(tab)
1295 verify_address = QLineEdit()
1296 layout.addWidget(QLabel(_('Address')), 1, 0)
1297 layout.addWidget(verify_address, 1, 1)
1299 verify_message = QTextEdit()
1300 layout.addWidget(QLabel(_('Message')), 2, 0)
1301 layout.addWidget(verify_message, 2, 1, 2, 1)
1303 verify_signature = QLineEdit()
1304 layout.addWidget(QLabel(_('Signature')), 3, 0)
1305 layout.addWidget(verify_signature, 3, 1)
1309 self.wallet.verify_message(verify_address.text(), verify_signature.text(), str(verify_message.toPlainText()))
1310 self.show_message("Signature verified")
1311 except BaseException, e:
1312 self.show_message(str(e))
1315 hbox = QHBoxLayout()
1316 b = QPushButton(_("Verify"))
1317 b.clicked.connect(do_verify)
1319 b = QPushButton(_("Close"))
1320 b.clicked.connect(d.accept)
1322 layout.addLayout(hbox, 4, 1)
1323 tab_widget.addTab(tab, "Verify")
1325 vbox = QVBoxLayout()
1326 vbox.addWidget(tab_widget)
1331 def toggle_QR_window(self, show):
1332 if show and not self.qr_window:
1333 self.qr_window = QR_Window()
1334 self.qr_window.setVisible(True)
1335 self.qr_window_geometry = self.qr_window.geometry()
1336 item = self.receive_list.currentItem()
1338 address = str(item.text(1))
1339 label = self.wallet.labels.get(address)
1340 amount = self.wallet.requested_amounts.get(address)
1341 self.qr_window.set_content( address, label, amount )
1343 elif show and self.qr_window and not self.qr_window.isVisible():
1344 self.qr_window.setVisible(True)
1345 self.qr_window.setGeometry(self.qr_window_geometry)
1347 elif not show and self.qr_window and self.qr_window.isVisible():
1348 self.qr_window_geometry = self.qr_window.geometry()
1349 self.qr_window.setVisible(False)
1351 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1352 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1353 self.receive_list.setColumnWidth(2, 200)
1356 def question(self, msg):
1357 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1359 def show_message(self, msg):
1360 QMessageBox.information(self, _('Message'), msg, _('OK'))
1362 def password_dialog(self ):
1369 vbox = QVBoxLayout()
1370 msg = _('Please enter your password')
1371 vbox.addWidget(QLabel(msg))
1373 grid = QGridLayout()
1375 grid.addWidget(QLabel(_('Password')), 1, 0)
1376 grid.addWidget(pw, 1, 1)
1377 vbox.addLayout(grid)
1379 vbox.addLayout(ok_cancel_buttons(d))
1382 if not d.exec_(): return
1383 return unicode(pw.text())
1390 def change_password_dialog( wallet, parent=None ):
1393 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1401 new_pw = QLineEdit()
1402 new_pw.setEchoMode(2)
1403 conf_pw = QLineEdit()
1404 conf_pw.setEchoMode(2)
1406 vbox = QVBoxLayout()
1408 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1409 +_('To disable wallet encryption, enter an empty new password.')) \
1410 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1412 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1413 +_("Leave these fields empty if you want to disable encryption.")
1414 vbox.addWidget(QLabel(msg))
1416 grid = QGridLayout()
1419 if wallet.use_encryption:
1420 grid.addWidget(QLabel(_('Password')), 1, 0)
1421 grid.addWidget(pw, 1, 1)
1423 grid.addWidget(QLabel(_('New Password')), 2, 0)
1424 grid.addWidget(new_pw, 2, 1)
1426 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1427 grid.addWidget(conf_pw, 3, 1)
1428 vbox.addLayout(grid)
1430 vbox.addLayout(ok_cancel_buttons(d))
1433 if not d.exec_(): return
1435 password = unicode(pw.text()) if wallet.use_encryption else None
1436 new_password = unicode(new_pw.text())
1437 new_password2 = unicode(conf_pw.text())
1440 seed = wallet.pw_decode( wallet.seed, password)
1442 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1445 if new_password != new_password2:
1446 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1447 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1449 wallet.update_password(seed, password, new_password)
1452 def seed_dialog(wallet, parent=None):
1456 vbox = QVBoxLayout()
1457 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1458 vbox.addWidget(QLabel(msg))
1460 grid = QGridLayout()
1463 seed_e = QLineEdit()
1464 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1465 grid.addWidget(seed_e, 1, 1)
1469 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1470 grid.addWidget(gap_e, 2, 1)
1471 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1472 vbox.addLayout(grid)
1474 vbox.addLayout(ok_cancel_buttons(d))
1477 if not d.exec_(): return
1480 gap = int(unicode(gap_e.text()))
1482 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1486 seed = unicode(seed_e.text())
1489 print_error("Warning: Not hex, trying decode")
1491 seed = mnemonic.mn_decode( seed.split(' ') )
1493 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1496 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1499 wallet.seed = str(seed)
1500 #print repr(wallet.seed)
1501 wallet.gap_limit = gap
1506 def settings_dialog(self):
1508 d.setWindowTitle(_('Electrum Settings'))
1510 vbox = QVBoxLayout()
1512 tabs = QTabWidget(self)
1513 vbox.addWidget(tabs)
1516 grid_wallet = QGridLayout(tab)
1517 grid_wallet.setColumnStretch(0,1)
1518 tabs.addTab(tab, _('Wallet') )
1521 grid_ui = QGridLayout(tab2)
1522 grid_ui.setColumnStretch(0,1)
1523 tabs.addTab(tab2, _('Display') )
1525 fee_label = QLabel(_('Transaction fee'))
1526 grid_wallet.addWidget(fee_label, 2, 0)
1528 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1529 grid_wallet.addWidget(fee_e, 2, 1)
1530 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1531 + _('Recommended value') + ': 0.001'
1532 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1533 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1534 if not self.config.is_modifiable('fee'):
1535 for w in [fee_e, fee_label]: w.setEnabled(False)
1537 nz_label = QLabel(_('Display zeros'))
1538 grid_ui.addWidget(nz_label, 3, 0)
1540 nz_e.setText("%d"% self.wallet.num_zeros)
1541 grid_ui.addWidget(nz_e, 3, 1)
1542 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1543 grid_ui.addWidget(HelpButton(msg), 3, 2)
1544 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1545 if not self.config.is_modifiable('num_zeros'):
1546 for w in [nz_e, nz_label]: w.setEnabled(False)
1549 usechange_label = QLabel(_('Use change addresses'))
1550 grid_wallet.addWidget(usechange_label, 5, 0)
1551 usechange_combo = QComboBox()
1552 usechange_combo.addItems(['Yes', 'No'])
1553 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1554 grid_wallet.addWidget(usechange_combo, 5, 1)
1555 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1556 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1558 gap_label = QLabel(_('Gap limit'))
1559 grid_wallet.addWidget(gap_label, 6, 0)
1561 gap_e.setText("%d"% self.wallet.gap_limit)
1562 grid_wallet.addWidget(gap_e, 6, 1)
1563 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1564 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1565 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1566 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1567 + _('Warning') + ': ' \
1568 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1569 + _('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'
1570 grid_wallet.addWidget(HelpButton(msg), 6, 2)
1571 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1572 if not self.config.is_modifiable('gap_limit'):
1573 for w in [gap_e, gap_label]: w.setEnabled(False)
1575 gui_label=QLabel(_('Default GUI') + ':')
1576 grid_ui.addWidget(gui_label , 7, 0)
1577 gui_combo = QComboBox()
1578 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1579 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1580 if index==-1: index = 1
1581 gui_combo.setCurrentIndex(index)
1582 grid_ui.addWidget(gui_combo, 7, 1)
1583 grid_ui.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1584 if not self.config.is_modifiable('gui'):
1585 for w in [gui_combo, gui_label]: w.setEnabled(False)
1587 lang_label=QLabel(_('Language') + ':')
1588 grid_ui.addWidget(lang_label , 8, 0)
1589 lang_combo = QComboBox()
1590 languages = {'':_('Default'), 'br':_('Brasilian'), 'cs':_('Czech'), 'de':_('German'),
1591 'eo':_('Esperanto'), 'en':_('English'), 'es':_('Spanish'), 'fr':_('French'),
1592 'it':_('Italian'), 'lv':_('Latvian'), 'nl':_('Dutch'), 'ru':_('Russian'),
1593 'sl':_('Slovenian'), 'vi':_('Vietnamese'), 'zh':_('Chinese')
1595 lang_combo.addItems(languages.values())
1597 index = languages.keys().index(self.config.get("language",''))
1600 lang_combo.setCurrentIndex(index)
1601 grid_ui.addWidget(lang_combo, 8, 1)
1602 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart). ')), 8, 2)
1603 if not self.config.is_modifiable('language'):
1604 for w in [lang_combo, lang_label]: w.setEnabled(False)
1607 view_label=QLabel(_('Receive Tab') + ':')
1608 grid_ui.addWidget(view_label , 9, 0)
1609 view_combo = QComboBox()
1610 view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
1611 view_combo.setCurrentIndex(self.receive_tab_mode)
1612 grid_ui.addWidget(view_combo, 9, 1)
1613 hh = _('This selects the interaction mode of the "Receive" tab. ') + '\n\n' \
1614 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1615 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
1616 + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n'
1618 grid_ui.addWidget(HelpButton(hh), 9, 2)
1620 vbox.addLayout(ok_cancel_buttons(d))
1624 if not d.exec_(): return
1626 fee = unicode(fee_e.text())
1628 fee = int( 100000000 * Decimal(fee) )
1630 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1633 if self.wallet.fee != fee:
1634 self.wallet.fee = fee
1637 nz = unicode(nz_e.text())
1642 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1645 if self.wallet.num_zeros != nz:
1646 self.wallet.num_zeros = nz
1647 self.config.set_key('num_zeros', nz, True)
1648 self.update_history_tab()
1649 self.update_receive_tab()
1651 usechange_result = usechange_combo.currentIndex() == 0
1652 if self.wallet.use_change != usechange_result:
1653 self.wallet.use_change = usechange_result
1654 self.config.set_key('use_change', self.wallet.use_change, True)
1657 n = int(gap_e.text())
1659 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1662 if self.wallet.gap_limit != n:
1663 r = self.wallet.change_gap_limit(n)
1665 self.update_receive_tab()
1666 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1668 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1670 need_restart = False
1672 gui_request = str(gui_combo.currentText()).lower()
1673 if gui_request != self.config.get('gui'):
1674 self.config.set_key('gui', gui_request, True)
1677 lang_request = languages.keys()[lang_combo.currentIndex()]
1678 if lang_request != self.config.get('language'):
1679 self.config.set_key("language", lang_request, True)
1683 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
1685 self.receive_tab_set_mode(view_combo.currentIndex())
1689 def network_dialog(wallet, parent=None):
1690 interface = wallet.interface
1692 if interface.is_connected:
1693 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1695 status = _("Not connected")
1696 server = interface.server
1699 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1700 server = interface.server
1702 plist, servers_list = interface.get_servers_list()
1706 d.setWindowTitle(_('Server'))
1707 d.setMinimumSize(375, 20)
1709 vbox = QVBoxLayout()
1712 hbox = QHBoxLayout()
1714 l.setPixmap(QPixmap(":icons/network.png"))
1717 hbox.addWidget(QLabel(status))
1719 vbox.addLayout(hbox)
1723 grid = QGridLayout()
1725 vbox.addLayout(grid)
1728 server_protocol = QComboBox()
1729 server_host = QLineEdit()
1730 server_host.setFixedWidth(200)
1731 server_port = QLineEdit()
1732 server_port.setFixedWidth(60)
1734 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1735 protocol_letters = 'thsg'
1736 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1737 server_protocol.addItems(protocol_names)
1739 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1740 grid.addWidget(server_protocol, 0, 1)
1741 grid.addWidget(server_host, 0, 2)
1742 grid.addWidget(server_port, 0, 3)
1744 def change_protocol(p):
1745 protocol = protocol_letters[p]
1746 host = unicode(server_host.text())
1747 pp = plist.get(host,DEFAULT_PORTS)
1748 if protocol not in pp.keys():
1749 protocol = pp.keys()[0]
1751 server_host.setText( host )
1752 server_port.setText( port )
1754 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1756 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1757 servers_list_widget = QTreeWidget(parent)
1758 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1759 servers_list_widget.setMaximumHeight(150)
1760 servers_list_widget.setColumnWidth(0, 240)
1761 for _host in servers_list.keys():
1762 _type = 'P' if servers_list[_host].get('pruning') else 'F'
1763 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1765 def change_server(host, protocol=None):
1766 pp = plist.get(host,DEFAULT_PORTS)
1768 port = pp.get(protocol)
1769 if not port: protocol = None
1772 if 't' in pp.keys():
1774 port = pp.get(protocol)
1776 protocol = pp.keys()[0]
1777 port = pp.get(protocol)
1779 server_host.setText( host )
1780 server_port.setText( port )
1781 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1783 if not plist: return
1784 for p in protocol_letters:
1785 i = protocol_letters.index(p)
1786 j = server_protocol.model().index(i,0)
1787 if p not in pp.keys():
1788 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1790 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1794 host, port, protocol = server.split(':')
1795 change_server(host,protocol)
1797 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1798 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1800 if not wallet.config.is_modifiable('server'):
1801 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1804 autocycle_cb = QCheckBox('Try random servers if disconnected')
1805 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
1806 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
1807 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
1810 proxy_mode = QComboBox()
1811 proxy_host = QLineEdit()
1812 proxy_host.setFixedWidth(200)
1813 proxy_port = QLineEdit()
1814 proxy_port.setFixedWidth(60)
1815 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1817 def check_for_disable(index = False):
1818 if proxy_mode.currentText() != 'NONE':
1819 proxy_host.setEnabled(True)
1820 proxy_port.setEnabled(True)
1822 proxy_host.setEnabled(False)
1823 proxy_port.setEnabled(False)
1826 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1828 if not wallet.config.is_modifiable('proxy'):
1829 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1831 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1832 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1833 proxy_host.setText(proxy_config.get("host"))
1834 proxy_port.setText(proxy_config.get("port"))
1836 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1837 grid.addWidget(proxy_mode, 2, 1)
1838 grid.addWidget(proxy_host, 2, 2)
1839 grid.addWidget(proxy_port, 2, 3)
1842 vbox.addLayout(ok_cancel_buttons(d))
1845 if not d.exec_(): return
1847 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1848 if proxy_mode.currentText() != 'NONE':
1849 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1853 wallet.config.set_key("proxy", proxy, True)
1854 wallet.config.set_key("server", server, True)
1855 interface.set_server(server, proxy)
1856 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
1859 def closeEvent(self, event):
1861 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1867 def __init__(self, wallet, config, app=None):
1868 self.wallet = wallet
1869 self.config = config
1871 self.app = QApplication(sys.argv)
1874 def restore_or_create(self):
1875 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1876 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1877 if r==2: return None
1878 return 'restore' if r==1 else 'create'
1880 def seed_dialog(self):
1881 return ElectrumWindow.seed_dialog( self.wallet )
1883 def network_dialog(self):
1884 return ElectrumWindow.network_dialog( self.wallet, parent=None )
1887 def show_seed(self):
1888 ElectrumWindow.show_seed_dialog(self.wallet)
1891 def password_dialog(self):
1892 ElectrumWindow.change_password_dialog(self.wallet)
1895 def restore_wallet(self):
1896 wallet = self.wallet
1897 # wait until we are connected, because the user might have selected another server
1898 if not wallet.interface.is_connected:
1899 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1900 waiting_dialog(waiting)
1902 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1903 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1905 wallet.set_up_to_date(False)
1906 wallet.interface.poke('synchronizer')
1907 waiting_dialog(waiting)
1908 if wallet.is_found():
1909 print_error( "Recovery successful" )
1911 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1918 w = ElectrumWindow(self.wallet, self.config)
1919 if url: w.set_url(url)