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: 18pt'>%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.receive_tab_mode = config.get('qt_receive_tab_mode', 0)
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()
301 self.toggle_QR_window(self.receive_tab_mode == 2)
303 g = self.config.get("winpos-qt",[100, 100, 840, 400])
304 self.setGeometry(g[0], g[1], g[2], g[3])
305 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
306 if not self.wallet.seed: title += ' [seedless]'
307 self.setWindowTitle( title )
309 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
310 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
311 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
312 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
314 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
315 #self.connect(self, SIGNAL('editamount'), self.edit_amount)
316 self.history_list.setFocus(True)
318 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
319 if platform.system() == 'Windows':
320 n = 3 if self.wallet.seed else 2
321 tabs.setCurrentIndex (n)
322 tabs.setCurrentIndex (0)
325 QMainWindow.close(self)
327 self.qr_window.close()
328 self.qr_window = None
330 def connect_slots(self, sender):
332 self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient)
333 self.previous_payto_e=''
335 def check_recipient(self):
336 if self.payto_e.hasFocus():
338 r = unicode( self.payto_e.text() )
339 if r != self.previous_payto_e:
340 self.previous_payto_e = r
342 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
344 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
348 s = r + ' <' + to_address + '>'
349 self.payto_e.setText(s)
352 def update_callback(self):
353 self.emit(QtCore.SIGNAL('updatesignal'))
355 def update_wallet(self):
356 if self.wallet.interface and self.wallet.interface.is_connected:
357 if not self.wallet.up_to_date:
358 text = _( "Synchronizing..." )
359 icon = QIcon(":icons/status_waiting.png")
361 c, u = self.wallet.get_balance()
362 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
363 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
364 icon = QIcon(":icons/status_connected.png")
366 text = _( "Not connected" )
367 icon = QIcon(":icons/status_disconnected.png")
370 text = _( "Not enough funds" )
372 self.statusBar().showMessage(text)
373 self.status_button.setIcon( icon )
375 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
376 self.textbox.setText( self.wallet.banner )
377 self.update_history_tab()
378 self.update_receive_tab()
379 self.update_contacts_tab()
380 self.update_completions()
383 def create_history_tab(self):
384 self.history_list = l = MyTreeWidget(self)
386 l.setColumnWidth(0, 40)
387 l.setColumnWidth(1, 140)
388 l.setColumnWidth(2, 350)
389 l.setColumnWidth(3, 140)
390 l.setColumnWidth(4, 140)
391 l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
392 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
393 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
395 l.setContextMenuPolicy(Qt.CustomContextMenu)
396 l.customContextMenuRequested.connect(self.create_history_menu)
400 def create_history_menu(self, position):
401 self.history_list.selectedIndexes()
402 item = self.history_list.currentItem()
404 tx_hash = str(item.toolTip(0))
405 if not tx_hash: return
407 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
408 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
409 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
410 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
413 def tx_details(self, tx_hash):
414 tx_details = self.wallet.get_tx_details(tx_hash)
415 QMessageBox.information(self, 'Details', tx_details, 'OK')
418 def tx_label_clicked(self, item, column):
419 if column==2 and item.isSelected():
420 tx_hash = str(item.toolTip(0))
422 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
423 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
424 self.history_list.editItem( item, column )
425 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
428 def tx_label_changed(self, item, column):
432 tx_hash = str(item.toolTip(0))
433 tx = self.wallet.transactions.get(tx_hash)
434 s = self.wallet.labels.get(tx_hash)
435 text = unicode( item.text(2) )
437 self.wallet.labels[tx_hash] = text
438 item.setForeground(2, QBrush(QColor('black')))
440 if s: self.wallet.labels.pop(tx_hash)
441 text = self.wallet.get_default_label(tx_hash)
442 item.setText(2, text)
443 item.setForeground(2, QBrush(QColor('gray')))
447 def edit_label(self, is_recv):
448 l = self.receive_list if is_recv else self.contacts_list
449 c = 2 if is_recv else 1
450 item = l.currentItem()
451 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
452 l.editItem( item, c )
453 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
455 def edit_amount(self):
456 l = self.receive_list
457 item = l.currentItem()
458 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
459 l.editItem( item, 3 )
460 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
463 def address_label_clicked(self, item, column, l, column_addr, column_label):
464 if column == column_label and item.isSelected():
465 addr = unicode( item.text(column_addr) )
466 label = unicode( item.text(column_label) )
467 if label in self.wallet.aliases.keys():
469 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
470 l.editItem( item, column )
471 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
474 def address_label_changed(self, item, column, l, column_addr, column_label):
476 if column == column_label:
477 addr = unicode( item.text(column_addr) )
478 text = unicode( item.text(column_label) )
482 if text not in self.wallet.aliases.keys():
483 old_addr = self.wallet.labels.get(text)
485 self.wallet.labels[addr] = text
488 print_error("Error: This is one of your aliases")
489 label = self.wallet.labels.get(addr,'')
490 item.setText(column_label, QString(label))
492 s = self.wallet.labels.get(addr)
494 self.wallet.labels.pop(addr)
498 self.update_history_tab()
499 self.update_completions()
501 self.recv_changed(item)
504 address = unicode( item.text(column_addr) )
505 text = unicode( item.text(3) )
507 index = self.wallet.addresses.index(address)
512 amount = int( Decimal( unicode(text)) * 100000000 )
513 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
515 amount = self.wallet.requested_amounts.get(address)
517 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
522 self.wallet.requested_amounts[address] = amount
524 label = self.wallet.labels.get(address)
526 label = 'invoice %04d'%(index+1)
528 self.update_receive_item(self.receive_list.currentItem())
530 self.qr_window.set_content( address, label, amount )
533 def recv_changed(self, a):
534 "current item changed"
535 if a is not None and self.qr_window and self.qr_window.isVisible():
536 address = str(a.text(1))
537 label = self.wallet.labels.get(address)
538 amount = self.wallet.requested_amounts.get(address)
539 self.qr_window.set_content( address, label, amount )
542 def update_history_tab(self):
544 self.history_list.clear()
545 for item in self.wallet.get_tx_history():
546 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
549 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
555 icon = QIcon(":icons/unconfirmed.png")
557 icon = QIcon(":icons/clock%d.png"%conf)
559 icon = QIcon(":icons/confirmed.png")
562 icon = QIcon(":icons/unconfirmed.png")
564 if value is not None:
565 v_str = format_satoshis(value, True, self.wallet.num_zeros)
569 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
572 label, is_default_label = self.wallet.get_label(tx_hash)
574 label = _('Pruned transaction outputs')
575 is_default_label = False
577 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
578 item.setFont(2, QFont(MONOSPACE_FONT))
579 item.setFont(3, QFont(MONOSPACE_FONT))
580 item.setFont(4, QFont(MONOSPACE_FONT))
582 item.setToolTip(0, tx_hash)
584 item.setForeground(2, QBrush(QColor('grey')))
586 item.setIcon(0, icon)
587 self.history_list.insertTopLevelItem(0,item)
590 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
593 def create_send_tab(self):
598 grid.setColumnMinimumWidth(3,300)
599 grid.setColumnStretch(5,1)
601 self.payto_e = QLineEdit()
602 grid.addWidget(QLabel(_('Pay to')), 1, 0)
603 grid.addWidget(self.payto_e, 1, 1, 1, 3)
606 qrcode = qrscanner.scan_qr()
607 if 'address' in qrcode:
608 self.payto_e.setText(qrcode['address'])
609 if 'amount' in qrcode:
610 self.amount_e.setText(str(qrcode['amount']))
611 if 'label' in qrcode:
612 self.message_e.setText(qrcode['label'])
613 if 'message' in qrcode:
614 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
617 if qrscanner.is_available():
618 b = QPushButton(_("Scan QR code"))
619 b.clicked.connect(fill_from_qr)
620 grid.addWidget(b, 1, 5)
622 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)
624 completer = QCompleter()
625 completer.setCaseSensitivity(False)
626 self.payto_e.setCompleter(completer)
627 completer.setModel(self.completions)
629 self.message_e = QLineEdit()
630 grid.addWidget(QLabel(_('Description')), 2, 0)
631 grid.addWidget(self.message_e, 2, 1, 1, 3)
632 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)
634 self.amount_e = QLineEdit()
635 grid.addWidget(QLabel(_('Amount')), 3, 0)
636 grid.addWidget(self.amount_e, 3, 1, 1, 2)
637 grid.addWidget(HelpButton(
638 _('Amount to be sent.') + '\n\n' \
639 + _('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)
641 self.fee_e = QLineEdit()
642 grid.addWidget(QLabel(_('Fee')), 4, 0)
643 grid.addWidget(self.fee_e, 4, 1, 1, 2)
644 grid.addWidget(HelpButton(
645 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
646 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
647 + _('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)
649 b = EnterButton(_("Send"), self.do_send)
650 grid.addWidget(b, 6, 1)
652 b = EnterButton(_("Clear"),self.do_clear)
653 grid.addWidget(b, 6, 2)
655 self.payto_sig = QLabel('')
656 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
658 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
659 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
668 def entry_changed( is_fee ):
669 self.funds_error = False
670 amount = numbify(self.amount_e)
671 fee = numbify(self.fee_e)
672 if not is_fee: fee = None
675 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
677 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
680 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
683 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
684 self.funds_error = True
685 self.amount_e.setPalette(palette)
686 self.fee_e.setPalette(palette)
688 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
689 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
694 def update_completions(self):
696 for addr,label in self.wallet.labels.items():
697 if addr in self.wallet.addressbook:
698 l.append( label + ' <' + addr + '>')
699 l = l + self.wallet.aliases.keys()
701 self.completions.setStringList(l)
707 label = unicode( self.message_e.text() )
708 r = unicode( self.payto_e.text() )
712 m1 = re.match(ALIAS_REGEXP, r)
713 # label or alias, with address in brackets
714 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
717 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
721 to_address = m2.group(2)
725 if not self.wallet.is_valid(to_address):
726 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
730 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
732 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
735 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
737 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
740 if self.wallet.use_encryption:
741 password = self.password_dialog()
748 tx = self.wallet.mktx( to_address, amount, label, password, fee)
749 except BaseException, e:
750 self.show_message(str(e))
753 h = self.wallet.send_tx(tx)
754 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
755 status, msg = self.wallet.receive_tx( h )
758 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
760 self.update_contacts_tab()
762 QMessageBox.warning(self, _('Error'), msg, _('OK'))
765 def set_url(self, url):
766 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
767 self.tabs.setCurrentIndex(1)
768 label = self.wallet.labels.get(payto)
769 m_addr = label + ' <'+ payto+'>' if label else payto
770 self.payto_e.setText(m_addr)
772 self.message_e.setText(message)
773 self.amount_e.setText(amount)
775 self.set_frozen(self.payto_e,True)
776 self.set_frozen(self.amount_e,True)
777 self.set_frozen(self.message_e,True)
778 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
780 self.payto_sig.setVisible(False)
783 self.payto_sig.setVisible(False)
784 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
786 self.set_frozen(e,False)
788 def set_frozen(self,entry,frozen):
790 entry.setReadOnly(True)
791 entry.setFrame(False)
793 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
794 entry.setPalette(palette)
796 entry.setReadOnly(False)
799 palette.setColor(entry.backgroundRole(), QColor('white'))
800 entry.setPalette(palette)
803 def toggle_freeze(self,addr):
805 if addr in self.wallet.frozen_addresses:
806 self.wallet.unfreeze(addr)
808 self.wallet.freeze(addr)
809 self.update_receive_tab()
811 def toggle_priority(self,addr):
813 if addr in self.wallet.prioritized_addresses:
814 self.wallet.unprioritize(addr)
816 self.wallet.prioritize(addr)
817 self.update_receive_tab()
820 def create_list_tab(self, headers):
821 "generic tab creation method"
822 l = MyTreeWidget(self)
823 l.setColumnCount( len(headers) )
824 l.setHeaderLabels( headers )
834 vbox.addWidget(buttons)
839 buttons.setLayout(hbox)
844 def create_receive_tab(self):
845 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
846 l.setContextMenuPolicy(Qt.CustomContextMenu)
847 l.customContextMenuRequested.connect(self.create_receive_menu)
848 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
849 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
850 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
851 self.receive_list = l
852 self.receive_buttons_hbox = hbox
853 view_combo = QComboBox()
854 view_combo.addItems(['Simple View', 'Detailed View', 'Point of Sale'])
855 view_combo.setCurrentIndex(self.receive_tab_mode)
856 hbox.addWidget(view_combo)
857 view_combo.currentIndexChanged.connect(self.receive_tab_set_mode)
864 self.qr_window.do_save()
865 self.show_message(_("QR code saved to file") + " " + self.qr_window.filename)
868 def receive_tab_set_mode(self, i):
869 self.receive_tab_mode = i
870 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
872 self.update_receive_tab()
873 self.toggle_QR_window(self.receive_tab_mode == 2)
876 def create_contacts_tab(self):
877 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
878 l.setContextMenuPolicy(Qt.CustomContextMenu)
879 l.customContextMenuRequested.connect(self.create_contact_menu)
880 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
881 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
882 self.contacts_list = l
883 self.contacts_buttons_hbox = hbox
884 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
889 def create_receive_menu(self, position):
890 # fixme: this function apparently has a side effect.
891 # if it is not called the menu pops up several times
892 #self.receive_list.selectedIndexes()
894 item = self.receive_list.itemAt(position)
896 addr = unicode(item.text(1))
898 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
899 if self.receive_tab_mode == 2:
900 menu.addAction(_("Request amount"), lambda: self.edit_amount())
901 menu.addAction(_("Print QR"), self.print_qr)
902 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
903 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
905 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
906 menu.addAction(t, lambda: self.toggle_freeze(addr))
907 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
908 menu.addAction(t, lambda: self.toggle_priority(addr))
909 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
912 def payto(self, x, is_alias):
919 label = self.wallet.labels.get(addr)
920 m_addr = label + ' <' + addr + '>' if label else addr
921 self.tabs.setCurrentIndex(1)
922 self.payto_e.setText(m_addr)
923 self.amount_e.setFocus()
925 def delete_contact(self, x, is_alias):
926 if self.question("Do you want to remove %s from your list of contacts?"%x):
927 if not is_alias and x in self.wallet.addressbook:
928 self.wallet.addressbook.remove(x)
929 if x in self.wallet.labels.keys():
930 self.wallet.labels.pop(x)
931 elif is_alias and x in self.wallet.aliases:
932 self.wallet.aliases.pop(x)
933 self.update_history_tab()
934 self.update_contacts_tab()
935 self.update_completions()
937 def create_contact_menu(self, position):
938 # fixme: this function apparently has a side effect.
939 # if it is not called the menu pops up several times
940 #self.contacts_list.selectedIndexes()
942 item = self.contacts_list.itemAt(position)
944 addr = unicode(item.text(0))
945 label = unicode(item.text(1))
946 is_alias = label in self.wallet.aliases.keys()
947 x = label if is_alias else addr
949 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
950 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
951 menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
953 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
955 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
956 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
957 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
960 def update_receive_item(self, item):
961 address = str( item.data(1,0).toString() )
963 flags = self.wallet.get_address_flags(address)
964 item.setData(0,0,flags)
966 label = self.wallet.labels.get(address,'')
967 item.setData(2,0,label)
969 amount = self.wallet.requested_amounts.get(address,None)
970 amount_str = format_satoshis( amount, False, self.wallet.num_zeros ) if amount is not None else ""
971 item.setData(3,0,amount_str)
973 c, u = self.wallet.get_addr_balance(address)
974 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
975 item.setData(4,0,balance)
977 if address in self.wallet.frozen_addresses:
978 item.setBackgroundColor(1, QColor('lightblue'))
979 elif address in self.wallet.prioritized_addresses:
980 item.setBackgroundColor(1, QColor('lightgreen'))
983 def update_receive_tab(self):
984 l = self.receive_list
987 l.setColumnHidden(0, not self.receive_tab_mode == 1)
988 l.setColumnHidden(3, not self.receive_tab_mode == 2)
989 l.setColumnHidden(4, not self.receive_tab_mode == 1)
990 l.setColumnHidden(5, not self.receive_tab_mode == 1)
991 l.setColumnWidth(0, 50)
992 l.setColumnWidth(1, 310)
993 l.setColumnWidth(2, 200)
994 l.setColumnWidth(3, 130)
995 l.setColumnWidth(4, 130)
996 l.setColumnWidth(5, 10)
1000 for address in self.wallet.all_addresses():
1002 if self.wallet.is_change(address) and self.receive_tab_mode != 1:
1006 h = self.wallet.history.get(address,[])
1009 for tx_hash, tx_height in h:
1010 tx = self.wallet.transactions.get(tx_hash)
1018 if address in self.wallet.addresses:
1020 if gap > self.wallet.gap_limit:
1023 if address in self.wallet.addresses:
1026 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1027 item.setFont(0, QFont(MONOSPACE_FONT))
1028 item.setFont(1, QFont(MONOSPACE_FONT))
1029 item.setFont(3, QFont(MONOSPACE_FONT))
1030 self.update_receive_item(item)
1031 if is_red and address in self.wallet.addresses:
1032 item.setBackgroundColor(1, QColor('red'))
1033 l.addTopLevelItem(item)
1035 # we use column 1 because column 0 may be hidden
1036 l.setCurrentItem(l.topLevelItem(0),1)
1038 def show_contact_details(self, m):
1039 a = self.wallet.aliases.get(m)
1041 if a[0] in self.wallet.authorities.keys():
1042 s = self.wallet.authorities.get(a[0])
1045 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1046 QMessageBox.information(self, 'Alias', msg, 'OK')
1048 def update_contacts_tab(self):
1050 l = self.contacts_list
1052 l.setColumnWidth(0, 350)
1053 l.setColumnWidth(1, 330)
1054 l.setColumnWidth(2, 100)
1057 for alias, v in self.wallet.aliases.items():
1059 alias_targets.append(target)
1060 item = QTreeWidgetItem( [ target, alias, '-'] )
1061 item.setBackgroundColor(0, QColor('lightgray'))
1062 l.addTopLevelItem(item)
1064 for address in self.wallet.addressbook:
1065 if address in alias_targets: continue
1066 label = self.wallet.labels.get(address,'')
1068 for item in self.wallet.transactions.values():
1069 if address in item['outputs'] : n=n+1
1071 item = QTreeWidgetItem( [ address, label, tx] )
1072 item.setFont(0, QFont(MONOSPACE_FONT))
1073 l.addTopLevelItem(item)
1075 l.setCurrentItem(l.topLevelItem(0))
1077 def create_wall_tab(self):
1078 self.textbox = textbox = QTextEdit(self)
1079 textbox.setFont(QFont(MONOSPACE_FONT))
1080 textbox.setReadOnly(True)
1083 def create_status_bar(self):
1085 sb.setFixedHeight(35)
1086 if self.wallet.seed:
1087 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1088 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1089 if self.wallet.seed:
1090 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1091 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
1092 sb.addPermanentWidget( self.status_button )
1093 self.setStatusBar(sb)
1095 def new_contact_dialog(self):
1096 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1097 address = unicode(text)
1099 if self.wallet.is_valid(address):
1100 self.wallet.addressbook.append(address)
1102 self.update_contacts_tab()
1103 self.update_history_tab()
1104 self.update_completions()
1106 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1109 def show_seed_dialog(wallet, parent=None):
1111 QMessageBox.information(parent, _('Message'),
1112 _('No seed'), _('OK'))
1115 if wallet.use_encryption:
1116 password = parent.password_dialog()
1123 seed = wallet.pw_decode(wallet.seed, password)
1125 QMessageBox.warning(parent, _('Error'),
1126 _('Incorrect Password'), _('OK'))
1129 dialog = QDialog(None)
1131 dialog.setWindowTitle("Electrum")
1133 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1135 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1136 + _("Please write down or memorize these 12 words (order is important).") + " " \
1137 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1138 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1140 main_text = QLabel(msg)
1141 main_text.setWordWrap(True)
1144 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1151 copy_function = lambda: app.clipboard().setText(brainwallet)
1152 copy_button = QPushButton(_("Copy to Clipboard"))
1153 copy_button.clicked.connect(copy_function)
1155 show_qr_function = lambda: ElectrumWindow.show_seed_qrcode(seed)
1156 qr_button = QPushButton(_("View as QR Code"))
1157 qr_button.clicked.connect(show_qr_function)
1159 ok_button = QPushButton(_("OK"))
1160 ok_button.setDefault(True)
1161 ok_button.clicked.connect(dialog.accept)
1163 main_layout = QGridLayout()
1164 main_layout.addWidget(logo, 0, 0)
1165 main_layout.addWidget(main_text, 0, 1, 1, -1)
1166 main_layout.addWidget(copy_button, 1, 1)
1167 main_layout.addWidget(qr_button, 1, 2)
1168 main_layout.addWidget(ok_button, 1, 3)
1169 dialog.setLayout(main_layout)
1174 def show_seed_qrcode(seed):
1178 d.setWindowTitle(_("Seed"))
1179 d.setMinimumSize(270, 300)
1180 vbox = QVBoxLayout()
1181 vbox.addWidget(QRCodeWidget(seed))
1182 hbox = QHBoxLayout()
1184 b = QPushButton(_("OK"))
1186 b.clicked.connect(d.accept)
1188 vbox.addLayout(hbox)
1192 def sign_message(self,address):
1193 if not address: return
1196 d.setWindowTitle('Sign Message')
1197 d.setMinimumSize(270, 350)
1199 tab_widget = QTabWidget()
1201 layout = QGridLayout(tab)
1203 sign_address = QLineEdit()
1204 sign_address.setText(address)
1205 layout.addWidget(QLabel(_('Address')), 1, 0)
1206 layout.addWidget(sign_address, 1, 1)
1208 sign_message = QTextEdit()
1209 layout.addWidget(QLabel(_('Message')), 2, 0)
1210 layout.addWidget(sign_message, 2, 1, 2, 1)
1212 sign_signature = QLineEdit()
1213 layout.addWidget(QLabel(_('Signature')), 3, 0)
1214 layout.addWidget(sign_signature, 3, 1)
1217 if self.wallet.use_encryption:
1218 password = self.password_dialog()
1225 signature = self.wallet.sign_message(sign_address.text(), sign_message.toPlainText(), password)
1226 sign_signature.setText(signature)
1227 except BaseException, e:
1228 self.show_message(str(e))
1231 hbox = QHBoxLayout()
1232 b = QPushButton(_("Sign"))
1234 b.clicked.connect(do_sign)
1235 b = QPushButton(_("Close"))
1236 b.clicked.connect(d.accept)
1238 layout.addLayout(hbox, 4, 1)
1239 tab_widget.addTab(tab, "Sign")
1243 layout = QGridLayout(tab)
1245 verify_address = QLineEdit()
1246 layout.addWidget(QLabel(_('Address')), 1, 0)
1247 layout.addWidget(verify_address, 1, 1)
1249 verify_message = QTextEdit()
1250 layout.addWidget(QLabel(_('Message')), 2, 0)
1251 layout.addWidget(verify_message, 2, 1, 2, 1)
1253 verify_signature = QLineEdit()
1254 layout.addWidget(QLabel(_('Signature')), 3, 0)
1255 layout.addWidget(verify_signature, 3, 1)
1259 self.wallet.verify_message(verify_address.text(), verify_signature.text(), verify_message.toPlainText())
1260 self.show_message("Signature verified")
1261 except BaseException, e:
1262 self.show_message(str(e))
1265 hbox = QHBoxLayout()
1266 b = QPushButton(_("Verify"))
1267 b.clicked.connect(do_verify)
1269 b = QPushButton(_("Close"))
1270 b.clicked.connect(d.accept)
1272 layout.addLayout(hbox, 4, 1)
1273 tab_widget.addTab(tab, "Verify")
1275 vbox = QVBoxLayout()
1276 vbox.addWidget(tab_widget)
1281 def toggle_QR_window(self, show):
1282 if show and not self.qr_window:
1283 self.qr_window = QR_Window()
1284 self.qr_window.setVisible(True)
1285 self.qr_window_geometry = self.qr_window.geometry()
1286 item = self.receive_list.currentItem()
1288 address = str(item.text(1))
1289 label = self.wallet.labels.get(address)
1290 amount = self.wallet.requested_amounts.get(address)
1291 self.qr_window.set_content( address, label, amount )
1293 elif show and self.qr_window and not self.qr_window.isVisible():
1294 self.qr_window.setVisible(True)
1295 self.qr_window.setGeometry(self.qr_window_geometry)
1297 elif not show and self.qr_window and self.qr_window.isVisible():
1298 self.qr_window_geometry = self.qr_window.geometry()
1299 self.qr_window.setVisible(False)
1301 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1302 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1303 self.receive_list.setColumnWidth(2, 200)
1306 def question(self, msg):
1307 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1309 def show_message(self, msg):
1310 QMessageBox.information(self, _('Message'), msg, _('OK'))
1312 def password_dialog(self ):
1319 vbox = QVBoxLayout()
1320 msg = _('Please enter your password')
1321 vbox.addWidget(QLabel(msg))
1323 grid = QGridLayout()
1325 grid.addWidget(QLabel(_('Password')), 1, 0)
1326 grid.addWidget(pw, 1, 1)
1327 vbox.addLayout(grid)
1329 vbox.addLayout(ok_cancel_buttons(d))
1332 if not d.exec_(): return
1333 return unicode(pw.text())
1340 def change_password_dialog( wallet, parent=None ):
1343 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1351 new_pw = QLineEdit()
1352 new_pw.setEchoMode(2)
1353 conf_pw = QLineEdit()
1354 conf_pw.setEchoMode(2)
1356 vbox = QVBoxLayout()
1358 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1359 +_('To disable wallet encryption, enter an empty new password.')) \
1360 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1362 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1363 +_("Leave these fields empty if you want to disable encryption.")
1364 vbox.addWidget(QLabel(msg))
1366 grid = QGridLayout()
1369 if wallet.use_encryption:
1370 grid.addWidget(QLabel(_('Password')), 1, 0)
1371 grid.addWidget(pw, 1, 1)
1373 grid.addWidget(QLabel(_('New Password')), 2, 0)
1374 grid.addWidget(new_pw, 2, 1)
1376 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1377 grid.addWidget(conf_pw, 3, 1)
1378 vbox.addLayout(grid)
1380 vbox.addLayout(ok_cancel_buttons(d))
1383 if not d.exec_(): return
1385 password = unicode(pw.text()) if wallet.use_encryption else None
1386 new_password = unicode(new_pw.text())
1387 new_password2 = unicode(conf_pw.text())
1390 seed = wallet.pw_decode( wallet.seed, password)
1392 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1395 if new_password != new_password2:
1396 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1399 wallet.update_password(seed, password, new_password)
1402 def seed_dialog(wallet, parent=None):
1406 vbox = QVBoxLayout()
1407 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1408 vbox.addWidget(QLabel(msg))
1410 grid = QGridLayout()
1413 seed_e = QLineEdit()
1414 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1415 grid.addWidget(seed_e, 1, 1)
1419 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1420 grid.addWidget(gap_e, 2, 1)
1421 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1422 vbox.addLayout(grid)
1424 vbox.addLayout(ok_cancel_buttons(d))
1427 if not d.exec_(): return
1430 gap = int(unicode(gap_e.text()))
1432 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1436 seed = unicode(seed_e.text())
1439 print_error("Warning: Not hex, trying decode")
1441 seed = mnemonic.mn_decode( seed.split(' ') )
1443 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1446 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1449 wallet.seed = str(seed)
1450 #print repr(wallet.seed)
1451 wallet.gap_limit = gap
1456 def settings_dialog(self):
1459 vbox = QVBoxLayout()
1460 msg = _('Here are the settings of your wallet.') + '\n'\
1461 + _('For more explanations, click on the help buttons next to each field.')
1464 label.setFixedWidth(250)
1465 label.setWordWrap(True)
1466 label.setAlignment(Qt.AlignJustify)
1467 vbox.addWidget(label)
1469 grid = QGridLayout()
1471 vbox.addLayout(grid)
1473 fee_label = QLabel(_('Transaction fee'))
1474 grid.addWidget(fee_label, 2, 0)
1476 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1477 grid.addWidget(fee_e, 2, 1)
1478 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1479 + _('Recommended value') + ': 0.001'
1480 grid.addWidget(HelpButton(msg), 2, 2)
1481 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1482 if not self.config.is_modifiable('fee'):
1483 for w in [fee_e, fee_label]: w.setEnabled(False)
1485 nz_label = QLabel(_('Display zeros'))
1486 grid.addWidget(nz_label, 3, 0)
1488 nz_e.setText("%d"% self.wallet.num_zeros)
1489 grid.addWidget(nz_e, 3, 1)
1490 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1491 grid.addWidget(HelpButton(msg), 3, 2)
1492 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1493 if not self.config.is_modifiable('num_zeros'):
1494 for w in [nz_e, nz_label]: w.setEnabled(False)
1496 usechange_cb = QCheckBox(_('Use change addresses'))
1497 grid.addWidget(usechange_cb, 5, 0)
1498 usechange_cb.setChecked(self.wallet.use_change)
1499 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1500 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1502 gap_label = QLabel(_('Gap limit'))
1503 grid.addWidget(gap_label, 6, 0)
1505 gap_e.setText("%d"% self.wallet.gap_limit)
1506 grid.addWidget(gap_e, 6, 1)
1507 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1508 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1509 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1510 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1511 + _('Warning') + ': ' \
1512 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1513 + _('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'
1514 grid.addWidget(HelpButton(msg), 6, 2)
1515 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1516 if not self.config.is_modifiable('gap_limit'):
1517 for w in [gap_e, gap_label]: w.setEnabled(False)
1519 gui_label=QLabel(_('Default GUI') + ':')
1520 grid.addWidget(gui_label , 7, 0)
1521 gui_combo = QComboBox()
1522 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1523 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1524 if index==-1: index = 1
1525 gui_combo.setCurrentIndex(index)
1526 grid.addWidget(gui_combo, 7, 1)
1527 grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1528 if not self.config.is_modifiable('gui'):
1529 for w in [gui_combo, gui_label]: w.setEnabled(False)
1531 vbox.addLayout(ok_cancel_buttons(d))
1535 if not d.exec_(): return
1537 fee = unicode(fee_e.text())
1539 fee = int( 100000000 * Decimal(fee) )
1541 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1544 if self.wallet.fee != fee:
1545 self.wallet.fee = fee
1548 nz = unicode(nz_e.text())
1553 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1556 if self.wallet.num_zeros != nz:
1557 self.wallet.num_zeros = nz
1558 self.config.set_key('num_zeros', nz, True)
1559 self.update_history_tab()
1560 self.update_receive_tab()
1562 if self.wallet.use_change != usechange_cb.isChecked():
1563 self.wallet.use_change = usechange_cb.isChecked()
1564 self.config.set_key('use_change', self.wallet.use_change, True)
1567 n = int(gap_e.text())
1569 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1572 if self.wallet.gap_limit != n:
1573 r = self.wallet.change_gap_limit(n)
1575 self.update_receive_tab()
1576 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1578 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1580 self.config.set_key("gui", str(gui_combo.currentText()).lower(), True)
1585 def network_dialog(wallet, parent=None):
1586 interface = wallet.interface
1588 if interface.is_connected:
1589 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1591 status = _("Not connected")
1592 server = interface.server
1595 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1596 server = interface.server
1598 plist, servers_list = interface.get_servers_list()
1602 d.setWindowTitle(_('Server'))
1603 d.setMinimumSize(375, 20)
1605 vbox = QVBoxLayout()
1608 hbox = QHBoxLayout()
1610 l.setPixmap(QPixmap(":icons/network.png"))
1613 hbox.addWidget(QLabel(status))
1615 vbox.addLayout(hbox)
1619 grid = QGridLayout()
1621 vbox.addLayout(grid)
1624 server_protocol = QComboBox()
1625 server_host = QLineEdit()
1626 server_host.setFixedWidth(200)
1627 server_port = QLineEdit()
1628 server_port.setFixedWidth(60)
1630 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1631 protocol_letters = 'thsg'
1632 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1633 server_protocol.addItems(protocol_names)
1635 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1636 grid.addWidget(server_protocol, 0, 1)
1637 grid.addWidget(server_host, 0, 2)
1638 grid.addWidget(server_port, 0, 3)
1640 def change_protocol(p):
1641 protocol = protocol_letters[p]
1642 host = unicode(server_host.text())
1643 pp = plist.get(host,DEFAULT_PORTS)
1644 if protocol not in pp.keys():
1645 protocol = pp.keys()[0]
1647 server_host.setText( host )
1648 server_port.setText( port )
1650 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1652 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1653 servers_list_widget = QTreeWidget(parent)
1654 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1655 servers_list_widget.setMaximumHeight(150)
1656 servers_list_widget.setColumnWidth(0, 240)
1657 for _host in servers_list.keys():
1658 _type = 'pruning' if servers_list[_host].get('pruning') else 'full'
1659 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1661 def change_server(host, protocol=None):
1662 pp = plist.get(host,DEFAULT_PORTS)
1664 port = pp.get(protocol)
1665 if not port: protocol = None
1668 if 't' in pp.keys():
1670 port = pp.get(protocol)
1672 protocol = pp.keys()[0]
1673 port = pp.get(protocol)
1675 server_host.setText( host )
1676 server_port.setText( port )
1677 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1679 if not plist: return
1680 for p in protocol_letters:
1681 i = protocol_letters.index(p)
1682 j = server_protocol.model().index(i,0)
1683 if p not in pp.keys():
1684 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1686 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1690 host, port, protocol = server.split(':')
1691 change_server(host,protocol)
1693 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1694 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1696 if not wallet.config.is_modifiable('server'):
1697 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1700 proxy_mode = QComboBox()
1701 proxy_host = QLineEdit()
1702 proxy_host.setFixedWidth(200)
1703 proxy_port = QLineEdit()
1704 proxy_port.setFixedWidth(60)
1705 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1707 def check_for_disable(index = False):
1708 if proxy_mode.currentText() != 'NONE':
1709 proxy_host.setEnabled(True)
1710 proxy_port.setEnabled(True)
1712 proxy_host.setEnabled(False)
1713 proxy_port.setEnabled(False)
1716 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1718 if not wallet.config.is_modifiable('proxy'):
1719 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1721 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1722 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1723 proxy_host.setText(proxy_config.get("host"))
1724 proxy_port.setText(proxy_config.get("port"))
1726 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1727 grid.addWidget(proxy_mode, 2, 1)
1728 grid.addWidget(proxy_host, 2, 2)
1729 grid.addWidget(proxy_port, 2, 3)
1732 vbox.addLayout(ok_cancel_buttons(d))
1735 if not d.exec_(): return
1737 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1738 if proxy_mode.currentText() != 'NONE':
1739 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1743 wallet.config.set_key("proxy", proxy, True)
1744 wallet.config.set_key("server", server, True)
1745 interface.set_server(server, proxy)
1749 def closeEvent(self, event):
1751 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1757 def __init__(self, wallet, config, app=None):
1758 self.wallet = wallet
1759 self.config = config
1761 self.app = QApplication(sys.argv)
1764 def restore_or_create(self):
1765 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1766 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1767 if r==2: return None
1768 return 'restore' if r==1 else 'create'
1770 def seed_dialog(self):
1771 return ElectrumWindow.seed_dialog( self.wallet )
1773 def network_dialog(self):
1774 return ElectrumWindow.network_dialog( self.wallet, parent=None )
1777 def show_seed(self):
1778 ElectrumWindow.show_seed_dialog(self.wallet)
1781 def password_dialog(self):
1782 ElectrumWindow.change_password_dialog(self.wallet)
1785 def restore_wallet(self):
1786 wallet = self.wallet
1787 # wait until we are connected, because the user might have selected another server
1788 if not wallet.interface.is_connected:
1789 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1790 waiting_dialog(waiting)
1792 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1793 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1795 wallet.set_up_to_date(False)
1796 wallet.interface.poke('synchronizer')
1797 waiting_dialog(waiting)
1798 if wallet.is_found():
1799 print_error( "Recovery successful" )
1801 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1808 w = ElectrumWindow(self.wallet, self.config)
1809 if url: w.set_url(url)