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 self.wallet.blocks == -1:
281 text = _( "Connecting..." )
282 icon = QIcon(":icons/status_disconnected.png")
283 elif self.wallet.blocks == 0:
284 text = _( "Server not ready" )
285 icon = QIcon(":icons/status_disconnected.png")
286 elif not self.wallet.up_to_date:
287 text = _( "Synchronizing..." )
288 icon = QIcon(":icons/status_waiting.png")
290 c, u = self.wallet.get_balance()
291 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
292 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
293 icon = QIcon(":icons/status_connected.png")
295 text = _( "Not connected" )
296 icon = QIcon(":icons/status_disconnected.png")
299 text = _( "Not enough funds" )
301 self.statusBar().showMessage(text)
302 self.status_button.setIcon( icon )
304 if self.wallet.up_to_date:
305 self.textbox.setText( self.wallet.banner )
306 self.update_history_tab()
307 self.update_receive_tab()
308 self.update_contacts_tab()
309 self.update_completions()
312 def create_history_tab(self):
313 self.history_list = l = MyTreeWidget(self)
315 l.setColumnWidth(0, 40)
316 l.setColumnWidth(1, 140)
317 l.setColumnWidth(2, 350)
318 l.setColumnWidth(3, 140)
319 l.setColumnWidth(4, 140)
320 l.setHeaderLabels( [ '', _( 'Date' ), _( 'To / From' ) , _('Amount'), _('Balance')] )
321 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
322 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
323 l.setContextMenuPolicy(Qt.CustomContextMenu)
324 l.customContextMenuRequested.connect(self.create_history_menu)
327 def create_history_menu(self, position):
328 self.history_list.selectedIndexes()
329 item = self.history_list.currentItem()
331 tx_hash = str(item.toolTip(0))
333 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
334 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
335 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
336 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
338 def tx_details(self, tx_hash):
339 tx = self.wallet.tx_history.get(tx_hash)
342 conf = self.wallet.blocks - tx['height'] + 1
343 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
348 tx_details = _("Transaction Details") +"\n\n" \
349 + "Transaction ID:\n" + tx_hash + "\n\n" \
350 + "Status: %d confirmations\n\n"%conf \
351 + "Date: %s\n\n"%time_str \
352 + "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" \
353 + "Outputs:\n-"+ '\n-'.join(tx['outputs'])
355 r = self.wallet.receipts.get(tx_hash)
357 tx_details += "\n_______________________________________" \
358 + '\n\nSigned URI: ' + r[2] \
359 + "\n\nSigned by: " + r[0] \
360 + '\n\nSignature: ' + r[1]
362 QMessageBox.information(self, 'Details', tx_details, 'OK')
365 def tx_label_clicked(self, item, column):
366 if column==2 and item.isSelected():
367 tx_hash = str(item.toolTip(0))
369 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
370 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
371 self.history_list.editItem( item, column )
372 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
375 def tx_label_changed(self, item, column):
379 tx_hash = str(item.toolTip(0))
380 tx = self.wallet.tx_history.get(tx_hash)
381 s = self.wallet.labels.get(tx_hash)
382 text = unicode( item.text(2) )
384 self.wallet.labels[tx_hash] = text
385 item.setForeground(2, QBrush(QColor('black')))
387 if s: self.wallet.labels.pop(tx_hash)
388 text = tx['default_label']
389 item.setText(2, text)
390 item.setForeground(2, QBrush(QColor('gray')))
393 def edit_label(self, is_recv):
394 l = self.receive_list if is_recv else self.contacts_list
395 c = 2 if is_recv else 1
396 item = l.currentItem()
397 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
398 l.editItem( item, c )
399 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
401 def address_label_clicked(self, item, column, l, column_addr, column_label):
402 if column==column_label and item.isSelected():
403 addr = unicode( item.text(column_addr) )
404 label = unicode( item.text(column_label) )
405 if label in self.wallet.aliases.keys():
407 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
408 l.editItem( item, column )
409 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
411 def address_label_changed(self, item, column, l, column_addr, column_label):
412 addr = unicode( item.text(column_addr) )
413 text = unicode( item.text(column_label) )
417 if text not in self.wallet.aliases.keys():
418 old_addr = self.wallet.labels.get(text)
420 self.wallet.labels[addr] = text
423 print_error("Error: This is one of your aliases")
424 label = self.wallet.labels.get(addr,'')
425 item.setText(column_label, QString(label))
427 s = self.wallet.labels.get(addr)
429 self.wallet.labels.pop(addr)
433 self.wallet.update_tx_labels()
434 self.update_history_tab()
435 self.update_completions()
438 def update_history_tab(self):
439 self.history_list.clear()
441 for tx in self.wallet.get_tx_history():
442 tx_hash = tx['tx_hash']
444 conf = self.wallet.blocks - tx['height'] + 1
445 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
447 icon = QIcon(":icons/clock%d.png"%conf)
449 icon = QIcon(":icons/confirmed.png")
453 icon = QIcon(":icons/unconfirmed.png")
456 label = self.wallet.labels.get(tx_hash)
457 is_default_label = (label == '') or (label is None)
458 if is_default_label: label = tx['default_label']
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,[])
855 if not item['is_input'] : n=n+1
859 if address in self.wallet.addresses:
861 if gap > self.wallet.gap_limit:
864 if address in self.wallet.addresses:
867 c, u = self.wallet.get_addr_balance(address)
868 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
869 flags = self.wallet.get_address_flags(address)
870 item = QTreeWidgetItem( [ flags, address, label, balance, tx] )
872 item.setFont(0, QFont(MONOSPACE_FONT))
873 item.setFont(1, QFont(MONOSPACE_FONT))
874 item.setFont(3, QFont(MONOSPACE_FONT))
875 if address in self.wallet.frozen_addresses:
876 item.setBackgroundColor(1, QColor('lightblue'))
877 elif address in self.wallet.prioritized_addresses:
878 item.setBackgroundColor(1, QColor('lightgreen'))
879 if is_red and address in self.wallet.addresses:
880 item.setBackgroundColor(1, QColor('red'))
881 l.addTopLevelItem(item)
883 # we use column 1 because column 0 may be hidden
884 l.setCurrentItem(l.topLevelItem(0),1)
886 def show_contact_details(self, m):
887 a = self.wallet.aliases.get(m)
889 if a[0] in self.wallet.authorities.keys():
890 s = self.wallet.authorities.get(a[0])
893 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
894 QMessageBox.information(self, 'Alias', msg, 'OK')
896 def update_contacts_tab(self):
898 l = self.contacts_list
900 l.setColumnHidden(2, not self.detailed_view)
901 l.setColumnWidth(0, 350)
902 l.setColumnWidth(1, 330)
903 l.setColumnWidth(2, 100)
906 for alias, v in self.wallet.aliases.items():
908 alias_targets.append(target)
909 item = QTreeWidgetItem( [ target, alias, '-'] )
910 item.setBackgroundColor(0, QColor('lightgray'))
911 l.addTopLevelItem(item)
913 for address in self.wallet.addressbook:
914 if address in alias_targets: continue
915 label = self.wallet.labels.get(address,'')
917 for item in self.wallet.tx_history.values():
918 if address in item['outputs'] : n=n+1
920 item = QTreeWidgetItem( [ address, label, tx] )
921 item.setFont(0, QFont(MONOSPACE_FONT))
922 l.addTopLevelItem(item)
924 l.setCurrentItem(l.topLevelItem(0))
926 def create_wall_tab(self):
927 self.textbox = textbox = QTextEdit(self)
928 textbox.setFont(QFont(MONOSPACE_FONT))
929 textbox.setReadOnly(True)
932 def create_status_bar(self):
934 sb.setFixedHeight(35)
936 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
937 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
939 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
940 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
941 sb.addPermanentWidget( self.status_button )
942 self.setStatusBar(sb)
944 def new_contact_dialog(self):
945 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
946 address = unicode(text)
948 if self.wallet.is_valid(address):
949 self.wallet.addressbook.append(address)
951 self.update_contacts_tab()
952 self.update_history_tab()
953 self.update_completions()
955 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
958 def show_seed_dialog(wallet, parent=None):
960 QMessageBox.information(parent, _('Message'),
961 _('No seed'), _('OK'))
964 if wallet.use_encryption:
965 password = parent.password_dialog()
972 seed = wallet.pw_decode(wallet.seed, password)
974 QMessageBox.warning(parent, _('Error'),
975 _('Incorrect Password'), _('OK'))
978 dialog = QDialog(None)
980 dialog.setWindowTitle(_("Seed"))
982 brainwallet = ' '.join(mnemonic.mn_encode(seed))
984 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
985 + _("Please write down or memorize these 12 words (order is important).") + " " \
986 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
987 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
989 main_text = QLabel(msg)
990 main_text.setWordWrap(True)
993 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1000 copy_function = lambda: app.clipboard().setText(brainwallet)
1001 copy_button = QPushButton(_("Copy to Clipboard"))
1002 copy_button.clicked.connect(copy_function)
1004 show_qr_function = lambda: ElectrumWindow.show_seed_qrcode(seed)
1005 qr_button = QPushButton(_("View as QR Code"))
1006 qr_button.clicked.connect(show_qr_function)
1008 ok_button = QPushButton(_("OK"))
1009 ok_button.setDefault(True)
1010 ok_button.clicked.connect(dialog.accept)
1012 main_layout = QGridLayout()
1013 main_layout.addWidget(logo, 0, 0)
1014 main_layout.addWidget(main_text, 0, 1, 1, -1)
1015 main_layout.addWidget(copy_button, 1, 1)
1016 main_layout.addWidget(qr_button, 1, 2)
1017 main_layout.addWidget(ok_button, 1, 3)
1018 dialog.setLayout(main_layout)
1023 def show_seed_qrcode(seed):
1027 d.setWindowTitle(_("Seed"))
1028 d.setMinimumSize(270, 300)
1029 vbox = QVBoxLayout()
1030 vbox.addWidget(QRCodeWidget(seed))
1031 hbox = QHBoxLayout()
1033 b = QPushButton(_("OK"))
1035 b.clicked.connect(d.accept)
1037 vbox.addLayout(hbox)
1042 def show_address_qrcode(self,address):
1043 if not address: return
1046 d.setWindowTitle(address)
1047 d.setMinimumSize(270, 350)
1048 vbox = QVBoxLayout()
1049 qrw = QRCodeWidget(address)
1052 hbox = QHBoxLayout()
1053 amount_e = QLineEdit()
1054 hbox.addWidget(QLabel(_('Amount')))
1055 hbox.addWidget(amount_e)
1056 vbox.addLayout(hbox)
1058 #hbox = QHBoxLayout()
1059 #label_e = QLineEdit()
1060 #hbox.addWidget(QLabel('Label'))
1061 #hbox.addWidget(label_e)
1062 #vbox.addLayout(hbox)
1064 def amount_changed():
1065 amount = numbify(amount_e)
1066 #label = str( label_e.getText() )
1067 if amount is not None:
1068 qrw.set_addr('bitcoin:%s?amount=%s'%(address,str( Decimal(amount) /100000000)))
1070 qrw.set_addr( address )
1074 bmp.save_qrcode(qrw.qr, "qrcode.bmp")
1075 self.show_message(_("QR code saved to file") + " 'qrcode.bmp'")
1077 amount_e.textChanged.connect( amount_changed )
1079 hbox = QHBoxLayout()
1081 b = QPushButton(_("Save"))
1082 b.clicked.connect(do_save)
1084 b = QPushButton(_("Close"))
1086 b.clicked.connect(d.accept)
1088 vbox.addLayout(hbox)
1092 def question(self, msg):
1093 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1095 def show_message(self, msg):
1096 QMessageBox.information(self, _('Message'), msg, _('OK'))
1098 def password_dialog(self ):
1105 vbox = QVBoxLayout()
1106 msg = _('Please enter your password')
1107 vbox.addWidget(QLabel(msg))
1109 grid = QGridLayout()
1111 grid.addWidget(QLabel(_('Password')), 1, 0)
1112 grid.addWidget(pw, 1, 1)
1113 vbox.addLayout(grid)
1115 vbox.addLayout(ok_cancel_buttons(d))
1118 if not d.exec_(): return
1119 return unicode(pw.text())
1126 def change_password_dialog( wallet, parent=None ):
1129 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1137 new_pw = QLineEdit()
1138 new_pw.setEchoMode(2)
1139 conf_pw = QLineEdit()
1140 conf_pw.setEchoMode(2)
1142 vbox = QVBoxLayout()
1144 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')
1146 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'+_("Leave these fields empty if you want to disable encryption.")
1147 vbox.addWidget(QLabel(msg))
1149 grid = QGridLayout()
1152 if wallet.use_encryption:
1153 grid.addWidget(QLabel(_('Password')), 1, 0)
1154 grid.addWidget(pw, 1, 1)
1156 grid.addWidget(QLabel(_('New Password')), 2, 0)
1157 grid.addWidget(new_pw, 2, 1)
1159 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1160 grid.addWidget(conf_pw, 3, 1)
1161 vbox.addLayout(grid)
1163 vbox.addLayout(ok_cancel_buttons(d))
1166 if not d.exec_(): return
1168 password = unicode(pw.text()) if wallet.use_encryption else None
1169 new_password = unicode(new_pw.text())
1170 new_password2 = unicode(conf_pw.text())
1173 seed = wallet.pw_decode( wallet.seed, password)
1175 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1178 if new_password != new_password2:
1179 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1182 wallet.update_password(seed, password, new_password)
1185 def seed_dialog(wallet, parent=None):
1189 vbox = QVBoxLayout()
1190 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1191 vbox.addWidget(QLabel(msg))
1193 grid = QGridLayout()
1196 seed_e = QLineEdit()
1197 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1198 grid.addWidget(seed_e, 1, 1)
1202 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1203 grid.addWidget(gap_e, 2, 1)
1204 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1205 vbox.addLayout(grid)
1207 vbox.addLayout(ok_cancel_buttons(d))
1210 if not d.exec_(): return
1213 gap = int(unicode(gap_e.text()))
1215 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1219 seed = unicode(seed_e.text())
1222 print_error("Warning: Not hex, trying decode")
1224 seed = mnemonic.mn_decode( seed.split(' ') )
1226 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1229 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1232 wallet.seed = str(seed)
1233 #print repr(wallet.seed)
1234 wallet.gap_limit = gap
1239 def settings_dialog(self):
1242 vbox = QVBoxLayout()
1243 msg = _('Here are the settings of your wallet.') + '\n'\
1244 + _('For more explanations, click on the help buttons next to each field.')
1247 label.setFixedWidth(250)
1248 label.setWordWrap(True)
1249 label.setAlignment(Qt.AlignJustify)
1250 vbox.addWidget(label)
1252 grid = QGridLayout()
1254 vbox.addLayout(grid)
1256 fee_label = QLabel(_('Transaction fee'))
1257 grid.addWidget(fee_label, 2, 0)
1259 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1260 grid.addWidget(fee_e, 2, 1)
1261 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1262 + _('Recommended value') + ': 0.001'
1263 grid.addWidget(HelpButton(msg), 2, 2)
1264 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1265 if not self.config.is_modifiable('fee'):
1266 for w in [fee_e, fee_label]: w.setEnabled(False)
1268 nz_label = QLabel(_('Display zeros'))
1269 grid.addWidget(nz_label, 3, 0)
1271 nz_e.setText("%d"% self.wallet.num_zeros)
1272 grid.addWidget(nz_e, 3, 1)
1273 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1274 grid.addWidget(HelpButton(msg), 3, 2)
1275 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1276 if not self.config.is_modifiable('num_zeros'):
1277 for w in [nz_e, nz_label]: w.setEnabled(False)
1279 usechange_cb = QCheckBox(_('Use change addresses'))
1280 grid.addWidget(usechange_cb, 5, 0)
1281 usechange_cb.setChecked(self.wallet.use_change)
1282 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1283 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1285 gap_label = QLabel(_('Gap limit'))
1286 grid.addWidget(gap_label, 6, 0)
1288 gap_e.setText("%d"% self.wallet.gap_limit)
1289 grid.addWidget(gap_e, 6, 1)
1290 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1291 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1292 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1293 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1294 + _('Warning') + ': ' \
1295 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1296 + _('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'
1297 grid.addWidget(HelpButton(msg), 6, 2)
1298 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1299 if not self.config.is_modifiable('gap_limit'):
1300 for w in [gap_e, gap_label]: w.setEnabled(False)
1302 gui_label=QLabel(_('Default GUI') + ':')
1303 grid.addWidget(gui_label , 7, 0)
1304 gui_combo = QComboBox()
1305 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1306 gui_combo.setCurrentIndex(gui_combo.findText(self.config.get("gui","classic").capitalize()))
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.blocks)
1372 status = _("Not connected")
1375 status = _("Please choose a server.")
1377 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
1388 for item in servers_list:
1392 _protocol, _port = item2
1393 z[_protocol] = _port
1398 d.setWindowTitle(_('Server'))
1399 d.setMinimumSize(375, 20)
1401 vbox = QVBoxLayout()
1404 hbox = QHBoxLayout()
1406 l.setPixmap(QPixmap(":icons/network.png"))
1409 hbox.addWidget(QLabel(status))
1411 vbox.addLayout(hbox)
1415 grid = QGridLayout()
1417 vbox.addLayout(grid)
1420 server_protocol = QComboBox()
1421 server_host = QLineEdit()
1422 server_host.setFixedWidth(200)
1423 server_port = QLineEdit()
1424 server_port.setFixedWidth(60)
1426 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1427 protocol_letters = 'thsg'
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())
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 in plist.keys():
1454 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host ] ))
1457 def change_server(host, protocol=None):
1458 pp = plist.get(host,{})
1460 port = pp.get(protocol)
1461 if not port: protocol = None
1467 elif 't' in pp.keys():
1469 port = pp.get(protocol)
1471 protocol = pp.keys()[0]
1472 port = pp.get(protocol)
1475 server_host.setText( host )
1476 server_port.setText( port )
1477 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1479 for p in protocol_letters:
1480 i = protocol_letters.index(p)
1481 j = server_protocol.model().index(i,0)
1482 if p not in pp.keys():
1483 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1485 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1487 change_server(host,protocol)
1490 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1491 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1493 if not wallet.config.is_modifiable('server'):
1494 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1497 proxy_mode = QComboBox()
1498 proxy_host = QLineEdit()
1499 proxy_host.setFixedWidth(200)
1500 proxy_port = QLineEdit()
1501 proxy_port.setFixedWidth(60)
1502 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1504 def check_for_disable(index = False):
1505 if proxy_mode.currentText() != 'NONE':
1506 proxy_host.setEnabled(True)
1507 proxy_port.setEnabled(True)
1509 proxy_host.setEnabled(False)
1510 proxy_port.setEnabled(False)
1513 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1515 if not wallet.config.is_modifiable('proxy'):
1516 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1518 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1519 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1520 proxy_host.setText(proxy_config.get("host"))
1521 proxy_port.setText(proxy_config.get("port"))
1523 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1524 grid.addWidget(proxy_mode, 2, 1)
1525 grid.addWidget(proxy_host, 2, 2)
1526 grid.addWidget(proxy_port, 2, 3)
1529 vbox.addLayout(ok_cancel_buttons(d))
1532 if not d.exec_(): return
1534 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1535 if proxy_mode.currentText() != 'NONE':
1536 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1540 wallet.config.set_key("proxy", proxy, True)
1541 wallet.config.set_key("server", server, True)
1542 interface.set_server(server, proxy)
1546 def closeEvent(self, event):
1548 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1554 def __init__(self, wallet, config, app=None):
1555 self.wallet = wallet
1556 self.config = config
1558 self.app = QApplication(sys.argv)
1560 def server_list_changed(self):
1564 def restore_or_create(self):
1566 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1567 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1568 if r==2: return False
1570 is_recovery = (r==1)
1571 wallet = self.wallet
1572 # ask for the server.
1573 if not ElectrumWindow.network_dialog( wallet, parent=None ): return False
1575 # wait until we are connected, because the user might have selected another server
1576 if not wallet.interface.is_connected:
1577 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1578 waiting_dialog(waiting)
1580 waiting = lambda: False if wallet.up_to_date else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1581 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1584 wallet.new_seed(None)
1585 wallet.init_mpk( wallet.seed )
1586 wallet.up_to_date_event.clear()
1587 wallet.up_to_date = False
1588 wallet.interface.poke('synchronizer')
1589 waiting_dialog(waiting)
1590 # run a dialog indicating the seed, ask the user to remember it
1591 ElectrumWindow.show_seed_dialog(wallet)
1593 ElectrumWindow.change_password_dialog(wallet)
1595 # ask for seed and gap.
1596 if not ElectrumWindow.seed_dialog( wallet ): return False
1597 wallet.init_mpk( wallet.seed )
1598 wallet.up_to_date_event.clear()
1599 wallet.up_to_date = False
1600 wallet.interface.poke('synchronizer')
1601 waiting_dialog(waiting)
1602 if wallet.is_found():
1603 # history and addressbook
1604 wallet.update_tx_history()
1605 wallet.fill_addressbook()
1606 print "Recovery successful"
1609 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1617 w = ElectrumWindow(self.wallet, self.config)
1618 if url: w.set_url(url)