3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re
21 from util import print_error
26 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
28 from PyQt4.QtGui import *
29 from PyQt4.QtCore import *
30 import PyQt4.QtCore as QtCore
31 import PyQt4.QtGui as QtGui
32 from interface import DEFAULT_SERVERS
37 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o lib/icons_rc.py'")
39 from wallet import format_satoshis
40 import bmp, mnemonic, pyqrnative, qrscanner
42 from decimal import Decimal
46 if platform.system() == 'Windows':
47 MONOSPACE_FONT = 'Lucida Console'
48 elif platform.system() == 'Darwin':
49 MONOSPACE_FONT = 'Monaco'
51 MONOSPACE_FONT = 'monospace'
53 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
55 def numbify(entry, is_int = False):
56 text = unicode(entry.text()).strip()
57 pos = entry.cursorPosition()
59 if not is_int: chars +='.'
60 s = ''.join([i for i in text if i in chars])
65 s = s[:p] + '.' + s[p:p+8]
67 amount = int( Decimal(s) * 100000000 )
76 entry.setCursorPosition(pos)
80 class Timer(QtCore.QThread):
83 self.emit(QtCore.SIGNAL('timersignal'))
86 class HelpButton(QPushButton):
87 def __init__(self, text):
88 QPushButton.__init__(self, '?')
89 self.setFocusPolicy(Qt.NoFocus)
90 self.setFixedWidth(20)
91 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
94 class EnterButton(QPushButton):
95 def __init__(self, text, func):
96 QPushButton.__init__(self, text)
98 self.clicked.connect(func)
100 def keyPressEvent(self, e):
101 if e.key() == QtCore.Qt.Key_Return:
104 class MyTreeWidget(QTreeWidget):
105 def __init__(self, parent):
106 QTreeWidget.__init__(self, parent)
109 for i in range(0,self.viewport().height()/5):
110 if self.itemAt(QPoint(0,i*5)) == item:
114 for j in range(0,30):
115 if self.itemAt(QPoint(0,i*5 + j)) != item:
117 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
119 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
124 class StatusBarButton(QPushButton):
125 def __init__(self, icon, tooltip, func):
126 QPushButton.__init__(self, icon, '')
127 self.setToolTip(tooltip)
129 self.setMaximumWidth(25)
130 self.clicked.connect(func)
133 def keyPressEvent(self, e):
134 if e.key() == QtCore.Qt.Key_Return:
138 class QRCodeWidget(QWidget):
140 def __init__(self, data = None):
141 QWidget.__init__(self)
142 self.setMinimumSize(210, 210)
149 def set_addr(self, addr):
150 if self.addr != addr:
156 if self.addr and not self.qr:
157 self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
158 self.qr.addData(self.addr)
162 def paintEvent(self, e):
167 black = QColor(0, 0, 0, 255)
168 white = QColor(255, 255, 255, 255)
172 qp = QtGui.QPainter()
176 qp.drawRect(0, 0, 198, 198)
180 size = self.qr.getModuleCount()*boxsize
181 k = self.qr.getModuleCount()
182 qp = QtGui.QPainter()
186 if self.qr.isDark(r, c):
192 qp.drawRect(c*boxsize, r*boxsize, boxsize, boxsize)
197 class QR_Window(QWidget):
200 QWidget.__init__(self)
201 self.setWindowTitle('Electrum - Invoice')
202 self.setMinimumSize(800, 250)
206 self.setFocusPolicy(QtCore.Qt.NoFocus)
208 main_box = QHBoxLayout()
210 self.qrw = QRCodeWidget()
211 main_box.addWidget(self.qrw)
214 main_box.addLayout(vbox)
216 main_box.addStretch(1)
218 self.address_label = QLabel("")
219 self.address_label.setFont(QFont(MONOSPACE_FONT))
220 vbox.addWidget(self.address_label)
222 self.label_label = QLabel("")
223 vbox.addWidget(self.label_label)
225 self.amount_label = QLabel("")
226 vbox.addWidget(self.amount_label)
229 self.setLayout(main_box)
232 def set_content(self, addr, label, amount):
234 address_text = "<span style='font-size: 18pt'>%s</span>" % addr if addr else ""
235 self.address_label.setText(address_text)
238 amount_text = "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % format_satoshis(amount) if amount else ""
239 self.amount_label.setText(amount_text)
242 label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
243 self.label_label.setText(label_text)
245 msg = 'bitcoin:'+self.address
246 if self.amount is not None:
247 msg += '?amount=%s'%(str( Decimal(self.amount) /100000000))
248 if self.label is not None:
249 msg += '&label=%s'%(self.label)
250 elif self.label is not None:
251 msg += '?label=%s'%(self.label)
253 self.qrw.set_addr( msg )
258 def waiting_dialog(f):
264 w.setWindowTitle('Electrum')
274 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
279 def ok_cancel_buttons(dialog):
282 b = QPushButton("OK")
284 b.clicked.connect(dialog.accept)
285 b = QPushButton("Cancel")
287 b.clicked.connect(dialog.reject)
291 class ElectrumWindow(QMainWindow):
293 def __init__(self, wallet, config):
294 QMainWindow.__init__(self)
297 self.wallet.interface.register_callback('updated', self.update_callback)
298 self.wallet.interface.register_callback('connected', self.update_callback)
299 self.wallet.interface.register_callback('disconnected', self.update_callback)
300 self.wallet.interface.register_callback('disconnecting', self.update_callback)
302 self.receive_tab_mode = config.get('qt_receive_tab_mode', 0)
303 self.merchant_name = config.get('merchant_name', 'Invoice')
305 self.qr_window = None
306 self.funds_error = False
307 self.completions = QStringListModel()
309 self.tabs = tabs = QTabWidget(self)
310 tabs.addTab(self.create_history_tab(), _('History') )
311 tabs.addTab(self.create_send_tab(), _('Send') )
312 tabs.addTab(self.create_receive_tab(), _('Receive') )
313 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
314 tabs.addTab(self.create_wall_tab(), _('Wall') )
315 tabs.setMinimumSize(600, 400)
316 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
317 self.setCentralWidget(tabs)
318 self.create_status_bar()
319 self.toggle_QR_window(self.receive_tab_mode == 2)
321 g = self.config.get("winpos-qt",[100, 100, 840, 400])
322 self.setGeometry(g[0], g[1], g[2], g[3])
323 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
324 if not self.wallet.seed: title += ' [seedless]'
325 self.setWindowTitle( title )
327 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
328 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
329 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
330 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
332 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
333 #self.connect(self, SIGNAL('editamount'), self.edit_amount)
334 self.history_list.setFocus(True)
336 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
337 if platform.system() == 'Windows':
338 n = 3 if self.wallet.seed else 2
339 tabs.setCurrentIndex (n)
340 tabs.setCurrentIndex (0)
343 QMainWindow.close(self)
345 self.qr_window.close()
346 self.qr_window = None
348 def connect_slots(self, sender):
349 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
350 self.previous_payto_e=''
352 def timer_actions(self):
354 self.qr_window.qrw.update_qr()
356 if self.payto_e.hasFocus():
358 r = unicode( self.payto_e.text() )
359 if r != self.previous_payto_e:
360 self.previous_payto_e = r
362 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
364 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
368 s = r + ' <' + to_address + '>'
369 self.payto_e.setText(s)
372 def update_callback(self):
373 self.emit(QtCore.SIGNAL('updatesignal'))
375 def update_wallet(self):
376 if self.wallet.interface and self.wallet.interface.is_connected:
377 if not self.wallet.up_to_date:
378 text = _( "Synchronizing..." )
379 icon = QIcon(":icons/status_waiting.png")
381 c, u = self.wallet.get_balance()
382 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
383 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
384 icon = QIcon(":icons/status_connected.png")
386 text = _( "Not connected" )
387 icon = QIcon(":icons/status_disconnected.png")
390 text = _( "Not enough funds" )
392 self.statusBar().showMessage(text)
393 self.status_button.setIcon( icon )
395 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
396 self.textbox.setText( self.wallet.banner )
397 self.update_history_tab()
398 self.update_receive_tab()
399 self.update_contacts_tab()
400 self.update_completions()
403 def create_history_tab(self):
404 self.history_list = l = MyTreeWidget(self)
406 l.setColumnWidth(0, 40)
407 l.setColumnWidth(1, 140)
408 l.setColumnWidth(2, 350)
409 l.setColumnWidth(3, 140)
410 l.setColumnWidth(4, 140)
411 l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
412 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
413 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
415 l.setContextMenuPolicy(Qt.CustomContextMenu)
416 l.customContextMenuRequested.connect(self.create_history_menu)
420 def create_history_menu(self, position):
421 self.history_list.selectedIndexes()
422 item = self.history_list.currentItem()
424 tx_hash = str(item.toolTip(0))
425 if not tx_hash: return
427 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
428 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
429 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
430 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
433 def tx_details(self, tx_hash):
434 tx_details = self.wallet.get_tx_details(tx_hash)
435 QMessageBox.information(self, 'Details', tx_details, 'OK')
438 def tx_label_clicked(self, item, column):
439 if column==2 and item.isSelected():
440 tx_hash = str(item.toolTip(0))
442 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
443 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
444 self.history_list.editItem( item, column )
445 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
448 def tx_label_changed(self, item, column):
452 tx_hash = str(item.toolTip(0))
453 tx = self.wallet.transactions.get(tx_hash)
454 s = self.wallet.labels.get(tx_hash)
455 text = unicode( item.text(2) )
457 self.wallet.labels[tx_hash] = text
458 item.setForeground(2, QBrush(QColor('black')))
460 if s: self.wallet.labels.pop(tx_hash)
461 text = self.wallet.get_default_label(tx_hash)
462 item.setText(2, text)
463 item.setForeground(2, QBrush(QColor('gray')))
467 def edit_label(self, is_recv):
468 l = self.receive_list if is_recv else self.contacts_list
469 c = 2 if is_recv else 1
470 item = l.currentItem()
471 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
472 l.editItem( item, c )
473 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
475 def edit_amount(self):
476 l = self.receive_list
477 item = l.currentItem()
478 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
479 l.editItem( item, 3 )
480 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
483 def address_label_clicked(self, item, column, l, column_addr, column_label):
484 if column == column_label and item.isSelected():
485 addr = unicode( item.text(column_addr) )
486 label = unicode( item.text(column_label) )
487 if label in self.wallet.aliases.keys():
489 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
490 l.editItem( item, column )
491 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
494 def address_label_changed(self, item, column, l, column_addr, column_label):
496 if column == column_label:
497 addr = unicode( item.text(column_addr) )
498 text = unicode( item.text(column_label) )
502 if text not in self.wallet.aliases.keys():
503 old_addr = self.wallet.labels.get(text)
505 self.wallet.labels[addr] = text
508 print_error("Error: This is one of your aliases")
509 label = self.wallet.labels.get(addr,'')
510 item.setText(column_label, QString(label))
512 s = self.wallet.labels.get(addr)
514 self.wallet.labels.pop(addr)
518 self.update_history_tab()
519 self.update_completions()
521 self.recv_changed(item)
524 address = unicode( item.text(column_addr) )
525 text = unicode( item.text(3) )
527 index = self.wallet.addresses.index(address)
532 amount = int( Decimal(text) * 100000000 )
533 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
535 amount = self.wallet.requested_amounts.get(address)
537 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
542 self.wallet.requested_amounts[address] = amount
544 label = self.wallet.labels.get(address)
546 label = self.merchant_name + ' - %04d'%(index+1)
547 self.wallet.labels[address] = label
549 self.update_receive_item(self.receive_list.currentItem())
551 self.qr_window.set_content( address, label, amount )
554 def recv_changed(self, a):
555 "current item changed"
556 if a is not None and self.qr_window and self.qr_window.isVisible():
557 address = str(a.text(1))
558 label = self.wallet.labels.get(address)
559 amount = self.wallet.requested_amounts.get(address)
560 self.qr_window.set_content( address, label, amount )
563 def update_history_tab(self):
565 self.history_list.clear()
566 for item in self.wallet.get_tx_history():
567 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
570 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
576 icon = QIcon(":icons/unconfirmed.png")
578 icon = QIcon(":icons/clock%d.png"%conf)
580 icon = QIcon(":icons/confirmed.png")
583 icon = QIcon(":icons/unconfirmed.png")
585 if value is not None:
586 v_str = format_satoshis(value, True, self.wallet.num_zeros)
590 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
593 label, is_default_label = self.wallet.get_label(tx_hash)
595 label = _('Pruned transaction outputs')
596 is_default_label = False
598 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
599 item.setFont(2, QFont(MONOSPACE_FONT))
600 item.setFont(3, QFont(MONOSPACE_FONT))
601 item.setFont(4, QFont(MONOSPACE_FONT))
603 item.setToolTip(0, tx_hash)
605 item.setForeground(2, QBrush(QColor('grey')))
607 item.setIcon(0, icon)
608 self.history_list.insertTopLevelItem(0,item)
611 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
614 def create_send_tab(self):
619 grid.setColumnMinimumWidth(3,300)
620 grid.setColumnStretch(5,1)
622 self.payto_e = QLineEdit()
623 grid.addWidget(QLabel(_('Pay to')), 1, 0)
624 grid.addWidget(self.payto_e, 1, 1, 1, 3)
627 qrcode = qrscanner.scan_qr()
628 if 'address' in qrcode:
629 self.payto_e.setText(qrcode['address'])
630 if 'amount' in qrcode:
631 self.amount_e.setText(str(qrcode['amount']))
632 if 'label' in qrcode:
633 self.message_e.setText(qrcode['label'])
634 if 'message' in qrcode:
635 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
638 if qrscanner.is_available():
639 b = QPushButton(_("Scan QR code"))
640 b.clicked.connect(fill_from_qr)
641 grid.addWidget(b, 1, 5)
643 grid.addWidget(HelpButton(_('Recipient of the funds.') + '\n\n' + _('You may enter a Bitcoin address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)')), 1, 4)
645 completer = QCompleter()
646 completer.setCaseSensitivity(False)
647 self.payto_e.setCompleter(completer)
648 completer.setModel(self.completions)
650 self.message_e = QLineEdit()
651 grid.addWidget(QLabel(_('Description')), 2, 0)
652 grid.addWidget(self.message_e, 2, 1, 1, 3)
653 grid.addWidget(HelpButton(_('Description of the transaction (not mandatory).') + '\n\n' + _('The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.')), 2, 4)
655 self.amount_e = QLineEdit()
656 grid.addWidget(QLabel(_('Amount')), 3, 0)
657 grid.addWidget(self.amount_e, 3, 1, 1, 2)
658 grid.addWidget(HelpButton(
659 _('Amount to be sent.') + '\n\n' \
660 + _('The amount will be displayed in red if you do not have enough funds in your wallet. Note that if you have frozen some of your addresses, the available funds will be lower than your total balance.')), 3, 3)
662 self.fee_e = QLineEdit()
663 grid.addWidget(QLabel(_('Fee')), 4, 0)
664 grid.addWidget(self.fee_e, 4, 1, 1, 2)
665 grid.addWidget(HelpButton(
666 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
667 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
668 + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 4, 3)
670 b = EnterButton(_("Send"), self.do_send)
671 grid.addWidget(b, 6, 1)
673 b = EnterButton(_("Clear"),self.do_clear)
674 grid.addWidget(b, 6, 2)
676 self.payto_sig = QLabel('')
677 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
679 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
680 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
689 def entry_changed( is_fee ):
690 self.funds_error = False
691 amount = numbify(self.amount_e)
692 fee = numbify(self.fee_e)
693 if not is_fee: fee = None
696 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
698 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
701 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
704 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
705 self.funds_error = True
706 self.amount_e.setPalette(palette)
707 self.fee_e.setPalette(palette)
709 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
710 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
715 def update_completions(self):
717 for addr,label in self.wallet.labels.items():
718 if addr in self.wallet.addressbook:
719 l.append( label + ' <' + addr + '>')
720 l = l + self.wallet.aliases.keys()
722 self.completions.setStringList(l)
728 label = unicode( self.message_e.text() )
729 r = unicode( self.payto_e.text() )
733 m1 = re.match(ALIAS_REGEXP, r)
734 # label or alias, with address in brackets
735 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
738 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
742 to_address = m2.group(2)
746 if not self.wallet.is_valid(to_address):
747 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
751 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
753 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
756 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
758 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
761 if self.wallet.use_encryption:
762 password = self.password_dialog()
769 tx = self.wallet.mktx( [(to_address, amount)], label, password, fee)
770 except BaseException, e:
771 self.show_message(str(e))
775 h = self.wallet.send_tx(tx)
776 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
777 status, msg = self.wallet.receive_tx( h )
779 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
781 self.update_contacts_tab()
783 QMessageBox.warning(self, _('Error'), msg, _('OK'))
785 filename = 'unsigned_tx'
786 f = open(filename,'w')
789 QMessageBox.information(self, _('Unsigned transaction'), _("Unsigned transaction was saved to file:") + " " +filename, _('OK'))
792 def set_url(self, url):
793 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
794 self.tabs.setCurrentIndex(1)
795 label = self.wallet.labels.get(payto)
796 m_addr = label + ' <'+ payto+'>' if label else payto
797 self.payto_e.setText(m_addr)
799 self.message_e.setText(message)
800 self.amount_e.setText(amount)
802 self.set_frozen(self.payto_e,True)
803 self.set_frozen(self.amount_e,True)
804 self.set_frozen(self.message_e,True)
805 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
807 self.payto_sig.setVisible(False)
810 self.payto_sig.setVisible(False)
811 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
813 self.set_frozen(e,False)
815 def set_frozen(self,entry,frozen):
817 entry.setReadOnly(True)
818 entry.setFrame(False)
820 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
821 entry.setPalette(palette)
823 entry.setReadOnly(False)
826 palette.setColor(entry.backgroundRole(), QColor('white'))
827 entry.setPalette(palette)
830 def toggle_freeze(self,addr):
832 if addr in self.wallet.frozen_addresses:
833 self.wallet.unfreeze(addr)
835 self.wallet.freeze(addr)
836 self.update_receive_tab()
838 def toggle_priority(self,addr):
840 if addr in self.wallet.prioritized_addresses:
841 self.wallet.unprioritize(addr)
843 self.wallet.prioritize(addr)
844 self.update_receive_tab()
847 def create_list_tab(self, headers):
848 "generic tab creation method"
849 l = MyTreeWidget(self)
850 l.setColumnCount( len(headers) )
851 l.setHeaderLabels( headers )
861 vbox.addWidget(buttons)
866 buttons.setLayout(hbox)
871 def create_receive_tab(self):
872 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
873 l.setContextMenuPolicy(Qt.CustomContextMenu)
874 l.customContextMenuRequested.connect(self.create_receive_menu)
875 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
876 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
877 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
878 self.receive_list = l
879 self.receive_buttons_hbox = hbox
885 def receive_tab_set_mode(self, i):
886 self.receive_tab_mode = i
887 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
889 self.update_receive_tab()
890 self.toggle_QR_window(self.receive_tab_mode == 2)
893 def create_contacts_tab(self):
894 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
895 l.setContextMenuPolicy(Qt.CustomContextMenu)
896 l.customContextMenuRequested.connect(self.create_contact_menu)
897 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
898 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
899 self.contacts_list = l
900 self.contacts_buttons_hbox = hbox
901 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
906 def create_receive_menu(self, position):
907 # fixme: this function apparently has a side effect.
908 # if it is not called the menu pops up several times
909 #self.receive_list.selectedIndexes()
911 item = self.receive_list.itemAt(position)
913 addr = unicode(item.text(1))
915 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
916 if self.receive_tab_mode == 2:
917 menu.addAction(_("Request amount"), lambda: self.edit_amount())
918 menu.addAction(_("View QR"), lambda: ElectrumWindow.show_qrcode("Address","bitcoin:"+addr) )
919 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
920 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
922 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
923 menu.addAction(t, lambda: self.toggle_freeze(addr))
924 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
925 menu.addAction(t, lambda: self.toggle_priority(addr))
926 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
929 def payto(self, x, is_alias):
936 label = self.wallet.labels.get(addr)
937 m_addr = label + ' <' + addr + '>' if label else addr
938 self.tabs.setCurrentIndex(1)
939 self.payto_e.setText(m_addr)
940 self.amount_e.setFocus()
942 def delete_contact(self, x, is_alias):
943 if self.question("Do you want to remove %s from your list of contacts?"%x):
944 if not is_alias and x in self.wallet.addressbook:
945 self.wallet.addressbook.remove(x)
946 if x in self.wallet.labels.keys():
947 self.wallet.labels.pop(x)
948 elif is_alias and x in self.wallet.aliases:
949 self.wallet.aliases.pop(x)
950 self.update_history_tab()
951 self.update_contacts_tab()
952 self.update_completions()
954 def create_contact_menu(self, position):
955 # fixme: this function apparently has a side effect.
956 # if it is not called the menu pops up several times
957 #self.contacts_list.selectedIndexes()
959 item = self.contacts_list.itemAt(position)
961 addr = unicode(item.text(0))
962 label = unicode(item.text(1))
963 is_alias = label in self.wallet.aliases.keys()
964 x = label if is_alias else addr
966 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
967 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
968 menu.addAction(_("View QR code"),lambda: self.show_qrcode("Address","bitcoin:"+addr))
970 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
972 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
973 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
974 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
977 def update_receive_item(self, item):
978 address = str( item.data(1,0).toString() )
980 flags = self.wallet.get_address_flags(address)
981 item.setData(0,0,flags)
983 label = self.wallet.labels.get(address,'')
984 item.setData(2,0,label)
986 amount = self.wallet.requested_amounts.get(address,None)
987 amount_str = format_satoshis( amount, False, self.wallet.num_zeros ) if amount is not None else ""
988 item.setData(3,0,amount_str)
990 c, u = self.wallet.get_addr_balance(address)
991 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
992 item.setData(4,0,balance)
994 if address in self.wallet.frozen_addresses:
995 item.setBackgroundColor(1, QColor('lightblue'))
996 elif address in self.wallet.prioritized_addresses:
997 item.setBackgroundColor(1, QColor('lightgreen'))
1000 def update_receive_tab(self):
1001 l = self.receive_list
1004 l.setColumnHidden(0, not self.receive_tab_mode == 1)
1005 l.setColumnHidden(3, not self.receive_tab_mode == 2)
1006 l.setColumnHidden(4, self.receive_tab_mode == 0)
1007 l.setColumnHidden(5, not self.receive_tab_mode == 1)
1008 l.setColumnWidth(0, 50)
1009 l.setColumnWidth(1, 310)
1010 l.setColumnWidth(2, 200)
1011 l.setColumnWidth(3, 130)
1012 l.setColumnWidth(4, 130)
1013 l.setColumnWidth(5, 10)
1017 for address in self.wallet.all_addresses():
1019 if self.wallet.is_change(address) and self.receive_tab_mode != 1:
1023 h = self.wallet.history.get(address,[])
1026 for tx_hash, tx_height in h:
1027 tx = self.wallet.transactions.get(tx_hash)
1035 if address in self.wallet.addresses:
1037 if gap > self.wallet.gap_limit:
1040 if address in self.wallet.addresses:
1043 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1044 item.setFont(0, QFont(MONOSPACE_FONT))
1045 item.setFont(1, QFont(MONOSPACE_FONT))
1046 item.setFont(3, QFont(MONOSPACE_FONT))
1047 self.update_receive_item(item)
1048 if is_red and address in self.wallet.addresses:
1049 item.setBackgroundColor(1, QColor('red'))
1050 l.addTopLevelItem(item)
1052 # we use column 1 because column 0 may be hidden
1053 l.setCurrentItem(l.topLevelItem(0),1)
1055 def show_contact_details(self, m):
1056 a = self.wallet.aliases.get(m)
1058 if a[0] in self.wallet.authorities.keys():
1059 s = self.wallet.authorities.get(a[0])
1062 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1063 QMessageBox.information(self, 'Alias', msg, 'OK')
1065 def update_contacts_tab(self):
1067 l = self.contacts_list
1069 l.setColumnWidth(0, 350)
1070 l.setColumnWidth(1, 330)
1071 l.setColumnWidth(2, 100)
1074 for alias, v in self.wallet.aliases.items():
1076 alias_targets.append(target)
1077 item = QTreeWidgetItem( [ target, alias, '-'] )
1078 item.setBackgroundColor(0, QColor('lightgray'))
1079 l.addTopLevelItem(item)
1081 for address in self.wallet.addressbook:
1082 if address in alias_targets: continue
1083 label = self.wallet.labels.get(address,'')
1085 for item in self.wallet.transactions.values():
1086 if address in item['outputs'] : n=n+1
1088 item = QTreeWidgetItem( [ address, label, tx] )
1089 item.setFont(0, QFont(MONOSPACE_FONT))
1090 l.addTopLevelItem(item)
1092 l.setCurrentItem(l.topLevelItem(0))
1094 def create_wall_tab(self):
1095 self.textbox = textbox = QTextEdit(self)
1096 textbox.setFont(QFont(MONOSPACE_FONT))
1097 textbox.setReadOnly(True)
1100 def create_status_bar(self):
1102 sb.setFixedHeight(35)
1103 if self.wallet.seed:
1104 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1105 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1106 if self.wallet.seed:
1107 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1108 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
1109 sb.addPermanentWidget( self.status_button )
1110 self.setStatusBar(sb)
1112 def new_contact_dialog(self):
1113 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1114 address = unicode(text)
1116 if self.wallet.is_valid(address):
1117 self.wallet.addressbook.append(address)
1119 self.update_contacts_tab()
1120 self.update_history_tab()
1121 self.update_completions()
1123 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1126 def show_seed_dialog(wallet, parent=None):
1128 QMessageBox.information(parent, _('Message'),
1129 _('No seed'), _('OK'))
1132 if wallet.use_encryption:
1133 password = parent.password_dialog()
1140 seed = wallet.pw_decode(wallet.seed, password)
1142 QMessageBox.warning(parent, _('Error'),
1143 _('Incorrect Password'), _('OK'))
1146 dialog = QDialog(None)
1148 dialog.setWindowTitle("Electrum")
1150 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1152 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1153 + _("Please write down or memorize these 12 words (order is important).") + " " \
1154 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1155 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1157 main_text = QLabel(msg)
1158 main_text.setWordWrap(True)
1161 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1168 copy_function = lambda: app.clipboard().setText(brainwallet)
1169 copy_button = QPushButton(_("Copy to Clipboard"))
1170 copy_button.clicked.connect(copy_function)
1172 show_qr_function = lambda: ElectrumWindow.show_qrcode(_("Seed"), seed)
1173 qr_button = QPushButton(_("View as QR Code"))
1174 qr_button.clicked.connect(show_qr_function)
1176 ok_button = QPushButton(_("OK"))
1177 ok_button.setDefault(True)
1178 ok_button.clicked.connect(dialog.accept)
1180 main_layout = QGridLayout()
1181 main_layout.addWidget(logo, 0, 0)
1182 main_layout.addWidget(main_text, 0, 1, 1, -1)
1183 main_layout.addWidget(copy_button, 1, 1)
1184 main_layout.addWidget(qr_button, 1, 2)
1185 main_layout.addWidget(ok_button, 1, 3)
1186 dialog.setLayout(main_layout)
1191 def show_qrcode(title, data):
1195 d.setWindowTitle(title)
1196 d.setMinimumSize(270, 300)
1197 vbox = QVBoxLayout()
1198 qrw = QRCodeWidget(data)
1200 vbox.addWidget(QLabel(data))
1201 hbox = QHBoxLayout()
1205 filename = "qrcode.bmp"
1206 bmp.save_qrcode(qrw.qr, filename)
1207 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1209 b = QPushButton(_("Print"))
1211 b.clicked.connect(print_qr)
1213 b = QPushButton(_("Close"))
1215 b.clicked.connect(d.accept)
1217 vbox.addLayout(hbox)
1221 def sign_message(self,address):
1222 if not address: return
1225 d.setWindowTitle('Sign Message')
1226 d.setMinimumSize(270, 350)
1228 tab_widget = QTabWidget()
1230 layout = QGridLayout(tab)
1232 sign_address = QLineEdit()
1233 sign_address.setText(address)
1234 layout.addWidget(QLabel(_('Address')), 1, 0)
1235 layout.addWidget(sign_address, 1, 1)
1237 sign_message = QTextEdit()
1238 layout.addWidget(QLabel(_('Message')), 2, 0)
1239 layout.addWidget(sign_message, 2, 1, 2, 1)
1241 sign_signature = QLineEdit()
1242 layout.addWidget(QLabel(_('Signature')), 3, 0)
1243 layout.addWidget(sign_signature, 3, 1)
1246 if self.wallet.use_encryption:
1247 password = self.password_dialog()
1254 signature = self.wallet.sign_message(sign_address.text(), str(sign_message.toPlainText()), password)
1255 sign_signature.setText(signature)
1256 except BaseException, e:
1257 self.show_message(str(e))
1260 hbox = QHBoxLayout()
1261 b = QPushButton(_("Sign"))
1263 b.clicked.connect(do_sign)
1264 b = QPushButton(_("Close"))
1265 b.clicked.connect(d.accept)
1267 layout.addLayout(hbox, 4, 1)
1268 tab_widget.addTab(tab, "Sign")
1272 layout = QGridLayout(tab)
1274 verify_address = QLineEdit()
1275 layout.addWidget(QLabel(_('Address')), 1, 0)
1276 layout.addWidget(verify_address, 1, 1)
1278 verify_message = QTextEdit()
1279 layout.addWidget(QLabel(_('Message')), 2, 0)
1280 layout.addWidget(verify_message, 2, 1, 2, 1)
1282 verify_signature = QLineEdit()
1283 layout.addWidget(QLabel(_('Signature')), 3, 0)
1284 layout.addWidget(verify_signature, 3, 1)
1288 self.wallet.verify_message(verify_address.text(), verify_signature.text(), str(verify_message.toPlainText()))
1289 self.show_message("Signature verified")
1290 except BaseException, e:
1291 self.show_message(str(e))
1294 hbox = QHBoxLayout()
1295 b = QPushButton(_("Verify"))
1296 b.clicked.connect(do_verify)
1298 b = QPushButton(_("Close"))
1299 b.clicked.connect(d.accept)
1301 layout.addLayout(hbox, 4, 1)
1302 tab_widget.addTab(tab, "Verify")
1304 vbox = QVBoxLayout()
1305 vbox.addWidget(tab_widget)
1310 def toggle_QR_window(self, show):
1311 if show and not self.qr_window:
1312 self.qr_window = QR_Window()
1313 self.qr_window.setVisible(True)
1314 self.qr_window_geometry = self.qr_window.geometry()
1315 item = self.receive_list.currentItem()
1317 address = str(item.text(1))
1318 label = self.wallet.labels.get(address)
1319 amount = self.wallet.requested_amounts.get(address)
1320 self.qr_window.set_content( address, label, amount )
1322 elif show and self.qr_window and not self.qr_window.isVisible():
1323 self.qr_window.setVisible(True)
1324 self.qr_window.setGeometry(self.qr_window_geometry)
1326 elif not show and self.qr_window and self.qr_window.isVisible():
1327 self.qr_window_geometry = self.qr_window.geometry()
1328 self.qr_window.setVisible(False)
1330 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1331 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1332 self.receive_list.setColumnWidth(2, 200)
1335 def question(self, msg):
1336 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1338 def show_message(self, msg):
1339 QMessageBox.information(self, _('Message'), msg, _('OK'))
1341 def password_dialog(self ):
1348 vbox = QVBoxLayout()
1349 msg = _('Please enter your password')
1350 vbox.addWidget(QLabel(msg))
1352 grid = QGridLayout()
1354 grid.addWidget(QLabel(_('Password')), 1, 0)
1355 grid.addWidget(pw, 1, 1)
1356 vbox.addLayout(grid)
1358 vbox.addLayout(ok_cancel_buttons(d))
1361 if not d.exec_(): return
1362 return unicode(pw.text())
1369 def change_password_dialog( wallet, parent=None ):
1372 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1380 new_pw = QLineEdit()
1381 new_pw.setEchoMode(2)
1382 conf_pw = QLineEdit()
1383 conf_pw.setEchoMode(2)
1385 vbox = QVBoxLayout()
1387 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1388 +_('To disable wallet encryption, enter an empty new password.')) \
1389 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1391 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1392 +_("Leave these fields empty if you want to disable encryption.")
1393 vbox.addWidget(QLabel(msg))
1395 grid = QGridLayout()
1398 if wallet.use_encryption:
1399 grid.addWidget(QLabel(_('Password')), 1, 0)
1400 grid.addWidget(pw, 1, 1)
1402 grid.addWidget(QLabel(_('New Password')), 2, 0)
1403 grid.addWidget(new_pw, 2, 1)
1405 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1406 grid.addWidget(conf_pw, 3, 1)
1407 vbox.addLayout(grid)
1409 vbox.addLayout(ok_cancel_buttons(d))
1412 if not d.exec_(): return
1414 password = unicode(pw.text()) if wallet.use_encryption else None
1415 new_password = unicode(new_pw.text())
1416 new_password2 = unicode(conf_pw.text())
1419 seed = wallet.pw_decode( wallet.seed, password)
1421 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1424 if new_password != new_password2:
1425 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1426 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1428 wallet.update_password(seed, password, new_password)
1431 def seed_dialog(wallet, parent=None):
1435 vbox = QVBoxLayout()
1436 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1437 vbox.addWidget(QLabel(msg))
1439 grid = QGridLayout()
1442 seed_e = QLineEdit()
1443 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1444 grid.addWidget(seed_e, 1, 1)
1448 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1449 grid.addWidget(gap_e, 2, 1)
1450 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1451 vbox.addLayout(grid)
1453 vbox.addLayout(ok_cancel_buttons(d))
1456 if not d.exec_(): return
1459 gap = int(unicode(gap_e.text()))
1461 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1465 seed = unicode(seed_e.text())
1468 print_error("Warning: Not hex, trying decode")
1470 seed = mnemonic.mn_decode( seed.split(' ') )
1472 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1475 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1478 wallet.seed = str(seed)
1479 #print repr(wallet.seed)
1480 wallet.gap_limit = gap
1485 def settings_dialog(self):
1487 d.setWindowTitle(_('Electrum Settings'))
1489 vbox = QVBoxLayout()
1490 msg = _('Here are the settings of your wallet.') + '\n'\
1491 + _('For more explanations, click on the help buttons next to each field.')
1494 label.setFixedWidth(250)
1495 label.setWordWrap(True)
1496 label.setAlignment(Qt.AlignJustify)
1498 tabs = QTabWidget(self)
1499 vbox.addWidget(tabs)
1501 vbox.addWidget(label)
1504 grid_wallet = QGridLayout(tab)
1505 grid_wallet.setColumnStretch(0,1)
1506 tabs.addTab(tab, _('Wallet') )
1509 grid_ui = QGridLayout(tab2)
1510 grid_ui.setColumnStretch(0,1)
1511 tabs.addTab(tab2, _('Display') )
1513 fee_label = QLabel(_('Transaction fee'))
1514 grid_wallet.addWidget(fee_label, 2, 0)
1516 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1517 grid_wallet.addWidget(fee_e, 2, 1)
1518 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1519 + _('Recommended value') + ': 0.001'
1520 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1521 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1522 if not self.config.is_modifiable('fee'):
1523 for w in [fee_e, fee_label]: w.setEnabled(False)
1525 nz_label = QLabel(_('Display zeros'))
1526 grid_ui.addWidget(nz_label, 3, 0)
1528 nz_e.setText("%d"% self.wallet.num_zeros)
1529 grid_ui.addWidget(nz_e, 3, 1)
1530 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1531 grid_ui.addWidget(HelpButton(msg), 3, 2)
1532 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1533 if not self.config.is_modifiable('num_zeros'):
1534 for w in [nz_e, nz_label]: w.setEnabled(False)
1537 usechange_label = QLabel(_('Use change addresses'))
1538 grid_wallet.addWidget(usechange_label, 5, 0)
1539 usechange_combo = QComboBox()
1540 usechange_combo.addItems(['Yes', 'No'])
1541 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1542 grid_wallet.addWidget(usechange_combo, 5, 1)
1543 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1544 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1546 gap_label = QLabel(_('Gap limit'))
1547 grid_wallet.addWidget(gap_label, 6, 0)
1549 gap_e.setText("%d"% self.wallet.gap_limit)
1550 grid_wallet.addWidget(gap_e, 6, 1)
1551 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1552 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1553 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1554 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1555 + _('Warning') + ': ' \
1556 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1557 + _('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'
1558 grid_wallet.addWidget(HelpButton(msg), 6, 2)
1559 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1560 if not self.config.is_modifiable('gap_limit'):
1561 for w in [gap_e, gap_label]: w.setEnabled(False)
1563 gui_label=QLabel(_('Default GUI') + ':')
1564 grid_ui.addWidget(gui_label , 7, 0)
1565 gui_combo = QComboBox()
1566 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1567 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1568 if index==-1: index = 1
1569 gui_combo.setCurrentIndex(index)
1570 grid_ui.addWidget(gui_combo, 7, 1)
1571 grid_ui.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1572 if not self.config.is_modifiable('gui'):
1573 for w in [gui_combo, gui_label]: w.setEnabled(False)
1575 lang_label=QLabel(_('Language') + ':')
1576 grid_ui.addWidget(lang_label , 8, 0)
1577 lang_combo = QComboBox()
1578 languages = {'':_('Default'), 'br':_('Brasilian'), 'cs':_('Czech'), 'de':_('German'),
1579 'eo':_('Esperanto'), 'en':_('English'), 'es':_('Spanish'), 'fr':_('French'),
1580 'it':_('Italian'), 'lv':_('Latvian'), 'nl':_('Dutch'), 'ru':_('Russian'),
1581 'sl':_('Slovenian'), 'vi':_('Vietnamese'), 'zh':_('Chinese')
1583 lang_combo.addItems(languages.values())
1585 index = languages.keys().index(self.config.get("language",''))
1588 lang_combo.setCurrentIndex(index)
1589 grid_ui.addWidget(lang_combo, 8, 1)
1590 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart). ')), 8, 2)
1591 if not self.config.is_modifiable('language'):
1592 for w in [lang_combo, lang_label]: w.setEnabled(False)
1595 view_label=QLabel(_('Receive mode') + ':')
1596 grid_ui.addWidget(view_label , 9, 0)
1597 view_combo = QComboBox()
1598 view_combo.addItems([_('Simple View'), _('Detailed View'), _('Point of Sale')])
1599 view_combo.setCurrentIndex(self.receive_tab_mode)
1600 grid_ui.addWidget(view_combo, 9, 1)
1601 grid_ui.addWidget(HelpButton(_('View mode for your "Receive" tab. ')), 9, 2)
1603 vbox.addLayout(ok_cancel_buttons(d))
1607 if not d.exec_(): return
1609 fee = unicode(fee_e.text())
1611 fee = int( 100000000 * Decimal(fee) )
1613 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1616 if self.wallet.fee != fee:
1617 self.wallet.fee = fee
1620 nz = unicode(nz_e.text())
1625 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1628 if self.wallet.num_zeros != nz:
1629 self.wallet.num_zeros = nz
1630 self.config.set_key('num_zeros', nz, True)
1631 self.update_history_tab()
1632 self.update_receive_tab()
1634 usechange_result = usechange_combo.currentIndex() == 0
1635 if self.wallet.use_change != usechange_result:
1636 self.wallet.use_change = usechange_result
1637 self.config.set_key('use_change', self.wallet.use_change, True)
1640 n = int(gap_e.text())
1642 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1645 if self.wallet.gap_limit != n:
1646 r = self.wallet.change_gap_limit(n)
1648 self.update_receive_tab()
1649 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1651 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1653 need_restart = False
1655 gui_request = str(gui_combo.currentText()).lower()
1656 if gui_request != self.config.get('gui'):
1657 self.config.set_key('gui', gui_request, True)
1660 lang_request = languages.keys()[lang_combo.currentIndex()]
1661 if lang_request != self.config.get('language'):
1662 self.config.set_key("language", lang_request, True)
1666 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
1668 self.receive_tab_set_mode(view_combo.currentIndex())
1672 def network_dialog(wallet, parent=None):
1673 interface = wallet.interface
1675 if interface.is_connected:
1676 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1678 status = _("Not connected")
1679 server = interface.server
1682 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1683 server = interface.server
1685 plist, servers_list = interface.get_servers_list()
1689 d.setWindowTitle(_('Server'))
1690 d.setMinimumSize(375, 20)
1692 vbox = QVBoxLayout()
1695 hbox = QHBoxLayout()
1697 l.setPixmap(QPixmap(":icons/network.png"))
1700 hbox.addWidget(QLabel(status))
1702 vbox.addLayout(hbox)
1706 grid = QGridLayout()
1708 vbox.addLayout(grid)
1711 server_protocol = QComboBox()
1712 server_host = QLineEdit()
1713 server_host.setFixedWidth(200)
1714 server_port = QLineEdit()
1715 server_port.setFixedWidth(60)
1717 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1718 protocol_letters = 'thsg'
1719 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1720 server_protocol.addItems(protocol_names)
1722 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1723 grid.addWidget(server_protocol, 0, 1)
1724 grid.addWidget(server_host, 0, 2)
1725 grid.addWidget(server_port, 0, 3)
1727 def change_protocol(p):
1728 protocol = protocol_letters[p]
1729 host = unicode(server_host.text())
1730 pp = plist.get(host,DEFAULT_PORTS)
1731 if protocol not in pp.keys():
1732 protocol = pp.keys()[0]
1734 server_host.setText( host )
1735 server_port.setText( port )
1737 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1739 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1740 servers_list_widget = QTreeWidget(parent)
1741 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1742 servers_list_widget.setMaximumHeight(150)
1743 servers_list_widget.setColumnWidth(0, 240)
1744 for _host in servers_list.keys():
1745 _type = 'P' if servers_list[_host].get('pruning') else 'F'
1746 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1748 def change_server(host, protocol=None):
1749 pp = plist.get(host,DEFAULT_PORTS)
1751 port = pp.get(protocol)
1752 if not port: protocol = None
1755 if 't' in pp.keys():
1757 port = pp.get(protocol)
1759 protocol = pp.keys()[0]
1760 port = pp.get(protocol)
1762 server_host.setText( host )
1763 server_port.setText( port )
1764 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1766 if not plist: return
1767 for p in protocol_letters:
1768 i = protocol_letters.index(p)
1769 j = server_protocol.model().index(i,0)
1770 if p not in pp.keys():
1771 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1773 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1777 host, port, protocol = server.split(':')
1778 change_server(host,protocol)
1780 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1781 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1783 if not wallet.config.is_modifiable('server'):
1784 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1787 proxy_mode = QComboBox()
1788 proxy_host = QLineEdit()
1789 proxy_host.setFixedWidth(200)
1790 proxy_port = QLineEdit()
1791 proxy_port.setFixedWidth(60)
1792 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1794 def check_for_disable(index = False):
1795 if proxy_mode.currentText() != 'NONE':
1796 proxy_host.setEnabled(True)
1797 proxy_port.setEnabled(True)
1799 proxy_host.setEnabled(False)
1800 proxy_port.setEnabled(False)
1803 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1805 if not wallet.config.is_modifiable('proxy'):
1806 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1808 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1809 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1810 proxy_host.setText(proxy_config.get("host"))
1811 proxy_port.setText(proxy_config.get("port"))
1813 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1814 grid.addWidget(proxy_mode, 2, 1)
1815 grid.addWidget(proxy_host, 2, 2)
1816 grid.addWidget(proxy_port, 2, 3)
1819 vbox.addLayout(ok_cancel_buttons(d))
1822 if not d.exec_(): return
1824 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1825 if proxy_mode.currentText() != 'NONE':
1826 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1830 wallet.config.set_key("proxy", proxy, True)
1831 wallet.config.set_key("server", server, True)
1832 interface.set_server(server, proxy)
1836 def closeEvent(self, event):
1838 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1844 def __init__(self, wallet, config, app=None):
1845 self.wallet = wallet
1846 self.config = config
1848 self.app = QApplication(sys.argv)
1851 def restore_or_create(self):
1852 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1853 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1854 if r==2: return None
1855 return 'restore' if r==1 else 'create'
1857 def seed_dialog(self):
1858 return ElectrumWindow.seed_dialog( self.wallet )
1860 def network_dialog(self):
1861 return ElectrumWindow.network_dialog( self.wallet, parent=None )
1864 def show_seed(self):
1865 ElectrumWindow.show_seed_dialog(self.wallet)
1868 def password_dialog(self):
1869 ElectrumWindow.change_password_dialog(self.wallet)
1872 def restore_wallet(self):
1873 wallet = self.wallet
1874 # wait until we are connected, because the user might have selected another server
1875 if not wallet.interface.is_connected:
1876 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1877 waiting_dialog(waiting)
1879 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1880 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1882 wallet.set_up_to_date(False)
1883 wallet.interface.poke('synchronizer')
1884 waiting_dialog(waiting)
1885 if wallet.is_found():
1886 print_error( "Recovery successful" )
1888 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1895 w = ElectrumWindow(self.wallet, self.config)
1896 if url: w.set_url(url)