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)
456 def address_label_clicked(self, item, column, l, column_addr, column_label):
457 if column == column_label and item.isSelected():
458 addr = unicode( item.text(column_addr) )
459 label = unicode( item.text(column_label) )
460 if label in self.wallet.aliases.keys():
462 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
463 l.editItem( item, column )
464 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
467 def address_label_changed(self, item, column, l, column_addr, column_label):
469 if column == column_label:
470 addr = unicode( item.text(column_addr) )
471 text = unicode( item.text(column_label) )
475 if text not in self.wallet.aliases.keys():
476 old_addr = self.wallet.labels.get(text)
478 self.wallet.labels[addr] = text
481 print_error("Error: This is one of your aliases")
482 label = self.wallet.labels.get(addr,'')
483 item.setText(column_label, QString(label))
485 s = self.wallet.labels.get(addr)
487 self.wallet.labels.pop(addr)
491 self.update_history_tab()
492 self.update_completions()
494 self.recv_changed(item)
497 def recv_changed(self, a):
498 "current item changed"
499 if a is not None and self.qr_window and self.qr_window.isVisible():
500 address = str(a.text(1))
501 label = self.wallet.labels.get(address)
502 amount = self.wallet.requested_amounts.get(address)
503 self.qr_window.set_content( address, label, amount )
506 def update_history_tab(self):
508 self.history_list.clear()
509 for item in self.wallet.get_tx_history():
510 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
513 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
519 icon = QIcon(":icons/unconfirmed.png")
521 icon = QIcon(":icons/clock%d.png"%conf)
523 icon = QIcon(":icons/confirmed.png")
526 icon = QIcon(":icons/unconfirmed.png")
528 if value is not None:
529 v_str = format_satoshis(value, True, self.wallet.num_zeros)
533 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
536 label, is_default_label = self.wallet.get_label(tx_hash)
538 label = _('Pruned transaction outputs')
539 is_default_label = False
541 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
542 item.setFont(2, QFont(MONOSPACE_FONT))
543 item.setFont(3, QFont(MONOSPACE_FONT))
544 item.setFont(4, QFont(MONOSPACE_FONT))
546 item.setToolTip(0, tx_hash)
548 item.setForeground(2, QBrush(QColor('grey')))
550 item.setIcon(0, icon)
551 self.history_list.insertTopLevelItem(0,item)
554 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
557 def create_send_tab(self):
562 grid.setColumnMinimumWidth(3,300)
563 grid.setColumnStretch(5,1)
565 self.payto_e = QLineEdit()
566 grid.addWidget(QLabel(_('Pay to')), 1, 0)
567 grid.addWidget(self.payto_e, 1, 1, 1, 3)
570 qrcode = qrscanner.scan_qr()
571 if 'address' in qrcode:
572 self.payto_e.setText(qrcode['address'])
573 if 'amount' in qrcode:
574 self.amount_e.setText(str(qrcode['amount']))
575 if 'label' in qrcode:
576 self.message_e.setText(qrcode['label'])
577 if 'message' in qrcode:
578 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
581 if qrscanner.is_available():
582 b = QPushButton(_("Scan QR code"))
583 b.clicked.connect(fill_from_qr)
584 grid.addWidget(b, 1, 5)
586 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)
588 completer = QCompleter()
589 completer.setCaseSensitivity(False)
590 self.payto_e.setCompleter(completer)
591 completer.setModel(self.completions)
593 self.message_e = QLineEdit()
594 grid.addWidget(QLabel(_('Description')), 2, 0)
595 grid.addWidget(self.message_e, 2, 1, 1, 3)
596 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)
598 self.amount_e = QLineEdit()
599 grid.addWidget(QLabel(_('Amount')), 3, 0)
600 grid.addWidget(self.amount_e, 3, 1, 1, 2)
601 grid.addWidget(HelpButton(
602 _('Amount to be sent.') + '\n\n' \
603 + _('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)
605 self.fee_e = QLineEdit()
606 grid.addWidget(QLabel(_('Fee')), 4, 0)
607 grid.addWidget(self.fee_e, 4, 1, 1, 2)
608 grid.addWidget(HelpButton(
609 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
610 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
611 + _('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)
613 b = EnterButton(_("Send"), self.do_send)
614 grid.addWidget(b, 6, 1)
616 b = EnterButton(_("Clear"),self.do_clear)
617 grid.addWidget(b, 6, 2)
619 self.payto_sig = QLabel('')
620 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
622 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
623 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
632 def entry_changed( is_fee ):
633 self.funds_error = False
634 amount = numbify(self.amount_e)
635 fee = numbify(self.fee_e)
636 if not is_fee: fee = None
639 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
641 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
644 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
647 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
648 self.funds_error = True
649 self.amount_e.setPalette(palette)
650 self.fee_e.setPalette(palette)
652 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
653 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
658 def update_completions(self):
660 for addr,label in self.wallet.labels.items():
661 if addr in self.wallet.addressbook:
662 l.append( label + ' <' + addr + '>')
663 l = l + self.wallet.aliases.keys()
665 self.completions.setStringList(l)
671 label = unicode( self.message_e.text() )
672 r = unicode( self.payto_e.text() )
676 m1 = re.match(ALIAS_REGEXP, r)
677 # label or alias, with address in brackets
678 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
681 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
685 to_address = m2.group(2)
689 if not self.wallet.is_valid(to_address):
690 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
694 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
696 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
699 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
701 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
704 if self.wallet.use_encryption:
705 password = self.password_dialog()
712 tx = self.wallet.mktx( to_address, amount, label, password, fee)
713 except BaseException, e:
714 self.show_message(str(e))
717 h = self.wallet.send_tx(tx)
718 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
719 status, msg = self.wallet.receive_tx( h )
722 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
724 self.update_contacts_tab()
726 QMessageBox.warning(self, _('Error'), msg, _('OK'))
729 def set_url(self, url):
730 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
731 self.tabs.setCurrentIndex(1)
732 label = self.wallet.labels.get(payto)
733 m_addr = label + ' <'+ payto+'>' if label else payto
734 self.payto_e.setText(m_addr)
736 self.message_e.setText(message)
737 self.amount_e.setText(amount)
739 self.set_frozen(self.payto_e,True)
740 self.set_frozen(self.amount_e,True)
741 self.set_frozen(self.message_e,True)
742 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
744 self.payto_sig.setVisible(False)
747 self.payto_sig.setVisible(False)
748 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
750 self.set_frozen(e,False)
752 def set_frozen(self,entry,frozen):
754 entry.setReadOnly(True)
755 entry.setFrame(False)
757 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
758 entry.setPalette(palette)
760 entry.setReadOnly(False)
763 palette.setColor(entry.backgroundRole(), QColor('white'))
764 entry.setPalette(palette)
767 def toggle_freeze(self,addr):
769 if addr in self.wallet.frozen_addresses:
770 self.wallet.unfreeze(addr)
772 self.wallet.freeze(addr)
773 self.update_receive_tab()
775 def toggle_priority(self,addr):
777 if addr in self.wallet.prioritized_addresses:
778 self.wallet.unprioritize(addr)
780 self.wallet.prioritize(addr)
781 self.update_receive_tab()
784 def create_list_tab(self, headers):
785 "generic tab creation method"
786 l = MyTreeWidget(self)
787 l.setColumnCount( len(headers) )
788 l.setHeaderLabels( headers )
798 vbox.addWidget(buttons)
803 buttons.setLayout(hbox)
808 def create_receive_tab(self):
809 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
810 l.setContextMenuPolicy(Qt.CustomContextMenu)
811 l.customContextMenuRequested.connect(self.create_receive_menu)
812 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
813 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
814 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
815 self.receive_list = l
816 self.receive_buttons_hbox = hbox
817 self.qr_button = EnterButton(self.qr_button_text(), self.toggle_QR_window)
818 hbox.addWidget(self.qr_button)
819 self.print_button = EnterButton(_("Print QR"), self.print_qr)
820 self.print_button.setHidden(True)
821 hbox.addWidget(self.print_button)
823 self.details_button = EnterButton(self.details_button_text(), self.toggle_detailed_view)
824 hbox.addWidget(self.details_button)
830 self.qr_window.do_save()
831 self.show_message(_("QR code saved to file") + " " + self.qr_window.filename)
834 def request_amount_dialog(self, address):
835 # pick the first unused address
836 # print "please wait" in qr window
837 # enter amount in usd
840 d.setWindowTitle('Request payment')
844 vbox.addWidget(QLabel(address))
851 index = self.wallet.addresses.index(address)
855 label = self.wallet.labels.get(address,'invoice %04d'%(index+1))
856 grid.addWidget(QLabel(_('Label')), 0, 0)
857 label_e = QLineEdit()
858 label_e.setText(label)
859 grid.addWidget(label_e, 0, 1)
861 amount_e = QLineEdit()
862 amount_e.textChanged.connect(lambda: numbify(amount_e))
864 grid.addWidget(QLabel(_('Amount')), 1, 0)
865 grid.addWidget(amount_e, 1, 1, 1, 3)
867 vbox.addLayout(ok_cancel_buttons(d))
871 if not d.exec_(): return
873 amount = int( Decimal( unicode( amount_e.text())) * 100000000 )
876 self.wallet.requested_amounts[address] = amount
878 label = unicode(label_e.text())
880 self.wallet.labels[address] = label
882 self.update_receive_item(self.receive_list.currentItem())
884 self.qr_window.set_content( address, label, amount )
887 def details_button_text(self):
888 return _('Hide details') if self.detailed_view else _('Show details')
890 def qr_button_text(self):
891 return _('Hide QR') if self.qr_window and self.qr_window.isVisible() else _('Show QR')
894 def toggle_detailed_view(self):
895 self.detailed_view = not self.detailed_view
896 self.config.set_key('qt_detailed_view', self.detailed_view, True)
898 self.details_button.setText(self.details_button_text())
900 self.update_receive_tab()
901 self.update_contacts_tab()
904 def create_contacts_tab(self):
905 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
906 l.setContextMenuPolicy(Qt.CustomContextMenu)
907 l.customContextMenuRequested.connect(self.create_contact_menu)
908 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
909 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
910 self.contacts_list = l
911 self.contacts_buttons_hbox = hbox
912 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
917 def create_receive_menu(self, position):
918 # fixme: this function apparently has a side effect.
919 # if it is not called the menu pops up several times
920 #self.receive_list.selectedIndexes()
922 item = self.receive_list.itemAt(position)
924 addr = unicode(item.text(1))
926 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
927 if self.qr_window and self.qr_window.isVisible():
928 menu.addAction(_("Request amount"), lambda: self.request_amount_dialog(addr))
929 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
930 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
932 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
933 menu.addAction(t, lambda: self.toggle_freeze(addr))
934 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
935 menu.addAction(t, lambda: self.toggle_priority(addr))
936 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
939 def payto(self, x, is_alias):
946 label = self.wallet.labels.get(addr)
947 m_addr = label + ' <' + addr + '>' if label else addr
948 self.tabs.setCurrentIndex(1)
949 self.payto_e.setText(m_addr)
950 self.amount_e.setFocus()
952 def delete_contact(self, x, is_alias):
953 if self.question("Do you want to remove %s from your list of contacts?"%x):
954 if not is_alias and x in self.wallet.addressbook:
955 self.wallet.addressbook.remove(x)
956 if x in self.wallet.labels.keys():
957 self.wallet.labels.pop(x)
958 elif is_alias and x in self.wallet.aliases:
959 self.wallet.aliases.pop(x)
960 self.update_history_tab()
961 self.update_contacts_tab()
962 self.update_completions()
964 def create_contact_menu(self, position):
965 # fixme: this function apparently has a side effect.
966 # if it is not called the menu pops up several times
967 #self.contacts_list.selectedIndexes()
969 item = self.contacts_list.itemAt(position)
971 addr = unicode(item.text(0))
972 label = unicode(item.text(1))
973 is_alias = label in self.wallet.aliases.keys()
974 x = label if is_alias else addr
976 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
977 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
978 menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
980 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
982 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
983 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
984 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
987 def update_receive_item(self, item):
988 address = str( item.data(1,0).toString() )
990 flags = self.wallet.get_address_flags(address)
991 item.setData(0,0,flags)
993 label = self.wallet.labels.get(address,'')
994 item.setData(2,0,label)
996 amount = self.wallet.requested_amounts.get(address,None)
997 amount_str = format_satoshis( amount, False, self.wallet.num_zeros ) if amount is not None else "--"
998 item.setData(3,0,amount_str)
1000 c, u = self.wallet.get_addr_balance(address)
1001 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1002 item.setData(4,0,balance)
1004 if address in self.wallet.frozen_addresses:
1005 item.setBackgroundColor(1, QColor('lightblue'))
1006 elif address in self.wallet.prioritized_addresses:
1007 item.setBackgroundColor(1, QColor('lightgreen'))
1010 def update_receive_tab(self):
1011 l = self.receive_list
1014 l.setColumnHidden(0, not self.detailed_view)
1015 l.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1016 l.setColumnHidden(4, not self.detailed_view)
1017 l.setColumnHidden(5, not self.detailed_view)
1018 l.setColumnWidth(0, 50)
1019 l.setColumnWidth(1, 310)
1020 l.setColumnWidth(2, 200)
1021 l.setColumnWidth(3, 130)
1022 l.setColumnWidth(4, 130)
1023 l.setColumnWidth(5, 10)
1027 for address in self.wallet.all_addresses():
1029 if self.wallet.is_change(address) and not self.detailed_view:
1033 h = self.wallet.history.get(address,[])
1036 for tx_hash, tx_height in h:
1037 tx = self.wallet.transactions.get(tx_hash)
1045 if address in self.wallet.addresses:
1047 if gap > self.wallet.gap_limit:
1050 if address in self.wallet.addresses:
1053 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1054 item.setFont(0, QFont(MONOSPACE_FONT))
1055 item.setFont(1, QFont(MONOSPACE_FONT))
1056 item.setFont(3, QFont(MONOSPACE_FONT))
1057 self.update_receive_item(item)
1058 if is_red and address in self.wallet.addresses:
1059 item.setBackgroundColor(1, QColor('red'))
1060 l.addTopLevelItem(item)
1062 # we use column 1 because column 0 may be hidden
1063 l.setCurrentItem(l.topLevelItem(0),1)
1065 def show_contact_details(self, m):
1066 a = self.wallet.aliases.get(m)
1068 if a[0] in self.wallet.authorities.keys():
1069 s = self.wallet.authorities.get(a[0])
1072 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1073 QMessageBox.information(self, 'Alias', msg, 'OK')
1075 def update_contacts_tab(self):
1077 l = self.contacts_list
1079 l.setColumnHidden(2, not self.detailed_view)
1080 l.setColumnWidth(0, 350)
1081 l.setColumnWidth(1, 330)
1082 l.setColumnWidth(2, 100)
1085 for alias, v in self.wallet.aliases.items():
1087 alias_targets.append(target)
1088 item = QTreeWidgetItem( [ target, alias, '-'] )
1089 item.setBackgroundColor(0, QColor('lightgray'))
1090 l.addTopLevelItem(item)
1092 for address in self.wallet.addressbook:
1093 if address in alias_targets: continue
1094 label = self.wallet.labels.get(address,'')
1096 for item in self.wallet.transactions.values():
1097 if address in item['outputs'] : n=n+1
1099 item = QTreeWidgetItem( [ address, label, tx] )
1100 item.setFont(0, QFont(MONOSPACE_FONT))
1101 l.addTopLevelItem(item)
1103 l.setCurrentItem(l.topLevelItem(0))
1105 def create_wall_tab(self):
1106 self.textbox = textbox = QTextEdit(self)
1107 textbox.setFont(QFont(MONOSPACE_FONT))
1108 textbox.setReadOnly(True)
1111 def create_status_bar(self):
1113 sb.setFixedHeight(35)
1114 if self.wallet.seed:
1115 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1116 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1117 if self.wallet.seed:
1118 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1119 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
1120 sb.addPermanentWidget( self.status_button )
1121 self.setStatusBar(sb)
1123 def new_contact_dialog(self):
1124 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1125 address = unicode(text)
1127 if self.wallet.is_valid(address):
1128 self.wallet.addressbook.append(address)
1130 self.update_contacts_tab()
1131 self.update_history_tab()
1132 self.update_completions()
1134 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1137 def show_seed_dialog(wallet, parent=None):
1139 QMessageBox.information(parent, _('Message'),
1140 _('No seed'), _('OK'))
1143 if wallet.use_encryption:
1144 password = parent.password_dialog()
1151 seed = wallet.pw_decode(wallet.seed, password)
1153 QMessageBox.warning(parent, _('Error'),
1154 _('Incorrect Password'), _('OK'))
1157 dialog = QDialog(None)
1159 dialog.setWindowTitle("Electrum")
1161 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1163 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1164 + _("Please write down or memorize these 12 words (order is important).") + " " \
1165 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1166 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1168 main_text = QLabel(msg)
1169 main_text.setWordWrap(True)
1172 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1179 copy_function = lambda: app.clipboard().setText(brainwallet)
1180 copy_button = QPushButton(_("Copy to Clipboard"))
1181 copy_button.clicked.connect(copy_function)
1183 show_qr_function = lambda: ElectrumWindow.show_seed_qrcode(seed)
1184 qr_button = QPushButton(_("View as QR Code"))
1185 qr_button.clicked.connect(show_qr_function)
1187 ok_button = QPushButton(_("OK"))
1188 ok_button.setDefault(True)
1189 ok_button.clicked.connect(dialog.accept)
1191 main_layout = QGridLayout()
1192 main_layout.addWidget(logo, 0, 0)
1193 main_layout.addWidget(main_text, 0, 1, 1, -1)
1194 main_layout.addWidget(copy_button, 1, 1)
1195 main_layout.addWidget(qr_button, 1, 2)
1196 main_layout.addWidget(ok_button, 1, 3)
1197 dialog.setLayout(main_layout)
1202 def show_seed_qrcode(seed):
1206 d.setWindowTitle(_("Seed"))
1207 d.setMinimumSize(270, 300)
1208 vbox = QVBoxLayout()
1209 vbox.addWidget(QRCodeWidget(seed))
1210 hbox = QHBoxLayout()
1212 b = QPushButton(_("OK"))
1214 b.clicked.connect(d.accept)
1216 vbox.addLayout(hbox)
1220 def sign_message(self,address):
1221 if not address: return
1224 d.setWindowTitle('Sign Message')
1225 d.setMinimumSize(270, 350)
1227 tab_widget = QTabWidget()
1229 layout = QGridLayout(tab)
1231 sign_address = QLineEdit()
1232 sign_address.setText(address)
1233 layout.addWidget(QLabel(_('Address')), 1, 0)
1234 layout.addWidget(sign_address, 1, 1)
1236 sign_message = QTextEdit()
1237 layout.addWidget(QLabel(_('Message')), 2, 0)
1238 layout.addWidget(sign_message, 2, 1, 2, 1)
1240 sign_signature = QLineEdit()
1241 layout.addWidget(QLabel(_('Signature')), 3, 0)
1242 layout.addWidget(sign_signature, 3, 1)
1245 if self.wallet.use_encryption:
1246 password = self.password_dialog()
1253 signature = self.wallet.sign_message(sign_address.text(), sign_message.toPlainText(), password)
1254 sign_signature.setText(signature)
1255 except BaseException, e:
1256 self.show_message(str(e))
1259 hbox = QHBoxLayout()
1260 b = QPushButton(_("Sign"))
1262 b.clicked.connect(do_sign)
1263 b = QPushButton(_("Close"))
1264 b.clicked.connect(d.accept)
1266 layout.addLayout(hbox, 4, 1)
1267 tab_widget.addTab(tab, "Sign")
1271 layout = QGridLayout(tab)
1273 verify_address = QLineEdit()
1274 layout.addWidget(QLabel(_('Address')), 1, 0)
1275 layout.addWidget(verify_address, 1, 1)
1277 verify_message = QTextEdit()
1278 layout.addWidget(QLabel(_('Message')), 2, 0)
1279 layout.addWidget(verify_message, 2, 1, 2, 1)
1281 verify_signature = QLineEdit()
1282 layout.addWidget(QLabel(_('Signature')), 3, 0)
1283 layout.addWidget(verify_signature, 3, 1)
1287 self.wallet.verify_message(verify_address.text(), verify_signature.text(), verify_message.toPlainText())
1288 self.show_message("Signature verified")
1289 except BaseException, e:
1290 self.show_message(str(e))
1293 hbox = QHBoxLayout()
1294 b = QPushButton(_("Verify"))
1295 b.clicked.connect(do_verify)
1297 b = QPushButton(_("Close"))
1298 b.clicked.connect(d.accept)
1300 layout.addLayout(hbox, 4, 1)
1301 tab_widget.addTab(tab, "Verify")
1303 vbox = QVBoxLayout()
1304 vbox.addWidget(tab_widget)
1309 def toggle_QR_window(self):
1310 if not self.qr_window:
1311 self.qr_window = QR_Window()
1312 self.qr_window.setVisible(True)
1313 #print self.qr_window.isVisible()
1314 self.qr_window_geometry = self.qr_window.geometry()
1315 item = self.receive_list.currentItem()
1317 address = str(item.text(1))
1318 label = self.wallet.labels.get(address)
1319 amount = self.wallet.requested_amounts.get(address)
1320 self.qr_window.set_content( address, label, amount )
1321 self.update_receive_tab()
1323 if self.qr_window.isVisible():
1324 self.qr_window_geometry = self.qr_window.geometry()
1325 self.qr_window.setVisible(False)
1327 self.qr_window.setVisible(True)
1328 self.qr_window.setGeometry(self.qr_window_geometry)
1330 self.qr_button.setText(self.qr_button_text())
1331 self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1332 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1333 self.receive_list.setColumnWidth(2, 200)
1336 def question(self, msg):
1337 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1339 def show_message(self, msg):
1340 QMessageBox.information(self, _('Message'), msg, _('OK'))
1342 def password_dialog(self ):
1349 vbox = QVBoxLayout()
1350 msg = _('Please enter your password')
1351 vbox.addWidget(QLabel(msg))
1353 grid = QGridLayout()
1355 grid.addWidget(QLabel(_('Password')), 1, 0)
1356 grid.addWidget(pw, 1, 1)
1357 vbox.addLayout(grid)
1359 vbox.addLayout(ok_cancel_buttons(d))
1362 if not d.exec_(): return
1363 return unicode(pw.text())
1370 def change_password_dialog( wallet, parent=None ):
1373 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1381 new_pw = QLineEdit()
1382 new_pw.setEchoMode(2)
1383 conf_pw = QLineEdit()
1384 conf_pw.setEchoMode(2)
1386 vbox = QVBoxLayout()
1388 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1389 +_('To disable wallet encryption, enter an empty new password.')) \
1390 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1392 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1393 +_("Leave these fields empty if you want to disable encryption.")
1394 vbox.addWidget(QLabel(msg))
1396 grid = QGridLayout()
1399 if wallet.use_encryption:
1400 grid.addWidget(QLabel(_('Password')), 1, 0)
1401 grid.addWidget(pw, 1, 1)
1403 grid.addWidget(QLabel(_('New Password')), 2, 0)
1404 grid.addWidget(new_pw, 2, 1)
1406 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1407 grid.addWidget(conf_pw, 3, 1)
1408 vbox.addLayout(grid)
1410 vbox.addLayout(ok_cancel_buttons(d))
1413 if not d.exec_(): return
1415 password = unicode(pw.text()) if wallet.use_encryption else None
1416 new_password = unicode(new_pw.text())
1417 new_password2 = unicode(conf_pw.text())
1420 seed = wallet.pw_decode( wallet.seed, password)
1422 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1425 if new_password != new_password2:
1426 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1429 wallet.update_password(seed, password, new_password)
1432 def seed_dialog(wallet, parent=None):
1436 vbox = QVBoxLayout()
1437 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1438 vbox.addWidget(QLabel(msg))
1440 grid = QGridLayout()
1443 seed_e = QLineEdit()
1444 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1445 grid.addWidget(seed_e, 1, 1)
1449 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1450 grid.addWidget(gap_e, 2, 1)
1451 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1452 vbox.addLayout(grid)
1454 vbox.addLayout(ok_cancel_buttons(d))
1457 if not d.exec_(): return
1460 gap = int(unicode(gap_e.text()))
1462 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1466 seed = unicode(seed_e.text())
1469 print_error("Warning: Not hex, trying decode")
1471 seed = mnemonic.mn_decode( seed.split(' ') )
1473 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1476 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1479 wallet.seed = str(seed)
1480 #print repr(wallet.seed)
1481 wallet.gap_limit = gap
1486 def settings_dialog(self):
1489 vbox = QVBoxLayout()
1490 msg = _('Here are the settings of your wallet.') + '\n'\
1491 + _('For more explanations, click on the help buttons next to each field.')
1494 label.setFixedWidth(250)
1495 label.setWordWrap(True)
1496 label.setAlignment(Qt.AlignJustify)
1497 vbox.addWidget(label)
1499 grid = QGridLayout()
1501 vbox.addLayout(grid)
1503 fee_label = QLabel(_('Transaction fee'))
1504 grid.addWidget(fee_label, 2, 0)
1506 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1507 grid.addWidget(fee_e, 2, 1)
1508 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1509 + _('Recommended value') + ': 0.001'
1510 grid.addWidget(HelpButton(msg), 2, 2)
1511 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1512 if not self.config.is_modifiable('fee'):
1513 for w in [fee_e, fee_label]: w.setEnabled(False)
1515 nz_label = QLabel(_('Display zeros'))
1516 grid.addWidget(nz_label, 3, 0)
1518 nz_e.setText("%d"% self.wallet.num_zeros)
1519 grid.addWidget(nz_e, 3, 1)
1520 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1521 grid.addWidget(HelpButton(msg), 3, 2)
1522 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1523 if not self.config.is_modifiable('num_zeros'):
1524 for w in [nz_e, nz_label]: w.setEnabled(False)
1526 usechange_cb = QCheckBox(_('Use change addresses'))
1527 grid.addWidget(usechange_cb, 5, 0)
1528 usechange_cb.setChecked(self.wallet.use_change)
1529 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1530 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1532 gap_label = QLabel(_('Gap limit'))
1533 grid.addWidget(gap_label, 6, 0)
1535 gap_e.setText("%d"% self.wallet.gap_limit)
1536 grid.addWidget(gap_e, 6, 1)
1537 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1538 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1539 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1540 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1541 + _('Warning') + ': ' \
1542 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1543 + _('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'
1544 grid.addWidget(HelpButton(msg), 6, 2)
1545 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1546 if not self.config.is_modifiable('gap_limit'):
1547 for w in [gap_e, gap_label]: w.setEnabled(False)
1549 gui_label=QLabel(_('Default GUI') + ':')
1550 grid.addWidget(gui_label , 7, 0)
1551 gui_combo = QComboBox()
1552 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1553 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1554 if index==-1: index = 1
1555 gui_combo.setCurrentIndex(index)
1556 grid.addWidget(gui_combo, 7, 1)
1557 grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1558 if not self.config.is_modifiable('gui'):
1559 for w in [gui_combo, gui_label]: w.setEnabled(False)
1561 vbox.addLayout(ok_cancel_buttons(d))
1565 if not d.exec_(): return
1567 fee = unicode(fee_e.text())
1569 fee = int( 100000000 * Decimal(fee) )
1571 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1574 if self.wallet.fee != fee:
1575 self.wallet.fee = fee
1578 nz = unicode(nz_e.text())
1583 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1586 if self.wallet.num_zeros != nz:
1587 self.wallet.num_zeros = nz
1588 self.config.set_key('num_zeros', nz, True)
1589 self.update_history_tab()
1590 self.update_receive_tab()
1592 if self.wallet.use_change != usechange_cb.isChecked():
1593 self.wallet.use_change = usechange_cb.isChecked()
1594 self.config.set_key('use_change', self.wallet.use_change, True)
1597 n = int(gap_e.text())
1599 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1602 if self.wallet.gap_limit != n:
1603 r = self.wallet.change_gap_limit(n)
1605 self.update_receive_tab()
1606 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1608 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1610 self.config.set_key("gui", str(gui_combo.currentText()).lower(), True)
1615 def network_dialog(wallet, parent=None):
1616 interface = wallet.interface
1618 if interface.is_connected:
1619 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1621 status = _("Not connected")
1622 server = interface.server
1625 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1626 server = interface.server
1628 plist, servers_list = interface.get_servers_list()
1632 d.setWindowTitle(_('Server'))
1633 d.setMinimumSize(375, 20)
1635 vbox = QVBoxLayout()
1638 hbox = QHBoxLayout()
1640 l.setPixmap(QPixmap(":icons/network.png"))
1643 hbox.addWidget(QLabel(status))
1645 vbox.addLayout(hbox)
1649 grid = QGridLayout()
1651 vbox.addLayout(grid)
1654 server_protocol = QComboBox()
1655 server_host = QLineEdit()
1656 server_host.setFixedWidth(200)
1657 server_port = QLineEdit()
1658 server_port.setFixedWidth(60)
1660 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1661 protocol_letters = 'thsg'
1662 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1663 server_protocol.addItems(protocol_names)
1665 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1666 grid.addWidget(server_protocol, 0, 1)
1667 grid.addWidget(server_host, 0, 2)
1668 grid.addWidget(server_port, 0, 3)
1670 def change_protocol(p):
1671 protocol = protocol_letters[p]
1672 host = unicode(server_host.text())
1673 pp = plist.get(host,DEFAULT_PORTS)
1674 if protocol not in pp.keys():
1675 protocol = pp.keys()[0]
1677 server_host.setText( host )
1678 server_port.setText( port )
1680 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1682 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1683 servers_list_widget = QTreeWidget(parent)
1684 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1685 servers_list_widget.setMaximumHeight(150)
1686 servers_list_widget.setColumnWidth(0, 240)
1687 for _host in servers_list.keys():
1688 _type = 'pruning' if servers_list[_host].get('pruning') else 'full'
1689 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1691 def change_server(host, protocol=None):
1692 pp = plist.get(host,DEFAULT_PORTS)
1694 port = pp.get(protocol)
1695 if not port: protocol = None
1698 if 't' in pp.keys():
1700 port = pp.get(protocol)
1702 protocol = pp.keys()[0]
1703 port = pp.get(protocol)
1705 server_host.setText( host )
1706 server_port.setText( port )
1707 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1709 if not plist: return
1710 for p in protocol_letters:
1711 i = protocol_letters.index(p)
1712 j = server_protocol.model().index(i,0)
1713 if p not in pp.keys():
1714 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1716 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1720 host, port, protocol = server.split(':')
1721 change_server(host,protocol)
1723 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1724 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1726 if not wallet.config.is_modifiable('server'):
1727 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1730 proxy_mode = QComboBox()
1731 proxy_host = QLineEdit()
1732 proxy_host.setFixedWidth(200)
1733 proxy_port = QLineEdit()
1734 proxy_port.setFixedWidth(60)
1735 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1737 def check_for_disable(index = False):
1738 if proxy_mode.currentText() != 'NONE':
1739 proxy_host.setEnabled(True)
1740 proxy_port.setEnabled(True)
1742 proxy_host.setEnabled(False)
1743 proxy_port.setEnabled(False)
1746 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1748 if not wallet.config.is_modifiable('proxy'):
1749 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1751 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1752 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1753 proxy_host.setText(proxy_config.get("host"))
1754 proxy_port.setText(proxy_config.get("port"))
1756 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1757 grid.addWidget(proxy_mode, 2, 1)
1758 grid.addWidget(proxy_host, 2, 2)
1759 grid.addWidget(proxy_port, 2, 3)
1762 vbox.addLayout(ok_cancel_buttons(d))
1765 if not d.exec_(): return
1767 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1768 if proxy_mode.currentText() != 'NONE':
1769 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1773 wallet.config.set_key("proxy", proxy, True)
1774 wallet.config.set_key("server", server, True)
1775 interface.set_server(server, proxy)
1779 def closeEvent(self, event):
1781 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1787 def __init__(self, wallet, config, app=None):
1788 self.wallet = wallet
1789 self.config = config
1791 self.app = QApplication(sys.argv)
1794 def restore_or_create(self):
1795 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1796 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1797 if r==2: return None
1798 return 'restore' if r==1 else 'create'
1800 def seed_dialog(self):
1801 return ElectrumWindow.seed_dialog( self.wallet )
1803 def network_dialog(self):
1804 return ElectrumWindow.network_dialog( self.wallet, parent=None )
1807 def show_seed(self):
1808 ElectrumWindow.show_seed_dialog(self.wallet)
1811 def password_dialog(self):
1812 ElectrumWindow.change_password_dialog(self.wallet)
1815 def restore_wallet(self):
1816 wallet = self.wallet
1817 # wait until we are connected, because the user might have selected another server
1818 if not wallet.interface.is_connected:
1819 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1820 waiting_dialog(waiting)
1822 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1823 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1825 wallet.set_up_to_date(False)
1826 wallet.interface.poke('synchronizer')
1827 waiting_dialog(waiting)
1828 if wallet.is_found():
1829 print_error( "Recovery successful" )
1831 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1838 w = ElectrumWindow(self.wallet, self.config)
1839 if url: w.set_url(url)