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 if h == ['*']: h = []
850 for tx_hash, tx_height in h:
851 tx = self.wallet.transactions.get(tx_hash)
856 if address in self.wallet.addresses:
858 if gap > self.wallet.gap_limit:
861 if address in self.wallet.addresses:
864 c, u = self.wallet.get_addr_balance(address)
865 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
866 flags = self.wallet.get_address_flags(address)
867 item = QTreeWidgetItem( [ flags, address, label, balance, tx] )
869 item.setFont(0, QFont(MONOSPACE_FONT))
870 item.setFont(1, QFont(MONOSPACE_FONT))
871 item.setFont(3, QFont(MONOSPACE_FONT))
872 if address in self.wallet.frozen_addresses:
873 item.setBackgroundColor(1, QColor('lightblue'))
874 elif address in self.wallet.prioritized_addresses:
875 item.setBackgroundColor(1, QColor('lightgreen'))
876 if is_red and address in self.wallet.addresses:
877 item.setBackgroundColor(1, QColor('red'))
878 l.addTopLevelItem(item)
880 # we use column 1 because column 0 may be hidden
881 l.setCurrentItem(l.topLevelItem(0),1)
883 def show_contact_details(self, m):
884 a = self.wallet.aliases.get(m)
886 if a[0] in self.wallet.authorities.keys():
887 s = self.wallet.authorities.get(a[0])
890 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
891 QMessageBox.information(self, 'Alias', msg, 'OK')
893 def update_contacts_tab(self):
895 l = self.contacts_list
897 l.setColumnHidden(2, not self.detailed_view)
898 l.setColumnWidth(0, 350)
899 l.setColumnWidth(1, 330)
900 l.setColumnWidth(2, 100)
903 for alias, v in self.wallet.aliases.items():
905 alias_targets.append(target)
906 item = QTreeWidgetItem( [ target, alias, '-'] )
907 item.setBackgroundColor(0, QColor('lightgray'))
908 l.addTopLevelItem(item)
910 for address in self.wallet.addressbook:
911 if address in alias_targets: continue
912 label = self.wallet.labels.get(address,'')
914 for item in self.wallet.transactions.values():
915 if address in item['outputs'] : n=n+1
917 item = QTreeWidgetItem( [ address, label, tx] )
918 item.setFont(0, QFont(MONOSPACE_FONT))
919 l.addTopLevelItem(item)
921 l.setCurrentItem(l.topLevelItem(0))
923 def create_wall_tab(self):
924 self.textbox = textbox = QTextEdit(self)
925 textbox.setFont(QFont(MONOSPACE_FONT))
926 textbox.setReadOnly(True)
929 def create_status_bar(self):
931 sb.setFixedHeight(35)
933 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
934 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
936 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
937 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
938 sb.addPermanentWidget( self.status_button )
939 self.setStatusBar(sb)
941 def new_contact_dialog(self):
942 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
943 address = unicode(text)
945 if self.wallet.is_valid(address):
946 self.wallet.addressbook.append(address)
948 self.update_contacts_tab()
949 self.update_history_tab()
950 self.update_completions()
952 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
955 def show_seed_dialog(wallet, parent=None):
957 QMessageBox.information(parent, _('Message'),
958 _('No seed'), _('OK'))
961 if wallet.use_encryption:
962 password = parent.password_dialog()
969 seed = wallet.pw_decode(wallet.seed, password)
971 QMessageBox.warning(parent, _('Error'),
972 _('Incorrect Password'), _('OK'))
975 dialog = QDialog(None)
977 dialog.setWindowTitle("Electrum")
979 brainwallet = ' '.join(mnemonic.mn_encode(seed))
981 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
982 + _("Please write down or memorize these 12 words (order is important).") + " " \
983 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
984 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
986 main_text = QLabel(msg)
987 main_text.setWordWrap(True)
990 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
997 copy_function = lambda: app.clipboard().setText(brainwallet)
998 copy_button = QPushButton(_("Copy to Clipboard"))
999 copy_button.clicked.connect(copy_function)
1001 show_qr_function = lambda: ElectrumWindow.show_seed_qrcode(seed)
1002 qr_button = QPushButton(_("View as QR Code"))
1003 qr_button.clicked.connect(show_qr_function)
1005 ok_button = QPushButton(_("OK"))
1006 ok_button.setDefault(True)
1007 ok_button.clicked.connect(dialog.accept)
1009 main_layout = QGridLayout()
1010 main_layout.addWidget(logo, 0, 0)
1011 main_layout.addWidget(main_text, 0, 1, 1, -1)
1012 main_layout.addWidget(copy_button, 1, 1)
1013 main_layout.addWidget(qr_button, 1, 2)
1014 main_layout.addWidget(ok_button, 1, 3)
1015 dialog.setLayout(main_layout)
1020 def show_seed_qrcode(seed):
1024 d.setWindowTitle(_("Seed"))
1025 d.setMinimumSize(270, 300)
1026 vbox = QVBoxLayout()
1027 vbox.addWidget(QRCodeWidget(seed))
1028 hbox = QHBoxLayout()
1030 b = QPushButton(_("OK"))
1032 b.clicked.connect(d.accept)
1034 vbox.addLayout(hbox)
1039 def show_address_qrcode(self,address):
1040 if not address: return
1043 d.setWindowTitle(address)
1044 d.setMinimumSize(270, 350)
1045 vbox = QVBoxLayout()
1046 qrw = QRCodeWidget(address)
1049 hbox = QHBoxLayout()
1050 amount_e = QLineEdit()
1051 hbox.addWidget(QLabel(_('Amount')))
1052 hbox.addWidget(amount_e)
1053 vbox.addLayout(hbox)
1055 #hbox = QHBoxLayout()
1056 #label_e = QLineEdit()
1057 #hbox.addWidget(QLabel('Label'))
1058 #hbox.addWidget(label_e)
1059 #vbox.addLayout(hbox)
1061 def amount_changed():
1062 amount = numbify(amount_e)
1063 #label = str( label_e.getText() )
1064 if amount is not None:
1065 qrw.set_addr('bitcoin:%s?amount=%s'%(address,str( Decimal(amount) /100000000)))
1067 qrw.set_addr( address )
1071 bmp.save_qrcode(qrw.qr, "qrcode.bmp")
1072 self.show_message(_("QR code saved to file") + " 'qrcode.bmp'")
1074 amount_e.textChanged.connect( amount_changed )
1076 hbox = QHBoxLayout()
1078 b = QPushButton(_("Save"))
1079 b.clicked.connect(do_save)
1081 b = QPushButton(_("Close"))
1083 b.clicked.connect(d.accept)
1085 vbox.addLayout(hbox)
1089 def question(self, msg):
1090 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1092 def show_message(self, msg):
1093 QMessageBox.information(self, _('Message'), msg, _('OK'))
1095 def password_dialog(self ):
1102 vbox = QVBoxLayout()
1103 msg = _('Please enter your password')
1104 vbox.addWidget(QLabel(msg))
1106 grid = QGridLayout()
1108 grid.addWidget(QLabel(_('Password')), 1, 0)
1109 grid.addWidget(pw, 1, 1)
1110 vbox.addLayout(grid)
1112 vbox.addLayout(ok_cancel_buttons(d))
1115 if not d.exec_(): return
1116 return unicode(pw.text())
1123 def change_password_dialog( wallet, parent=None ):
1126 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1134 new_pw = QLineEdit()
1135 new_pw.setEchoMode(2)
1136 conf_pw = QLineEdit()
1137 conf_pw.setEchoMode(2)
1139 vbox = QVBoxLayout()
1141 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')
1143 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'+_("Leave these fields empty if you want to disable encryption.")
1144 vbox.addWidget(QLabel(msg))
1146 grid = QGridLayout()
1149 if wallet.use_encryption:
1150 grid.addWidget(QLabel(_('Password')), 1, 0)
1151 grid.addWidget(pw, 1, 1)
1153 grid.addWidget(QLabel(_('New Password')), 2, 0)
1154 grid.addWidget(new_pw, 2, 1)
1156 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1157 grid.addWidget(conf_pw, 3, 1)
1158 vbox.addLayout(grid)
1160 vbox.addLayout(ok_cancel_buttons(d))
1163 if not d.exec_(): return
1165 password = unicode(pw.text()) if wallet.use_encryption else None
1166 new_password = unicode(new_pw.text())
1167 new_password2 = unicode(conf_pw.text())
1170 seed = wallet.pw_decode( wallet.seed, password)
1172 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1175 if new_password != new_password2:
1176 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1179 wallet.update_password(seed, password, new_password)
1182 def seed_dialog(wallet, parent=None):
1186 vbox = QVBoxLayout()
1187 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1188 vbox.addWidget(QLabel(msg))
1190 grid = QGridLayout()
1193 seed_e = QLineEdit()
1194 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1195 grid.addWidget(seed_e, 1, 1)
1199 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1200 grid.addWidget(gap_e, 2, 1)
1201 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1202 vbox.addLayout(grid)
1204 vbox.addLayout(ok_cancel_buttons(d))
1207 if not d.exec_(): return
1210 gap = int(unicode(gap_e.text()))
1212 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1216 seed = unicode(seed_e.text())
1219 print_error("Warning: Not hex, trying decode")
1221 seed = mnemonic.mn_decode( seed.split(' ') )
1223 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1226 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1229 wallet.seed = str(seed)
1230 #print repr(wallet.seed)
1231 wallet.gap_limit = gap
1236 def settings_dialog(self):
1239 vbox = QVBoxLayout()
1240 msg = _('Here are the settings of your wallet.') + '\n'\
1241 + _('For more explanations, click on the help buttons next to each field.')
1244 label.setFixedWidth(250)
1245 label.setWordWrap(True)
1246 label.setAlignment(Qt.AlignJustify)
1247 vbox.addWidget(label)
1249 grid = QGridLayout()
1251 vbox.addLayout(grid)
1253 fee_label = QLabel(_('Transaction fee'))
1254 grid.addWidget(fee_label, 2, 0)
1256 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1257 grid.addWidget(fee_e, 2, 1)
1258 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1259 + _('Recommended value') + ': 0.001'
1260 grid.addWidget(HelpButton(msg), 2, 2)
1261 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1262 if not self.config.is_modifiable('fee'):
1263 for w in [fee_e, fee_label]: w.setEnabled(False)
1265 nz_label = QLabel(_('Display zeros'))
1266 grid.addWidget(nz_label, 3, 0)
1268 nz_e.setText("%d"% self.wallet.num_zeros)
1269 grid.addWidget(nz_e, 3, 1)
1270 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1271 grid.addWidget(HelpButton(msg), 3, 2)
1272 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1273 if not self.config.is_modifiable('num_zeros'):
1274 for w in [nz_e, nz_label]: w.setEnabled(False)
1276 usechange_cb = QCheckBox(_('Use change addresses'))
1277 grid.addWidget(usechange_cb, 5, 0)
1278 usechange_cb.setChecked(self.wallet.use_change)
1279 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1280 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1282 gap_label = QLabel(_('Gap limit'))
1283 grid.addWidget(gap_label, 6, 0)
1285 gap_e.setText("%d"% self.wallet.gap_limit)
1286 grid.addWidget(gap_e, 6, 1)
1287 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1288 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1289 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1290 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1291 + _('Warning') + ': ' \
1292 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1293 + _('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'
1294 grid.addWidget(HelpButton(msg), 6, 2)
1295 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1296 if not self.config.is_modifiable('gap_limit'):
1297 for w in [gap_e, gap_label]: w.setEnabled(False)
1299 gui_label=QLabel(_('Default GUI') + ':')
1300 grid.addWidget(gui_label , 7, 0)
1301 gui_combo = QComboBox()
1302 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1303 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1304 if index==-1: index = 1
1305 gui_combo.setCurrentIndex(index)
1306 grid.addWidget(gui_combo, 7, 1)
1307 grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1308 if not self.config.is_modifiable('gui'):
1309 for w in [gui_combo, gui_label]: w.setEnabled(False)
1311 vbox.addLayout(ok_cancel_buttons(d))
1315 if not d.exec_(): return
1317 fee = unicode(fee_e.text())
1319 fee = int( 100000000 * Decimal(fee) )
1321 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1324 if self.wallet.fee != fee:
1325 self.wallet.fee = fee
1328 nz = unicode(nz_e.text())
1333 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1336 if self.wallet.num_zeros != nz:
1337 self.wallet.num_zeros = nz
1338 self.config.set_key('num_zeros', nz, True)
1339 self.update_history_tab()
1340 self.update_receive_tab()
1342 if self.wallet.use_change != usechange_cb.isChecked():
1343 self.wallet.use_change = usechange_cb.isChecked()
1344 self.config.set_key('use_change', self.wallet.use_change, True)
1347 n = int(gap_e.text())
1349 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1352 if self.wallet.gap_limit != n:
1353 r = self.wallet.change_gap_limit(n)
1355 self.update_receive_tab()
1356 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1358 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1360 self.config.set_key("gui", str(gui_combo.currentText()).lower(), True)
1365 def network_dialog(wallet, parent=None):
1366 interface = wallet.interface
1368 if interface.is_connected:
1369 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1371 status = _("Not connected")
1374 status = _("Please choose a server.")
1376 server = interface.server
1379 if not wallet.interface.servers:
1381 for x in DEFAULT_SERVERS:
1382 h,port,protocol = x.split(':')
1383 servers_list.append( (h,[(protocol,port)] ) )
1385 servers_list = wallet.interface.servers
1386 for item in servers_list:
1390 _protocol, _port = item2
1391 z[_protocol] = _port
1396 d.setWindowTitle(_('Server'))
1397 d.setMinimumSize(375, 20)
1399 vbox = QVBoxLayout()
1402 hbox = QHBoxLayout()
1404 l.setPixmap(QPixmap(":icons/network.png"))
1407 hbox.addWidget(QLabel(status))
1409 vbox.addLayout(hbox)
1413 grid = QGridLayout()
1415 vbox.addLayout(grid)
1418 server_protocol = QComboBox()
1419 server_host = QLineEdit()
1420 server_host.setFixedWidth(200)
1421 server_port = QLineEdit()
1422 server_port.setFixedWidth(60)
1424 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1425 protocol_letters = 'thsg'
1426 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1427 server_protocol.addItems(protocol_names)
1429 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1430 grid.addWidget(server_protocol, 0, 1)
1431 grid.addWidget(server_host, 0, 2)
1432 grid.addWidget(server_port, 0, 3)
1434 host, port, protocol = server.split(':')
1436 def change_protocol(p):
1437 protocol = protocol_letters[p]
1438 host = unicode(server_host.text())
1439 pp = plist.get(host,DEFAULT_PORTS)
1440 if protocol not in pp.keys():
1441 protocol = pp.keys()[0]
1443 server_host.setText( host )
1444 server_port.setText( port )
1446 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1448 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1449 servers_list_widget = QTreeWidget(parent)
1450 servers_list_widget.setHeaderLabels( [ label ] )
1451 servers_list_widget.setMaximumHeight(150)
1452 for _host, _x in servers_list:
1453 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host ] ))
1456 def change_server(host, protocol=None):
1457 pp = plist.get(host,DEFAULT_PORTS)
1459 port = pp.get(protocol)
1460 if not port: protocol = None
1463 if 't' in pp.keys():
1465 port = pp.get(protocol)
1467 protocol = pp.keys()[0]
1468 port = pp.get(protocol)
1470 server_host.setText( host )
1471 server_port.setText( port )
1472 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1474 if not plist: return
1475 for p in protocol_letters:
1476 i = protocol_letters.index(p)
1477 j = server_protocol.model().index(i,0)
1478 if p not in pp.keys():
1479 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1481 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1484 change_server(host,protocol)
1485 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1486 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1488 if not wallet.config.is_modifiable('server'):
1489 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1492 proxy_mode = QComboBox()
1493 proxy_host = QLineEdit()
1494 proxy_host.setFixedWidth(200)
1495 proxy_port = QLineEdit()
1496 proxy_port.setFixedWidth(60)
1497 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1499 def check_for_disable(index = False):
1500 if proxy_mode.currentText() != 'NONE':
1501 proxy_host.setEnabled(True)
1502 proxy_port.setEnabled(True)
1504 proxy_host.setEnabled(False)
1505 proxy_port.setEnabled(False)
1508 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1510 if not wallet.config.is_modifiable('proxy'):
1511 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1513 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1514 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1515 proxy_host.setText(proxy_config.get("host"))
1516 proxy_port.setText(proxy_config.get("port"))
1518 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1519 grid.addWidget(proxy_mode, 2, 1)
1520 grid.addWidget(proxy_host, 2, 2)
1521 grid.addWidget(proxy_port, 2, 3)
1524 vbox.addLayout(ok_cancel_buttons(d))
1527 if not d.exec_(): return
1529 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1530 if proxy_mode.currentText() != 'NONE':
1531 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1535 wallet.config.set_key("proxy", proxy, True)
1536 wallet.config.set_key("server", server, True)
1537 interface.set_server(server, proxy)
1541 def closeEvent(self, event):
1543 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1549 def __init__(self, wallet, config, app=None):
1550 self.wallet = wallet
1551 self.config = config
1553 self.app = QApplication(sys.argv)
1555 def server_list_changed(self):
1559 def restore_or_create(self):
1561 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1562 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1563 if r==2: return False
1565 is_recovery = (r==1)
1566 wallet = self.wallet
1567 # ask for the server.
1568 if not ElectrumWindow.network_dialog( wallet, parent=None ): return False
1570 # wait until we are connected, because the user might have selected another server
1571 if not wallet.interface.is_connected:
1572 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1573 waiting_dialog(waiting)
1575 waiting = lambda: False if wallet.up_to_date else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1576 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1579 wallet.new_seed(None)
1580 wallet.init_mpk( wallet.seed )
1581 wallet.up_to_date_event.clear()
1582 wallet.up_to_date = False
1583 wallet.interface.poke('synchronizer')
1584 waiting_dialog(waiting)
1585 # run a dialog indicating the seed, ask the user to remember it
1586 ElectrumWindow.show_seed_dialog(wallet)
1588 ElectrumWindow.change_password_dialog(wallet)
1590 # ask for seed and gap.
1591 if not ElectrumWindow.seed_dialog( wallet ): return False
1592 wallet.init_mpk( wallet.seed )
1593 wallet.up_to_date_event.clear()
1594 wallet.up_to_date = False
1595 wallet.interface.poke('synchronizer')
1596 waiting_dialog(waiting)
1597 if wallet.is_found():
1598 # history and addressbook
1599 wallet.fill_addressbook()
1600 print "Recovery successful"
1603 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1611 w = ElectrumWindow(self.wallet, self.config)
1612 if url: w.set_url(url)