3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re
21 from util import print_error
26 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
28 from PyQt4.QtGui import *
29 from PyQt4.QtCore import *
30 import PyQt4.QtCore as QtCore
31 import PyQt4.QtGui as QtGui
32 from interface import DEFAULT_SERVERS
37 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o lib/icons_rc.py'")
39 from wallet import format_satoshis
40 import bmp, mnemonic, pyqrnative, qrscanner
42 from decimal import Decimal
46 if platform.system() == 'Windows':
47 MONOSPACE_FONT = 'Lucida Console'
48 elif platform.system() == 'Darwin':
49 MONOSPACE_FONT = 'Monaco'
51 MONOSPACE_FONT = 'monospace'
53 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
55 def numbify(entry, is_int = False):
56 text = unicode(entry.text()).strip()
57 pos = entry.cursorPosition()
59 if not is_int: chars +='.'
60 s = ''.join([i for i in text if i in chars])
65 s = s[:p] + '.' + s[p:p+8]
67 amount = int( Decimal(s) * 100000000 )
76 entry.setCursorPosition(pos)
80 class Timer(QtCore.QThread):
83 self.emit(QtCore.SIGNAL('timersignal'))
86 class HelpButton(QPushButton):
87 def __init__(self, text):
88 QPushButton.__init__(self, '?')
89 self.setFocusPolicy(Qt.NoFocus)
90 self.setFixedWidth(20)
91 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
94 class EnterButton(QPushButton):
95 def __init__(self, text, func):
96 QPushButton.__init__(self, text)
98 self.clicked.connect(func)
100 def keyPressEvent(self, e):
101 if e.key() == QtCore.Qt.Key_Return:
104 class MyTreeWidget(QTreeWidget):
105 def __init__(self, parent):
106 QTreeWidget.__init__(self, parent)
109 for i in range(0,self.viewport().height()/5):
110 if self.itemAt(QPoint(0,i*5)) == item:
114 for j in range(0,30):
115 if self.itemAt(QPoint(0,i*5 + j)) != item:
117 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
119 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
124 class StatusBarButton(QPushButton):
125 def __init__(self, icon, tooltip, func):
126 QPushButton.__init__(self, icon, '')
127 self.setToolTip(tooltip)
129 self.setMaximumWidth(25)
130 self.clicked.connect(func)
133 def keyPressEvent(self, e):
134 if e.key() == QtCore.Qt.Key_Return:
138 class QRCodeWidget(QWidget):
140 def __init__(self, data = None):
141 QWidget.__init__(self)
142 self.setMinimumSize(210, 210)
149 def set_addr(self, addr):
150 if self.addr != addr:
156 if self.addr and not self.qr:
157 self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
158 self.qr.addData(self.addr)
162 def paintEvent(self, e):
167 black = QColor(0, 0, 0, 255)
168 white = QColor(255, 255, 255, 255)
171 qp = QtGui.QPainter()
175 qp.drawRect(0, 0, 198, 198)
179 k = self.qr.getModuleCount()
180 qp = QtGui.QPainter()
183 boxsize = min(r.width(), r.height())*0.8/k
185 left = (r.width() - size)/2
186 top = (r.height() - size)/2
190 if self.qr.isDark(r, c):
196 qp.drawRect(left+c*boxsize, top+r*boxsize, boxsize, boxsize)
201 class QR_Window(QWidget):
204 QWidget.__init__(self)
205 self.setWindowTitle('Electrum - Invoice')
206 self.setMinimumSize(800, 250)
210 self.setFocusPolicy(QtCore.Qt.NoFocus)
212 main_box = QHBoxLayout()
214 self.qrw = QRCodeWidget()
215 main_box.addWidget(self.qrw, 1)
218 main_box.addLayout(vbox)
220 self.address_label = QLabel("")
221 self.address_label.setFont(QFont(MONOSPACE_FONT))
222 vbox.addWidget(self.address_label)
224 self.label_label = QLabel("")
225 vbox.addWidget(self.label_label)
227 self.amount_label = QLabel("")
228 vbox.addWidget(self.amount_label)
231 self.setLayout(main_box)
234 def set_content(self, addr, label, amount):
236 address_text = "<span style='font-size: 18pt'>%s</span>" % addr if addr else ""
237 self.address_label.setText(address_text)
240 amount_text = "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % format_satoshis(amount) if amount else ""
241 self.amount_label.setText(amount_text)
244 label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
245 self.label_label.setText(label_text)
247 msg = 'bitcoin:'+self.address
248 if self.amount is not None:
249 msg += '?amount=%s'%(str( Decimal(self.amount) /100000000))
250 if self.label is not None:
251 msg += '&label=%s'%(self.label)
252 elif self.label is not None:
253 msg += '?label=%s'%(self.label)
255 self.qrw.set_addr( msg )
260 def waiting_dialog(f):
266 w.setWindowTitle('Electrum')
276 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
281 def ok_cancel_buttons(dialog):
284 b = QPushButton("OK")
286 b.clicked.connect(dialog.accept)
287 b = QPushButton("Cancel")
289 b.clicked.connect(dialog.reject)
293 class ElectrumWindow(QMainWindow):
295 def __init__(self, wallet, config):
296 QMainWindow.__init__(self)
299 self.wallet.interface.register_callback('updated', self.update_callback)
300 self.wallet.interface.register_callback('connected', self.update_callback)
301 self.wallet.interface.register_callback('disconnected', self.update_callback)
302 self.wallet.interface.register_callback('disconnecting', self.update_callback)
304 self.receive_tab_mode = config.get('qt_receive_tab_mode', 0)
305 self.merchant_name = config.get('merchant_name', 'Invoice')
307 self.qr_window = None
308 self.funds_error = False
309 self.completions = QStringListModel()
311 self.tabs = tabs = QTabWidget(self)
312 tabs.addTab(self.create_history_tab(), _('History') )
313 tabs.addTab(self.create_send_tab(), _('Send') )
314 tabs.addTab(self.create_receive_tab(), _('Receive') )
315 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
316 tabs.addTab(self.create_wall_tab(), _('Wall') )
317 tabs.setMinimumSize(600, 400)
318 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
319 self.setCentralWidget(tabs)
320 self.create_status_bar()
321 self.toggle_QR_window(self.receive_tab_mode == 2)
323 g = self.config.get("winpos-qt",[100, 100, 840, 400])
324 self.setGeometry(g[0], g[1], g[2], g[3])
325 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
326 if not self.wallet.seed: title += ' [seedless]'
327 self.setWindowTitle( title )
329 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
330 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
331 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
332 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
334 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
335 #self.connect(self, SIGNAL('editamount'), self.edit_amount)
336 self.history_list.setFocus(True)
338 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
339 if platform.system() == 'Windows':
340 n = 3 if self.wallet.seed else 2
341 tabs.setCurrentIndex (n)
342 tabs.setCurrentIndex (0)
345 QMainWindow.close(self)
347 self.qr_window.close()
348 self.qr_window = None
350 def connect_slots(self, sender):
351 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
352 self.previous_payto_e=''
354 def timer_actions(self):
356 self.qr_window.qrw.update_qr()
358 if self.payto_e.hasFocus():
360 r = unicode( self.payto_e.text() )
361 if r != self.previous_payto_e:
362 self.previous_payto_e = r
364 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
366 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
370 s = r + ' <' + to_address + '>'
371 self.payto_e.setText(s)
374 def update_callback(self):
375 self.emit(QtCore.SIGNAL('updatesignal'))
377 def update_wallet(self):
378 if self.wallet.interface and self.wallet.interface.is_connected:
379 if not self.wallet.up_to_date:
380 text = _( "Synchronizing..." )
381 icon = QIcon(":icons/status_waiting.png")
383 c, u = self.wallet.get_balance()
384 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
385 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
386 icon = QIcon(":icons/status_connected.png")
388 text = _( "Not connected" )
389 icon = QIcon(":icons/status_disconnected.png")
392 text = _( "Not enough funds" )
394 self.statusBar().showMessage(text)
395 self.status_button.setIcon( icon )
397 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
398 self.textbox.setText( self.wallet.banner )
399 self.update_history_tab()
400 self.update_receive_tab()
401 self.update_contacts_tab()
402 self.update_completions()
405 def create_history_tab(self):
406 self.history_list = l = MyTreeWidget(self)
408 l.setColumnWidth(0, 40)
409 l.setColumnWidth(1, 140)
410 l.setColumnWidth(2, 350)
411 l.setColumnWidth(3, 140)
412 l.setColumnWidth(4, 140)
413 l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
414 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
415 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
417 l.setContextMenuPolicy(Qt.CustomContextMenu)
418 l.customContextMenuRequested.connect(self.create_history_menu)
422 def create_history_menu(self, position):
423 self.history_list.selectedIndexes()
424 item = self.history_list.currentItem()
426 tx_hash = str(item.toolTip(0))
427 if not tx_hash: return
429 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
430 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
431 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
432 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
435 def tx_details(self, tx_hash):
436 tx_details = self.wallet.get_tx_details(tx_hash)
437 QMessageBox.information(self, 'Details', tx_details, 'OK')
440 def tx_label_clicked(self, item, column):
441 if column==2 and item.isSelected():
442 tx_hash = str(item.toolTip(0))
444 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
445 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
446 self.history_list.editItem( item, column )
447 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
450 def tx_label_changed(self, item, column):
454 tx_hash = str(item.toolTip(0))
455 tx = self.wallet.transactions.get(tx_hash)
456 s = self.wallet.labels.get(tx_hash)
457 text = unicode( item.text(2) )
459 self.wallet.labels[tx_hash] = text
460 item.setForeground(2, QBrush(QColor('black')))
462 if s: self.wallet.labels.pop(tx_hash)
463 text = self.wallet.get_default_label(tx_hash)
464 item.setText(2, text)
465 item.setForeground(2, QBrush(QColor('gray')))
469 def edit_label(self, is_recv):
470 l = self.receive_list if is_recv else self.contacts_list
471 c = 2 if is_recv else 1
472 item = l.currentItem()
473 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
474 l.editItem( item, c )
475 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
477 def edit_amount(self):
478 l = self.receive_list
479 item = l.currentItem()
480 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
481 l.editItem( item, 3 )
482 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
485 def address_label_clicked(self, item, column, l, column_addr, column_label):
486 if column == column_label and item.isSelected():
487 addr = unicode( item.text(column_addr) )
488 label = unicode( item.text(column_label) )
489 if label in self.wallet.aliases.keys():
491 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
492 l.editItem( item, column )
493 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
496 def address_label_changed(self, item, column, l, column_addr, column_label):
498 if column == column_label:
499 addr = unicode( item.text(column_addr) )
500 text = unicode( item.text(column_label) )
504 if text not in self.wallet.aliases.keys():
505 old_addr = self.wallet.labels.get(text)
507 self.wallet.labels[addr] = text
510 print_error("Error: This is one of your aliases")
511 label = self.wallet.labels.get(addr,'')
512 item.setText(column_label, QString(label))
514 s = self.wallet.labels.get(addr)
516 self.wallet.labels.pop(addr)
520 self.update_history_tab()
521 self.update_completions()
523 self.recv_changed(item)
526 address = unicode( item.text(column_addr) )
527 text = unicode( item.text(3) )
529 index = self.wallet.addresses.index(address)
534 amount = int( Decimal(text) * 100000000 )
535 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
537 amount = self.wallet.requested_amounts.get(address)
539 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
544 self.wallet.requested_amounts[address] = amount
546 label = self.wallet.labels.get(address)
548 label = self.merchant_name + ' - %04d'%(index+1)
549 self.wallet.labels[address] = label
551 self.update_receive_item(self.receive_list.currentItem())
553 self.qr_window.set_content( address, label, amount )
556 def recv_changed(self, a):
557 "current item changed"
558 if a is not None and self.qr_window and self.qr_window.isVisible():
559 address = str(a.text(1))
560 label = self.wallet.labels.get(address)
561 amount = self.wallet.requested_amounts.get(address)
562 self.qr_window.set_content( address, label, amount )
565 def update_history_tab(self):
567 self.history_list.clear()
568 for item in self.wallet.get_tx_history():
569 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
572 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
578 icon = QIcon(":icons/unconfirmed.png")
580 icon = QIcon(":icons/clock%d.png"%conf)
582 icon = QIcon(":icons/confirmed.png")
585 icon = QIcon(":icons/unconfirmed.png")
587 if value is not None:
588 v_str = format_satoshis(value, True, self.wallet.num_zeros)
592 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
595 label, is_default_label = self.wallet.get_label(tx_hash)
597 label = _('Pruned transaction outputs')
598 is_default_label = False
600 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
601 item.setFont(2, QFont(MONOSPACE_FONT))
602 item.setFont(3, QFont(MONOSPACE_FONT))
603 item.setFont(4, QFont(MONOSPACE_FONT))
605 item.setToolTip(0, tx_hash)
607 item.setForeground(2, QBrush(QColor('grey')))
609 item.setIcon(0, icon)
610 self.history_list.insertTopLevelItem(0,item)
613 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
616 def create_send_tab(self):
621 grid.setColumnMinimumWidth(3,300)
622 grid.setColumnStretch(5,1)
624 self.payto_e = QLineEdit()
625 grid.addWidget(QLabel(_('Pay to')), 1, 0)
626 grid.addWidget(self.payto_e, 1, 1, 1, 3)
629 qrcode = qrscanner.scan_qr()
630 if 'address' in qrcode:
631 self.payto_e.setText(qrcode['address'])
632 if 'amount' in qrcode:
633 self.amount_e.setText(str(qrcode['amount']))
634 if 'label' in qrcode:
635 self.message_e.setText(qrcode['label'])
636 if 'message' in qrcode:
637 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
640 if qrscanner.is_available():
641 b = QPushButton(_("Scan QR code"))
642 b.clicked.connect(fill_from_qr)
643 grid.addWidget(b, 1, 5)
645 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)
647 completer = QCompleter()
648 completer.setCaseSensitivity(False)
649 self.payto_e.setCompleter(completer)
650 completer.setModel(self.completions)
652 self.message_e = QLineEdit()
653 grid.addWidget(QLabel(_('Description')), 2, 0)
654 grid.addWidget(self.message_e, 2, 1, 1, 3)
655 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)
657 self.amount_e = QLineEdit()
658 grid.addWidget(QLabel(_('Amount')), 3, 0)
659 grid.addWidget(self.amount_e, 3, 1, 1, 2)
660 grid.addWidget(HelpButton(
661 _('Amount to be sent.') + '\n\n' \
662 + _('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)
664 self.fee_e = QLineEdit()
665 grid.addWidget(QLabel(_('Fee')), 4, 0)
666 grid.addWidget(self.fee_e, 4, 1, 1, 2)
667 grid.addWidget(HelpButton(
668 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
669 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
670 + _('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)
672 b = EnterButton(_("Send"), self.do_send)
673 grid.addWidget(b, 6, 1)
675 b = EnterButton(_("Clear"),self.do_clear)
676 grid.addWidget(b, 6, 2)
678 self.payto_sig = QLabel('')
679 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
681 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
682 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
691 def entry_changed( is_fee ):
692 self.funds_error = False
693 amount = numbify(self.amount_e)
694 fee = numbify(self.fee_e)
695 if not is_fee: fee = None
698 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
700 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
703 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
706 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
707 self.funds_error = True
708 self.amount_e.setPalette(palette)
709 self.fee_e.setPalette(palette)
712 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
713 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
718 def update_completions(self):
720 for addr,label in self.wallet.labels.items():
721 if addr in self.wallet.addressbook:
722 l.append( label + ' <' + addr + '>')
723 l = l + self.wallet.aliases.keys()
725 self.completions.setStringList(l)
731 label = unicode( self.message_e.text() )
732 r = unicode( self.payto_e.text() )
736 m1 = re.match(ALIAS_REGEXP, r)
737 # label or alias, with address in brackets
738 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
741 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
745 to_address = m2.group(2)
749 if not self.wallet.is_valid(to_address):
750 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
754 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
756 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
759 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
761 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
764 if self.wallet.use_encryption:
765 password = self.password_dialog()
772 tx = self.wallet.mktx( [(to_address, amount)], label, password, fee)
773 except BaseException, e:
774 self.show_message(str(e))
778 h = self.wallet.send_tx(tx)
779 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
780 status, msg = self.wallet.receive_tx( h )
782 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
784 self.update_contacts_tab()
786 QMessageBox.warning(self, _('Error'), msg, _('OK'))
788 filename = 'unsigned_tx'
789 f = open(filename,'w')
792 QMessageBox.information(self, _('Unsigned transaction'), _("Unsigned transaction was saved to file:") + " " +filename, _('OK'))
795 def set_url(self, url):
796 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
797 self.tabs.setCurrentIndex(1)
798 label = self.wallet.labels.get(payto)
799 m_addr = label + ' <'+ payto+'>' if label else payto
800 self.payto_e.setText(m_addr)
802 self.message_e.setText(message)
803 self.amount_e.setText(amount)
805 self.set_frozen(self.payto_e,True)
806 self.set_frozen(self.amount_e,True)
807 self.set_frozen(self.message_e,True)
808 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
810 self.payto_sig.setVisible(False)
813 self.payto_sig.setVisible(False)
814 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
816 self.set_frozen(e,False)
818 def set_frozen(self,entry,frozen):
820 entry.setReadOnly(True)
821 entry.setFrame(False)
823 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
824 entry.setPalette(palette)
826 entry.setReadOnly(False)
829 palette.setColor(entry.backgroundRole(), QColor('white'))
830 entry.setPalette(palette)
833 def toggle_freeze(self,addr):
835 if addr in self.wallet.frozen_addresses:
836 self.wallet.unfreeze(addr)
838 self.wallet.freeze(addr)
839 self.update_receive_tab()
841 def toggle_priority(self,addr):
843 if addr in self.wallet.prioritized_addresses:
844 self.wallet.unprioritize(addr)
846 self.wallet.prioritize(addr)
847 self.update_receive_tab()
850 def create_list_tab(self, headers):
851 "generic tab creation method"
852 l = MyTreeWidget(self)
853 l.setColumnCount( len(headers) )
854 l.setHeaderLabels( headers )
864 vbox.addWidget(buttons)
869 buttons.setLayout(hbox)
874 def create_receive_tab(self):
875 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
876 l.setContextMenuPolicy(Qt.CustomContextMenu)
877 l.customContextMenuRequested.connect(self.create_receive_menu)
878 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
879 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
880 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
881 self.receive_list = l
882 self.receive_buttons_hbox = hbox
888 def receive_tab_set_mode(self, i):
889 self.receive_tab_mode = i
890 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
892 self.update_receive_tab()
893 self.toggle_QR_window(self.receive_tab_mode == 2)
896 def create_contacts_tab(self):
897 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
898 l.setContextMenuPolicy(Qt.CustomContextMenu)
899 l.customContextMenuRequested.connect(self.create_contact_menu)
900 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
901 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
902 self.contacts_list = l
903 self.contacts_buttons_hbox = hbox
904 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
909 def create_receive_menu(self, position):
910 # fixme: this function apparently has a side effect.
911 # if it is not called the menu pops up several times
912 #self.receive_list.selectedIndexes()
914 item = self.receive_list.itemAt(position)
916 addr = unicode(item.text(1))
918 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
919 if self.receive_tab_mode == 2:
920 menu.addAction(_("Request amount"), lambda: self.edit_amount())
921 menu.addAction(_("View QR"), lambda: ElectrumWindow.show_qrcode("Address","bitcoin:"+addr) )
922 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
923 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
925 if self.receive_tab_mode == 1:
926 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
927 menu.addAction(t, lambda: self.toggle_freeze(addr))
928 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
929 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 self.receive_tab_mode == 1:
1000 if address in self.wallet.frozen_addresses:
1001 item.setBackgroundColor(1, QColor('lightblue'))
1002 elif address in self.wallet.prioritized_addresses:
1003 item.setBackgroundColor(1, QColor('lightgreen'))
1006 def update_receive_tab(self):
1007 l = self.receive_list
1010 l.setColumnHidden(0, not self.receive_tab_mode == 1)
1011 l.setColumnHidden(3, not self.receive_tab_mode == 2)
1012 l.setColumnHidden(4, self.receive_tab_mode == 0)
1013 l.setColumnHidden(5, not self.receive_tab_mode == 1)
1014 l.setColumnWidth(0, 50)
1015 l.setColumnWidth(1, 310)
1016 l.setColumnWidth(2, 200)
1017 l.setColumnWidth(3, 130)
1018 l.setColumnWidth(4, 130)
1019 l.setColumnWidth(5, 10)
1023 for address in self.wallet.all_addresses():
1025 if self.wallet.is_change(address) and self.receive_tab_mode != 1:
1029 h = self.wallet.history.get(address,[])
1032 for tx_hash, tx_height in h:
1033 tx = self.wallet.transactions.get(tx_hash)
1041 if address in self.wallet.addresses:
1043 if gap > self.wallet.gap_limit:
1046 if address in self.wallet.addresses:
1049 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1050 item.setFont(0, QFont(MONOSPACE_FONT))
1051 item.setFont(1, QFont(MONOSPACE_FONT))
1052 item.setFont(3, QFont(MONOSPACE_FONT))
1053 self.update_receive_item(item)
1054 if is_red and address in self.wallet.addresses:
1055 item.setBackgroundColor(1, QColor('red'))
1056 l.addTopLevelItem(item)
1058 # we use column 1 because column 0 may be hidden
1059 l.setCurrentItem(l.topLevelItem(0),1)
1061 def show_contact_details(self, m):
1062 a = self.wallet.aliases.get(m)
1064 if a[0] in self.wallet.authorities.keys():
1065 s = self.wallet.authorities.get(a[0])
1068 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1069 QMessageBox.information(self, 'Alias', msg, 'OK')
1071 def update_contacts_tab(self):
1073 l = self.contacts_list
1075 l.setColumnWidth(0, 350)
1076 l.setColumnWidth(1, 330)
1077 l.setColumnWidth(2, 100)
1080 for alias, v in self.wallet.aliases.items():
1082 alias_targets.append(target)
1083 item = QTreeWidgetItem( [ target, alias, '-'] )
1084 item.setBackgroundColor(0, QColor('lightgray'))
1085 l.addTopLevelItem(item)
1087 for address in self.wallet.addressbook:
1088 if address in alias_targets: continue
1089 label = self.wallet.labels.get(address,'')
1091 for item in self.wallet.transactions.values():
1092 if address in item['outputs'] : n=n+1
1094 item = QTreeWidgetItem( [ address, label, tx] )
1095 item.setFont(0, QFont(MONOSPACE_FONT))
1096 l.addTopLevelItem(item)
1098 l.setCurrentItem(l.topLevelItem(0))
1100 def create_wall_tab(self):
1101 self.textbox = textbox = QTextEdit(self)
1102 textbox.setFont(QFont(MONOSPACE_FONT))
1103 textbox.setReadOnly(True)
1106 def create_status_bar(self):
1108 sb.setFixedHeight(35)
1109 if self.wallet.seed:
1110 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1111 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1112 if self.wallet.seed:
1113 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1114 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
1115 sb.addPermanentWidget( self.status_button )
1116 self.setStatusBar(sb)
1118 def new_contact_dialog(self):
1119 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1120 address = unicode(text)
1122 if self.wallet.is_valid(address):
1123 self.wallet.addressbook.append(address)
1125 self.update_contacts_tab()
1126 self.update_history_tab()
1127 self.update_completions()
1129 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1132 def show_seed_dialog(wallet, parent=None):
1134 QMessageBox.information(parent, _('Message'),
1135 _('No seed'), _('OK'))
1138 if wallet.use_encryption:
1139 password = parent.password_dialog()
1146 seed = wallet.pw_decode(wallet.seed, password)
1148 QMessageBox.warning(parent, _('Error'),
1149 _('Incorrect Password'), _('OK'))
1152 dialog = QDialog(None)
1154 dialog.setWindowTitle("Electrum")
1156 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1158 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1159 + _("Please write down or memorize these 12 words (order is important).") + " " \
1160 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1161 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1163 main_text = QLabel(msg)
1164 main_text.setWordWrap(True)
1167 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1174 copy_function = lambda: app.clipboard().setText(brainwallet)
1175 copy_button = QPushButton(_("Copy to Clipboard"))
1176 copy_button.clicked.connect(copy_function)
1178 show_qr_function = lambda: ElectrumWindow.show_qrcode(_("Seed"), seed)
1179 qr_button = QPushButton(_("View as QR Code"))
1180 qr_button.clicked.connect(show_qr_function)
1182 ok_button = QPushButton(_("OK"))
1183 ok_button.setDefault(True)
1184 ok_button.clicked.connect(dialog.accept)
1186 main_layout = QGridLayout()
1187 main_layout.addWidget(logo, 0, 0)
1188 main_layout.addWidget(main_text, 0, 1, 1, -1)
1189 main_layout.addWidget(copy_button, 1, 1)
1190 main_layout.addWidget(qr_button, 1, 2)
1191 main_layout.addWidget(ok_button, 1, 3)
1192 dialog.setLayout(main_layout)
1197 def show_qrcode(title, data):
1201 d.setWindowTitle(title)
1202 d.setMinimumSize(270, 300)
1203 vbox = QVBoxLayout()
1204 qrw = QRCodeWidget(data)
1205 vbox.addWidget(qrw, 1)
1206 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1207 hbox = QHBoxLayout()
1211 filename = "qrcode.bmp"
1212 bmp.save_qrcode(qrw.qr, filename)
1213 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1215 b = QPushButton(_("Print"))
1217 b.clicked.connect(print_qr)
1219 b = QPushButton(_("Close"))
1221 b.clicked.connect(d.accept)
1223 vbox.addLayout(hbox)
1227 def sign_message(self,address):
1228 if not address: return
1231 d.setWindowTitle('Sign Message')
1232 d.setMinimumSize(270, 350)
1234 tab_widget = QTabWidget()
1236 layout = QGridLayout(tab)
1238 sign_address = QLineEdit()
1239 sign_address.setText(address)
1240 layout.addWidget(QLabel(_('Address')), 1, 0)
1241 layout.addWidget(sign_address, 1, 1)
1243 sign_message = QTextEdit()
1244 layout.addWidget(QLabel(_('Message')), 2, 0)
1245 layout.addWidget(sign_message, 2, 1, 2, 1)
1247 sign_signature = QLineEdit()
1248 layout.addWidget(QLabel(_('Signature')), 3, 0)
1249 layout.addWidget(sign_signature, 3, 1)
1252 if self.wallet.use_encryption:
1253 password = self.password_dialog()
1260 signature = self.wallet.sign_message(sign_address.text(), str(sign_message.toPlainText()), password)
1261 sign_signature.setText(signature)
1262 except BaseException, e:
1263 self.show_message(str(e))
1266 hbox = QHBoxLayout()
1267 b = QPushButton(_("Sign"))
1269 b.clicked.connect(do_sign)
1270 b = QPushButton(_("Close"))
1271 b.clicked.connect(d.accept)
1273 layout.addLayout(hbox, 4, 1)
1274 tab_widget.addTab(tab, "Sign")
1278 layout = QGridLayout(tab)
1280 verify_address = QLineEdit()
1281 layout.addWidget(QLabel(_('Address')), 1, 0)
1282 layout.addWidget(verify_address, 1, 1)
1284 verify_message = QTextEdit()
1285 layout.addWidget(QLabel(_('Message')), 2, 0)
1286 layout.addWidget(verify_message, 2, 1, 2, 1)
1288 verify_signature = QLineEdit()
1289 layout.addWidget(QLabel(_('Signature')), 3, 0)
1290 layout.addWidget(verify_signature, 3, 1)
1294 self.wallet.verify_message(verify_address.text(), verify_signature.text(), str(verify_message.toPlainText()))
1295 self.show_message("Signature verified")
1296 except BaseException, e:
1297 self.show_message(str(e))
1300 hbox = QHBoxLayout()
1301 b = QPushButton(_("Verify"))
1302 b.clicked.connect(do_verify)
1304 b = QPushButton(_("Close"))
1305 b.clicked.connect(d.accept)
1307 layout.addLayout(hbox, 4, 1)
1308 tab_widget.addTab(tab, "Verify")
1310 vbox = QVBoxLayout()
1311 vbox.addWidget(tab_widget)
1316 def toggle_QR_window(self, show):
1317 if show and not self.qr_window:
1318 self.qr_window = QR_Window()
1319 self.qr_window.setVisible(True)
1320 self.qr_window_geometry = self.qr_window.geometry()
1321 item = self.receive_list.currentItem()
1323 address = str(item.text(1))
1324 label = self.wallet.labels.get(address)
1325 amount = self.wallet.requested_amounts.get(address)
1326 self.qr_window.set_content( address, label, amount )
1328 elif show and self.qr_window and not self.qr_window.isVisible():
1329 self.qr_window.setVisible(True)
1330 self.qr_window.setGeometry(self.qr_window_geometry)
1332 elif not show and self.qr_window and self.qr_window.isVisible():
1333 self.qr_window_geometry = self.qr_window.geometry()
1334 self.qr_window.setVisible(False)
1336 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1337 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1338 self.receive_list.setColumnWidth(2, 200)
1341 def question(self, msg):
1342 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1344 def show_message(self, msg):
1345 QMessageBox.information(self, _('Message'), msg, _('OK'))
1347 def password_dialog(self ):
1354 vbox = QVBoxLayout()
1355 msg = _('Please enter your password')
1356 vbox.addWidget(QLabel(msg))
1358 grid = QGridLayout()
1360 grid.addWidget(QLabel(_('Password')), 1, 0)
1361 grid.addWidget(pw, 1, 1)
1362 vbox.addLayout(grid)
1364 vbox.addLayout(ok_cancel_buttons(d))
1367 if not d.exec_(): return
1368 return unicode(pw.text())
1375 def change_password_dialog( wallet, parent=None ):
1378 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1386 new_pw = QLineEdit()
1387 new_pw.setEchoMode(2)
1388 conf_pw = QLineEdit()
1389 conf_pw.setEchoMode(2)
1391 vbox = QVBoxLayout()
1393 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1394 +_('To disable wallet encryption, enter an empty new password.')) \
1395 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1397 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1398 +_("Leave these fields empty if you want to disable encryption.")
1399 vbox.addWidget(QLabel(msg))
1401 grid = QGridLayout()
1404 if wallet.use_encryption:
1405 grid.addWidget(QLabel(_('Password')), 1, 0)
1406 grid.addWidget(pw, 1, 1)
1408 grid.addWidget(QLabel(_('New Password')), 2, 0)
1409 grid.addWidget(new_pw, 2, 1)
1411 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1412 grid.addWidget(conf_pw, 3, 1)
1413 vbox.addLayout(grid)
1415 vbox.addLayout(ok_cancel_buttons(d))
1418 if not d.exec_(): return
1420 password = unicode(pw.text()) if wallet.use_encryption else None
1421 new_password = unicode(new_pw.text())
1422 new_password2 = unicode(conf_pw.text())
1425 seed = wallet.pw_decode( wallet.seed, password)
1427 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1430 if new_password != new_password2:
1431 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1432 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1434 wallet.update_password(seed, password, new_password)
1437 def seed_dialog(wallet, parent=None):
1441 vbox = QVBoxLayout()
1442 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1443 vbox.addWidget(QLabel(msg))
1445 grid = QGridLayout()
1448 seed_e = QLineEdit()
1449 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1450 grid.addWidget(seed_e, 1, 1)
1454 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1455 grid.addWidget(gap_e, 2, 1)
1456 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1457 vbox.addLayout(grid)
1459 vbox.addLayout(ok_cancel_buttons(d))
1462 if not d.exec_(): return
1465 gap = int(unicode(gap_e.text()))
1467 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1471 seed = unicode(seed_e.text())
1474 print_error("Warning: Not hex, trying decode")
1476 seed = mnemonic.mn_decode( seed.split(' ') )
1478 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1481 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1484 wallet.seed = str(seed)
1485 #print repr(wallet.seed)
1486 wallet.gap_limit = gap
1491 def settings_dialog(self):
1493 d.setWindowTitle(_('Electrum Settings'))
1495 vbox = QVBoxLayout()
1497 tabs = QTabWidget(self)
1498 vbox.addWidget(tabs)
1501 grid_wallet = QGridLayout(tab)
1502 grid_wallet.setColumnStretch(0,1)
1503 tabs.addTab(tab, _('Wallet') )
1506 grid_ui = QGridLayout(tab2)
1507 grid_ui.setColumnStretch(0,1)
1508 tabs.addTab(tab2, _('Display') )
1510 fee_label = QLabel(_('Transaction fee'))
1511 grid_wallet.addWidget(fee_label, 2, 0)
1513 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1514 grid_wallet.addWidget(fee_e, 2, 1)
1515 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1516 + _('Recommended value') + ': 0.001'
1517 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1518 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1519 if not self.config.is_modifiable('fee'):
1520 for w in [fee_e, fee_label]: w.setEnabled(False)
1522 nz_label = QLabel(_('Display zeros'))
1523 grid_ui.addWidget(nz_label, 3, 0)
1525 nz_e.setText("%d"% self.wallet.num_zeros)
1526 grid_ui.addWidget(nz_e, 3, 1)
1527 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1528 grid_ui.addWidget(HelpButton(msg), 3, 2)
1529 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1530 if not self.config.is_modifiable('num_zeros'):
1531 for w in [nz_e, nz_label]: w.setEnabled(False)
1534 usechange_label = QLabel(_('Use change addresses'))
1535 grid_wallet.addWidget(usechange_label, 5, 0)
1536 usechange_combo = QComboBox()
1537 usechange_combo.addItems(['Yes', 'No'])
1538 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1539 grid_wallet.addWidget(usechange_combo, 5, 1)
1540 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1541 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1543 gap_label = QLabel(_('Gap limit'))
1544 grid_wallet.addWidget(gap_label, 6, 0)
1546 gap_e.setText("%d"% self.wallet.gap_limit)
1547 grid_wallet.addWidget(gap_e, 6, 1)
1548 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1549 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1550 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1551 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1552 + _('Warning') + ': ' \
1553 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1554 + _('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'
1555 grid_wallet.addWidget(HelpButton(msg), 6, 2)
1556 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1557 if not self.config.is_modifiable('gap_limit'):
1558 for w in [gap_e, gap_label]: w.setEnabled(False)
1560 gui_label=QLabel(_('Default GUI') + ':')
1561 grid_ui.addWidget(gui_label , 7, 0)
1562 gui_combo = QComboBox()
1563 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1564 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1565 if index==-1: index = 1
1566 gui_combo.setCurrentIndex(index)
1567 grid_ui.addWidget(gui_combo, 7, 1)
1568 grid_ui.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1569 if not self.config.is_modifiable('gui'):
1570 for w in [gui_combo, gui_label]: w.setEnabled(False)
1572 lang_label=QLabel(_('Language') + ':')
1573 grid_ui.addWidget(lang_label , 8, 0)
1574 lang_combo = QComboBox()
1575 languages = {'':_('Default'), 'br':_('Brasilian'), 'cs':_('Czech'), 'de':_('German'),
1576 'eo':_('Esperanto'), 'en':_('English'), 'es':_('Spanish'), 'fr':_('French'),
1577 'it':_('Italian'), 'lv':_('Latvian'), 'nl':_('Dutch'), 'ru':_('Russian'),
1578 'sl':_('Slovenian'), 'vi':_('Vietnamese'), 'zh':_('Chinese')
1580 lang_combo.addItems(languages.values())
1582 index = languages.keys().index(self.config.get("language",''))
1585 lang_combo.setCurrentIndex(index)
1586 grid_ui.addWidget(lang_combo, 8, 1)
1587 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart). ')), 8, 2)
1588 if not self.config.is_modifiable('language'):
1589 for w in [lang_combo, lang_label]: w.setEnabled(False)
1592 view_label=QLabel(_('Receive Tab') + ':')
1593 grid_ui.addWidget(view_label , 9, 0)
1594 view_combo = QComboBox()
1595 view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
1596 view_combo.setCurrentIndex(self.receive_tab_mode)
1597 grid_ui.addWidget(view_combo, 9, 1)
1598 hh = _('This selects the interaction mode of the "Receive" tab. ') + '\n\n' \
1599 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1600 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
1601 + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n'
1603 grid_ui.addWidget(HelpButton(hh), 9, 2)
1605 vbox.addLayout(ok_cancel_buttons(d))
1609 if not d.exec_(): return
1611 fee = unicode(fee_e.text())
1613 fee = int( 100000000 * Decimal(fee) )
1615 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1618 if self.wallet.fee != fee:
1619 self.wallet.fee = fee
1622 nz = unicode(nz_e.text())
1627 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1630 if self.wallet.num_zeros != nz:
1631 self.wallet.num_zeros = nz
1632 self.config.set_key('num_zeros', nz, True)
1633 self.update_history_tab()
1634 self.update_receive_tab()
1636 usechange_result = usechange_combo.currentIndex() == 0
1637 if self.wallet.use_change != usechange_result:
1638 self.wallet.use_change = usechange_result
1639 self.config.set_key('use_change', self.wallet.use_change, True)
1642 n = int(gap_e.text())
1644 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1647 if self.wallet.gap_limit != n:
1648 r = self.wallet.change_gap_limit(n)
1650 self.update_receive_tab()
1651 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1653 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1655 need_restart = False
1657 gui_request = str(gui_combo.currentText()).lower()
1658 if gui_request != self.config.get('gui'):
1659 self.config.set_key('gui', gui_request, True)
1662 lang_request = languages.keys()[lang_combo.currentIndex()]
1663 if lang_request != self.config.get('language'):
1664 self.config.set_key("language", lang_request, True)
1668 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
1670 self.receive_tab_set_mode(view_combo.currentIndex())
1674 def network_dialog(wallet, parent=None):
1675 interface = wallet.interface
1677 if interface.is_connected:
1678 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1680 status = _("Not connected")
1681 server = interface.server
1684 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1685 server = interface.server
1687 plist, servers_list = interface.get_servers_list()
1691 d.setWindowTitle(_('Server'))
1692 d.setMinimumSize(375, 20)
1694 vbox = QVBoxLayout()
1697 hbox = QHBoxLayout()
1699 l.setPixmap(QPixmap(":icons/network.png"))
1702 hbox.addWidget(QLabel(status))
1704 vbox.addLayout(hbox)
1708 grid = QGridLayout()
1710 vbox.addLayout(grid)
1713 server_protocol = QComboBox()
1714 server_host = QLineEdit()
1715 server_host.setFixedWidth(200)
1716 server_port = QLineEdit()
1717 server_port.setFixedWidth(60)
1719 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1720 protocol_letters = 'thsg'
1721 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1722 server_protocol.addItems(protocol_names)
1724 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1725 grid.addWidget(server_protocol, 0, 1)
1726 grid.addWidget(server_host, 0, 2)
1727 grid.addWidget(server_port, 0, 3)
1729 def change_protocol(p):
1730 protocol = protocol_letters[p]
1731 host = unicode(server_host.text())
1732 pp = plist.get(host,DEFAULT_PORTS)
1733 if protocol not in pp.keys():
1734 protocol = pp.keys()[0]
1736 server_host.setText( host )
1737 server_port.setText( port )
1739 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1741 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1742 servers_list_widget = QTreeWidget(parent)
1743 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1744 servers_list_widget.setMaximumHeight(150)
1745 servers_list_widget.setColumnWidth(0, 240)
1746 for _host in servers_list.keys():
1747 _type = 'P' if servers_list[_host].get('pruning') else 'F'
1748 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1750 def change_server(host, protocol=None):
1751 pp = plist.get(host,DEFAULT_PORTS)
1753 port = pp.get(protocol)
1754 if not port: protocol = None
1757 if 't' in pp.keys():
1759 port = pp.get(protocol)
1761 protocol = pp.keys()[0]
1762 port = pp.get(protocol)
1764 server_host.setText( host )
1765 server_port.setText( port )
1766 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1768 if not plist: return
1769 for p in protocol_letters:
1770 i = protocol_letters.index(p)
1771 j = server_protocol.model().index(i,0)
1772 if p not in pp.keys():
1773 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1775 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1779 host, port, protocol = server.split(':')
1780 change_server(host,protocol)
1782 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1783 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1785 if not wallet.config.is_modifiable('server'):
1786 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1789 autocycle_cb = QCheckBox('Try random servers if disconnected')
1790 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
1791 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
1792 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
1795 proxy_mode = QComboBox()
1796 proxy_host = QLineEdit()
1797 proxy_host.setFixedWidth(200)
1798 proxy_port = QLineEdit()
1799 proxy_port.setFixedWidth(60)
1800 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1802 def check_for_disable(index = False):
1803 if proxy_mode.currentText() != 'NONE':
1804 proxy_host.setEnabled(True)
1805 proxy_port.setEnabled(True)
1807 proxy_host.setEnabled(False)
1808 proxy_port.setEnabled(False)
1811 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1813 if not wallet.config.is_modifiable('proxy'):
1814 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1816 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1817 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1818 proxy_host.setText(proxy_config.get("host"))
1819 proxy_port.setText(proxy_config.get("port"))
1821 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1822 grid.addWidget(proxy_mode, 2, 1)
1823 grid.addWidget(proxy_host, 2, 2)
1824 grid.addWidget(proxy_port, 2, 3)
1827 vbox.addLayout(ok_cancel_buttons(d))
1830 if not d.exec_(): return
1832 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1833 if proxy_mode.currentText() != 'NONE':
1834 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1838 wallet.config.set_key("proxy", proxy, True)
1839 wallet.config.set_key("server", server, True)
1840 interface.set_server(server, proxy)
1841 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
1844 def closeEvent(self, event):
1846 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1852 def __init__(self, wallet, config, app=None):
1853 self.wallet = wallet
1854 self.config = config
1856 self.app = QApplication(sys.argv)
1859 def restore_or_create(self):
1860 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1861 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1862 if r==2: return None
1863 return 'restore' if r==1 else 'create'
1865 def seed_dialog(self):
1866 return ElectrumWindow.seed_dialog( self.wallet )
1868 def network_dialog(self):
1869 return ElectrumWindow.network_dialog( self.wallet, parent=None )
1872 def show_seed(self):
1873 ElectrumWindow.show_seed_dialog(self.wallet)
1876 def password_dialog(self):
1877 ElectrumWindow.change_password_dialog(self.wallet)
1880 def restore_wallet(self):
1881 wallet = self.wallet
1882 # wait until we are connected, because the user might have selected another server
1883 if not wallet.interface.is_connected:
1884 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1885 waiting_dialog(waiting)
1887 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1888 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1890 wallet.set_up_to_date(False)
1891 wallet.interface.poke('synchronizer')
1892 waiting_dialog(waiting)
1893 if wallet.is_found():
1894 print_error( "Recovery successful" )
1896 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1903 w = ElectrumWindow(self.wallet, self.config)
1904 if url: w.set_url(url)