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, addr):
141 super(QRCodeWidget, self).__init__()
142 self.setMinimumSize(210, 210)
145 def set_addr(self, addr):
147 self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
148 self.qr.addData(self.addr)
151 def paintEvent(self, e):
152 if not self.addr: return
154 qp = QtGui.QPainter()
157 size = self.qr.getModuleCount()*boxsize
158 k = self.qr.getModuleCount()
159 black = QColor(0, 0, 0, 255)
160 white = QColor(255, 255, 255, 255)
163 if self.qr.isDark(r, c):
169 qp.drawRect(c*boxsize, r*boxsize, boxsize, boxsize)
174 class QR_Window(QWidget):
177 QWidget.__init__(self)
178 self.setWindowTitle('Electrum - Invoice')
179 self.setMinimumSize(800, 250)
183 self.setFocusPolicy(QtCore.Qt.NoFocus)
185 main_box = QHBoxLayout()
187 self.qrw = QRCodeWidget('')
188 main_box.addWidget(self.qrw)
191 main_box.addLayout(vbox)
193 main_box.addStretch(1)
195 self.address_label = QLabel("")
196 self.address_label.setFont(QFont(MONOSPACE_FONT))
197 vbox.addWidget(self.address_label)
199 self.label_label = QLabel("")
200 vbox.addWidget(self.label_label)
202 self.amount_label = QLabel("")
203 vbox.addWidget(self.amount_label)
207 self.setLayout(main_box)
210 self.filename = "qrcode.bmp"
211 bmp.save_qrcode(self.qrw.qr, self.filename)
213 def set_content(self, addr, label, amount):
215 address_text = "<span style='font-size: 21pt'>%s</span>" % addr if addr else ""
216 self.address_label.setText(address_text)
219 amount_text = "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % format_satoshis(amount) if amount else ""
220 self.amount_label.setText(amount_text)
223 label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
224 self.label_label.setText(label_text)
226 msg = 'bitcoin:'+self.address
227 if self.amount is not None:
228 msg += '?amount=%s'%(str( Decimal(self.amount) /100000000))
229 if self.label is not None:
230 msg += '&label=%s'%(self.label)
231 elif self.label is not None:
232 msg += '?label=%s'%(self.label)
234 self.qrw.set_addr( msg )
240 def waiting_dialog(f):
246 w.setWindowTitle('Electrum')
256 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
261 def ok_cancel_buttons(dialog):
264 b = QPushButton("OK")
266 b.clicked.connect(dialog.accept)
267 b = QPushButton("Cancel")
269 b.clicked.connect(dialog.reject)
273 class ElectrumWindow(QMainWindow):
275 def __init__(self, wallet, config):
276 QMainWindow.__init__(self)
279 self.wallet.interface.register_callback('updated', self.update_callback)
280 self.wallet.interface.register_callback('connected', self.update_callback)
281 self.wallet.interface.register_callback('disconnected', self.update_callback)
282 self.wallet.interface.register_callback('disconnecting', self.update_callback)
284 self.detailed_view = config.get('qt_detailed_view', False)
286 self.qr_window = None
287 self.funds_error = False
288 self.completions = QStringListModel()
290 self.tabs = tabs = QTabWidget(self)
291 tabs.addTab(self.create_history_tab(), _('History') )
293 tabs.addTab(self.create_send_tab(), _('Send') )
294 tabs.addTab(self.create_receive_tab(), _('Receive') )
295 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
296 tabs.addTab(self.create_wall_tab(), _('Wall') )
297 tabs.setMinimumSize(600, 400)
298 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
299 self.setCentralWidget(tabs)
300 self.create_status_bar()
302 g = self.config.get("winpos-qt",[100, 100, 840, 400])
303 self.setGeometry(g[0], g[1], g[2], g[3])
304 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
305 if not self.wallet.seed: title += ' [seedless]'
306 self.setWindowTitle( title )
308 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
309 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
310 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
311 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
313 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
314 #self.connect(self, SIGNAL('editamount'), self.edit_amount)
315 self.history_list.setFocus(True)
317 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
318 if platform.system() == 'Windows':
319 n = 3 if self.wallet.seed else 2
320 tabs.setCurrentIndex (n)
321 tabs.setCurrentIndex (0)
324 QMainWindow.close(self)
326 self.qr_window.close()
327 self.qr_window = None
329 def connect_slots(self, sender):
331 self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient)
332 self.previous_payto_e=''
334 def check_recipient(self):
335 if self.payto_e.hasFocus():
337 r = unicode( self.payto_e.text() )
338 if r != self.previous_payto_e:
339 self.previous_payto_e = r
341 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
343 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
347 s = r + ' <' + to_address + '>'
348 self.payto_e.setText(s)
351 def update_callback(self):
352 self.emit(QtCore.SIGNAL('updatesignal'))
354 def update_wallet(self):
355 if self.wallet.interface and self.wallet.interface.is_connected:
356 if not self.wallet.up_to_date:
357 text = _( "Synchronizing..." )
358 icon = QIcon(":icons/status_waiting.png")
360 c, u = self.wallet.get_balance()
361 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
362 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
363 icon = QIcon(":icons/status_connected.png")
365 text = _( "Not connected" )
366 icon = QIcon(":icons/status_disconnected.png")
369 text = _( "Not enough funds" )
371 self.statusBar().showMessage(text)
372 self.status_button.setIcon( icon )
374 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
375 self.textbox.setText( self.wallet.banner )
376 self.update_history_tab()
377 self.update_receive_tab()
378 self.update_contacts_tab()
379 self.update_completions()
382 def create_history_tab(self):
383 self.history_list = l = MyTreeWidget(self)
385 l.setColumnWidth(0, 40)
386 l.setColumnWidth(1, 140)
387 l.setColumnWidth(2, 350)
388 l.setColumnWidth(3, 140)
389 l.setColumnWidth(4, 140)
390 l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
391 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
392 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
394 l.setContextMenuPolicy(Qt.CustomContextMenu)
395 l.customContextMenuRequested.connect(self.create_history_menu)
399 def create_history_menu(self, position):
400 self.history_list.selectedIndexes()
401 item = self.history_list.currentItem()
403 tx_hash = str(item.toolTip(0))
404 if not tx_hash: return
406 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
407 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
408 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
409 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
412 def tx_details(self, tx_hash):
413 tx_details = self.wallet.get_tx_details(tx_hash)
414 QMessageBox.information(self, 'Details', tx_details, 'OK')
417 def tx_label_clicked(self, item, column):
418 if column==2 and item.isSelected():
419 tx_hash = str(item.toolTip(0))
421 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
422 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
423 self.history_list.editItem( item, column )
424 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
427 def tx_label_changed(self, item, column):
431 tx_hash = str(item.toolTip(0))
432 tx = self.wallet.transactions.get(tx_hash)
433 s = self.wallet.labels.get(tx_hash)
434 text = unicode( item.text(2) )
436 self.wallet.labels[tx_hash] = text
437 item.setForeground(2, QBrush(QColor('black')))
439 if s: self.wallet.labels.pop(tx_hash)
440 text = self.wallet.get_default_label(tx_hash)
441 item.setText(2, text)
442 item.setForeground(2, QBrush(QColor('gray')))
446 def edit_label(self, is_recv):
447 l = self.receive_list if is_recv else self.contacts_list
448 c = 2 if is_recv else 1
449 item = l.currentItem()
450 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
451 l.editItem( item, c )
452 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
454 def edit_amount(self):
455 l = self.receive_list
456 item = l.currentItem()
457 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
458 l.editItem( item, 3 )
459 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
462 def address_label_clicked(self, item, column, l, column_addr, column_label):
463 if column == column_label and item.isSelected():
464 addr = unicode( item.text(column_addr) )
465 label = unicode( item.text(column_label) )
466 if label in self.wallet.aliases.keys():
468 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
469 l.editItem( item, column )
470 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
473 def address_label_changed(self, item, column, l, column_addr, column_label):
475 if column == column_label:
476 addr = unicode( item.text(column_addr) )
477 text = unicode( item.text(column_label) )
481 if text not in self.wallet.aliases.keys():
482 old_addr = self.wallet.labels.get(text)
484 self.wallet.labels[addr] = text
487 print_error("Error: This is one of your aliases")
488 label = self.wallet.labels.get(addr,'')
489 item.setText(column_label, QString(label))
491 s = self.wallet.labels.get(addr)
493 self.wallet.labels.pop(addr)
497 self.update_history_tab()
498 self.update_completions()
500 self.recv_changed(item)
503 address = unicode( item.text(column_addr) )
504 text = unicode( item.text(3) )
506 index = self.wallet.addresses.index(address)
511 amount = int( Decimal( unicode(text)) * 100000000 )
512 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
514 amount = self.wallet.requested_amounts.get(address)
516 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
521 self.wallet.requested_amounts[address] = amount
523 label = self.wallet.labels.get(address)
525 label = 'invoice %04d'%(index+1)
527 self.update_receive_item(self.receive_list.currentItem())
529 self.qr_window.set_content( address, label, amount )
532 def recv_changed(self, a):
533 "current item changed"
534 if a is not None and self.qr_window and self.qr_window.isVisible():
535 address = str(a.text(1))
536 label = self.wallet.labels.get(address)
537 amount = self.wallet.requested_amounts.get(address)
538 self.qr_window.set_content( address, label, amount )
541 def update_history_tab(self):
543 self.history_list.clear()
544 for item in self.wallet.get_tx_history():
545 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
548 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
554 icon = QIcon(":icons/unconfirmed.png")
556 icon = QIcon(":icons/clock%d.png"%conf)
558 icon = QIcon(":icons/confirmed.png")
561 icon = QIcon(":icons/unconfirmed.png")
563 if value is not None:
564 v_str = format_satoshis(value, True, self.wallet.num_zeros)
568 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
571 label, is_default_label = self.wallet.get_label(tx_hash)
573 label = _('Pruned transaction outputs')
574 is_default_label = False
576 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
577 item.setFont(2, QFont(MONOSPACE_FONT))
578 item.setFont(3, QFont(MONOSPACE_FONT))
579 item.setFont(4, QFont(MONOSPACE_FONT))
581 item.setToolTip(0, tx_hash)
583 item.setForeground(2, QBrush(QColor('grey')))
585 item.setIcon(0, icon)
586 self.history_list.insertTopLevelItem(0,item)
589 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
592 def create_send_tab(self):
597 grid.setColumnMinimumWidth(3,300)
598 grid.setColumnStretch(5,1)
600 self.payto_e = QLineEdit()
601 grid.addWidget(QLabel(_('Pay to')), 1, 0)
602 grid.addWidget(self.payto_e, 1, 1, 1, 3)
605 qrcode = qrscanner.scan_qr()
606 if 'address' in qrcode:
607 self.payto_e.setText(qrcode['address'])
608 if 'amount' in qrcode:
609 self.amount_e.setText(str(qrcode['amount']))
610 if 'label' in qrcode:
611 self.message_e.setText(qrcode['label'])
612 if 'message' in qrcode:
613 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
616 if qrscanner.is_available():
617 b = QPushButton(_("Scan QR code"))
618 b.clicked.connect(fill_from_qr)
619 grid.addWidget(b, 1, 5)
621 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)
623 completer = QCompleter()
624 completer.setCaseSensitivity(False)
625 self.payto_e.setCompleter(completer)
626 completer.setModel(self.completions)
628 self.message_e = QLineEdit()
629 grid.addWidget(QLabel(_('Description')), 2, 0)
630 grid.addWidget(self.message_e, 2, 1, 1, 3)
631 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)
633 self.amount_e = QLineEdit()
634 grid.addWidget(QLabel(_('Amount')), 3, 0)
635 grid.addWidget(self.amount_e, 3, 1, 1, 2)
636 grid.addWidget(HelpButton(
637 _('Amount to be sent.') + '\n\n' \
638 + _('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)
640 self.fee_e = QLineEdit()
641 grid.addWidget(QLabel(_('Fee')), 4, 0)
642 grid.addWidget(self.fee_e, 4, 1, 1, 2)
643 grid.addWidget(HelpButton(
644 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
645 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
646 + _('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)
648 b = EnterButton(_("Send"), self.do_send)
649 grid.addWidget(b, 6, 1)
651 b = EnterButton(_("Clear"),self.do_clear)
652 grid.addWidget(b, 6, 2)
654 self.payto_sig = QLabel('')
655 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
657 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
658 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
667 def entry_changed( is_fee ):
668 self.funds_error = False
669 amount = numbify(self.amount_e)
670 fee = numbify(self.fee_e)
671 if not is_fee: fee = None
674 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
676 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
679 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
682 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
683 self.funds_error = True
684 self.amount_e.setPalette(palette)
685 self.fee_e.setPalette(palette)
687 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
688 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
693 def update_completions(self):
695 for addr,label in self.wallet.labels.items():
696 if addr in self.wallet.addressbook:
697 l.append( label + ' <' + addr + '>')
698 l = l + self.wallet.aliases.keys()
700 self.completions.setStringList(l)
706 label = unicode( self.message_e.text() )
707 r = unicode( self.payto_e.text() )
711 m1 = re.match(ALIAS_REGEXP, r)
712 # label or alias, with address in brackets
713 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
716 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
720 to_address = m2.group(2)
724 if not self.wallet.is_valid(to_address):
725 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
729 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
731 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
734 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
736 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
739 if self.wallet.use_encryption:
740 password = self.password_dialog()
747 tx = self.wallet.mktx( to_address, amount, label, password, fee)
748 except BaseException, e:
749 self.show_message(str(e))
752 h = self.wallet.send_tx(tx)
753 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
754 status, msg = self.wallet.receive_tx( h )
757 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
759 self.update_contacts_tab()
761 QMessageBox.warning(self, _('Error'), msg, _('OK'))
764 def set_url(self, url):
765 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
766 self.tabs.setCurrentIndex(1)
767 label = self.wallet.labels.get(payto)
768 m_addr = label + ' <'+ payto+'>' if label else payto
769 self.payto_e.setText(m_addr)
771 self.message_e.setText(message)
772 self.amount_e.setText(amount)
774 self.set_frozen(self.payto_e,True)
775 self.set_frozen(self.amount_e,True)
776 self.set_frozen(self.message_e,True)
777 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
779 self.payto_sig.setVisible(False)
782 self.payto_sig.setVisible(False)
783 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
785 self.set_frozen(e,False)
787 def set_frozen(self,entry,frozen):
789 entry.setReadOnly(True)
790 entry.setFrame(False)
792 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
793 entry.setPalette(palette)
795 entry.setReadOnly(False)
798 palette.setColor(entry.backgroundRole(), QColor('white'))
799 entry.setPalette(palette)
802 def toggle_freeze(self,addr):
804 if addr in self.wallet.frozen_addresses:
805 self.wallet.unfreeze(addr)
807 self.wallet.freeze(addr)
808 self.update_receive_tab()
810 def toggle_priority(self,addr):
812 if addr in self.wallet.prioritized_addresses:
813 self.wallet.unprioritize(addr)
815 self.wallet.prioritize(addr)
816 self.update_receive_tab()
819 def create_list_tab(self, headers):
820 "generic tab creation method"
821 l = MyTreeWidget(self)
822 l.setColumnCount( len(headers) )
823 l.setHeaderLabels( headers )
833 vbox.addWidget(buttons)
838 buttons.setLayout(hbox)
843 def create_receive_tab(self):
844 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
845 l.setContextMenuPolicy(Qt.CustomContextMenu)
846 l.customContextMenuRequested.connect(self.create_receive_menu)
847 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
848 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
849 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
850 self.receive_list = l
851 self.receive_buttons_hbox = hbox
852 self.qr_button = EnterButton(self.qr_button_text(), self.toggle_QR_window)
853 hbox.addWidget(self.qr_button)
854 self.print_button = EnterButton(_("Print QR"), self.print_qr)
855 self.print_button.setHidden(True)
856 hbox.addWidget(self.print_button)
858 self.details_button = EnterButton(self.details_button_text(), self.toggle_detailed_view)
859 hbox.addWidget(self.details_button)
865 self.qr_window.do_save()
866 self.show_message(_("QR code saved to file") + " " + self.qr_window.filename)
869 def details_button_text(self):
870 return _('Hide details') if self.detailed_view else _('Show details')
872 def qr_button_text(self):
873 return _('Hide QR') if self.qr_window and self.qr_window.isVisible() else _('Show QR')
876 def toggle_detailed_view(self):
877 self.detailed_view = not self.detailed_view
878 self.config.set_key('qt_detailed_view', self.detailed_view, True)
880 self.details_button.setText(self.details_button_text())
882 self.update_receive_tab()
883 self.update_contacts_tab()
886 def create_contacts_tab(self):
887 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
888 l.setContextMenuPolicy(Qt.CustomContextMenu)
889 l.customContextMenuRequested.connect(self.create_contact_menu)
890 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
891 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
892 self.contacts_list = l
893 self.contacts_buttons_hbox = hbox
894 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
899 def create_receive_menu(self, position):
900 # fixme: this function apparently has a side effect.
901 # if it is not called the menu pops up several times
902 #self.receive_list.selectedIndexes()
904 item = self.receive_list.itemAt(position)
906 addr = unicode(item.text(1))
908 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
909 if self.qr_window and self.qr_window.isVisible():
910 menu.addAction(_("Request amount"), lambda: self.edit_amount())
911 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
912 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
914 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
915 menu.addAction(t, lambda: self.toggle_freeze(addr))
916 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
917 menu.addAction(t, lambda: self.toggle_priority(addr))
918 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
921 def payto(self, x, is_alias):
928 label = self.wallet.labels.get(addr)
929 m_addr = label + ' <' + addr + '>' if label else addr
930 self.tabs.setCurrentIndex(1)
931 self.payto_e.setText(m_addr)
932 self.amount_e.setFocus()
934 def delete_contact(self, x, is_alias):
935 if self.question("Do you want to remove %s from your list of contacts?"%x):
936 if not is_alias and x in self.wallet.addressbook:
937 self.wallet.addressbook.remove(x)
938 if x in self.wallet.labels.keys():
939 self.wallet.labels.pop(x)
940 elif is_alias and x in self.wallet.aliases:
941 self.wallet.aliases.pop(x)
942 self.update_history_tab()
943 self.update_contacts_tab()
944 self.update_completions()
946 def create_contact_menu(self, position):
947 # fixme: this function apparently has a side effect.
948 # if it is not called the menu pops up several times
949 #self.contacts_list.selectedIndexes()
951 item = self.contacts_list.itemAt(position)
953 addr = unicode(item.text(0))
954 label = unicode(item.text(1))
955 is_alias = label in self.wallet.aliases.keys()
956 x = label if is_alias else addr
958 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
959 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
960 menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
962 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
964 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
965 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
966 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
969 def update_receive_item(self, item):
970 address = str( item.data(1,0).toString() )
972 flags = self.wallet.get_address_flags(address)
973 item.setData(0,0,flags)
975 label = self.wallet.labels.get(address,'')
976 item.setData(2,0,label)
978 amount = self.wallet.requested_amounts.get(address,None)
979 amount_str = format_satoshis( amount, False, self.wallet.num_zeros ) if amount is not None else ""
980 item.setData(3,0,amount_str)
982 c, u = self.wallet.get_addr_balance(address)
983 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
984 item.setData(4,0,balance)
986 if address in self.wallet.frozen_addresses:
987 item.setBackgroundColor(1, QColor('lightblue'))
988 elif address in self.wallet.prioritized_addresses:
989 item.setBackgroundColor(1, QColor('lightgreen'))
992 def update_receive_tab(self):
993 l = self.receive_list
996 l.setColumnHidden(0, not self.detailed_view)
997 l.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
998 l.setColumnHidden(4, not self.detailed_view)
999 l.setColumnHidden(5, not self.detailed_view)
1000 l.setColumnWidth(0, 50)
1001 l.setColumnWidth(1, 310)
1002 l.setColumnWidth(2, 200)
1003 l.setColumnWidth(3, 130)
1004 l.setColumnWidth(4, 130)
1005 l.setColumnWidth(5, 10)
1009 for address in self.wallet.all_addresses():
1011 if self.wallet.is_change(address) and not self.detailed_view:
1015 h = self.wallet.history.get(address,[])
1018 for tx_hash, tx_height in h:
1019 tx = self.wallet.transactions.get(tx_hash)
1027 if address in self.wallet.addresses:
1029 if gap > self.wallet.gap_limit:
1032 if address in self.wallet.addresses:
1035 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1036 item.setFont(0, QFont(MONOSPACE_FONT))
1037 item.setFont(1, QFont(MONOSPACE_FONT))
1038 item.setFont(3, QFont(MONOSPACE_FONT))
1039 self.update_receive_item(item)
1040 if is_red and address in self.wallet.addresses:
1041 item.setBackgroundColor(1, QColor('red'))
1042 l.addTopLevelItem(item)
1044 # we use column 1 because column 0 may be hidden
1045 l.setCurrentItem(l.topLevelItem(0),1)
1047 def show_contact_details(self, m):
1048 a = self.wallet.aliases.get(m)
1050 if a[0] in self.wallet.authorities.keys():
1051 s = self.wallet.authorities.get(a[0])
1054 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1055 QMessageBox.information(self, 'Alias', msg, 'OK')
1057 def update_contacts_tab(self):
1059 l = self.contacts_list
1061 l.setColumnHidden(2, not self.detailed_view)
1062 l.setColumnWidth(0, 350)
1063 l.setColumnWidth(1, 330)
1064 l.setColumnWidth(2, 100)
1067 for alias, v in self.wallet.aliases.items():
1069 alias_targets.append(target)
1070 item = QTreeWidgetItem( [ target, alias, '-'] )
1071 item.setBackgroundColor(0, QColor('lightgray'))
1072 l.addTopLevelItem(item)
1074 for address in self.wallet.addressbook:
1075 if address in alias_targets: continue
1076 label = self.wallet.labels.get(address,'')
1078 for item in self.wallet.transactions.values():
1079 if address in item['outputs'] : n=n+1
1081 item = QTreeWidgetItem( [ address, label, tx] )
1082 item.setFont(0, QFont(MONOSPACE_FONT))
1083 l.addTopLevelItem(item)
1085 l.setCurrentItem(l.topLevelItem(0))
1087 def create_wall_tab(self):
1088 self.textbox = textbox = QTextEdit(self)
1089 textbox.setFont(QFont(MONOSPACE_FONT))
1090 textbox.setReadOnly(True)
1093 def create_status_bar(self):
1095 sb.setFixedHeight(35)
1096 if self.wallet.seed:
1097 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1098 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1099 if self.wallet.seed:
1100 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1101 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
1102 sb.addPermanentWidget( self.status_button )
1103 self.setStatusBar(sb)
1105 def new_contact_dialog(self):
1106 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1107 address = unicode(text)
1109 if self.wallet.is_valid(address):
1110 self.wallet.addressbook.append(address)
1112 self.update_contacts_tab()
1113 self.update_history_tab()
1114 self.update_completions()
1116 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1119 def show_seed_dialog(wallet, parent=None):
1121 QMessageBox.information(parent, _('Message'),
1122 _('No seed'), _('OK'))
1125 if wallet.use_encryption:
1126 password = parent.password_dialog()
1133 seed = wallet.pw_decode(wallet.seed, password)
1135 QMessageBox.warning(parent, _('Error'),
1136 _('Incorrect Password'), _('OK'))
1139 dialog = QDialog(None)
1141 dialog.setWindowTitle("Electrum")
1143 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1145 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1146 + _("Please write down or memorize these 12 words (order is important).") + " " \
1147 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1148 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1150 main_text = QLabel(msg)
1151 main_text.setWordWrap(True)
1154 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1161 copy_function = lambda: app.clipboard().setText(brainwallet)
1162 copy_button = QPushButton(_("Copy to Clipboard"))
1163 copy_button.clicked.connect(copy_function)
1165 show_qr_function = lambda: ElectrumWindow.show_seed_qrcode(seed)
1166 qr_button = QPushButton(_("View as QR Code"))
1167 qr_button.clicked.connect(show_qr_function)
1169 ok_button = QPushButton(_("OK"))
1170 ok_button.setDefault(True)
1171 ok_button.clicked.connect(dialog.accept)
1173 main_layout = QGridLayout()
1174 main_layout.addWidget(logo, 0, 0)
1175 main_layout.addWidget(main_text, 0, 1, 1, -1)
1176 main_layout.addWidget(copy_button, 1, 1)
1177 main_layout.addWidget(qr_button, 1, 2)
1178 main_layout.addWidget(ok_button, 1, 3)
1179 dialog.setLayout(main_layout)
1184 def show_seed_qrcode(seed):
1188 d.setWindowTitle(_("Seed"))
1189 d.setMinimumSize(270, 300)
1190 vbox = QVBoxLayout()
1191 vbox.addWidget(QRCodeWidget(seed))
1192 hbox = QHBoxLayout()
1194 b = QPushButton(_("OK"))
1196 b.clicked.connect(d.accept)
1198 vbox.addLayout(hbox)
1202 def sign_message(self,address):
1203 if not address: return
1206 d.setWindowTitle('Sign Message')
1207 d.setMinimumSize(270, 350)
1209 tab_widget = QTabWidget()
1211 layout = QGridLayout(tab)
1213 sign_address = QLineEdit()
1214 sign_address.setText(address)
1215 layout.addWidget(QLabel(_('Address')), 1, 0)
1216 layout.addWidget(sign_address, 1, 1)
1218 sign_message = QTextEdit()
1219 layout.addWidget(QLabel(_('Message')), 2, 0)
1220 layout.addWidget(sign_message, 2, 1, 2, 1)
1222 sign_signature = QLineEdit()
1223 layout.addWidget(QLabel(_('Signature')), 3, 0)
1224 layout.addWidget(sign_signature, 3, 1)
1227 if self.wallet.use_encryption:
1228 password = self.password_dialog()
1235 signature = self.wallet.sign_message(sign_address.text(), sign_message.toPlainText(), password)
1236 sign_signature.setText(signature)
1237 except BaseException, e:
1238 self.show_message(str(e))
1241 hbox = QHBoxLayout()
1242 b = QPushButton(_("Sign"))
1244 b.clicked.connect(do_sign)
1245 b = QPushButton(_("Close"))
1246 b.clicked.connect(d.accept)
1248 layout.addLayout(hbox, 4, 1)
1249 tab_widget.addTab(tab, "Sign")
1253 layout = QGridLayout(tab)
1255 verify_address = QLineEdit()
1256 layout.addWidget(QLabel(_('Address')), 1, 0)
1257 layout.addWidget(verify_address, 1, 1)
1259 verify_message = QTextEdit()
1260 layout.addWidget(QLabel(_('Message')), 2, 0)
1261 layout.addWidget(verify_message, 2, 1, 2, 1)
1263 verify_signature = QLineEdit()
1264 layout.addWidget(QLabel(_('Signature')), 3, 0)
1265 layout.addWidget(verify_signature, 3, 1)
1269 self.wallet.verify_message(verify_address.text(), verify_signature.text(), verify_message.toPlainText())
1270 self.show_message("Signature verified")
1271 except BaseException, e:
1272 self.show_message(str(e))
1275 hbox = QHBoxLayout()
1276 b = QPushButton(_("Verify"))
1277 b.clicked.connect(do_verify)
1279 b = QPushButton(_("Close"))
1280 b.clicked.connect(d.accept)
1282 layout.addLayout(hbox, 4, 1)
1283 tab_widget.addTab(tab, "Verify")
1285 vbox = QVBoxLayout()
1286 vbox.addWidget(tab_widget)
1291 def toggle_QR_window(self):
1292 if not self.qr_window:
1293 self.qr_window = QR_Window()
1294 self.qr_window.setVisible(True)
1295 #print self.qr_window.isVisible()
1296 self.qr_window_geometry = self.qr_window.geometry()
1297 item = self.receive_list.currentItem()
1299 address = str(item.text(1))
1300 label = self.wallet.labels.get(address)
1301 amount = self.wallet.requested_amounts.get(address)
1302 self.qr_window.set_content( address, label, amount )
1303 self.update_receive_tab()
1305 if self.qr_window.isVisible():
1306 self.qr_window_geometry = self.qr_window.geometry()
1307 self.qr_window.setVisible(False)
1309 self.qr_window.setVisible(True)
1310 self.qr_window.setGeometry(self.qr_window_geometry)
1312 self.qr_button.setText(self.qr_button_text())
1313 self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1314 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1315 self.receive_list.setColumnWidth(2, 200)
1318 def question(self, msg):
1319 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1321 def show_message(self, msg):
1322 QMessageBox.information(self, _('Message'), msg, _('OK'))
1324 def password_dialog(self ):
1331 vbox = QVBoxLayout()
1332 msg = _('Please enter your password')
1333 vbox.addWidget(QLabel(msg))
1335 grid = QGridLayout()
1337 grid.addWidget(QLabel(_('Password')), 1, 0)
1338 grid.addWidget(pw, 1, 1)
1339 vbox.addLayout(grid)
1341 vbox.addLayout(ok_cancel_buttons(d))
1344 if not d.exec_(): return
1345 return unicode(pw.text())
1352 def change_password_dialog( wallet, parent=None ):
1355 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1363 new_pw = QLineEdit()
1364 new_pw.setEchoMode(2)
1365 conf_pw = QLineEdit()
1366 conf_pw.setEchoMode(2)
1368 vbox = QVBoxLayout()
1370 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1371 +_('To disable wallet encryption, enter an empty new password.')) \
1372 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1374 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1375 +_("Leave these fields empty if you want to disable encryption.")
1376 vbox.addWidget(QLabel(msg))
1378 grid = QGridLayout()
1381 if wallet.use_encryption:
1382 grid.addWidget(QLabel(_('Password')), 1, 0)
1383 grid.addWidget(pw, 1, 1)
1385 grid.addWidget(QLabel(_('New Password')), 2, 0)
1386 grid.addWidget(new_pw, 2, 1)
1388 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1389 grid.addWidget(conf_pw, 3, 1)
1390 vbox.addLayout(grid)
1392 vbox.addLayout(ok_cancel_buttons(d))
1395 if not d.exec_(): return
1397 password = unicode(pw.text()) if wallet.use_encryption else None
1398 new_password = unicode(new_pw.text())
1399 new_password2 = unicode(conf_pw.text())
1402 seed = wallet.pw_decode( wallet.seed, password)
1404 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1407 if new_password != new_password2:
1408 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1411 wallet.update_password(seed, password, new_password)
1414 def seed_dialog(wallet, parent=None):
1418 vbox = QVBoxLayout()
1419 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1420 vbox.addWidget(QLabel(msg))
1422 grid = QGridLayout()
1425 seed_e = QLineEdit()
1426 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1427 grid.addWidget(seed_e, 1, 1)
1431 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1432 grid.addWidget(gap_e, 2, 1)
1433 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1434 vbox.addLayout(grid)
1436 vbox.addLayout(ok_cancel_buttons(d))
1439 if not d.exec_(): return
1442 gap = int(unicode(gap_e.text()))
1444 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1448 seed = unicode(seed_e.text())
1451 print_error("Warning: Not hex, trying decode")
1453 seed = mnemonic.mn_decode( seed.split(' ') )
1455 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1458 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1461 wallet.seed = str(seed)
1462 #print repr(wallet.seed)
1463 wallet.gap_limit = gap
1468 def settings_dialog(self):
1471 vbox = QVBoxLayout()
1472 msg = _('Here are the settings of your wallet.') + '\n'\
1473 + _('For more explanations, click on the help buttons next to each field.')
1476 label.setFixedWidth(250)
1477 label.setWordWrap(True)
1478 label.setAlignment(Qt.AlignJustify)
1479 vbox.addWidget(label)
1481 grid = QGridLayout()
1483 vbox.addLayout(grid)
1485 fee_label = QLabel(_('Transaction fee'))
1486 grid.addWidget(fee_label, 2, 0)
1488 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1489 grid.addWidget(fee_e, 2, 1)
1490 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1491 + _('Recommended value') + ': 0.001'
1492 grid.addWidget(HelpButton(msg), 2, 2)
1493 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1494 if not self.config.is_modifiable('fee'):
1495 for w in [fee_e, fee_label]: w.setEnabled(False)
1497 nz_label = QLabel(_('Display zeros'))
1498 grid.addWidget(nz_label, 3, 0)
1500 nz_e.setText("%d"% self.wallet.num_zeros)
1501 grid.addWidget(nz_e, 3, 1)
1502 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1503 grid.addWidget(HelpButton(msg), 3, 2)
1504 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1505 if not self.config.is_modifiable('num_zeros'):
1506 for w in [nz_e, nz_label]: w.setEnabled(False)
1508 usechange_cb = QCheckBox(_('Use change addresses'))
1509 grid.addWidget(usechange_cb, 5, 0)
1510 usechange_cb.setChecked(self.wallet.use_change)
1511 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1512 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1514 gap_label = QLabel(_('Gap limit'))
1515 grid.addWidget(gap_label, 6, 0)
1517 gap_e.setText("%d"% self.wallet.gap_limit)
1518 grid.addWidget(gap_e, 6, 1)
1519 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1520 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1521 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1522 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1523 + _('Warning') + ': ' \
1524 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1525 + _('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'
1526 grid.addWidget(HelpButton(msg), 6, 2)
1527 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1528 if not self.config.is_modifiable('gap_limit'):
1529 for w in [gap_e, gap_label]: w.setEnabled(False)
1531 gui_label=QLabel(_('Default GUI') + ':')
1532 grid.addWidget(gui_label , 7, 0)
1533 gui_combo = QComboBox()
1534 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1535 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1536 if index==-1: index = 1
1537 gui_combo.setCurrentIndex(index)
1538 grid.addWidget(gui_combo, 7, 1)
1539 grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1540 if not self.config.is_modifiable('gui'):
1541 for w in [gui_combo, gui_label]: w.setEnabled(False)
1543 vbox.addLayout(ok_cancel_buttons(d))
1547 if not d.exec_(): return
1549 fee = unicode(fee_e.text())
1551 fee = int( 100000000 * Decimal(fee) )
1553 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1556 if self.wallet.fee != fee:
1557 self.wallet.fee = fee
1560 nz = unicode(nz_e.text())
1565 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1568 if self.wallet.num_zeros != nz:
1569 self.wallet.num_zeros = nz
1570 self.config.set_key('num_zeros', nz, True)
1571 self.update_history_tab()
1572 self.update_receive_tab()
1574 if self.wallet.use_change != usechange_cb.isChecked():
1575 self.wallet.use_change = usechange_cb.isChecked()
1576 self.config.set_key('use_change', self.wallet.use_change, True)
1579 n = int(gap_e.text())
1581 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1584 if self.wallet.gap_limit != n:
1585 r = self.wallet.change_gap_limit(n)
1587 self.update_receive_tab()
1588 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1590 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1592 self.config.set_key("gui", str(gui_combo.currentText()).lower(), True)
1597 def network_dialog(wallet, parent=None):
1598 interface = wallet.interface
1600 if interface.is_connected:
1601 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1603 status = _("Not connected")
1604 server = interface.server
1607 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1608 server = interface.server
1610 plist, servers_list = interface.get_servers_list()
1614 d.setWindowTitle(_('Server'))
1615 d.setMinimumSize(375, 20)
1617 vbox = QVBoxLayout()
1620 hbox = QHBoxLayout()
1622 l.setPixmap(QPixmap(":icons/network.png"))
1625 hbox.addWidget(QLabel(status))
1627 vbox.addLayout(hbox)
1631 grid = QGridLayout()
1633 vbox.addLayout(grid)
1636 server_protocol = QComboBox()
1637 server_host = QLineEdit()
1638 server_host.setFixedWidth(200)
1639 server_port = QLineEdit()
1640 server_port.setFixedWidth(60)
1642 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1643 protocol_letters = 'thsg'
1644 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1645 server_protocol.addItems(protocol_names)
1647 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1648 grid.addWidget(server_protocol, 0, 1)
1649 grid.addWidget(server_host, 0, 2)
1650 grid.addWidget(server_port, 0, 3)
1652 def change_protocol(p):
1653 protocol = protocol_letters[p]
1654 host = unicode(server_host.text())
1655 pp = plist.get(host,DEFAULT_PORTS)
1656 if protocol not in pp.keys():
1657 protocol = pp.keys()[0]
1659 server_host.setText( host )
1660 server_port.setText( port )
1662 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1664 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1665 servers_list_widget = QTreeWidget(parent)
1666 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1667 servers_list_widget.setMaximumHeight(150)
1668 servers_list_widget.setColumnWidth(0, 240)
1669 for _host in servers_list.keys():
1670 _type = 'pruning' if servers_list[_host].get('pruning') else 'full'
1671 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1673 def change_server(host, protocol=None):
1674 pp = plist.get(host,DEFAULT_PORTS)
1676 port = pp.get(protocol)
1677 if not port: protocol = None
1680 if 't' in pp.keys():
1682 port = pp.get(protocol)
1684 protocol = pp.keys()[0]
1685 port = pp.get(protocol)
1687 server_host.setText( host )
1688 server_port.setText( port )
1689 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1691 if not plist: return
1692 for p in protocol_letters:
1693 i = protocol_letters.index(p)
1694 j = server_protocol.model().index(i,0)
1695 if p not in pp.keys():
1696 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1698 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1702 host, port, protocol = server.split(':')
1703 change_server(host,protocol)
1705 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1706 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1708 if not wallet.config.is_modifiable('server'):
1709 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1712 proxy_mode = QComboBox()
1713 proxy_host = QLineEdit()
1714 proxy_host.setFixedWidth(200)
1715 proxy_port = QLineEdit()
1716 proxy_port.setFixedWidth(60)
1717 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1719 def check_for_disable(index = False):
1720 if proxy_mode.currentText() != 'NONE':
1721 proxy_host.setEnabled(True)
1722 proxy_port.setEnabled(True)
1724 proxy_host.setEnabled(False)
1725 proxy_port.setEnabled(False)
1728 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1730 if not wallet.config.is_modifiable('proxy'):
1731 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1733 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1734 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1735 proxy_host.setText(proxy_config.get("host"))
1736 proxy_port.setText(proxy_config.get("port"))
1738 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1739 grid.addWidget(proxy_mode, 2, 1)
1740 grid.addWidget(proxy_host, 2, 2)
1741 grid.addWidget(proxy_port, 2, 3)
1744 vbox.addLayout(ok_cancel_buttons(d))
1747 if not d.exec_(): return
1749 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1750 if proxy_mode.currentText() != 'NONE':
1751 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1755 wallet.config.set_key("proxy", proxy, True)
1756 wallet.config.set_key("server", server, True)
1757 interface.set_server(server, proxy)
1761 def closeEvent(self, event):
1763 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1769 def __init__(self, wallet, config, app=None):
1770 self.wallet = wallet
1771 self.config = config
1773 self.app = QApplication(sys.argv)
1776 def restore_or_create(self):
1777 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1778 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1779 if r==2: return None
1780 return 'restore' if r==1 else 'create'
1782 def seed_dialog(self):
1783 return ElectrumWindow.seed_dialog( self.wallet )
1785 def network_dialog(self):
1786 return ElectrumWindow.network_dialog( self.wallet, parent=None )
1789 def show_seed(self):
1790 ElectrumWindow.show_seed_dialog(self.wallet)
1793 def password_dialog(self):
1794 ElectrumWindow.change_password_dialog(self.wallet)
1797 def restore_wallet(self):
1798 wallet = self.wallet
1799 # wait until we are connected, because the user might have selected another server
1800 if not wallet.interface.is_connected:
1801 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1802 waiting_dialog(waiting)
1804 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1805 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1807 wallet.set_up_to_date(False)
1808 wallet.interface.poke('synchronizer')
1809 waiting_dialog(waiting)
1810 if wallet.is_found():
1811 print_error( "Recovery successful" )
1813 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1820 w = ElectrumWindow(self.wallet, self.config)
1821 if url: w.set_url(url)