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.setGeometry(300, 300, 350, 350)
145 def set_addr(self, addr):
147 self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
148 self.qr.addData(addr)
151 def paintEvent(self, e):
152 qp = QtGui.QPainter()
155 size = self.qr.getModuleCount()*boxsize
156 k = self.qr.getModuleCount()
157 black = QColor(0, 0, 0, 255)
158 white = QColor(255, 255, 255, 255)
161 if self.qr.isDark(r, c):
167 qp.drawRect(c*boxsize, r*boxsize, boxsize, boxsize)
171 def waiting_dialog(f):
177 w.setWindowTitle('Electrum')
187 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
192 def ok_cancel_buttons(dialog):
195 b = QPushButton("OK")
197 b.clicked.connect(dialog.accept)
198 b = QPushButton("Cancel")
200 b.clicked.connect(dialog.reject)
204 class ElectrumWindow(QMainWindow):
206 def __init__(self, wallet, config):
207 QMainWindow.__init__(self)
210 self.wallet.interface.register_callback('updated', self.update_callback)
211 self.wallet.interface.register_callback('connected', self.update_callback)
212 self.wallet.interface.register_callback('disconnected', self.update_callback)
213 self.wallet.interface.register_callback('disconnecting', self.update_callback)
215 self.detailed_view = config.get('qt_detailed_view', False)
217 self.funds_error = False
218 self.completions = QStringListModel()
220 self.tabs = tabs = QTabWidget(self)
221 tabs.addTab(self.create_history_tab(), _('History') )
223 tabs.addTab(self.create_send_tab(), _('Send') )
224 tabs.addTab(self.create_receive_tab(), _('Receive') )
225 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
226 tabs.addTab(self.create_wall_tab(), _('Wall') )
227 tabs.setMinimumSize(600, 400)
228 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
229 self.setCentralWidget(tabs)
230 self.create_status_bar()
232 g = self.config.get("winpos-qt",[100, 100, 840, 400])
233 self.setGeometry(g[0], g[1], g[2], g[3])
234 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
235 if not self.wallet.seed: title += ' [seedless]'
236 self.setWindowTitle( title )
238 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
239 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
240 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
241 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
243 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
244 self.history_list.setFocus(True)
246 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
247 if platform.system() == 'Windows':
248 n = 3 if self.wallet.seed else 2
249 tabs.setCurrentIndex (n)
250 tabs.setCurrentIndex (0)
253 def connect_slots(self, sender):
255 self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient)
256 self.previous_payto_e=''
258 def check_recipient(self):
259 if self.payto_e.hasFocus():
261 r = unicode( self.payto_e.text() )
262 if r != self.previous_payto_e:
263 self.previous_payto_e = r
265 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
267 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
271 s = r + ' <' + to_address + '>'
272 self.payto_e.setText(s)
275 def update_callback(self):
276 self.emit(QtCore.SIGNAL('updatesignal'))
278 def update_wallet(self):
279 if self.wallet.interface and self.wallet.interface.is_connected:
280 if not self.wallet.up_to_date:
281 text = _( "Synchronizing..." )
282 icon = QIcon(":icons/status_waiting.png")
284 c, u = self.wallet.get_balance()
285 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
286 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
287 icon = QIcon(":icons/status_connected.png")
289 text = _( "Not connected" )
290 icon = QIcon(":icons/status_disconnected.png")
293 text = _( "Not enough funds" )
295 self.statusBar().showMessage(text)
296 self.status_button.setIcon( icon )
298 if self.wallet.up_to_date:
299 self.textbox.setText( self.wallet.banner )
300 self.update_history_tab()
301 self.update_receive_tab()
302 self.update_contacts_tab()
303 self.update_completions()
306 def create_history_tab(self):
307 self.history_list = l = MyTreeWidget(self)
309 l.setColumnWidth(0, 40)
310 l.setColumnWidth(1, 140)
311 l.setColumnWidth(2, 350)
312 l.setColumnWidth(3, 140)
313 l.setColumnWidth(4, 140)
314 l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
315 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
316 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
317 l.setContextMenuPolicy(Qt.CustomContextMenu)
318 l.customContextMenuRequested.connect(self.create_history_menu)
321 def create_history_menu(self, position):
322 self.history_list.selectedIndexes()
323 item = self.history_list.currentItem()
325 tx_hash = str(item.toolTip(0))
327 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
328 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
329 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
330 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
332 def tx_details(self, tx_hash):
333 tx = self.wallet.transactions.get(tx_hash)
335 conf = self.wallet.verifier.get_confirmations(tx_hash)
337 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
341 inputs = map(lambda x: x.get('address'), tx['inputs'])
342 outputs = map(lambda x: x.get('address'), tx['outputs'])
343 tx_details = _("Transaction Details") +"\n\n" \
344 + "Transaction ID:\n" + tx_hash + "\n\n" \
345 + "Status: %d confirmations\n\n"%conf \
346 + "Date: %s\n\n"%time_str \
347 + "Inputs:\n-"+ '\n-'.join(inputs) + "\n\n" \
348 + "Outputs:\n-"+ '\n-'.join(outputs)
350 r = self.wallet.receipts.get(tx_hash)
352 tx_details += "\n_______________________________________" \
353 + '\n\nSigned URI: ' + r[2] \
354 + "\n\nSigned by: " + r[0] \
355 + '\n\nSignature: ' + r[1]
357 QMessageBox.information(self, 'Details', tx_details, 'OK')
360 def tx_label_clicked(self, item, column):
361 if column==2 and item.isSelected():
362 tx_hash = str(item.toolTip(0))
364 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
365 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
366 self.history_list.editItem( item, column )
367 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
370 def tx_label_changed(self, item, column):
374 tx_hash = str(item.toolTip(0))
375 tx = self.wallet.transactions.get(tx_hash)
376 s = self.wallet.labels.get(tx_hash)
377 text = unicode( item.text(2) )
379 self.wallet.labels[tx_hash] = text
380 item.setForeground(2, QBrush(QColor('black')))
382 if s: self.wallet.labels.pop(tx_hash)
383 text = self.wallet.get_default_label(tx_hash)
384 item.setText(2, text)
385 item.setForeground(2, QBrush(QColor('gray')))
388 def edit_label(self, is_recv):
389 l = self.receive_list if is_recv else self.contacts_list
390 c = 2 if is_recv else 1
391 item = l.currentItem()
392 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
393 l.editItem( item, c )
394 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
396 def address_label_clicked(self, item, column, l, column_addr, column_label):
397 if column==column_label and item.isSelected():
398 addr = unicode( item.text(column_addr) )
399 label = unicode( item.text(column_label) )
400 if label in self.wallet.aliases.keys():
402 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
403 l.editItem( item, column )
404 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
406 def address_label_changed(self, item, column, l, column_addr, column_label):
407 addr = unicode( item.text(column_addr) )
408 text = unicode( item.text(column_label) )
412 if text not in self.wallet.aliases.keys():
413 old_addr = self.wallet.labels.get(text)
415 self.wallet.labels[addr] = text
418 print_error("Error: This is one of your aliases")
419 label = self.wallet.labels.get(addr,'')
420 item.setText(column_label, QString(label))
422 s = self.wallet.labels.get(addr)
424 self.wallet.labels.pop(addr)
428 self.wallet.update_tx_labels()
429 self.update_history_tab()
430 self.update_completions()
433 def update_history_tab(self):
434 self.history_list.clear()
436 for tx in self.wallet.get_tx_history():
437 tx_hash = tx['tx_hash']
438 conf = self.wallet.verifier.get_confirmations(tx_hash)
440 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
442 icon = QIcon(":icons/unconfirmed.png")
444 icon = QIcon(":icons/clock%d.png"%conf)
446 icon = QIcon(":icons/confirmed.png")
449 icon = QIcon(":icons/unconfirmed.png")
450 v = self.wallet.get_tx_value(tx_hash)
452 label, is_default_label = self.wallet.get_label(tx_hash)
454 item = QTreeWidgetItem( [ '', time_str, label, format_satoshis(v,True,self.wallet.num_zeros), format_satoshis(balance,False,self.wallet.num_zeros)] )
455 item.setFont(2, QFont(MONOSPACE_FONT))
456 item.setFont(3, QFont(MONOSPACE_FONT))
457 item.setFont(4, QFont(MONOSPACE_FONT))
458 item.setToolTip(0, tx_hash)
460 item.setForeground(2, QBrush(QColor('grey')))
462 item.setIcon(0, icon)
463 self.history_list.insertTopLevelItem(0,item)
465 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
468 def create_send_tab(self):
473 grid.setColumnMinimumWidth(3,300)
474 grid.setColumnStretch(5,1)
476 self.payto_e = QLineEdit()
477 grid.addWidget(QLabel(_('Pay to')), 1, 0)
478 grid.addWidget(self.payto_e, 1, 1, 1, 3)
481 qrcode = qrscanner.scan_qr()
482 if 'address' in qrcode:
483 self.payto_e.setText(qrcode['address'])
484 if 'amount' in qrcode:
485 self.amount_e.setText(str(qrcode['amount']))
486 if 'label' in qrcode:
487 self.message_e.setText(qrcode['label'])
488 if 'message' in qrcode:
489 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
492 if qrscanner.is_available():
493 b = QPushButton(_("Scan QR code"))
494 b.clicked.connect(fill_from_qr)
495 grid.addWidget(b, 1, 5)
497 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)
499 completer = QCompleter()
500 completer.setCaseSensitivity(False)
501 self.payto_e.setCompleter(completer)
502 completer.setModel(self.completions)
504 self.message_e = QLineEdit()
505 grid.addWidget(QLabel(_('Description')), 2, 0)
506 grid.addWidget(self.message_e, 2, 1, 1, 3)
507 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)
509 self.amount_e = QLineEdit()
510 grid.addWidget(QLabel(_('Amount')), 3, 0)
511 grid.addWidget(self.amount_e, 3, 1, 1, 2)
512 grid.addWidget(HelpButton(
513 _('Amount to be sent.') + '\n\n' \
514 + _('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)
516 self.fee_e = QLineEdit()
517 grid.addWidget(QLabel(_('Fee')), 4, 0)
518 grid.addWidget(self.fee_e, 4, 1, 1, 2)
519 grid.addWidget(HelpButton(
520 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
521 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
522 + _('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)
524 b = EnterButton(_("Send"), self.do_send)
525 grid.addWidget(b, 6, 1)
527 b = EnterButton(_("Clear"),self.do_clear)
528 grid.addWidget(b, 6, 2)
530 self.payto_sig = QLabel('')
531 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
533 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
534 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
543 def entry_changed( is_fee ):
544 self.funds_error = False
545 amount = numbify(self.amount_e)
546 fee = numbify(self.fee_e)
547 if not is_fee: fee = None
550 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
552 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
555 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
558 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
559 self.funds_error = True
560 self.amount_e.setPalette(palette)
561 self.fee_e.setPalette(palette)
563 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
564 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
569 def update_completions(self):
571 for addr,label in self.wallet.labels.items():
572 if addr in self.wallet.addressbook:
573 l.append( label + ' <' + addr + '>')
574 l = l + self.wallet.aliases.keys()
576 self.completions.setStringList(l)
582 label = unicode( self.message_e.text() )
583 r = unicode( self.payto_e.text() )
587 m1 = re.match(ALIAS_REGEXP, r)
588 # label or alias, with address in brackets
589 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
592 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
596 to_address = m2.group(2)
600 if not self.wallet.is_valid(to_address):
601 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
605 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
607 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
610 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
612 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
615 if self.wallet.use_encryption:
616 password = self.password_dialog()
623 tx = self.wallet.mktx( to_address, amount, label, password, fee)
624 except BaseException, e:
625 self.show_message(str(e))
628 h = self.wallet.send_tx(tx)
629 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
630 status, msg = self.wallet.receive_tx( h )
633 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
635 self.update_contacts_tab()
637 QMessageBox.warning(self, _('Error'), msg, _('OK'))
640 def set_url(self, url):
641 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
642 self.tabs.setCurrentIndex(1)
643 label = self.wallet.labels.get(payto)
644 m_addr = label + ' <'+ payto+'>' if label else payto
645 self.payto_e.setText(m_addr)
647 self.message_e.setText(message)
648 self.amount_e.setText(amount)
650 self.set_frozen(self.payto_e,True)
651 self.set_frozen(self.amount_e,True)
652 self.set_frozen(self.message_e,True)
653 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
655 self.payto_sig.setVisible(False)
658 self.payto_sig.setVisible(False)
659 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
661 self.set_frozen(e,False)
663 def set_frozen(self,entry,frozen):
665 entry.setReadOnly(True)
666 entry.setFrame(False)
668 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
669 entry.setPalette(palette)
671 entry.setReadOnly(False)
674 palette.setColor(entry.backgroundRole(), QColor('white'))
675 entry.setPalette(palette)
678 def toggle_freeze(self,addr):
680 if addr in self.wallet.frozen_addresses:
681 self.wallet.unfreeze(addr)
683 self.wallet.freeze(addr)
684 self.update_receive_tab()
686 def toggle_priority(self,addr):
688 if addr in self.wallet.prioritized_addresses:
689 self.wallet.unprioritize(addr)
691 self.wallet.prioritize(addr)
692 self.update_receive_tab()
695 def create_list_tab(self, headers):
696 "generic tab creation method"
697 l = MyTreeWidget(self)
698 l.setColumnCount( len(headers) )
699 l.setHeaderLabels( headers )
709 vbox.addWidget(buttons)
714 buttons.setLayout(hbox)
719 def create_receive_tab(self):
720 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Balance'), _('Tx')])
721 l.setContextMenuPolicy(Qt.CustomContextMenu)
722 l.customContextMenuRequested.connect(self.create_receive_menu)
723 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
724 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
725 self.receive_list = l
726 self.receive_buttons_hbox = hbox
727 self.details_button = EnterButton(self.details_button_text(), self.toggle_detailed_view)
728 hbox.addWidget(self.details_button)
732 def details_button_text(self):
733 return _('Hide details') if self.detailed_view else _('Show details')
735 def toggle_detailed_view(self):
736 self.detailed_view = not self.detailed_view
737 self.config.set_key('qt_detailed_view', self.detailed_view, True)
739 self.details_button.setText(self.details_button_text())
741 self.update_receive_tab()
742 self.update_contacts_tab()
745 def create_contacts_tab(self):
746 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
747 l.setContextMenuPolicy(Qt.CustomContextMenu)
748 l.customContextMenuRequested.connect(self.create_contact_menu)
749 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
750 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
751 self.contacts_list = l
752 self.contacts_buttons_hbox = hbox
753 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
758 def create_receive_menu(self, position):
759 # fixme: this function apparently has a side effect.
760 # if it is not called the menu pops up several times
761 #self.receive_list.selectedIndexes()
763 item = self.receive_list.itemAt(position)
765 addr = unicode(item.text(1))
767 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
768 menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
769 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
771 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
772 menu.addAction(t, lambda: self.toggle_freeze(addr))
773 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
774 menu.addAction(t, lambda: self.toggle_priority(addr))
775 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
778 def payto(self, x, is_alias):
785 label = self.wallet.labels.get(addr)
786 m_addr = label + ' <' + addr + '>' if label else addr
787 self.tabs.setCurrentIndex(1)
788 self.payto_e.setText(m_addr)
789 self.amount_e.setFocus()
791 def delete_contact(self, x, is_alias):
792 if self.question("Do you want to remove %s from your list of contacts?"%x):
793 if not is_alias and x in self.wallet.addressbook:
794 self.wallet.addressbook.remove(x)
795 if x in self.wallet.labels.keys():
796 self.wallet.labels.pop(x)
797 elif is_alias and x in self.wallet.aliases:
798 self.wallet.aliases.pop(x)
799 self.update_history_tab()
800 self.update_contacts_tab()
801 self.update_completions()
803 def create_contact_menu(self, position):
804 # fixme: this function apparently has a side effect.
805 # if it is not called the menu pops up several times
806 #self.contacts_list.selectedIndexes()
808 item = self.contacts_list.itemAt(position)
810 addr = unicode(item.text(0))
811 label = unicode(item.text(1))
812 is_alias = label in self.wallet.aliases.keys()
813 x = label if is_alias else addr
815 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
816 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
817 menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
819 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
821 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
822 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
823 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
826 def update_receive_tab(self):
827 l = self.receive_list
829 l.setColumnHidden(0,not self.detailed_view)
830 l.setColumnHidden(3,not self.detailed_view)
831 l.setColumnHidden(4,not self.detailed_view)
832 l.setColumnWidth(0, 50)
833 l.setColumnWidth(1, 310)
834 l.setColumnWidth(2, 250)
835 l.setColumnWidth(3, 130)
836 l.setColumnWidth(4, 10)
840 for address in self.wallet.all_addresses():
842 if self.wallet.is_change(address) and not self.detailed_view:
845 label = self.wallet.labels.get(address,'')
847 h = self.wallet.history.get(address,[])
848 for tx_hash, tx_height in h:
849 tx = self.wallet.transactions.get(tx_hash)
854 if address in self.wallet.addresses:
856 if gap > self.wallet.gap_limit:
859 if address in self.wallet.addresses:
862 c, u = self.wallet.get_addr_balance(address)
863 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
864 flags = self.wallet.get_address_flags(address)
865 item = QTreeWidgetItem( [ flags, address, label, balance, tx] )
867 item.setFont(0, QFont(MONOSPACE_FONT))
868 item.setFont(1, QFont(MONOSPACE_FONT))
869 item.setFont(3, QFont(MONOSPACE_FONT))
870 if address in self.wallet.frozen_addresses:
871 item.setBackgroundColor(1, QColor('lightblue'))
872 elif address in self.wallet.prioritized_addresses:
873 item.setBackgroundColor(1, QColor('lightgreen'))
874 if is_red and address in self.wallet.addresses:
875 item.setBackgroundColor(1, QColor('red'))
876 l.addTopLevelItem(item)
878 # we use column 1 because column 0 may be hidden
879 l.setCurrentItem(l.topLevelItem(0),1)
881 def show_contact_details(self, m):
882 a = self.wallet.aliases.get(m)
884 if a[0] in self.wallet.authorities.keys():
885 s = self.wallet.authorities.get(a[0])
888 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
889 QMessageBox.information(self, 'Alias', msg, 'OK')
891 def update_contacts_tab(self):
893 l = self.contacts_list
895 l.setColumnHidden(2, not self.detailed_view)
896 l.setColumnWidth(0, 350)
897 l.setColumnWidth(1, 330)
898 l.setColumnWidth(2, 100)
901 for alias, v in self.wallet.aliases.items():
903 alias_targets.append(target)
904 item = QTreeWidgetItem( [ target, alias, '-'] )
905 item.setBackgroundColor(0, QColor('lightgray'))
906 l.addTopLevelItem(item)
908 for address in self.wallet.addressbook:
909 if address in alias_targets: continue
910 label = self.wallet.labels.get(address,'')
912 for item in self.wallet.transactions.values():
913 if address in item['outputs'] : n=n+1
915 item = QTreeWidgetItem( [ address, label, tx] )
916 item.setFont(0, QFont(MONOSPACE_FONT))
917 l.addTopLevelItem(item)
919 l.setCurrentItem(l.topLevelItem(0))
921 def create_wall_tab(self):
922 self.textbox = textbox = QTextEdit(self)
923 textbox.setFont(QFont(MONOSPACE_FONT))
924 textbox.setReadOnly(True)
927 def create_status_bar(self):
929 sb.setFixedHeight(35)
931 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
932 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
934 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
935 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
936 sb.addPermanentWidget( self.status_button )
937 self.setStatusBar(sb)
939 def new_contact_dialog(self):
940 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
941 address = unicode(text)
943 if self.wallet.is_valid(address):
944 self.wallet.addressbook.append(address)
946 self.update_contacts_tab()
947 self.update_history_tab()
948 self.update_completions()
950 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
953 def show_seed_dialog(wallet, parent=None):
955 QMessageBox.information(parent, _('Message'),
956 _('No seed'), _('OK'))
959 if wallet.use_encryption:
960 password = parent.password_dialog()
967 seed = wallet.pw_decode(wallet.seed, password)
969 QMessageBox.warning(parent, _('Error'),
970 _('Incorrect Password'), _('OK'))
973 dialog = QDialog(None)
975 dialog.setWindowTitle("Electrum")
977 brainwallet = ' '.join(mnemonic.mn_encode(seed))
979 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
980 + _("Please write down or memorize these 12 words (order is important).") + " " \
981 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
982 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
984 main_text = QLabel(msg)
985 main_text.setWordWrap(True)
988 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
995 copy_function = lambda: app.clipboard().setText(brainwallet)
996 copy_button = QPushButton(_("Copy to Clipboard"))
997 copy_button.clicked.connect(copy_function)
999 show_qr_function = lambda: ElectrumWindow.show_seed_qrcode(seed)
1000 qr_button = QPushButton(_("View as QR Code"))
1001 qr_button.clicked.connect(show_qr_function)
1003 ok_button = QPushButton(_("OK"))
1004 ok_button.setDefault(True)
1005 ok_button.clicked.connect(dialog.accept)
1007 main_layout = QGridLayout()
1008 main_layout.addWidget(logo, 0, 0)
1009 main_layout.addWidget(main_text, 0, 1, 1, -1)
1010 main_layout.addWidget(copy_button, 1, 1)
1011 main_layout.addWidget(qr_button, 1, 2)
1012 main_layout.addWidget(ok_button, 1, 3)
1013 dialog.setLayout(main_layout)
1018 def show_seed_qrcode(seed):
1022 d.setWindowTitle(_("Seed"))
1023 d.setMinimumSize(270, 300)
1024 vbox = QVBoxLayout()
1025 vbox.addWidget(QRCodeWidget(seed))
1026 hbox = QHBoxLayout()
1028 b = QPushButton(_("OK"))
1030 b.clicked.connect(d.accept)
1032 vbox.addLayout(hbox)
1037 def show_address_qrcode(self,address):
1038 if not address: return
1041 d.setWindowTitle(address)
1042 d.setMinimumSize(270, 350)
1043 vbox = QVBoxLayout()
1044 qrw = QRCodeWidget(address)
1047 hbox = QHBoxLayout()
1048 amount_e = QLineEdit()
1049 hbox.addWidget(QLabel(_('Amount')))
1050 hbox.addWidget(amount_e)
1051 vbox.addLayout(hbox)
1053 #hbox = QHBoxLayout()
1054 #label_e = QLineEdit()
1055 #hbox.addWidget(QLabel('Label'))
1056 #hbox.addWidget(label_e)
1057 #vbox.addLayout(hbox)
1059 def amount_changed():
1060 amount = numbify(amount_e)
1061 #label = str( label_e.getText() )
1062 if amount is not None:
1063 qrw.set_addr('bitcoin:%s?amount=%s'%(address,str( Decimal(amount) /100000000)))
1065 qrw.set_addr( address )
1069 bmp.save_qrcode(qrw.qr, "qrcode.bmp")
1070 self.show_message(_("QR code saved to file") + " 'qrcode.bmp'")
1072 amount_e.textChanged.connect( amount_changed )
1074 hbox = QHBoxLayout()
1076 b = QPushButton(_("Save"))
1077 b.clicked.connect(do_save)
1079 b = QPushButton(_("Close"))
1081 b.clicked.connect(d.accept)
1083 vbox.addLayout(hbox)
1087 def question(self, msg):
1088 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1090 def show_message(self, msg):
1091 QMessageBox.information(self, _('Message'), msg, _('OK'))
1093 def password_dialog(self ):
1100 vbox = QVBoxLayout()
1101 msg = _('Please enter your password')
1102 vbox.addWidget(QLabel(msg))
1104 grid = QGridLayout()
1106 grid.addWidget(QLabel(_('Password')), 1, 0)
1107 grid.addWidget(pw, 1, 1)
1108 vbox.addLayout(grid)
1110 vbox.addLayout(ok_cancel_buttons(d))
1113 if not d.exec_(): return
1114 return unicode(pw.text())
1121 def change_password_dialog( wallet, parent=None ):
1124 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1132 new_pw = QLineEdit()
1133 new_pw.setEchoMode(2)
1134 conf_pw = QLineEdit()
1135 conf_pw.setEchoMode(2)
1137 vbox = QVBoxLayout()
1139 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'+_('To disable wallet encryption, enter an empty new password.')) if wallet.use_encryption else _('Your wallet keys are not encrypted')
1141 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'+_("Leave these fields empty if you want to disable encryption.")
1142 vbox.addWidget(QLabel(msg))
1144 grid = QGridLayout()
1147 if wallet.use_encryption:
1148 grid.addWidget(QLabel(_('Password')), 1, 0)
1149 grid.addWidget(pw, 1, 1)
1151 grid.addWidget(QLabel(_('New Password')), 2, 0)
1152 grid.addWidget(new_pw, 2, 1)
1154 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1155 grid.addWidget(conf_pw, 3, 1)
1156 vbox.addLayout(grid)
1158 vbox.addLayout(ok_cancel_buttons(d))
1161 if not d.exec_(): return
1163 password = unicode(pw.text()) if wallet.use_encryption else None
1164 new_password = unicode(new_pw.text())
1165 new_password2 = unicode(conf_pw.text())
1168 seed = wallet.pw_decode( wallet.seed, password)
1170 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1173 if new_password != new_password2:
1174 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1177 wallet.update_password(seed, password, new_password)
1180 def seed_dialog(wallet, parent=None):
1184 vbox = QVBoxLayout()
1185 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1186 vbox.addWidget(QLabel(msg))
1188 grid = QGridLayout()
1191 seed_e = QLineEdit()
1192 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1193 grid.addWidget(seed_e, 1, 1)
1197 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1198 grid.addWidget(gap_e, 2, 1)
1199 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1200 vbox.addLayout(grid)
1202 vbox.addLayout(ok_cancel_buttons(d))
1205 if not d.exec_(): return
1208 gap = int(unicode(gap_e.text()))
1210 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1214 seed = unicode(seed_e.text())
1217 print_error("Warning: Not hex, trying decode")
1219 seed = mnemonic.mn_decode( seed.split(' ') )
1221 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1224 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1227 wallet.seed = str(seed)
1228 #print repr(wallet.seed)
1229 wallet.gap_limit = gap
1234 def settings_dialog(self):
1237 vbox = QVBoxLayout()
1238 msg = _('Here are the settings of your wallet.') + '\n'\
1239 + _('For more explanations, click on the help buttons next to each field.')
1242 label.setFixedWidth(250)
1243 label.setWordWrap(True)
1244 label.setAlignment(Qt.AlignJustify)
1245 vbox.addWidget(label)
1247 grid = QGridLayout()
1249 vbox.addLayout(grid)
1251 fee_label = QLabel(_('Transaction fee'))
1252 grid.addWidget(fee_label, 2, 0)
1254 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1255 grid.addWidget(fee_e, 2, 1)
1256 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1257 + _('Recommended value') + ': 0.001'
1258 grid.addWidget(HelpButton(msg), 2, 2)
1259 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1260 if not self.config.is_modifiable('fee'):
1261 for w in [fee_e, fee_label]: w.setEnabled(False)
1263 nz_label = QLabel(_('Display zeros'))
1264 grid.addWidget(nz_label, 3, 0)
1266 nz_e.setText("%d"% self.wallet.num_zeros)
1267 grid.addWidget(nz_e, 3, 1)
1268 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1269 grid.addWidget(HelpButton(msg), 3, 2)
1270 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1271 if not self.config.is_modifiable('num_zeros'):
1272 for w in [nz_e, nz_label]: w.setEnabled(False)
1274 usechange_cb = QCheckBox(_('Use change addresses'))
1275 grid.addWidget(usechange_cb, 5, 0)
1276 usechange_cb.setChecked(self.wallet.use_change)
1277 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1278 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1280 gap_label = QLabel(_('Gap limit'))
1281 grid.addWidget(gap_label, 6, 0)
1283 gap_e.setText("%d"% self.wallet.gap_limit)
1284 grid.addWidget(gap_e, 6, 1)
1285 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1286 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1287 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1288 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1289 + _('Warning') + ': ' \
1290 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1291 + _('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'
1292 grid.addWidget(HelpButton(msg), 6, 2)
1293 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1294 if not self.config.is_modifiable('gap_limit'):
1295 for w in [gap_e, gap_label]: w.setEnabled(False)
1297 gui_label=QLabel(_('Default GUI') + ':')
1298 grid.addWidget(gui_label , 7, 0)
1299 gui_combo = QComboBox()
1300 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1301 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1302 if index==-1: index = 1
1303 gui_combo.setCurrentIndex(index)
1304 grid.addWidget(gui_combo, 7, 1)
1305 grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1306 if not self.config.is_modifiable('gui'):
1307 for w in [gui_combo, gui_label]: w.setEnabled(False)
1309 vbox.addLayout(ok_cancel_buttons(d))
1313 if not d.exec_(): return
1315 fee = unicode(fee_e.text())
1317 fee = int( 100000000 * Decimal(fee) )
1319 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1322 if self.wallet.fee != fee:
1323 self.wallet.fee = fee
1326 nz = unicode(nz_e.text())
1331 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1334 if self.wallet.num_zeros != nz:
1335 self.wallet.num_zeros = nz
1336 self.config.set_key('num_zeros', nz, True)
1337 self.update_history_tab()
1338 self.update_receive_tab()
1340 if self.wallet.use_change != usechange_cb.isChecked():
1341 self.wallet.use_change = usechange_cb.isChecked()
1342 self.config.set_key('use_change', self.wallet.use_change, True)
1345 n = int(gap_e.text())
1347 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1350 if self.wallet.gap_limit != n:
1351 r = self.wallet.change_gap_limit(n)
1353 self.update_receive_tab()
1354 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1356 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1358 self.config.set_key("gui", str(gui_combo.currentText()).lower(), True)
1363 def network_dialog(wallet, parent=None):
1364 interface = wallet.interface
1366 if interface.is_connected:
1367 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1369 status = _("Not connected")
1372 status = _("Please choose a server.")
1374 server = interface.server
1377 if not wallet.interface.servers:
1379 for x in DEFAULT_SERVERS:
1380 h,port,protocol = x.split(':')
1381 servers_list.append( (h,[(protocol,port)] ) )
1383 servers_list = wallet.interface.servers
1384 for item in servers_list:
1388 _protocol, _port = item2
1389 z[_protocol] = _port
1394 d.setWindowTitle(_('Server'))
1395 d.setMinimumSize(375, 20)
1397 vbox = QVBoxLayout()
1400 hbox = QHBoxLayout()
1402 l.setPixmap(QPixmap(":icons/network.png"))
1405 hbox.addWidget(QLabel(status))
1407 vbox.addLayout(hbox)
1411 grid = QGridLayout()
1413 vbox.addLayout(grid)
1416 server_protocol = QComboBox()
1417 server_host = QLineEdit()
1418 server_host.setFixedWidth(200)
1419 server_port = QLineEdit()
1420 server_port.setFixedWidth(60)
1422 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1423 protocol_letters = 'thsg'
1424 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1425 server_protocol.addItems(protocol_names)
1427 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1428 grid.addWidget(server_protocol, 0, 1)
1429 grid.addWidget(server_host, 0, 2)
1430 grid.addWidget(server_port, 0, 3)
1432 host, port, protocol = server.split(':')
1434 def change_protocol(p):
1435 protocol = protocol_letters[p]
1436 host = unicode(server_host.text())
1437 pp = plist.get(host,DEFAULT_PORTS)
1438 if protocol not in pp.keys():
1439 protocol = pp.keys()[0]
1441 server_host.setText( host )
1442 server_port.setText( port )
1444 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1446 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1447 servers_list_widget = QTreeWidget(parent)
1448 servers_list_widget.setHeaderLabels( [ label ] )
1449 servers_list_widget.setMaximumHeight(150)
1450 for _host, _x in servers_list:
1451 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host ] ))
1454 def change_server(host, protocol=None):
1455 pp = plist.get(host,DEFAULT_PORTS)
1457 port = pp.get(protocol)
1458 if not port: protocol = None
1461 if 't' in pp.keys():
1463 port = pp.get(protocol)
1465 protocol = pp.keys()[0]
1466 port = pp.get(protocol)
1468 server_host.setText( host )
1469 server_port.setText( port )
1470 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1472 if not plist: return
1473 for p in protocol_letters:
1474 i = protocol_letters.index(p)
1475 j = server_protocol.model().index(i,0)
1476 if p not in pp.keys():
1477 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1479 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1482 change_server(host,protocol)
1483 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1484 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1486 if not wallet.config.is_modifiable('server'):
1487 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1490 proxy_mode = QComboBox()
1491 proxy_host = QLineEdit()
1492 proxy_host.setFixedWidth(200)
1493 proxy_port = QLineEdit()
1494 proxy_port.setFixedWidth(60)
1495 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1497 def check_for_disable(index = False):
1498 if proxy_mode.currentText() != 'NONE':
1499 proxy_host.setEnabled(True)
1500 proxy_port.setEnabled(True)
1502 proxy_host.setEnabled(False)
1503 proxy_port.setEnabled(False)
1506 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1508 if not wallet.config.is_modifiable('proxy'):
1509 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1511 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1512 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1513 proxy_host.setText(proxy_config.get("host"))
1514 proxy_port.setText(proxy_config.get("port"))
1516 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1517 grid.addWidget(proxy_mode, 2, 1)
1518 grid.addWidget(proxy_host, 2, 2)
1519 grid.addWidget(proxy_port, 2, 3)
1522 vbox.addLayout(ok_cancel_buttons(d))
1525 if not d.exec_(): return
1527 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1528 if proxy_mode.currentText() != 'NONE':
1529 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1533 wallet.config.set_key("proxy", proxy, True)
1534 wallet.config.set_key("server", server, True)
1535 interface.set_server(server, proxy)
1539 def closeEvent(self, event):
1541 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1547 def __init__(self, wallet, config, app=None):
1548 self.wallet = wallet
1549 self.config = config
1551 self.app = QApplication(sys.argv)
1553 def server_list_changed(self):
1557 def restore_or_create(self):
1559 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1560 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1561 if r==2: return False
1563 is_recovery = (r==1)
1564 wallet = self.wallet
1565 # ask for the server.
1566 if not ElectrumWindow.network_dialog( wallet, parent=None ): return False
1568 # wait until we are connected, because the user might have selected another server
1569 if not wallet.interface.is_connected:
1570 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1571 waiting_dialog(waiting)
1573 waiting = lambda: False if wallet.up_to_date else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1574 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1577 wallet.new_seed(None)
1578 wallet.init_mpk( wallet.seed )
1579 wallet.up_to_date_event.clear()
1580 wallet.up_to_date = False
1581 wallet.interface.poke('synchronizer')
1582 waiting_dialog(waiting)
1583 # run a dialog indicating the seed, ask the user to remember it
1584 ElectrumWindow.show_seed_dialog(wallet)
1586 ElectrumWindow.change_password_dialog(wallet)
1588 # ask for seed and gap.
1589 if not ElectrumWindow.seed_dialog( wallet ): return False
1590 wallet.init_mpk( wallet.seed )
1591 wallet.up_to_date_event.clear()
1592 wallet.up_to_date = False
1593 wallet.interface.poke('synchronizer')
1594 waiting_dialog(waiting)
1595 if wallet.is_found():
1596 # history and addressbook
1597 wallet.fill_addressbook()
1598 print "Recovery successful"
1601 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1609 w = ElectrumWindow(self.wallet, self.config)
1610 if url: w.set_url(url)