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)
336 conf = self.wallet.verifier.get_confirmations(tx_hash)
337 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
342 tx_details = _("Transaction Details") +"\n\n" \
343 + "Transaction ID:\n" + tx_hash + "\n\n" \
344 + "Status: %d confirmations\n\n"%conf \
345 + "Date: %s\n\n"%time_str \
346 + "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" \
347 + "Outputs:\n-"+ '\n-'.join(tx['outputs'])
349 r = self.wallet.receipts.get(tx_hash)
351 tx_details += "\n_______________________________________" \
352 + '\n\nSigned URI: ' + r[2] \
353 + "\n\nSigned by: " + r[0] \
354 + '\n\nSignature: ' + r[1]
356 QMessageBox.information(self, 'Details', tx_details, 'OK')
359 def tx_label_clicked(self, item, column):
360 if column==2 and item.isSelected():
361 tx_hash = str(item.toolTip(0))
363 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
364 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
365 self.history_list.editItem( item, column )
366 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
369 def tx_label_changed(self, item, column):
373 tx_hash = str(item.toolTip(0))
374 tx = self.wallet.transactions.get(tx_hash)
375 s = self.wallet.labels.get(tx_hash)
376 text = unicode( item.text(2) )
378 self.wallet.labels[tx_hash] = text
379 item.setForeground(2, QBrush(QColor('black')))
381 if s: self.wallet.labels.pop(tx_hash)
382 text = tx['default_label']
383 item.setText(2, text)
384 item.setForeground(2, QBrush(QColor('gray')))
387 def edit_label(self, is_recv):
388 l = self.receive_list if is_recv else self.contacts_list
389 c = 2 if is_recv else 1
390 item = l.currentItem()
391 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
392 l.editItem( item, c )
393 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
395 def address_label_clicked(self, item, column, l, column_addr, column_label):
396 if column==column_label and item.isSelected():
397 addr = unicode( item.text(column_addr) )
398 label = unicode( item.text(column_label) )
399 if label in self.wallet.aliases.keys():
401 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
402 l.editItem( item, column )
403 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
405 def address_label_changed(self, item, column, l, column_addr, column_label):
406 addr = unicode( item.text(column_addr) )
407 text = unicode( item.text(column_label) )
411 if text not in self.wallet.aliases.keys():
412 old_addr = self.wallet.labels.get(text)
414 self.wallet.labels[addr] = text
417 print_error("Error: This is one of your aliases")
418 label = self.wallet.labels.get(addr,'')
419 item.setText(column_label, QString(label))
421 s = self.wallet.labels.get(addr)
423 self.wallet.labels.pop(addr)
427 self.wallet.update_tx_labels()
428 self.update_history_tab()
429 self.update_completions()
432 def update_history_tab(self):
433 self.history_list.clear()
435 for tx in self.wallet.get_tx_history():
436 tx_hash = tx['tx_hash']
438 conf = self.wallet.verifier.get_confirmations(tx_hash)
439 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
441 icon = QIcon(":icons/unconfirmed.png")
443 icon = QIcon(":icons/clock%d.png"%conf)
445 icon = QIcon(":icons/confirmed.png")
449 icon = QIcon(":icons/unconfirmed.png")
450 v = self.wallet.get_tx_value(tx_hash)
452 label = self.wallet.labels.get(tx_hash)
453 is_default_label = (label == '') or (label is None)
455 label = self.wallet.get_default_label(tx_hash)
457 item = QTreeWidgetItem( [ '', time_str, label, format_satoshis(v,True,self.wallet.num_zeros), format_satoshis(balance,False,self.wallet.num_zeros)] )
458 item.setFont(2, QFont(MONOSPACE_FONT))
459 item.setFont(3, QFont(MONOSPACE_FONT))
460 item.setFont(4, QFont(MONOSPACE_FONT))
461 item.setToolTip(0, tx_hash)
463 item.setForeground(2, QBrush(QColor('grey')))
465 item.setIcon(0, icon)
466 self.history_list.insertTopLevelItem(0,item)
468 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
471 def create_send_tab(self):
476 grid.setColumnMinimumWidth(3,300)
477 grid.setColumnStretch(5,1)
479 self.payto_e = QLineEdit()
480 grid.addWidget(QLabel(_('Pay to')), 1, 0)
481 grid.addWidget(self.payto_e, 1, 1, 1, 3)
484 qrcode = qrscanner.scan_qr()
485 if 'address' in qrcode:
486 self.payto_e.setText(qrcode['address'])
487 if 'amount' in qrcode:
488 self.amount_e.setText(str(qrcode['amount']))
489 if 'label' in qrcode:
490 self.message_e.setText(qrcode['label'])
491 if 'message' in qrcode:
492 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
495 if qrscanner.is_available():
496 b = QPushButton(_("Scan QR code"))
497 b.clicked.connect(fill_from_qr)
498 grid.addWidget(b, 1, 5)
500 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)
502 completer = QCompleter()
503 completer.setCaseSensitivity(False)
504 self.payto_e.setCompleter(completer)
505 completer.setModel(self.completions)
507 self.message_e = QLineEdit()
508 grid.addWidget(QLabel(_('Description')), 2, 0)
509 grid.addWidget(self.message_e, 2, 1, 1, 3)
510 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)
512 self.amount_e = QLineEdit()
513 grid.addWidget(QLabel(_('Amount')), 3, 0)
514 grid.addWidget(self.amount_e, 3, 1, 1, 2)
515 grid.addWidget(HelpButton(
516 _('Amount to be sent.') + '\n\n' \
517 + _('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)
519 self.fee_e = QLineEdit()
520 grid.addWidget(QLabel(_('Fee')), 4, 0)
521 grid.addWidget(self.fee_e, 4, 1, 1, 2)
522 grid.addWidget(HelpButton(
523 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
524 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
525 + _('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)
527 b = EnterButton(_("Send"), self.do_send)
528 grid.addWidget(b, 6, 1)
530 b = EnterButton(_("Clear"),self.do_clear)
531 grid.addWidget(b, 6, 2)
533 self.payto_sig = QLabel('')
534 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
536 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
537 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
546 def entry_changed( is_fee ):
547 self.funds_error = False
548 amount = numbify(self.amount_e)
549 fee = numbify(self.fee_e)
550 if not is_fee: fee = None
553 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
555 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
558 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
561 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
562 self.funds_error = True
563 self.amount_e.setPalette(palette)
564 self.fee_e.setPalette(palette)
566 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
567 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
572 def update_completions(self):
574 for addr,label in self.wallet.labels.items():
575 if addr in self.wallet.addressbook:
576 l.append( label + ' <' + addr + '>')
577 l = l + self.wallet.aliases.keys()
579 self.completions.setStringList(l)
585 label = unicode( self.message_e.text() )
586 r = unicode( self.payto_e.text() )
590 m1 = re.match(ALIAS_REGEXP, r)
591 # label or alias, with address in brackets
592 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
595 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
599 to_address = m2.group(2)
603 if not self.wallet.is_valid(to_address):
604 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
608 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
610 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
613 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
615 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
618 if self.wallet.use_encryption:
619 password = self.password_dialog()
626 tx = self.wallet.mktx( to_address, amount, label, password, fee)
627 except BaseException, e:
628 self.show_message(str(e))
631 h = self.wallet.send_tx(tx)
632 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
633 status, msg = self.wallet.receive_tx( h )
636 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
638 self.update_contacts_tab()
640 QMessageBox.warning(self, _('Error'), msg, _('OK'))
643 def set_url(self, url):
644 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
645 self.tabs.setCurrentIndex(1)
646 label = self.wallet.labels.get(payto)
647 m_addr = label + ' <'+ payto+'>' if label else payto
648 self.payto_e.setText(m_addr)
650 self.message_e.setText(message)
651 self.amount_e.setText(amount)
653 self.set_frozen(self.payto_e,True)
654 self.set_frozen(self.amount_e,True)
655 self.set_frozen(self.message_e,True)
656 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
658 self.payto_sig.setVisible(False)
661 self.payto_sig.setVisible(False)
662 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
664 self.set_frozen(e,False)
666 def set_frozen(self,entry,frozen):
668 entry.setReadOnly(True)
669 entry.setFrame(False)
671 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
672 entry.setPalette(palette)
674 entry.setReadOnly(False)
677 palette.setColor(entry.backgroundRole(), QColor('white'))
678 entry.setPalette(palette)
681 def toggle_freeze(self,addr):
683 if addr in self.wallet.frozen_addresses:
684 self.wallet.unfreeze(addr)
686 self.wallet.freeze(addr)
687 self.update_receive_tab()
689 def toggle_priority(self,addr):
691 if addr in self.wallet.prioritized_addresses:
692 self.wallet.unprioritize(addr)
694 self.wallet.prioritize(addr)
695 self.update_receive_tab()
698 def create_list_tab(self, headers):
699 "generic tab creation method"
700 l = MyTreeWidget(self)
701 l.setColumnCount( len(headers) )
702 l.setHeaderLabels( headers )
712 vbox.addWidget(buttons)
717 buttons.setLayout(hbox)
722 def create_receive_tab(self):
723 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Balance'), _('Tx')])
724 l.setContextMenuPolicy(Qt.CustomContextMenu)
725 l.customContextMenuRequested.connect(self.create_receive_menu)
726 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
727 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
728 self.receive_list = l
729 self.receive_buttons_hbox = hbox
730 self.details_button = EnterButton(self.details_button_text(), self.toggle_detailed_view)
731 hbox.addWidget(self.details_button)
735 def details_button_text(self):
736 return _('Hide details') if self.detailed_view else _('Show details')
738 def toggle_detailed_view(self):
739 self.detailed_view = not self.detailed_view
740 self.config.set_key('qt_detailed_view', self.detailed_view, True)
742 self.details_button.setText(self.details_button_text())
744 self.update_receive_tab()
745 self.update_contacts_tab()
748 def create_contacts_tab(self):
749 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
750 l.setContextMenuPolicy(Qt.CustomContextMenu)
751 l.customContextMenuRequested.connect(self.create_contact_menu)
752 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
753 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
754 self.contacts_list = l
755 self.contacts_buttons_hbox = hbox
756 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
761 def create_receive_menu(self, position):
762 # fixme: this function apparently has a side effect.
763 # if it is not called the menu pops up several times
764 #self.receive_list.selectedIndexes()
766 item = self.receive_list.itemAt(position)
768 addr = unicode(item.text(1))
770 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
771 menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
772 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
774 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
775 menu.addAction(t, lambda: self.toggle_freeze(addr))
776 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
777 menu.addAction(t, lambda: self.toggle_priority(addr))
778 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
781 def payto(self, x, is_alias):
788 label = self.wallet.labels.get(addr)
789 m_addr = label + ' <' + addr + '>' if label else addr
790 self.tabs.setCurrentIndex(1)
791 self.payto_e.setText(m_addr)
792 self.amount_e.setFocus()
794 def delete_contact(self, x, is_alias):
795 if self.question("Do you want to remove %s from your list of contacts?"%x):
796 if not is_alias and x in self.wallet.addressbook:
797 self.wallet.addressbook.remove(x)
798 if x in self.wallet.labels.keys():
799 self.wallet.labels.pop(x)
800 elif is_alias and x in self.wallet.aliases:
801 self.wallet.aliases.pop(x)
802 self.update_history_tab()
803 self.update_contacts_tab()
804 self.update_completions()
806 def create_contact_menu(self, position):
807 # fixme: this function apparently has a side effect.
808 # if it is not called the menu pops up several times
809 #self.contacts_list.selectedIndexes()
811 item = self.contacts_list.itemAt(position)
813 addr = unicode(item.text(0))
814 label = unicode(item.text(1))
815 is_alias = label in self.wallet.aliases.keys()
816 x = label if is_alias else addr
818 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
819 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
820 menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
822 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
824 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
825 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
826 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
829 def update_receive_tab(self):
830 l = self.receive_list
832 l.setColumnHidden(0,not self.detailed_view)
833 l.setColumnHidden(3,not self.detailed_view)
834 l.setColumnHidden(4,not self.detailed_view)
835 l.setColumnWidth(0, 50)
836 l.setColumnWidth(1, 310)
837 l.setColumnWidth(2, 250)
838 l.setColumnWidth(3, 130)
839 l.setColumnWidth(4, 10)
843 for address in self.wallet.all_addresses():
845 if self.wallet.is_change(address) and not self.detailed_view:
848 label = self.wallet.labels.get(address,'')
850 h = self.wallet.history.get(address,[])
851 for tx_hash, tx_height in h:
852 tx = self.wallet.transactions.get(tx_hash)
857 if address in self.wallet.addresses:
859 if gap > self.wallet.gap_limit:
862 if address in self.wallet.addresses:
865 c, u = self.wallet.get_addr_balance(address)
866 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
867 flags = self.wallet.get_address_flags(address)
868 item = QTreeWidgetItem( [ flags, address, label, balance, tx] )
870 item.setFont(0, QFont(MONOSPACE_FONT))
871 item.setFont(1, QFont(MONOSPACE_FONT))
872 item.setFont(3, QFont(MONOSPACE_FONT))
873 if address in self.wallet.frozen_addresses:
874 item.setBackgroundColor(1, QColor('lightblue'))
875 elif address in self.wallet.prioritized_addresses:
876 item.setBackgroundColor(1, QColor('lightgreen'))
877 if is_red and address in self.wallet.addresses:
878 item.setBackgroundColor(1, QColor('red'))
879 l.addTopLevelItem(item)
881 # we use column 1 because column 0 may be hidden
882 l.setCurrentItem(l.topLevelItem(0),1)
884 def show_contact_details(self, m):
885 a = self.wallet.aliases.get(m)
887 if a[0] in self.wallet.authorities.keys():
888 s = self.wallet.authorities.get(a[0])
891 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
892 QMessageBox.information(self, 'Alias', msg, 'OK')
894 def update_contacts_tab(self):
896 l = self.contacts_list
898 l.setColumnHidden(2, not self.detailed_view)
899 l.setColumnWidth(0, 350)
900 l.setColumnWidth(1, 330)
901 l.setColumnWidth(2, 100)
904 for alias, v in self.wallet.aliases.items():
906 alias_targets.append(target)
907 item = QTreeWidgetItem( [ target, alias, '-'] )
908 item.setBackgroundColor(0, QColor('lightgray'))
909 l.addTopLevelItem(item)
911 for address in self.wallet.addressbook:
912 if address in alias_targets: continue
913 label = self.wallet.labels.get(address,'')
915 for item in self.wallet.transactions.values():
916 if address in item['outputs'] : n=n+1
918 item = QTreeWidgetItem( [ address, label, tx] )
919 item.setFont(0, QFont(MONOSPACE_FONT))
920 l.addTopLevelItem(item)
922 l.setCurrentItem(l.topLevelItem(0))
924 def create_wall_tab(self):
925 self.textbox = textbox = QTextEdit(self)
926 textbox.setFont(QFont(MONOSPACE_FONT))
927 textbox.setReadOnly(True)
930 def create_status_bar(self):
932 sb.setFixedHeight(35)
934 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
935 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
937 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
938 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
939 sb.addPermanentWidget( self.status_button )
940 self.setStatusBar(sb)
942 def new_contact_dialog(self):
943 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
944 address = unicode(text)
946 if self.wallet.is_valid(address):
947 self.wallet.addressbook.append(address)
949 self.update_contacts_tab()
950 self.update_history_tab()
951 self.update_completions()
953 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
956 def show_seed_dialog(wallet, parent=None):
958 QMessageBox.information(parent, _('Message'),
959 _('No seed'), _('OK'))
962 if wallet.use_encryption:
963 password = parent.password_dialog()
970 seed = wallet.pw_decode(wallet.seed, password)
972 QMessageBox.warning(parent, _('Error'),
973 _('Incorrect Password'), _('OK'))
976 dialog = QDialog(None)
978 dialog.setWindowTitle("Electrum")
980 brainwallet = ' '.join(mnemonic.mn_encode(seed))
982 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
983 + _("Please write down or memorize these 12 words (order is important).") + " " \
984 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
985 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
987 main_text = QLabel(msg)
988 main_text.setWordWrap(True)
991 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
998 copy_function = lambda: app.clipboard().setText(brainwallet)
999 copy_button = QPushButton(_("Copy to Clipboard"))
1000 copy_button.clicked.connect(copy_function)
1002 show_qr_function = lambda: ElectrumWindow.show_seed_qrcode(seed)
1003 qr_button = QPushButton(_("View as QR Code"))
1004 qr_button.clicked.connect(show_qr_function)
1006 ok_button = QPushButton(_("OK"))
1007 ok_button.setDefault(True)
1008 ok_button.clicked.connect(dialog.accept)
1010 main_layout = QGridLayout()
1011 main_layout.addWidget(logo, 0, 0)
1012 main_layout.addWidget(main_text, 0, 1, 1, -1)
1013 main_layout.addWidget(copy_button, 1, 1)
1014 main_layout.addWidget(qr_button, 1, 2)
1015 main_layout.addWidget(ok_button, 1, 3)
1016 dialog.setLayout(main_layout)
1021 def show_seed_qrcode(seed):
1025 d.setWindowTitle(_("Seed"))
1026 d.setMinimumSize(270, 300)
1027 vbox = QVBoxLayout()
1028 vbox.addWidget(QRCodeWidget(seed))
1029 hbox = QHBoxLayout()
1031 b = QPushButton(_("OK"))
1033 b.clicked.connect(d.accept)
1035 vbox.addLayout(hbox)
1040 def show_address_qrcode(self,address):
1041 if not address: return
1044 d.setWindowTitle(address)
1045 d.setMinimumSize(270, 350)
1046 vbox = QVBoxLayout()
1047 qrw = QRCodeWidget(address)
1050 hbox = QHBoxLayout()
1051 amount_e = QLineEdit()
1052 hbox.addWidget(QLabel(_('Amount')))
1053 hbox.addWidget(amount_e)
1054 vbox.addLayout(hbox)
1056 #hbox = QHBoxLayout()
1057 #label_e = QLineEdit()
1058 #hbox.addWidget(QLabel('Label'))
1059 #hbox.addWidget(label_e)
1060 #vbox.addLayout(hbox)
1062 def amount_changed():
1063 amount = numbify(amount_e)
1064 #label = str( label_e.getText() )
1065 if amount is not None:
1066 qrw.set_addr('bitcoin:%s?amount=%s'%(address,str( Decimal(amount) /100000000)))
1068 qrw.set_addr( address )
1072 bmp.save_qrcode(qrw.qr, "qrcode.bmp")
1073 self.show_message(_("QR code saved to file") + " 'qrcode.bmp'")
1075 amount_e.textChanged.connect( amount_changed )
1077 hbox = QHBoxLayout()
1079 b = QPushButton(_("Save"))
1080 b.clicked.connect(do_save)
1082 b = QPushButton(_("Close"))
1084 b.clicked.connect(d.accept)
1086 vbox.addLayout(hbox)
1090 def question(self, msg):
1091 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1093 def show_message(self, msg):
1094 QMessageBox.information(self, _('Message'), msg, _('OK'))
1096 def password_dialog(self ):
1103 vbox = QVBoxLayout()
1104 msg = _('Please enter your password')
1105 vbox.addWidget(QLabel(msg))
1107 grid = QGridLayout()
1109 grid.addWidget(QLabel(_('Password')), 1, 0)
1110 grid.addWidget(pw, 1, 1)
1111 vbox.addLayout(grid)
1113 vbox.addLayout(ok_cancel_buttons(d))
1116 if not d.exec_(): return
1117 return unicode(pw.text())
1124 def change_password_dialog( wallet, parent=None ):
1127 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1135 new_pw = QLineEdit()
1136 new_pw.setEchoMode(2)
1137 conf_pw = QLineEdit()
1138 conf_pw.setEchoMode(2)
1140 vbox = QVBoxLayout()
1142 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')
1144 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'+_("Leave these fields empty if you want to disable encryption.")
1145 vbox.addWidget(QLabel(msg))
1147 grid = QGridLayout()
1150 if wallet.use_encryption:
1151 grid.addWidget(QLabel(_('Password')), 1, 0)
1152 grid.addWidget(pw, 1, 1)
1154 grid.addWidget(QLabel(_('New Password')), 2, 0)
1155 grid.addWidget(new_pw, 2, 1)
1157 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1158 grid.addWidget(conf_pw, 3, 1)
1159 vbox.addLayout(grid)
1161 vbox.addLayout(ok_cancel_buttons(d))
1164 if not d.exec_(): return
1166 password = unicode(pw.text()) if wallet.use_encryption else None
1167 new_password = unicode(new_pw.text())
1168 new_password2 = unicode(conf_pw.text())
1171 seed = wallet.pw_decode( wallet.seed, password)
1173 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1176 if new_password != new_password2:
1177 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1180 wallet.update_password(seed, password, new_password)
1183 def seed_dialog(wallet, parent=None):
1187 vbox = QVBoxLayout()
1188 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1189 vbox.addWidget(QLabel(msg))
1191 grid = QGridLayout()
1194 seed_e = QLineEdit()
1195 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1196 grid.addWidget(seed_e, 1, 1)
1200 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1201 grid.addWidget(gap_e, 2, 1)
1202 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1203 vbox.addLayout(grid)
1205 vbox.addLayout(ok_cancel_buttons(d))
1208 if not d.exec_(): return
1211 gap = int(unicode(gap_e.text()))
1213 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1217 seed = unicode(seed_e.text())
1220 print_error("Warning: Not hex, trying decode")
1222 seed = mnemonic.mn_decode( seed.split(' ') )
1224 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1227 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1230 wallet.seed = str(seed)
1231 #print repr(wallet.seed)
1232 wallet.gap_limit = gap
1237 def settings_dialog(self):
1240 vbox = QVBoxLayout()
1241 msg = _('Here are the settings of your wallet.') + '\n'\
1242 + _('For more explanations, click on the help buttons next to each field.')
1245 label.setFixedWidth(250)
1246 label.setWordWrap(True)
1247 label.setAlignment(Qt.AlignJustify)
1248 vbox.addWidget(label)
1250 grid = QGridLayout()
1252 vbox.addLayout(grid)
1254 fee_label = QLabel(_('Transaction fee'))
1255 grid.addWidget(fee_label, 2, 0)
1257 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1258 grid.addWidget(fee_e, 2, 1)
1259 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1260 + _('Recommended value') + ': 0.001'
1261 grid.addWidget(HelpButton(msg), 2, 2)
1262 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1263 if not self.config.is_modifiable('fee'):
1264 for w in [fee_e, fee_label]: w.setEnabled(False)
1266 nz_label = QLabel(_('Display zeros'))
1267 grid.addWidget(nz_label, 3, 0)
1269 nz_e.setText("%d"% self.wallet.num_zeros)
1270 grid.addWidget(nz_e, 3, 1)
1271 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1272 grid.addWidget(HelpButton(msg), 3, 2)
1273 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1274 if not self.config.is_modifiable('num_zeros'):
1275 for w in [nz_e, nz_label]: w.setEnabled(False)
1277 usechange_cb = QCheckBox(_('Use change addresses'))
1278 grid.addWidget(usechange_cb, 5, 0)
1279 usechange_cb.setChecked(self.wallet.use_change)
1280 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1281 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1283 gap_label = QLabel(_('Gap limit'))
1284 grid.addWidget(gap_label, 6, 0)
1286 gap_e.setText("%d"% self.wallet.gap_limit)
1287 grid.addWidget(gap_e, 6, 1)
1288 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1289 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1290 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1291 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1292 + _('Warning') + ': ' \
1293 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1294 + _('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'
1295 grid.addWidget(HelpButton(msg), 6, 2)
1296 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1297 if not self.config.is_modifiable('gap_limit'):
1298 for w in [gap_e, gap_label]: w.setEnabled(False)
1300 gui_label=QLabel(_('Default GUI') + ':')
1301 grid.addWidget(gui_label , 7, 0)
1302 gui_combo = QComboBox()
1303 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1304 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1305 if index==-1: index = 1
1306 gui_combo.setCurrentIndex(index)
1307 grid.addWidget(gui_combo, 7, 1)
1308 grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1309 if not self.config.is_modifiable('gui'):
1310 for w in [gui_combo, gui_label]: w.setEnabled(False)
1312 vbox.addLayout(ok_cancel_buttons(d))
1316 if not d.exec_(): return
1318 fee = unicode(fee_e.text())
1320 fee = int( 100000000 * Decimal(fee) )
1322 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1325 if self.wallet.fee != fee:
1326 self.wallet.fee = fee
1329 nz = unicode(nz_e.text())
1334 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1337 if self.wallet.num_zeros != nz:
1338 self.wallet.num_zeros = nz
1339 self.config.set_key('num_zeros', nz, True)
1340 self.update_history_tab()
1341 self.update_receive_tab()
1343 if self.wallet.use_change != usechange_cb.isChecked():
1344 self.wallet.use_change = usechange_cb.isChecked()
1345 self.config.set_key('use_change', self.wallet.use_change, True)
1348 n = int(gap_e.text())
1350 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1353 if self.wallet.gap_limit != n:
1354 r = self.wallet.change_gap_limit(n)
1356 self.update_receive_tab()
1357 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1359 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1361 self.config.set_key("gui", str(gui_combo.currentText()).lower(), True)
1366 def network_dialog(wallet, parent=None):
1367 interface = wallet.interface
1369 if interface.is_connected:
1370 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1372 status = _("Not connected")
1375 status = _("Please choose a server.")
1377 server = interface.server
1380 if not wallet.interface.servers:
1382 for x in DEFAULT_SERVERS:
1383 h,port,protocol = x.split(':')
1384 servers_list.append( (h,[(protocol,port)] ) )
1386 servers_list = wallet.interface.servers
1387 for item in servers_list:
1391 _protocol, _port = item2
1392 z[_protocol] = _port
1397 d.setWindowTitle(_('Server'))
1398 d.setMinimumSize(375, 20)
1400 vbox = QVBoxLayout()
1403 hbox = QHBoxLayout()
1405 l.setPixmap(QPixmap(":icons/network.png"))
1408 hbox.addWidget(QLabel(status))
1410 vbox.addLayout(hbox)
1414 grid = QGridLayout()
1416 vbox.addLayout(grid)
1419 server_protocol = QComboBox()
1420 server_host = QLineEdit()
1421 server_host.setFixedWidth(200)
1422 server_port = QLineEdit()
1423 server_port.setFixedWidth(60)
1425 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1426 protocol_letters = 'thsg'
1427 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1428 server_protocol.addItems(protocol_names)
1430 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1431 grid.addWidget(server_protocol, 0, 1)
1432 grid.addWidget(server_host, 0, 2)
1433 grid.addWidget(server_port, 0, 3)
1435 host, port, protocol = server.split(':')
1437 def change_protocol(p):
1438 protocol = protocol_letters[p]
1439 host = unicode(server_host.text())
1440 pp = plist.get(host,DEFAULT_PORTS)
1441 if protocol not in pp.keys():
1442 protocol = pp.keys()[0]
1444 server_host.setText( host )
1445 server_port.setText( port )
1447 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1449 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1450 servers_list_widget = QTreeWidget(parent)
1451 servers_list_widget.setHeaderLabels( [ label ] )
1452 servers_list_widget.setMaximumHeight(150)
1453 for _host, _x in servers_list:
1454 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host ] ))
1457 def change_server(host, protocol=None):
1458 pp = plist.get(host,DEFAULT_PORTS)
1460 port = pp.get(protocol)
1461 if not port: protocol = None
1464 if 't' in pp.keys():
1466 port = pp.get(protocol)
1468 protocol = pp.keys()[0]
1469 port = pp.get(protocol)
1471 server_host.setText( host )
1472 server_port.setText( port )
1473 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1475 if not plist: return
1476 for p in protocol_letters:
1477 i = protocol_letters.index(p)
1478 j = server_protocol.model().index(i,0)
1479 if p not in pp.keys():
1480 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1482 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1485 change_server(host,protocol)
1486 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1487 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1489 if not wallet.config.is_modifiable('server'):
1490 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1493 proxy_mode = QComboBox()
1494 proxy_host = QLineEdit()
1495 proxy_host.setFixedWidth(200)
1496 proxy_port = QLineEdit()
1497 proxy_port.setFixedWidth(60)
1498 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1500 def check_for_disable(index = False):
1501 if proxy_mode.currentText() != 'NONE':
1502 proxy_host.setEnabled(True)
1503 proxy_port.setEnabled(True)
1505 proxy_host.setEnabled(False)
1506 proxy_port.setEnabled(False)
1509 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1511 if not wallet.config.is_modifiable('proxy'):
1512 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1514 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1515 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1516 proxy_host.setText(proxy_config.get("host"))
1517 proxy_port.setText(proxy_config.get("port"))
1519 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1520 grid.addWidget(proxy_mode, 2, 1)
1521 grid.addWidget(proxy_host, 2, 2)
1522 grid.addWidget(proxy_port, 2, 3)
1525 vbox.addLayout(ok_cancel_buttons(d))
1528 if not d.exec_(): return
1530 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1531 if proxy_mode.currentText() != 'NONE':
1532 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1536 wallet.config.set_key("proxy", proxy, True)
1537 wallet.config.set_key("server", server, True)
1538 interface.set_server(server, proxy)
1542 def closeEvent(self, event):
1544 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1550 def __init__(self, wallet, config, app=None):
1551 self.wallet = wallet
1552 self.config = config
1554 self.app = QApplication(sys.argv)
1556 def server_list_changed(self):
1560 def restore_or_create(self):
1562 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1563 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1564 if r==2: return False
1566 is_recovery = (r==1)
1567 wallet = self.wallet
1568 # ask for the server.
1569 if not ElectrumWindow.network_dialog( wallet, parent=None ): return False
1571 # wait until we are connected, because the user might have selected another server
1572 if not wallet.interface.is_connected:
1573 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1574 waiting_dialog(waiting)
1576 waiting = lambda: False if wallet.up_to_date else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1577 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1580 wallet.new_seed(None)
1581 wallet.init_mpk( wallet.seed )
1582 wallet.up_to_date_event.clear()
1583 wallet.up_to_date = False
1584 wallet.interface.poke('synchronizer')
1585 waiting_dialog(waiting)
1586 # run a dialog indicating the seed, ask the user to remember it
1587 ElectrumWindow.show_seed_dialog(wallet)
1589 ElectrumWindow.change_password_dialog(wallet)
1591 # ask for seed and gap.
1592 if not ElectrumWindow.seed_dialog( wallet ): return False
1593 wallet.init_mpk( wallet.seed )
1594 wallet.up_to_date_event.clear()
1595 wallet.up_to_date = False
1596 wallet.interface.poke('synchronizer')
1597 waiting_dialog(waiting)
1598 if wallet.is_found():
1599 # history and addressbook
1600 wallet.fill_addressbook()
1601 print "Recovery successful"
1604 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1612 w = ElectrumWindow(self.wallet, self.config)
1613 if url: w.set_url(url)