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