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
880 view_combo = QComboBox()
881 view_combo.addItems([_('Simple View'), _('Detailed View'), _('Point of Sale')])
882 view_combo.setCurrentIndex(self.receive_tab_mode)
883 hbox.addWidget(view_combo)
884 view_combo.currentIndexChanged.connect(self.receive_tab_set_mode)
890 def receive_tab_set_mode(self, i):
891 self.receive_tab_mode = i
892 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
894 self.update_receive_tab()
895 self.toggle_QR_window(self.receive_tab_mode == 2)
898 def create_contacts_tab(self):
899 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
900 l.setContextMenuPolicy(Qt.CustomContextMenu)
901 l.customContextMenuRequested.connect(self.create_contact_menu)
902 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
903 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
904 self.contacts_list = l
905 self.contacts_buttons_hbox = hbox
906 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
911 def create_receive_menu(self, position):
912 # fixme: this function apparently has a side effect.
913 # if it is not called the menu pops up several times
914 #self.receive_list.selectedIndexes()
916 item = self.receive_list.itemAt(position)
918 addr = unicode(item.text(1))
920 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
921 if self.receive_tab_mode == 2:
922 menu.addAction(_("Request amount"), lambda: self.edit_amount())
923 menu.addAction(_("View QR"), lambda: ElectrumWindow.show_qrcode("Address","bitcoin:"+addr) )
924 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
925 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
927 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
928 menu.addAction(t, lambda: self.toggle_freeze(addr))
929 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
930 menu.addAction(t, lambda: self.toggle_priority(addr))
931 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
934 def payto(self, x, is_alias):
941 label = self.wallet.labels.get(addr)
942 m_addr = label + ' <' + addr + '>' if label else addr
943 self.tabs.setCurrentIndex(1)
944 self.payto_e.setText(m_addr)
945 self.amount_e.setFocus()
947 def delete_contact(self, x, is_alias):
948 if self.question("Do you want to remove %s from your list of contacts?"%x):
949 if not is_alias and x in self.wallet.addressbook:
950 self.wallet.addressbook.remove(x)
951 if x in self.wallet.labels.keys():
952 self.wallet.labels.pop(x)
953 elif is_alias and x in self.wallet.aliases:
954 self.wallet.aliases.pop(x)
955 self.update_history_tab()
956 self.update_contacts_tab()
957 self.update_completions()
959 def create_contact_menu(self, position):
960 # fixme: this function apparently has a side effect.
961 # if it is not called the menu pops up several times
962 #self.contacts_list.selectedIndexes()
964 item = self.contacts_list.itemAt(position)
966 addr = unicode(item.text(0))
967 label = unicode(item.text(1))
968 is_alias = label in self.wallet.aliases.keys()
969 x = label if is_alias else addr
971 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
972 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
973 menu.addAction(_("View QR code"),lambda: self.show_qrcode("Address","bitcoin:"+addr))
975 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
977 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
978 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
979 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
982 def update_receive_item(self, item):
983 address = str( item.data(1,0).toString() )
985 flags = self.wallet.get_address_flags(address)
986 item.setData(0,0,flags)
988 label = self.wallet.labels.get(address,'')
989 item.setData(2,0,label)
991 amount = self.wallet.requested_amounts.get(address,None)
992 amount_str = format_satoshis( amount, False, self.wallet.num_zeros ) if amount is not None else ""
993 item.setData(3,0,amount_str)
995 c, u = self.wallet.get_addr_balance(address)
996 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
997 item.setData(4,0,balance)
999 if address in self.wallet.frozen_addresses:
1000 item.setBackgroundColor(1, QColor('lightblue'))
1001 elif address in self.wallet.prioritized_addresses:
1002 item.setBackgroundColor(1, QColor('lightgreen'))
1005 def update_receive_tab(self):
1006 l = self.receive_list
1009 l.setColumnHidden(0, not self.receive_tab_mode == 1)
1010 l.setColumnHidden(3, not self.receive_tab_mode == 2)
1011 l.setColumnHidden(4, self.receive_tab_mode == 0)
1012 l.setColumnHidden(5, not self.receive_tab_mode == 1)
1013 l.setColumnWidth(0, 50)
1014 l.setColumnWidth(1, 310)
1015 l.setColumnWidth(2, 200)
1016 l.setColumnWidth(3, 130)
1017 l.setColumnWidth(4, 130)
1018 l.setColumnWidth(5, 10)
1022 for address in self.wallet.all_addresses():
1024 if self.wallet.is_change(address) and self.receive_tab_mode != 1:
1028 h = self.wallet.history.get(address,[])
1031 for tx_hash, tx_height in h:
1032 tx = self.wallet.transactions.get(tx_hash)
1040 if address in self.wallet.addresses:
1042 if gap > self.wallet.gap_limit:
1045 if address in self.wallet.addresses:
1048 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1049 item.setFont(0, QFont(MONOSPACE_FONT))
1050 item.setFont(1, QFont(MONOSPACE_FONT))
1051 item.setFont(3, QFont(MONOSPACE_FONT))
1052 self.update_receive_item(item)
1053 if is_red and address in self.wallet.addresses:
1054 item.setBackgroundColor(1, QColor('red'))
1055 l.addTopLevelItem(item)
1057 # we use column 1 because column 0 may be hidden
1058 l.setCurrentItem(l.topLevelItem(0),1)
1060 def show_contact_details(self, m):
1061 a = self.wallet.aliases.get(m)
1063 if a[0] in self.wallet.authorities.keys():
1064 s = self.wallet.authorities.get(a[0])
1067 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1068 QMessageBox.information(self, 'Alias', msg, 'OK')
1070 def update_contacts_tab(self):
1072 l = self.contacts_list
1074 l.setColumnWidth(0, 350)
1075 l.setColumnWidth(1, 330)
1076 l.setColumnWidth(2, 100)
1079 for alias, v in self.wallet.aliases.items():
1081 alias_targets.append(target)
1082 item = QTreeWidgetItem( [ target, alias, '-'] )
1083 item.setBackgroundColor(0, QColor('lightgray'))
1084 l.addTopLevelItem(item)
1086 for address in self.wallet.addressbook:
1087 if address in alias_targets: continue
1088 label = self.wallet.labels.get(address,'')
1090 for item in self.wallet.transactions.values():
1091 if address in item['outputs'] : n=n+1
1093 item = QTreeWidgetItem( [ address, label, tx] )
1094 item.setFont(0, QFont(MONOSPACE_FONT))
1095 l.addTopLevelItem(item)
1097 l.setCurrentItem(l.topLevelItem(0))
1099 def create_wall_tab(self):
1100 self.textbox = textbox = QTextEdit(self)
1101 textbox.setFont(QFont(MONOSPACE_FONT))
1102 textbox.setReadOnly(True)
1105 def create_status_bar(self):
1107 sb.setFixedHeight(35)
1108 if self.wallet.seed:
1109 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1110 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1111 if self.wallet.seed:
1112 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1113 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
1114 sb.addPermanentWidget( self.status_button )
1115 self.setStatusBar(sb)
1117 def new_contact_dialog(self):
1118 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1119 address = unicode(text)
1121 if self.wallet.is_valid(address):
1122 self.wallet.addressbook.append(address)
1124 self.update_contacts_tab()
1125 self.update_history_tab()
1126 self.update_completions()
1128 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1131 def show_seed_dialog(wallet, parent=None):
1133 QMessageBox.information(parent, _('Message'),
1134 _('No seed'), _('OK'))
1137 if wallet.use_encryption:
1138 password = parent.password_dialog()
1145 seed = wallet.pw_decode(wallet.seed, password)
1147 QMessageBox.warning(parent, _('Error'),
1148 _('Incorrect Password'), _('OK'))
1151 dialog = QDialog(None)
1153 dialog.setWindowTitle("Electrum")
1155 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1157 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1158 + _("Please write down or memorize these 12 words (order is important).") + " " \
1159 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1160 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1162 main_text = QLabel(msg)
1163 main_text.setWordWrap(True)
1166 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1173 copy_function = lambda: app.clipboard().setText(brainwallet)
1174 copy_button = QPushButton(_("Copy to Clipboard"))
1175 copy_button.clicked.connect(copy_function)
1177 show_qr_function = lambda: ElectrumWindow.show_qrcode(_("Seed"), seed)
1178 qr_button = QPushButton(_("View as QR Code"))
1179 qr_button.clicked.connect(show_qr_function)
1181 ok_button = QPushButton(_("OK"))
1182 ok_button.setDefault(True)
1183 ok_button.clicked.connect(dialog.accept)
1185 main_layout = QGridLayout()
1186 main_layout.addWidget(logo, 0, 0)
1187 main_layout.addWidget(main_text, 0, 1, 1, -1)
1188 main_layout.addWidget(copy_button, 1, 1)
1189 main_layout.addWidget(qr_button, 1, 2)
1190 main_layout.addWidget(ok_button, 1, 3)
1191 dialog.setLayout(main_layout)
1196 def show_qrcode(title, data):
1200 d.setWindowTitle(title)
1201 d.setMinimumSize(270, 300)
1202 vbox = QVBoxLayout()
1203 qrw = QRCodeWidget(data)
1205 vbox.addWidget(QLabel(data))
1206 hbox = QHBoxLayout()
1210 filename = "qrcode.bmp"
1211 bmp.save_qrcode(qrw.qr, filename)
1212 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1214 b = QPushButton(_("Print"))
1216 b.clicked.connect(print_qr)
1218 b = QPushButton(_("Close"))
1220 b.clicked.connect(d.accept)
1222 vbox.addLayout(hbox)
1226 def sign_message(self,address):
1227 if not address: return
1230 d.setWindowTitle('Sign Message')
1231 d.setMinimumSize(270, 350)
1233 tab_widget = QTabWidget()
1235 layout = QGridLayout(tab)
1237 sign_address = QLineEdit()
1238 sign_address.setText(address)
1239 layout.addWidget(QLabel(_('Address')), 1, 0)
1240 layout.addWidget(sign_address, 1, 1)
1242 sign_message = QTextEdit()
1243 layout.addWidget(QLabel(_('Message')), 2, 0)
1244 layout.addWidget(sign_message, 2, 1, 2, 1)
1246 sign_signature = QLineEdit()
1247 layout.addWidget(QLabel(_('Signature')), 3, 0)
1248 layout.addWidget(sign_signature, 3, 1)
1251 if self.wallet.use_encryption:
1252 password = self.password_dialog()
1259 signature = self.wallet.sign_message(sign_address.text(), str(sign_message.toPlainText()), password)
1260 sign_signature.setText(signature)
1261 except BaseException, e:
1262 self.show_message(str(e))
1265 hbox = QHBoxLayout()
1266 b = QPushButton(_("Sign"))
1268 b.clicked.connect(do_sign)
1269 b = QPushButton(_("Close"))
1270 b.clicked.connect(d.accept)
1272 layout.addLayout(hbox, 4, 1)
1273 tab_widget.addTab(tab, "Sign")
1277 layout = QGridLayout(tab)
1279 verify_address = QLineEdit()
1280 layout.addWidget(QLabel(_('Address')), 1, 0)
1281 layout.addWidget(verify_address, 1, 1)
1283 verify_message = QTextEdit()
1284 layout.addWidget(QLabel(_('Message')), 2, 0)
1285 layout.addWidget(verify_message, 2, 1, 2, 1)
1287 verify_signature = QLineEdit()
1288 layout.addWidget(QLabel(_('Signature')), 3, 0)
1289 layout.addWidget(verify_signature, 3, 1)
1293 self.wallet.verify_message(verify_address.text(), verify_signature.text(), str(verify_message.toPlainText()))
1294 self.show_message("Signature verified")
1295 except BaseException, e:
1296 self.show_message(str(e))
1299 hbox = QHBoxLayout()
1300 b = QPushButton(_("Verify"))
1301 b.clicked.connect(do_verify)
1303 b = QPushButton(_("Close"))
1304 b.clicked.connect(d.accept)
1306 layout.addLayout(hbox, 4, 1)
1307 tab_widget.addTab(tab, "Verify")
1309 vbox = QVBoxLayout()
1310 vbox.addWidget(tab_widget)
1315 def toggle_QR_window(self, show):
1316 if show and not self.qr_window:
1317 self.qr_window = QR_Window()
1318 self.qr_window.setVisible(True)
1319 self.qr_window_geometry = self.qr_window.geometry()
1320 item = self.receive_list.currentItem()
1322 address = str(item.text(1))
1323 label = self.wallet.labels.get(address)
1324 amount = self.wallet.requested_amounts.get(address)
1325 self.qr_window.set_content( address, label, amount )
1327 elif show and self.qr_window and not self.qr_window.isVisible():
1328 self.qr_window.setVisible(True)
1329 self.qr_window.setGeometry(self.qr_window_geometry)
1331 elif not show and self.qr_window and self.qr_window.isVisible():
1332 self.qr_window_geometry = self.qr_window.geometry()
1333 self.qr_window.setVisible(False)
1335 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1336 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1337 self.receive_list.setColumnWidth(2, 200)
1340 def question(self, msg):
1341 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1343 def show_message(self, msg):
1344 QMessageBox.information(self, _('Message'), msg, _('OK'))
1346 def password_dialog(self ):
1353 vbox = QVBoxLayout()
1354 msg = _('Please enter your password')
1355 vbox.addWidget(QLabel(msg))
1357 grid = QGridLayout()
1359 grid.addWidget(QLabel(_('Password')), 1, 0)
1360 grid.addWidget(pw, 1, 1)
1361 vbox.addLayout(grid)
1363 vbox.addLayout(ok_cancel_buttons(d))
1366 if not d.exec_(): return
1367 return unicode(pw.text())
1374 def change_password_dialog( wallet, parent=None ):
1377 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1385 new_pw = QLineEdit()
1386 new_pw.setEchoMode(2)
1387 conf_pw = QLineEdit()
1388 conf_pw.setEchoMode(2)
1390 vbox = QVBoxLayout()
1392 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1393 +_('To disable wallet encryption, enter an empty new password.')) \
1394 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1396 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1397 +_("Leave these fields empty if you want to disable encryption.")
1398 vbox.addWidget(QLabel(msg))
1400 grid = QGridLayout()
1403 if wallet.use_encryption:
1404 grid.addWidget(QLabel(_('Password')), 1, 0)
1405 grid.addWidget(pw, 1, 1)
1407 grid.addWidget(QLabel(_('New Password')), 2, 0)
1408 grid.addWidget(new_pw, 2, 1)
1410 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1411 grid.addWidget(conf_pw, 3, 1)
1412 vbox.addLayout(grid)
1414 vbox.addLayout(ok_cancel_buttons(d))
1417 if not d.exec_(): return
1419 password = unicode(pw.text()) if wallet.use_encryption else None
1420 new_password = unicode(new_pw.text())
1421 new_password2 = unicode(conf_pw.text())
1424 seed = wallet.pw_decode( wallet.seed, password)
1426 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1429 if new_password != new_password2:
1430 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1431 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1433 wallet.update_password(seed, password, new_password)
1436 def seed_dialog(wallet, parent=None):
1440 vbox = QVBoxLayout()
1441 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1442 vbox.addWidget(QLabel(msg))
1444 grid = QGridLayout()
1447 seed_e = QLineEdit()
1448 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1449 grid.addWidget(seed_e, 1, 1)
1453 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1454 grid.addWidget(gap_e, 2, 1)
1455 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1456 vbox.addLayout(grid)
1458 vbox.addLayout(ok_cancel_buttons(d))
1461 if not d.exec_(): return
1464 gap = int(unicode(gap_e.text()))
1466 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1470 seed = unicode(seed_e.text())
1473 print_error("Warning: Not hex, trying decode")
1475 seed = mnemonic.mn_decode( seed.split(' ') )
1477 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1480 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1483 wallet.seed = str(seed)
1484 #print repr(wallet.seed)
1485 wallet.gap_limit = gap
1490 def settings_dialog(self):
1492 d.setWindowTitle(_('Electrum Settings'))
1494 vbox = QVBoxLayout()
1495 msg = _('Here are the settings of your wallet.') + '\n'\
1496 + _('For more explanations, click on the help buttons next to each field.')
1499 label.setFixedWidth(250)
1500 label.setWordWrap(True)
1501 label.setAlignment(Qt.AlignJustify)
1503 tabs = QTabWidget(self)
1504 vbox.addWidget(tabs)
1506 vbox.addWidget(label)
1509 grid_wallet = QGridLayout(tab)
1510 grid_wallet.setColumnStretch(0,1)
1511 tabs.addTab(tab, _('Wallet') )
1514 grid_ui = QGridLayout(tab2)
1515 grid_ui.setColumnStretch(0,1)
1516 tabs.addTab(tab2, _('Display') )
1518 fee_label = QLabel(_('Transaction fee'))
1519 grid_wallet.addWidget(fee_label, 2, 0)
1521 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1522 grid_wallet.addWidget(fee_e, 2, 1)
1523 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1524 + _('Recommended value') + ': 0.001'
1525 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1526 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1527 if not self.config.is_modifiable('fee'):
1528 for w in [fee_e, fee_label]: w.setEnabled(False)
1530 nz_label = QLabel(_('Display zeros'))
1531 grid_ui.addWidget(nz_label, 3, 0)
1533 nz_e.setText("%d"% self.wallet.num_zeros)
1534 grid_ui.addWidget(nz_e, 3, 1)
1535 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1536 grid_ui.addWidget(HelpButton(msg), 3, 2)
1537 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1538 if not self.config.is_modifiable('num_zeros'):
1539 for w in [nz_e, nz_label]: w.setEnabled(False)
1542 usechange_label = QLabel(_('Use change addresses'))
1543 grid_wallet.addWidget(usechange_label, 5, 0)
1544 usechange_combo = QComboBox()
1545 usechange_combo.addItems(['Yes', 'No'])
1546 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1547 grid_wallet.addWidget(usechange_combo, 5, 1)
1548 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1549 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1551 gap_label = QLabel(_('Gap limit'))
1552 grid_wallet.addWidget(gap_label, 6, 0)
1554 gap_e.setText("%d"% self.wallet.gap_limit)
1555 grid_wallet.addWidget(gap_e, 6, 1)
1556 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1557 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1558 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1559 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1560 + _('Warning') + ': ' \
1561 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1562 + _('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'
1563 grid_wallet.addWidget(HelpButton(msg), 6, 2)
1564 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1565 if not self.config.is_modifiable('gap_limit'):
1566 for w in [gap_e, gap_label]: w.setEnabled(False)
1568 gui_label=QLabel(_('Default GUI') + ':')
1569 grid_ui.addWidget(gui_label , 7, 0)
1570 gui_combo = QComboBox()
1571 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1572 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1573 if index==-1: index = 1
1574 gui_combo.setCurrentIndex(index)
1575 grid_ui.addWidget(gui_combo, 7, 1)
1576 grid_ui.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1577 if not self.config.is_modifiable('gui'):
1578 for w in [gui_combo, gui_label]: w.setEnabled(False)
1580 lang_label=QLabel(_('Language') + ':')
1581 grid_ui.addWidget(lang_label , 8, 0)
1582 lang_combo = QComboBox()
1583 languages = ['', 'br', 'cs', 'de', 'eo', 'en', 'es', 'fr', 'it', 'lv', 'nl', 'ru', 'sl', 'vi', 'zh']
1584 lang_combo.addItems(languages)
1586 index = languages.index(self.config.get("language",''))
1589 lang_combo.setCurrentIndex(index)
1590 grid_ui.addWidget(lang_combo, 8, 1)
1591 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart). ')), 8, 2)
1592 if not self.config.is_modifiable('language'):
1593 for w in [lang_combo, lang_label]: w.setEnabled(False)
1595 vbox.addLayout(ok_cancel_buttons(d))
1599 if not d.exec_(): return
1601 fee = unicode(fee_e.text())
1603 fee = int( 100000000 * Decimal(fee) )
1605 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1608 if self.wallet.fee != fee:
1609 self.wallet.fee = fee
1612 nz = unicode(nz_e.text())
1617 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1620 if self.wallet.num_zeros != nz:
1621 self.wallet.num_zeros = nz
1622 self.config.set_key('num_zeros', nz, True)
1623 self.update_history_tab()
1624 self.update_receive_tab()
1626 usechange_result = usechange_combo.currentIndex() == 0
1627 if self.wallet.use_change != usechange_result:
1628 self.wallet.use_change = usechange_result
1629 self.config.set_key('use_change', self.wallet.use_change, True)
1632 n = int(gap_e.text())
1634 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1637 if self.wallet.gap_limit != n:
1638 r = self.wallet.change_gap_limit(n)
1640 self.update_receive_tab()
1641 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1643 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1645 self.config.set_key("gui", str(gui_combo.currentText()).lower(), True)
1646 self.config.set_key("language", languages[lang_combo.currentIndex()], True)
1651 def network_dialog(wallet, parent=None):
1652 interface = wallet.interface
1654 if interface.is_connected:
1655 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1657 status = _("Not connected")
1658 server = interface.server
1661 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1662 server = interface.server
1664 plist, servers_list = interface.get_servers_list()
1668 d.setWindowTitle(_('Server'))
1669 d.setMinimumSize(375, 20)
1671 vbox = QVBoxLayout()
1674 hbox = QHBoxLayout()
1676 l.setPixmap(QPixmap(":icons/network.png"))
1679 hbox.addWidget(QLabel(status))
1681 vbox.addLayout(hbox)
1685 grid = QGridLayout()
1687 vbox.addLayout(grid)
1690 server_protocol = QComboBox()
1691 server_host = QLineEdit()
1692 server_host.setFixedWidth(200)
1693 server_port = QLineEdit()
1694 server_port.setFixedWidth(60)
1696 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1697 protocol_letters = 'thsg'
1698 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1699 server_protocol.addItems(protocol_names)
1701 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1702 grid.addWidget(server_protocol, 0, 1)
1703 grid.addWidget(server_host, 0, 2)
1704 grid.addWidget(server_port, 0, 3)
1706 def change_protocol(p):
1707 protocol = protocol_letters[p]
1708 host = unicode(server_host.text())
1709 pp = plist.get(host,DEFAULT_PORTS)
1710 if protocol not in pp.keys():
1711 protocol = pp.keys()[0]
1713 server_host.setText( host )
1714 server_port.setText( port )
1716 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1718 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1719 servers_list_widget = QTreeWidget(parent)
1720 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1721 servers_list_widget.setMaximumHeight(150)
1722 servers_list_widget.setColumnWidth(0, 240)
1723 for _host in servers_list.keys():
1724 _type = 'pruning' if servers_list[_host].get('pruning') else 'full'
1725 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1727 def change_server(host, protocol=None):
1728 pp = plist.get(host,DEFAULT_PORTS)
1730 port = pp.get(protocol)
1731 if not port: protocol = None
1734 if 't' in pp.keys():
1736 port = pp.get(protocol)
1738 protocol = pp.keys()[0]
1739 port = pp.get(protocol)
1741 server_host.setText( host )
1742 server_port.setText( port )
1743 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1745 if not plist: return
1746 for p in protocol_letters:
1747 i = protocol_letters.index(p)
1748 j = server_protocol.model().index(i,0)
1749 if p not in pp.keys():
1750 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1752 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1756 host, port, protocol = server.split(':')
1757 change_server(host,protocol)
1759 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1760 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1762 if not wallet.config.is_modifiable('server'):
1763 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1766 proxy_mode = QComboBox()
1767 proxy_host = QLineEdit()
1768 proxy_host.setFixedWidth(200)
1769 proxy_port = QLineEdit()
1770 proxy_port.setFixedWidth(60)
1771 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1773 def check_for_disable(index = False):
1774 if proxy_mode.currentText() != 'NONE':
1775 proxy_host.setEnabled(True)
1776 proxy_port.setEnabled(True)
1778 proxy_host.setEnabled(False)
1779 proxy_port.setEnabled(False)
1782 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1784 if not wallet.config.is_modifiable('proxy'):
1785 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1787 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1788 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1789 proxy_host.setText(proxy_config.get("host"))
1790 proxy_port.setText(proxy_config.get("port"))
1792 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1793 grid.addWidget(proxy_mode, 2, 1)
1794 grid.addWidget(proxy_host, 2, 2)
1795 grid.addWidget(proxy_port, 2, 3)
1798 vbox.addLayout(ok_cancel_buttons(d))
1801 if not d.exec_(): return
1803 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1804 if proxy_mode.currentText() != 'NONE':
1805 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1809 wallet.config.set_key("proxy", proxy, True)
1810 wallet.config.set_key("server", server, True)
1811 interface.set_server(server, proxy)
1815 def closeEvent(self, event):
1817 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1823 def __init__(self, wallet, config, app=None):
1824 self.wallet = wallet
1825 self.config = config
1827 self.app = QApplication(sys.argv)
1830 def restore_or_create(self):
1831 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1832 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1833 if r==2: return None
1834 return 'restore' if r==1 else 'create'
1836 def seed_dialog(self):
1837 return ElectrumWindow.seed_dialog( self.wallet )
1839 def network_dialog(self):
1840 return ElectrumWindow.network_dialog( self.wallet, parent=None )
1843 def show_seed(self):
1844 ElectrumWindow.show_seed_dialog(self.wallet)
1847 def password_dialog(self):
1848 ElectrumWindow.change_password_dialog(self.wallet)
1851 def restore_wallet(self):
1852 wallet = self.wallet
1853 # wait until we are connected, because the user might have selected another server
1854 if not wallet.interface.is_connected:
1855 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1856 waiting_dialog(waiting)
1858 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1859 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1861 wallet.set_up_to_date(False)
1862 wallet.interface.poke('synchronizer')
1863 waiting_dialog(waiting)
1864 if wallet.is_found():
1865 print_error( "Recovery successful" )
1867 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1874 w = ElectrumWindow(self.wallet, self.config)
1875 if url: w.set_url(url)