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):
1493 vbox = QVBoxLayout()
1494 msg = _('Here are the settings of your wallet.') + '\n'\
1495 + _('For more explanations, click on the help buttons next to each field.')
1498 label.setFixedWidth(250)
1499 label.setWordWrap(True)
1500 label.setAlignment(Qt.AlignJustify)
1501 vbox.addWidget(label)
1503 grid = QGridLayout()
1505 vbox.addLayout(grid)
1507 fee_label = QLabel(_('Transaction fee'))
1508 grid.addWidget(fee_label, 2, 0)
1510 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1511 grid.addWidget(fee_e, 2, 1)
1512 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1513 + _('Recommended value') + ': 0.001'
1514 grid.addWidget(HelpButton(msg), 2, 2)
1515 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1516 if not self.config.is_modifiable('fee'):
1517 for w in [fee_e, fee_label]: w.setEnabled(False)
1519 nz_label = QLabel(_('Display zeros'))
1520 grid.addWidget(nz_label, 3, 0)
1522 nz_e.setText("%d"% self.wallet.num_zeros)
1523 grid.addWidget(nz_e, 3, 1)
1524 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1525 grid.addWidget(HelpButton(msg), 3, 2)
1526 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1527 if not self.config.is_modifiable('num_zeros'):
1528 for w in [nz_e, nz_label]: w.setEnabled(False)
1530 usechange_cb = QCheckBox(_('Use change addresses'))
1531 grid.addWidget(usechange_cb, 5, 0)
1532 usechange_cb.setChecked(self.wallet.use_change)
1533 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1534 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1536 gap_label = QLabel(_('Gap limit'))
1537 grid.addWidget(gap_label, 6, 0)
1539 gap_e.setText("%d"% self.wallet.gap_limit)
1540 grid.addWidget(gap_e, 6, 1)
1541 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1542 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1543 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1544 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1545 + _('Warning') + ': ' \
1546 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1547 + _('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'
1548 grid.addWidget(HelpButton(msg), 6, 2)
1549 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1550 if not self.config.is_modifiable('gap_limit'):
1551 for w in [gap_e, gap_label]: w.setEnabled(False)
1553 gui_label=QLabel(_('Default GUI') + ':')
1554 grid.addWidget(gui_label , 7, 0)
1555 gui_combo = QComboBox()
1556 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1557 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1558 if index==-1: index = 1
1559 gui_combo.setCurrentIndex(index)
1560 grid.addWidget(gui_combo, 7, 1)
1561 grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1562 if not self.config.is_modifiable('gui'):
1563 for w in [gui_combo, gui_label]: w.setEnabled(False)
1565 vbox.addLayout(ok_cancel_buttons(d))
1569 if not d.exec_(): return
1571 fee = unicode(fee_e.text())
1573 fee = int( 100000000 * Decimal(fee) )
1575 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1578 if self.wallet.fee != fee:
1579 self.wallet.fee = fee
1582 nz = unicode(nz_e.text())
1587 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1590 if self.wallet.num_zeros != nz:
1591 self.wallet.num_zeros = nz
1592 self.config.set_key('num_zeros', nz, True)
1593 self.update_history_tab()
1594 self.update_receive_tab()
1596 if self.wallet.use_change != usechange_cb.isChecked():
1597 self.wallet.use_change = usechange_cb.isChecked()
1598 self.config.set_key('use_change', self.wallet.use_change, True)
1601 n = int(gap_e.text())
1603 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1606 if self.wallet.gap_limit != n:
1607 r = self.wallet.change_gap_limit(n)
1609 self.update_receive_tab()
1610 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1612 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1614 self.config.set_key("gui", str(gui_combo.currentText()).lower(), True)
1619 def network_dialog(wallet, parent=None):
1620 interface = wallet.interface
1622 if interface.is_connected:
1623 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1625 status = _("Not connected")
1626 server = interface.server
1629 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1630 server = interface.server
1632 plist, servers_list = interface.get_servers_list()
1636 d.setWindowTitle(_('Server'))
1637 d.setMinimumSize(375, 20)
1639 vbox = QVBoxLayout()
1642 hbox = QHBoxLayout()
1644 l.setPixmap(QPixmap(":icons/network.png"))
1647 hbox.addWidget(QLabel(status))
1649 vbox.addLayout(hbox)
1653 grid = QGridLayout()
1655 vbox.addLayout(grid)
1658 server_protocol = QComboBox()
1659 server_host = QLineEdit()
1660 server_host.setFixedWidth(200)
1661 server_port = QLineEdit()
1662 server_port.setFixedWidth(60)
1664 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1665 protocol_letters = 'thsg'
1666 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1667 server_protocol.addItems(protocol_names)
1669 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1670 grid.addWidget(server_protocol, 0, 1)
1671 grid.addWidget(server_host, 0, 2)
1672 grid.addWidget(server_port, 0, 3)
1674 def change_protocol(p):
1675 protocol = protocol_letters[p]
1676 host = unicode(server_host.text())
1677 pp = plist.get(host,DEFAULT_PORTS)
1678 if protocol not in pp.keys():
1679 protocol = pp.keys()[0]
1681 server_host.setText( host )
1682 server_port.setText( port )
1684 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1686 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1687 servers_list_widget = QTreeWidget(parent)
1688 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1689 servers_list_widget.setMaximumHeight(150)
1690 servers_list_widget.setColumnWidth(0, 240)
1691 for _host in servers_list.keys():
1692 _type = 'pruning' if servers_list[_host].get('pruning') else 'full'
1693 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1695 def change_server(host, protocol=None):
1696 pp = plist.get(host,DEFAULT_PORTS)
1698 port = pp.get(protocol)
1699 if not port: protocol = None
1702 if 't' in pp.keys():
1704 port = pp.get(protocol)
1706 protocol = pp.keys()[0]
1707 port = pp.get(protocol)
1709 server_host.setText( host )
1710 server_port.setText( port )
1711 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1713 if not plist: return
1714 for p in protocol_letters:
1715 i = protocol_letters.index(p)
1716 j = server_protocol.model().index(i,0)
1717 if p not in pp.keys():
1718 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1720 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1724 host, port, protocol = server.split(':')
1725 change_server(host,protocol)
1727 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1728 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1730 if not wallet.config.is_modifiable('server'):
1731 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1734 proxy_mode = QComboBox()
1735 proxy_host = QLineEdit()
1736 proxy_host.setFixedWidth(200)
1737 proxy_port = QLineEdit()
1738 proxy_port.setFixedWidth(60)
1739 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1741 def check_for_disable(index = False):
1742 if proxy_mode.currentText() != 'NONE':
1743 proxy_host.setEnabled(True)
1744 proxy_port.setEnabled(True)
1746 proxy_host.setEnabled(False)
1747 proxy_port.setEnabled(False)
1750 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1752 if not wallet.config.is_modifiable('proxy'):
1753 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1755 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1756 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1757 proxy_host.setText(proxy_config.get("host"))
1758 proxy_port.setText(proxy_config.get("port"))
1760 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1761 grid.addWidget(proxy_mode, 2, 1)
1762 grid.addWidget(proxy_host, 2, 2)
1763 grid.addWidget(proxy_port, 2, 3)
1766 vbox.addLayout(ok_cancel_buttons(d))
1769 if not d.exec_(): return
1771 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1772 if proxy_mode.currentText() != 'NONE':
1773 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1777 wallet.config.set_key("proxy", proxy, True)
1778 wallet.config.set_key("server", server, True)
1779 interface.set_server(server, proxy)
1783 def closeEvent(self, event):
1785 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1791 def __init__(self, wallet, config, app=None):
1792 self.wallet = wallet
1793 self.config = config
1795 self.app = QApplication(sys.argv)
1798 def restore_or_create(self):
1799 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1800 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1801 if r==2: return None
1802 return 'restore' if r==1 else 'create'
1804 def seed_dialog(self):
1805 return ElectrumWindow.seed_dialog( self.wallet )
1807 def network_dialog(self):
1808 return ElectrumWindow.network_dialog( self.wallet, parent=None )
1811 def show_seed(self):
1812 ElectrumWindow.show_seed_dialog(self.wallet)
1815 def password_dialog(self):
1816 ElectrumWindow.change_password_dialog(self.wallet)
1819 def restore_wallet(self):
1820 wallet = self.wallet
1821 # wait until we are connected, because the user might have selected another server
1822 if not wallet.interface.is_connected:
1823 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1824 waiting_dialog(waiting)
1826 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1827 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1829 wallet.set_up_to_date(False)
1830 wallet.interface.poke('synchronizer')
1831 waiting_dialog(waiting)
1832 if wallet.is_found():
1833 print_error( "Recovery successful" )
1835 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1842 w = ElectrumWindow(self.wallet, self.config)
1843 if url: w.set_url(url)