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)
336 timestamp = tx.get('timestamp')
337 if conf and timestamp:
338 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
342 inputs = map(lambda x: x.get('address'), tx['inputs'])
343 outputs = map(lambda x: x.get('address'), tx['outputs'])
344 tx_details = _("Transaction Details") +"\n\n" \
345 + "Transaction ID:\n" + tx_hash + "\n\n" \
346 + "Status: %d confirmations\n\n"%conf \
347 + "Date: %s\n\n"%time_str \
348 + "Inputs:\n-"+ '\n-'.join(inputs) + "\n\n" \
349 + "Outputs:\n-"+ '\n-'.join(outputs)
351 r = self.wallet.receipts.get(tx_hash)
353 tx_details += "\n_______________________________________" \
354 + '\n\nSigned URI: ' + r[2] \
355 + "\n\nSigned by: " + r[0] \
356 + '\n\nSignature: ' + r[1]
358 QMessageBox.information(self, 'Details', tx_details, 'OK')
361 def tx_label_clicked(self, item, column):
362 if column==2 and item.isSelected():
363 tx_hash = str(item.toolTip(0))
365 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
366 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
367 self.history_list.editItem( item, column )
368 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
371 def tx_label_changed(self, item, column):
375 tx_hash = str(item.toolTip(0))
376 tx = self.wallet.transactions.get(tx_hash)
377 s = self.wallet.labels.get(tx_hash)
378 text = unicode( item.text(2) )
380 self.wallet.labels[tx_hash] = text
381 item.setForeground(2, QBrush(QColor('black')))
383 if s: self.wallet.labels.pop(tx_hash)
384 text = self.wallet.get_default_label(tx_hash)
385 item.setText(2, text)
386 item.setForeground(2, QBrush(QColor('gray')))
389 def edit_label(self, is_recv):
390 l = self.receive_list if is_recv else self.contacts_list
391 c = 2 if is_recv else 1
392 item = l.currentItem()
393 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
394 l.editItem( item, c )
395 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
397 def address_label_clicked(self, item, column, l, column_addr, column_label):
398 if column==column_label and item.isSelected():
399 addr = unicode( item.text(column_addr) )
400 label = unicode( item.text(column_label) )
401 if label in self.wallet.aliases.keys():
403 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
404 l.editItem( item, column )
405 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
407 def address_label_changed(self, item, column, l, column_addr, column_label):
408 addr = unicode( item.text(column_addr) )
409 text = unicode( item.text(column_label) )
413 if text not in self.wallet.aliases.keys():
414 old_addr = self.wallet.labels.get(text)
416 self.wallet.labels[addr] = text
419 print_error("Error: This is one of your aliases")
420 label = self.wallet.labels.get(addr,'')
421 item.setText(column_label, QString(label))
423 s = self.wallet.labels.get(addr)
425 self.wallet.labels.pop(addr)
429 self.wallet.update_tx_labels()
430 self.update_history_tab()
431 self.update_completions()
434 def update_history_tab(self):
435 self.history_list.clear()
437 for tx in self.wallet.get_tx_history():
438 tx_hash = tx['tx_hash']
439 conf = self.wallet.verifier.get_confirmations(tx_hash)
442 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
448 icon = QIcon(":icons/unconfirmed.png")
450 icon = QIcon(":icons/clock%d.png"%conf)
452 icon = QIcon(":icons/confirmed.png")
455 icon = QIcon(":icons/unconfirmed.png")
456 v = self.wallet.get_tx_value(tx_hash)
458 label, is_default_label = self.wallet.get_label(tx_hash)
460 item = QTreeWidgetItem( [ '', time_str, label, format_satoshis(v,True,self.wallet.num_zeros), format_satoshis(balance,False,self.wallet.num_zeros)] )
461 item.setFont(2, QFont(MONOSPACE_FONT))
462 item.setFont(3, QFont(MONOSPACE_FONT))
463 item.setFont(4, QFont(MONOSPACE_FONT))
464 item.setToolTip(0, tx_hash)
466 item.setForeground(2, QBrush(QColor('grey')))
468 item.setIcon(0, icon)
469 self.history_list.insertTopLevelItem(0,item)
471 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
474 def create_send_tab(self):
479 grid.setColumnMinimumWidth(3,300)
480 grid.setColumnStretch(5,1)
482 self.payto_e = QLineEdit()
483 grid.addWidget(QLabel(_('Pay to')), 1, 0)
484 grid.addWidget(self.payto_e, 1, 1, 1, 3)
487 qrcode = qrscanner.scan_qr()
488 if 'address' in qrcode:
489 self.payto_e.setText(qrcode['address'])
490 if 'amount' in qrcode:
491 self.amount_e.setText(str(qrcode['amount']))
492 if 'label' in qrcode:
493 self.message_e.setText(qrcode['label'])
494 if 'message' in qrcode:
495 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
498 if qrscanner.is_available():
499 b = QPushButton(_("Scan QR code"))
500 b.clicked.connect(fill_from_qr)
501 grid.addWidget(b, 1, 5)
503 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)
505 completer = QCompleter()
506 completer.setCaseSensitivity(False)
507 self.payto_e.setCompleter(completer)
508 completer.setModel(self.completions)
510 self.message_e = QLineEdit()
511 grid.addWidget(QLabel(_('Description')), 2, 0)
512 grid.addWidget(self.message_e, 2, 1, 1, 3)
513 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)
515 self.amount_e = QLineEdit()
516 grid.addWidget(QLabel(_('Amount')), 3, 0)
517 grid.addWidget(self.amount_e, 3, 1, 1, 2)
518 grid.addWidget(HelpButton(
519 _('Amount to be sent.') + '\n\n' \
520 + _('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)
522 self.fee_e = QLineEdit()
523 grid.addWidget(QLabel(_('Fee')), 4, 0)
524 grid.addWidget(self.fee_e, 4, 1, 1, 2)
525 grid.addWidget(HelpButton(
526 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
527 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
528 + _('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)
530 b = EnterButton(_("Send"), self.do_send)
531 grid.addWidget(b, 6, 1)
533 b = EnterButton(_("Clear"),self.do_clear)
534 grid.addWidget(b, 6, 2)
536 self.payto_sig = QLabel('')
537 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
539 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
540 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
549 def entry_changed( is_fee ):
550 self.funds_error = False
551 amount = numbify(self.amount_e)
552 fee = numbify(self.fee_e)
553 if not is_fee: fee = None
556 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
558 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
561 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
564 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
565 self.funds_error = True
566 self.amount_e.setPalette(palette)
567 self.fee_e.setPalette(palette)
569 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
570 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
575 def update_completions(self):
577 for addr,label in self.wallet.labels.items():
578 if addr in self.wallet.addressbook:
579 l.append( label + ' <' + addr + '>')
580 l = l + self.wallet.aliases.keys()
582 self.completions.setStringList(l)
588 label = unicode( self.message_e.text() )
589 r = unicode( self.payto_e.text() )
593 m1 = re.match(ALIAS_REGEXP, r)
594 # label or alias, with address in brackets
595 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
598 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
602 to_address = m2.group(2)
606 if not self.wallet.is_valid(to_address):
607 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
611 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
613 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
616 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
618 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
621 if self.wallet.use_encryption:
622 password = self.password_dialog()
629 tx = self.wallet.mktx( to_address, amount, label, password, fee)
630 except BaseException, e:
631 self.show_message(str(e))
634 h = self.wallet.send_tx(tx)
635 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
636 status, msg = self.wallet.receive_tx( h )
639 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
641 self.update_contacts_tab()
643 QMessageBox.warning(self, _('Error'), msg, _('OK'))
646 def set_url(self, url):
647 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
648 self.tabs.setCurrentIndex(1)
649 label = self.wallet.labels.get(payto)
650 m_addr = label + ' <'+ payto+'>' if label else payto
651 self.payto_e.setText(m_addr)
653 self.message_e.setText(message)
654 self.amount_e.setText(amount)
656 self.set_frozen(self.payto_e,True)
657 self.set_frozen(self.amount_e,True)
658 self.set_frozen(self.message_e,True)
659 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
661 self.payto_sig.setVisible(False)
664 self.payto_sig.setVisible(False)
665 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
667 self.set_frozen(e,False)
669 def set_frozen(self,entry,frozen):
671 entry.setReadOnly(True)
672 entry.setFrame(False)
674 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
675 entry.setPalette(palette)
677 entry.setReadOnly(False)
680 palette.setColor(entry.backgroundRole(), QColor('white'))
681 entry.setPalette(palette)
684 def toggle_freeze(self,addr):
686 if addr in self.wallet.frozen_addresses:
687 self.wallet.unfreeze(addr)
689 self.wallet.freeze(addr)
690 self.update_receive_tab()
692 def toggle_priority(self,addr):
694 if addr in self.wallet.prioritized_addresses:
695 self.wallet.unprioritize(addr)
697 self.wallet.prioritize(addr)
698 self.update_receive_tab()
701 def create_list_tab(self, headers):
702 "generic tab creation method"
703 l = MyTreeWidget(self)
704 l.setColumnCount( len(headers) )
705 l.setHeaderLabels( headers )
715 vbox.addWidget(buttons)
720 buttons.setLayout(hbox)
725 def create_receive_tab(self):
726 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Balance'), _('Tx')])
727 l.setContextMenuPolicy(Qt.CustomContextMenu)
728 l.customContextMenuRequested.connect(self.create_receive_menu)
729 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
730 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
731 self.receive_list = l
732 self.receive_buttons_hbox = hbox
733 self.details_button = EnterButton(self.details_button_text(), self.toggle_detailed_view)
734 hbox.addWidget(self.details_button)
738 def details_button_text(self):
739 return _('Hide details') if self.detailed_view else _('Show details')
741 def toggle_detailed_view(self):
742 self.detailed_view = not self.detailed_view
743 self.config.set_key('qt_detailed_view', self.detailed_view, True)
745 self.details_button.setText(self.details_button_text())
747 self.update_receive_tab()
748 self.update_contacts_tab()
751 def create_contacts_tab(self):
752 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
753 l.setContextMenuPolicy(Qt.CustomContextMenu)
754 l.customContextMenuRequested.connect(self.create_contact_menu)
755 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
756 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
757 self.contacts_list = l
758 self.contacts_buttons_hbox = hbox
759 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
764 def create_receive_menu(self, position):
765 # fixme: this function apparently has a side effect.
766 # if it is not called the menu pops up several times
767 #self.receive_list.selectedIndexes()
769 item = self.receive_list.itemAt(position)
771 addr = unicode(item.text(1))
773 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
774 menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
775 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
777 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
778 menu.addAction(t, lambda: self.toggle_freeze(addr))
779 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
780 menu.addAction(t, lambda: self.toggle_priority(addr))
781 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
784 def payto(self, x, is_alias):
791 label = self.wallet.labels.get(addr)
792 m_addr = label + ' <' + addr + '>' if label else addr
793 self.tabs.setCurrentIndex(1)
794 self.payto_e.setText(m_addr)
795 self.amount_e.setFocus()
797 def delete_contact(self, x, is_alias):
798 if self.question("Do you want to remove %s from your list of contacts?"%x):
799 if not is_alias and x in self.wallet.addressbook:
800 self.wallet.addressbook.remove(x)
801 if x in self.wallet.labels.keys():
802 self.wallet.labels.pop(x)
803 elif is_alias and x in self.wallet.aliases:
804 self.wallet.aliases.pop(x)
805 self.update_history_tab()
806 self.update_contacts_tab()
807 self.update_completions()
809 def create_contact_menu(self, position):
810 # fixme: this function apparently has a side effect.
811 # if it is not called the menu pops up several times
812 #self.contacts_list.selectedIndexes()
814 item = self.contacts_list.itemAt(position)
816 addr = unicode(item.text(0))
817 label = unicode(item.text(1))
818 is_alias = label in self.wallet.aliases.keys()
819 x = label if is_alias else addr
821 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
822 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
823 menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
825 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
827 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
828 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
829 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
832 def update_receive_tab(self):
833 l = self.receive_list
835 l.setColumnHidden(0,not self.detailed_view)
836 l.setColumnHidden(3,not self.detailed_view)
837 l.setColumnHidden(4,not self.detailed_view)
838 l.setColumnWidth(0, 50)
839 l.setColumnWidth(1, 310)
840 l.setColumnWidth(2, 250)
841 l.setColumnWidth(3, 130)
842 l.setColumnWidth(4, 10)
846 for address in self.wallet.all_addresses():
848 if self.wallet.is_change(address) and not self.detailed_view:
851 label = self.wallet.labels.get(address,'')
853 h = self.wallet.history.get(address,[])
856 for tx_hash, tx_height in h:
857 tx = self.wallet.transactions.get(tx_hash)
865 if address in self.wallet.addresses:
867 if gap > self.wallet.gap_limit:
870 if address in self.wallet.addresses:
873 c, u = self.wallet.get_addr_balance(address)
874 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
875 flags = self.wallet.get_address_flags(address)
876 item = QTreeWidgetItem( [ flags, address, label, balance, num_tx] )
878 item.setFont(0, QFont(MONOSPACE_FONT))
879 item.setFont(1, QFont(MONOSPACE_FONT))
880 item.setFont(3, QFont(MONOSPACE_FONT))
881 if address in self.wallet.frozen_addresses:
882 item.setBackgroundColor(1, QColor('lightblue'))
883 elif address in self.wallet.prioritized_addresses:
884 item.setBackgroundColor(1, QColor('lightgreen'))
885 if is_red and address in self.wallet.addresses:
886 item.setBackgroundColor(1, QColor('red'))
887 l.addTopLevelItem(item)
889 # we use column 1 because column 0 may be hidden
890 l.setCurrentItem(l.topLevelItem(0),1)
892 def show_contact_details(self, m):
893 a = self.wallet.aliases.get(m)
895 if a[0] in self.wallet.authorities.keys():
896 s = self.wallet.authorities.get(a[0])
899 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
900 QMessageBox.information(self, 'Alias', msg, 'OK')
902 def update_contacts_tab(self):
904 l = self.contacts_list
906 l.setColumnHidden(2, not self.detailed_view)
907 l.setColumnWidth(0, 350)
908 l.setColumnWidth(1, 330)
909 l.setColumnWidth(2, 100)
912 for alias, v in self.wallet.aliases.items():
914 alias_targets.append(target)
915 item = QTreeWidgetItem( [ target, alias, '-'] )
916 item.setBackgroundColor(0, QColor('lightgray'))
917 l.addTopLevelItem(item)
919 for address in self.wallet.addressbook:
920 if address in alias_targets: continue
921 label = self.wallet.labels.get(address,'')
923 for item in self.wallet.transactions.values():
924 if address in item['outputs'] : n=n+1
926 item = QTreeWidgetItem( [ address, label, tx] )
927 item.setFont(0, QFont(MONOSPACE_FONT))
928 l.addTopLevelItem(item)
930 l.setCurrentItem(l.topLevelItem(0))
932 def create_wall_tab(self):
933 self.textbox = textbox = QTextEdit(self)
934 textbox.setFont(QFont(MONOSPACE_FONT))
935 textbox.setReadOnly(True)
938 def create_status_bar(self):
940 sb.setFixedHeight(35)
942 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
943 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
945 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
946 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
947 sb.addPermanentWidget( self.status_button )
948 self.setStatusBar(sb)
950 def new_contact_dialog(self):
951 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
952 address = unicode(text)
954 if self.wallet.is_valid(address):
955 self.wallet.addressbook.append(address)
957 self.update_contacts_tab()
958 self.update_history_tab()
959 self.update_completions()
961 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
964 def show_seed_dialog(wallet, parent=None):
966 QMessageBox.information(parent, _('Message'),
967 _('No seed'), _('OK'))
970 if wallet.use_encryption:
971 password = parent.password_dialog()
978 seed = wallet.pw_decode(wallet.seed, password)
980 QMessageBox.warning(parent, _('Error'),
981 _('Incorrect Password'), _('OK'))
984 dialog = QDialog(None)
986 dialog.setWindowTitle("Electrum")
988 brainwallet = ' '.join(mnemonic.mn_encode(seed))
990 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
991 + _("Please write down or memorize these 12 words (order is important).") + " " \
992 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
993 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
995 main_text = QLabel(msg)
996 main_text.setWordWrap(True)
999 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1006 copy_function = lambda: app.clipboard().setText(brainwallet)
1007 copy_button = QPushButton(_("Copy to Clipboard"))
1008 copy_button.clicked.connect(copy_function)
1010 show_qr_function = lambda: ElectrumWindow.show_seed_qrcode(seed)
1011 qr_button = QPushButton(_("View as QR Code"))
1012 qr_button.clicked.connect(show_qr_function)
1014 ok_button = QPushButton(_("OK"))
1015 ok_button.setDefault(True)
1016 ok_button.clicked.connect(dialog.accept)
1018 main_layout = QGridLayout()
1019 main_layout.addWidget(logo, 0, 0)
1020 main_layout.addWidget(main_text, 0, 1, 1, -1)
1021 main_layout.addWidget(copy_button, 1, 1)
1022 main_layout.addWidget(qr_button, 1, 2)
1023 main_layout.addWidget(ok_button, 1, 3)
1024 dialog.setLayout(main_layout)
1029 def show_seed_qrcode(seed):
1033 d.setWindowTitle(_("Seed"))
1034 d.setMinimumSize(270, 300)
1035 vbox = QVBoxLayout()
1036 vbox.addWidget(QRCodeWidget(seed))
1037 hbox = QHBoxLayout()
1039 b = QPushButton(_("OK"))
1041 b.clicked.connect(d.accept)
1043 vbox.addLayout(hbox)
1048 def show_address_qrcode(self,address):
1049 if not address: return
1052 d.setWindowTitle(address)
1053 d.setMinimumSize(270, 350)
1054 vbox = QVBoxLayout()
1055 qrw = QRCodeWidget(address)
1058 hbox = QHBoxLayout()
1059 amount_e = QLineEdit()
1060 hbox.addWidget(QLabel(_('Amount')))
1061 hbox.addWidget(amount_e)
1062 vbox.addLayout(hbox)
1064 #hbox = QHBoxLayout()
1065 #label_e = QLineEdit()
1066 #hbox.addWidget(QLabel('Label'))
1067 #hbox.addWidget(label_e)
1068 #vbox.addLayout(hbox)
1070 def amount_changed():
1071 amount = numbify(amount_e)
1072 #label = str( label_e.getText() )
1073 if amount is not None:
1074 qrw.set_addr('bitcoin:%s?amount=%s'%(address,str( Decimal(amount) /100000000)))
1076 qrw.set_addr( address )
1080 bmp.save_qrcode(qrw.qr, "qrcode.bmp")
1081 self.show_message(_("QR code saved to file") + " 'qrcode.bmp'")
1083 amount_e.textChanged.connect( amount_changed )
1085 hbox = QHBoxLayout()
1087 b = QPushButton(_("Save"))
1088 b.clicked.connect(do_save)
1090 b = QPushButton(_("Close"))
1092 b.clicked.connect(d.accept)
1094 vbox.addLayout(hbox)
1098 def question(self, msg):
1099 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1101 def show_message(self, msg):
1102 QMessageBox.information(self, _('Message'), msg, _('OK'))
1104 def password_dialog(self ):
1111 vbox = QVBoxLayout()
1112 msg = _('Please enter your password')
1113 vbox.addWidget(QLabel(msg))
1115 grid = QGridLayout()
1117 grid.addWidget(QLabel(_('Password')), 1, 0)
1118 grid.addWidget(pw, 1, 1)
1119 vbox.addLayout(grid)
1121 vbox.addLayout(ok_cancel_buttons(d))
1124 if not d.exec_(): return
1125 return unicode(pw.text())
1132 def change_password_dialog( wallet, parent=None ):
1135 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1143 new_pw = QLineEdit()
1144 new_pw.setEchoMode(2)
1145 conf_pw = QLineEdit()
1146 conf_pw.setEchoMode(2)
1148 vbox = QVBoxLayout()
1150 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')
1152 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'+_("Leave these fields empty if you want to disable encryption.")
1153 vbox.addWidget(QLabel(msg))
1155 grid = QGridLayout()
1158 if wallet.use_encryption:
1159 grid.addWidget(QLabel(_('Password')), 1, 0)
1160 grid.addWidget(pw, 1, 1)
1162 grid.addWidget(QLabel(_('New Password')), 2, 0)
1163 grid.addWidget(new_pw, 2, 1)
1165 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1166 grid.addWidget(conf_pw, 3, 1)
1167 vbox.addLayout(grid)
1169 vbox.addLayout(ok_cancel_buttons(d))
1172 if not d.exec_(): return
1174 password = unicode(pw.text()) if wallet.use_encryption else None
1175 new_password = unicode(new_pw.text())
1176 new_password2 = unicode(conf_pw.text())
1179 seed = wallet.pw_decode( wallet.seed, password)
1181 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1184 if new_password != new_password2:
1185 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1188 wallet.update_password(seed, password, new_password)
1191 def seed_dialog(wallet, parent=None):
1195 vbox = QVBoxLayout()
1196 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1197 vbox.addWidget(QLabel(msg))
1199 grid = QGridLayout()
1202 seed_e = QLineEdit()
1203 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1204 grid.addWidget(seed_e, 1, 1)
1208 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1209 grid.addWidget(gap_e, 2, 1)
1210 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1211 vbox.addLayout(grid)
1213 vbox.addLayout(ok_cancel_buttons(d))
1216 if not d.exec_(): return
1219 gap = int(unicode(gap_e.text()))
1221 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1225 seed = unicode(seed_e.text())
1228 print_error("Warning: Not hex, trying decode")
1230 seed = mnemonic.mn_decode( seed.split(' ') )
1232 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1235 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1238 wallet.seed = str(seed)
1239 #print repr(wallet.seed)
1240 wallet.gap_limit = gap
1245 def settings_dialog(self):
1248 vbox = QVBoxLayout()
1249 msg = _('Here are the settings of your wallet.') + '\n'\
1250 + _('For more explanations, click on the help buttons next to each field.')
1253 label.setFixedWidth(250)
1254 label.setWordWrap(True)
1255 label.setAlignment(Qt.AlignJustify)
1256 vbox.addWidget(label)
1258 grid = QGridLayout()
1260 vbox.addLayout(grid)
1262 fee_label = QLabel(_('Transaction fee'))
1263 grid.addWidget(fee_label, 2, 0)
1265 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1266 grid.addWidget(fee_e, 2, 1)
1267 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1268 + _('Recommended value') + ': 0.001'
1269 grid.addWidget(HelpButton(msg), 2, 2)
1270 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1271 if not self.config.is_modifiable('fee'):
1272 for w in [fee_e, fee_label]: w.setEnabled(False)
1274 nz_label = QLabel(_('Display zeros'))
1275 grid.addWidget(nz_label, 3, 0)
1277 nz_e.setText("%d"% self.wallet.num_zeros)
1278 grid.addWidget(nz_e, 3, 1)
1279 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1280 grid.addWidget(HelpButton(msg), 3, 2)
1281 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1282 if not self.config.is_modifiable('num_zeros'):
1283 for w in [nz_e, nz_label]: w.setEnabled(False)
1285 usechange_cb = QCheckBox(_('Use change addresses'))
1286 grid.addWidget(usechange_cb, 5, 0)
1287 usechange_cb.setChecked(self.wallet.use_change)
1288 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1289 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1291 gap_label = QLabel(_('Gap limit'))
1292 grid.addWidget(gap_label, 6, 0)
1294 gap_e.setText("%d"% self.wallet.gap_limit)
1295 grid.addWidget(gap_e, 6, 1)
1296 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1297 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1298 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1299 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1300 + _('Warning') + ': ' \
1301 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1302 + _('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'
1303 grid.addWidget(HelpButton(msg), 6, 2)
1304 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1305 if not self.config.is_modifiable('gap_limit'):
1306 for w in [gap_e, gap_label]: w.setEnabled(False)
1308 gui_label=QLabel(_('Default GUI') + ':')
1309 grid.addWidget(gui_label , 7, 0)
1310 gui_combo = QComboBox()
1311 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1312 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1313 if index==-1: index = 1
1314 gui_combo.setCurrentIndex(index)
1315 grid.addWidget(gui_combo, 7, 1)
1316 grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1317 if not self.config.is_modifiable('gui'):
1318 for w in [gui_combo, gui_label]: w.setEnabled(False)
1320 vbox.addLayout(ok_cancel_buttons(d))
1324 if not d.exec_(): return
1326 fee = unicode(fee_e.text())
1328 fee = int( 100000000 * Decimal(fee) )
1330 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1333 if self.wallet.fee != fee:
1334 self.wallet.fee = fee
1337 nz = unicode(nz_e.text())
1342 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1345 if self.wallet.num_zeros != nz:
1346 self.wallet.num_zeros = nz
1347 self.config.set_key('num_zeros', nz, True)
1348 self.update_history_tab()
1349 self.update_receive_tab()
1351 if self.wallet.use_change != usechange_cb.isChecked():
1352 self.wallet.use_change = usechange_cb.isChecked()
1353 self.config.set_key('use_change', self.wallet.use_change, True)
1356 n = int(gap_e.text())
1358 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1361 if self.wallet.gap_limit != n:
1362 r = self.wallet.change_gap_limit(n)
1364 self.update_receive_tab()
1365 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1367 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1369 self.config.set_key("gui", str(gui_combo.currentText()).lower(), True)
1374 def network_dialog(wallet, parent=None):
1375 interface = wallet.interface
1377 if interface.is_connected:
1378 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1380 status = _("Not connected")
1383 status = _("Please choose a server.")
1385 server = interface.server
1388 if not wallet.interface.servers:
1390 for x in DEFAULT_SERVERS:
1391 h,port,protocol = x.split(':')
1392 servers_list.append( (h,[(protocol,port)] ) )
1394 servers_list = wallet.interface.servers
1395 for item in servers_list:
1399 _protocol, _port = item2
1400 z[_protocol] = _port
1405 d.setWindowTitle(_('Server'))
1406 d.setMinimumSize(375, 20)
1408 vbox = QVBoxLayout()
1411 hbox = QHBoxLayout()
1413 l.setPixmap(QPixmap(":icons/network.png"))
1416 hbox.addWidget(QLabel(status))
1418 vbox.addLayout(hbox)
1422 grid = QGridLayout()
1424 vbox.addLayout(grid)
1427 server_protocol = QComboBox()
1428 server_host = QLineEdit()
1429 server_host.setFixedWidth(200)
1430 server_port = QLineEdit()
1431 server_port.setFixedWidth(60)
1433 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1434 protocol_letters = 'thsg'
1435 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1436 server_protocol.addItems(protocol_names)
1438 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1439 grid.addWidget(server_protocol, 0, 1)
1440 grid.addWidget(server_host, 0, 2)
1441 grid.addWidget(server_port, 0, 3)
1443 host, port, protocol = server.split(':')
1445 def change_protocol(p):
1446 protocol = protocol_letters[p]
1447 host = unicode(server_host.text())
1448 pp = plist.get(host,DEFAULT_PORTS)
1449 if protocol not in pp.keys():
1450 protocol = pp.keys()[0]
1452 server_host.setText( host )
1453 server_port.setText( port )
1455 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1457 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1458 servers_list_widget = QTreeWidget(parent)
1459 servers_list_widget.setHeaderLabels( [ label ] )
1460 servers_list_widget.setMaximumHeight(150)
1461 for _host, _x in servers_list:
1462 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host ] ))
1465 def change_server(host, protocol=None):
1466 pp = plist.get(host,DEFAULT_PORTS)
1468 port = pp.get(protocol)
1469 if not port: protocol = None
1472 if 't' in pp.keys():
1474 port = pp.get(protocol)
1476 protocol = pp.keys()[0]
1477 port = pp.get(protocol)
1479 server_host.setText( host )
1480 server_port.setText( port )
1481 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1483 if not plist: return
1484 for p in protocol_letters:
1485 i = protocol_letters.index(p)
1486 j = server_protocol.model().index(i,0)
1487 if p not in pp.keys():
1488 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1490 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1493 change_server(host,protocol)
1494 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1495 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1497 if not wallet.config.is_modifiable('server'):
1498 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1501 proxy_mode = QComboBox()
1502 proxy_host = QLineEdit()
1503 proxy_host.setFixedWidth(200)
1504 proxy_port = QLineEdit()
1505 proxy_port.setFixedWidth(60)
1506 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1508 def check_for_disable(index = False):
1509 if proxy_mode.currentText() != 'NONE':
1510 proxy_host.setEnabled(True)
1511 proxy_port.setEnabled(True)
1513 proxy_host.setEnabled(False)
1514 proxy_port.setEnabled(False)
1517 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1519 if not wallet.config.is_modifiable('proxy'):
1520 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1522 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1523 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1524 proxy_host.setText(proxy_config.get("host"))
1525 proxy_port.setText(proxy_config.get("port"))
1527 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1528 grid.addWidget(proxy_mode, 2, 1)
1529 grid.addWidget(proxy_host, 2, 2)
1530 grid.addWidget(proxy_port, 2, 3)
1533 vbox.addLayout(ok_cancel_buttons(d))
1536 if not d.exec_(): return
1538 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1539 if proxy_mode.currentText() != 'NONE':
1540 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1544 wallet.config.set_key("proxy", proxy, True)
1545 wallet.config.set_key("server", server, True)
1546 interface.set_server(server, proxy)
1550 def closeEvent(self, event):
1552 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1558 def __init__(self, wallet, config, app=None):
1559 self.wallet = wallet
1560 self.config = config
1562 self.app = QApplication(sys.argv)
1564 def server_list_changed(self):
1568 def restore_or_create(self):
1570 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1571 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1572 if r==2: return False
1574 is_recovery = (r==1)
1575 wallet = self.wallet
1576 # ask for the server.
1577 if not ElectrumWindow.network_dialog( wallet, parent=None ): return False
1579 # wait until we are connected, because the user might have selected another server
1580 if not wallet.interface.is_connected:
1581 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1582 waiting_dialog(waiting)
1584 waiting = lambda: False if wallet.up_to_date else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1585 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1588 wallet.new_seed(None)
1589 wallet.init_mpk( wallet.seed )
1590 wallet.up_to_date_event.clear()
1591 wallet.up_to_date = False
1592 wallet.interface.poke('synchronizer')
1593 waiting_dialog(waiting)
1594 # run a dialog indicating the seed, ask the user to remember it
1595 ElectrumWindow.show_seed_dialog(wallet)
1597 ElectrumWindow.change_password_dialog(wallet)
1599 # ask for seed and gap.
1600 if not ElectrumWindow.seed_dialog( wallet ): return False
1601 wallet.init_mpk( wallet.seed )
1602 wallet.up_to_date_event.clear()
1603 wallet.up_to_date = False
1604 wallet.interface.poke('synchronizer')
1605 waiting_dialog(waiting)
1606 if wallet.is_found():
1607 # history and addressbook
1608 wallet.fill_addressbook()
1609 print "Recovery successful"
1612 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1620 w = ElectrumWindow(self.wallet, self.config)
1621 if url: w.set_url(url)