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.register_callback(self.update_callback)
212 self.detailed_view = config.get('qt_detailed_view', False)
214 self.funds_error = False
215 self.completions = QStringListModel()
217 self.tabs = tabs = QTabWidget(self)
218 tabs.addTab(self.create_history_tab(), _('History') )
220 tabs.addTab(self.create_send_tab(), _('Send') )
221 tabs.addTab(self.create_receive_tab(), _('Receive') )
222 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
223 tabs.addTab(self.create_wall_tab(), _('Wall') )
224 tabs.setMinimumSize(600, 400)
225 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
226 self.setCentralWidget(tabs)
227 self.create_status_bar()
229 g = self.config.get("winpos-qt",[100, 100, 840, 400])
230 self.setGeometry(g[0], g[1], g[2], g[3])
231 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
232 if not self.wallet.seed: title += ' [seedless]'
233 self.setWindowTitle( title )
235 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
236 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
237 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
238 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
240 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
241 self.history_list.setFocus(True)
243 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
244 if platform.system() == 'Windows':
245 n = 3 if self.wallet.seed else 2
246 tabs.setCurrentIndex (n)
247 tabs.setCurrentIndex (0)
250 def connect_slots(self, sender):
252 self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient)
253 self.previous_payto_e=''
255 def check_recipient(self):
256 if self.payto_e.hasFocus():
258 r = unicode( self.payto_e.text() )
259 if r != self.previous_payto_e:
260 self.previous_payto_e = r
262 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
264 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
268 s = r + ' <' + to_address + '>'
269 self.payto_e.setText(s)
272 def update_callback(self):
273 self.emit(QtCore.SIGNAL('updatesignal'))
275 def update_wallet(self):
276 if self.wallet.interface and self.wallet.interface.is_connected:
277 if self.wallet.blocks == -1:
278 text = _( "Connecting..." )
279 icon = QIcon(":icons/status_disconnected.png")
280 elif self.wallet.blocks == 0:
281 text = _( "Server not ready" )
282 icon = QIcon(":icons/status_disconnected.png")
283 elif not self.wallet.up_to_date:
284 text = _( "Synchronizing..." )
285 icon = QIcon(":icons/status_waiting.png")
287 c, u = self.wallet.get_balance()
288 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
289 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
290 icon = QIcon(":icons/status_connected.png")
292 text = _( "Not connected" )
293 icon = QIcon(":icons/status_disconnected.png")
296 text = _( "Not enough funds" )
298 self.statusBar().showMessage(text)
299 self.status_button.setIcon( icon )
301 if self.wallet.up_to_date:
302 self.textbox.setText( self.wallet.banner )
303 self.update_history_tab()
304 self.update_receive_tab()
305 self.update_contacts_tab()
306 self.update_completions()
309 def create_history_tab(self):
310 self.history_list = l = MyTreeWidget(self)
312 l.setColumnWidth(0, 40)
313 l.setColumnWidth(1, 140)
314 l.setColumnWidth(2, 350)
315 l.setColumnWidth(3, 140)
316 l.setColumnWidth(4, 140)
317 l.setHeaderLabels( [ '', _( 'Date' ), _( 'To / From' ) , _('Amount'), _('Balance')] )
318 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
319 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
320 l.setContextMenuPolicy(Qt.CustomContextMenu)
321 l.customContextMenuRequested.connect(self.create_history_menu)
324 def create_history_menu(self, position):
325 self.history_list.selectedIndexes()
326 item = self.history_list.currentItem()
328 tx_hash = str(item.toolTip(0))
330 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
331 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
332 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
333 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
335 def tx_details(self, tx_hash):
336 tx = self.wallet.tx_history.get(tx_hash)
339 conf = self.wallet.blocks - tx['height'] + 1
340 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
345 tx_details = _("Transaction Details") +"\n\n" \
346 + "Transaction ID:\n" + tx_hash + "\n\n" \
347 + "Status: %d confirmations\n\n"%conf \
348 + "Date: %s\n\n"%time_str \
349 + "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" \
350 + "Outputs:\n-"+ '\n-'.join(tx['outputs'])
352 r = self.wallet.receipts.get(tx_hash)
354 tx_details += "\n_______________________________________" \
355 + '\n\nSigned URI: ' + r[2] \
356 + "\n\nSigned by: " + r[0] \
357 + '\n\nSignature: ' + r[1]
359 QMessageBox.information(self, 'Details', tx_details, 'OK')
362 def tx_label_clicked(self, item, column):
363 if column==2 and item.isSelected():
364 tx_hash = str(item.toolTip(0))
366 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
367 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
368 self.history_list.editItem( item, column )
369 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
372 def tx_label_changed(self, item, column):
376 tx_hash = str(item.toolTip(0))
377 tx = self.wallet.tx_history.get(tx_hash)
378 s = self.wallet.labels.get(tx_hash)
379 text = unicode( item.text(2) )
381 self.wallet.labels[tx_hash] = text
382 item.setForeground(2, QBrush(QColor('black')))
384 if s: self.wallet.labels.pop(tx_hash)
385 text = tx['default_label']
386 item.setText(2, text)
387 item.setForeground(2, QBrush(QColor('gray')))
390 def edit_label(self, is_recv):
391 l = self.receive_list if is_recv else self.contacts_list
392 c = 2 if is_recv else 1
393 item = l.currentItem()
394 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
395 l.editItem( item, c )
396 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
398 def address_label_clicked(self, item, column, l, column_addr, column_label):
399 if column==column_label and item.isSelected():
400 addr = unicode( item.text(column_addr) )
401 label = unicode( item.text(column_label) )
402 if label in self.wallet.aliases.keys():
404 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
405 l.editItem( item, column )
406 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
408 def address_label_changed(self, item, column, l, column_addr, column_label):
409 addr = unicode( item.text(column_addr) )
410 text = unicode( item.text(column_label) )
414 if text not in self.wallet.aliases.keys():
415 old_addr = self.wallet.labels.get(text)
417 self.wallet.labels[addr] = text
420 print_error("Error: This is one of your aliases")
421 label = self.wallet.labels.get(addr,'')
422 item.setText(column_label, QString(label))
424 s = self.wallet.labels.get(addr)
426 self.wallet.labels.pop(addr)
430 self.wallet.update_tx_labels()
431 self.update_history_tab()
432 self.update_completions()
435 def update_history_tab(self):
436 self.history_list.clear()
438 for tx in self.wallet.get_tx_history():
439 tx_hash = tx['tx_hash']
441 conf = self.wallet.blocks - tx['height'] + 1
442 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
444 icon = QIcon(":icons/clock%d.png"%conf)
446 icon = QIcon(":icons/confirmed.png")
450 icon = QIcon(":icons/unconfirmed.png")
453 label = self.wallet.labels.get(tx_hash)
454 is_default_label = (label == '') or (label is None)
455 if is_default_label: label = tx['default_label']
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,[])
852 if not item['is_input'] : n=n+1
856 if address in self.wallet.addresses:
858 if gap > self.wallet.gap_limit:
861 if address in self.wallet.addresses:
864 c, u = self.wallet.get_addr_balance(address)
865 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
866 flags = self.wallet.get_address_flags(address)
867 item = QTreeWidgetItem( [ flags, address, label, balance, tx] )
869 item.setFont(0, QFont(MONOSPACE_FONT))
870 item.setFont(1, QFont(MONOSPACE_FONT))
871 item.setFont(3, QFont(MONOSPACE_FONT))
872 if address in self.wallet.frozen_addresses:
873 item.setBackgroundColor(1, QColor('lightblue'))
874 elif address in self.wallet.prioritized_addresses:
875 item.setBackgroundColor(1, QColor('lightgreen'))
876 if is_red and address in self.wallet.addresses:
877 item.setBackgroundColor(1, QColor('red'))
878 l.addTopLevelItem(item)
880 # we use column 1 because column 0 may be hidden
881 l.setCurrentItem(l.topLevelItem(0),1)
883 def show_contact_details(self, m):
884 a = self.wallet.aliases.get(m)
886 if a[0] in self.wallet.authorities.keys():
887 s = self.wallet.authorities.get(a[0])
890 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
891 QMessageBox.information(self, 'Alias', msg, 'OK')
893 def update_contacts_tab(self):
895 l = self.contacts_list
897 l.setColumnHidden(2, not self.detailed_view)
898 l.setColumnWidth(0, 350)
899 l.setColumnWidth(1, 330)
900 l.setColumnWidth(2, 100)
903 for alias, v in self.wallet.aliases.items():
905 alias_targets.append(target)
906 item = QTreeWidgetItem( [ target, alias, '-'] )
907 item.setBackgroundColor(0, QColor('lightgray'))
908 l.addTopLevelItem(item)
910 for address in self.wallet.addressbook:
911 if address in alias_targets: continue
912 label = self.wallet.labels.get(address,'')
914 for item in self.wallet.tx_history.values():
915 if address in item['outputs'] : n=n+1
917 item = QTreeWidgetItem( [ address, label, tx] )
918 item.setFont(0, QFont(MONOSPACE_FONT))
919 l.addTopLevelItem(item)
921 l.setCurrentItem(l.topLevelItem(0))
923 def create_wall_tab(self):
924 self.textbox = textbox = QTextEdit(self)
925 textbox.setFont(QFont(MONOSPACE_FONT))
926 textbox.setReadOnly(True)
929 def create_status_bar(self):
931 sb.setFixedHeight(35)
933 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
934 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
936 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
937 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
938 sb.addPermanentWidget( self.status_button )
939 self.setStatusBar(sb)
941 def new_contact_dialog(self):
942 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
943 address = unicode(text)
945 if self.wallet.is_valid(address):
946 self.wallet.addressbook.append(address)
948 self.update_contacts_tab()
949 self.update_history_tab()
950 self.update_completions()
952 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
955 def show_seed_dialog(wallet, parent=None):
957 QMessageBox.information(parent, _('Message'),
958 _('No seed'), _('OK'))
961 if wallet.use_encryption:
962 password = parent.password_dialog()
969 seed = wallet.pw_decode(wallet.seed, password)
971 QMessageBox.warning(parent, _('Error'),
972 _('Incorrect Password'), _('OK'))
975 dialog = QDialog(None)
977 dialog.setWindowTitle(_("Seed"))
979 brainwallet = ' '.join(mnemonic.mn_encode(seed))
981 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
982 + _("Please write down or memorize these 12 words (order is important).") + " " \
983 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
984 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
986 main_text = QLabel(msg)
987 main_text.setWordWrap(True)
990 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
997 copy_function = lambda: app.clipboard().setText(brainwallet)
998 copy_button = QPushButton(_("Copy to Clipboard"))
999 copy_button.clicked.connect(copy_function)
1001 show_qr_function = lambda: ElectrumWindow.show_seed_qrcode(seed)
1002 qr_button = QPushButton(_("View as QR Code"))
1003 qr_button.clicked.connect(show_qr_function)
1005 ok_button = QPushButton(_("OK"))
1006 ok_button.setDefault(True)
1007 ok_button.clicked.connect(dialog.accept)
1009 main_layout = QGridLayout()
1010 main_layout.addWidget(logo, 0, 0)
1011 main_layout.addWidget(main_text, 0, 1, 1, -1)
1012 main_layout.addWidget(copy_button, 1, 1)
1013 main_layout.addWidget(qr_button, 1, 2)
1014 main_layout.addWidget(ok_button, 1, 3)
1015 dialog.setLayout(main_layout)
1020 def show_seed_qrcode(seed):
1024 d.setWindowTitle(_("Seed"))
1025 d.setMinimumSize(270, 300)
1026 vbox = QVBoxLayout()
1027 vbox.addWidget(QRCodeWidget(seed))
1028 hbox = QHBoxLayout()
1030 b = QPushButton(_("OK"))
1032 b.clicked.connect(d.accept)
1034 vbox.addLayout(hbox)
1039 def show_address_qrcode(self,address):
1040 if not address: return
1043 d.setWindowTitle(address)
1044 d.setMinimumSize(270, 350)
1045 vbox = QVBoxLayout()
1046 qrw = QRCodeWidget(address)
1049 hbox = QHBoxLayout()
1050 amount_e = QLineEdit()
1051 hbox.addWidget(QLabel(_('Amount')))
1052 hbox.addWidget(amount_e)
1053 vbox.addLayout(hbox)
1055 #hbox = QHBoxLayout()
1056 #label_e = QLineEdit()
1057 #hbox.addWidget(QLabel('Label'))
1058 #hbox.addWidget(label_e)
1059 #vbox.addLayout(hbox)
1061 def amount_changed():
1062 amount = numbify(amount_e)
1063 #label = str( label_e.getText() )
1064 if amount is not None:
1065 qrw.set_addr('bitcoin:%s?amount=%s'%(address,str( Decimal(amount) /100000000)))
1067 qrw.set_addr( address )
1071 bmp.save_qrcode(qrw.qr, "qrcode.bmp")
1072 self.show_message(_("QR code saved to file") + " 'qrcode.bmp'")
1074 amount_e.textChanged.connect( amount_changed )
1076 hbox = QHBoxLayout()
1078 b = QPushButton(_("Save"))
1079 b.clicked.connect(do_save)
1081 b = QPushButton(_("Close"))
1083 b.clicked.connect(d.accept)
1085 vbox.addLayout(hbox)
1089 def question(self, msg):
1090 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1092 def show_message(self, msg):
1093 QMessageBox.information(self, _('Message'), msg, _('OK'))
1095 def password_dialog(self ):
1102 vbox = QVBoxLayout()
1103 msg = _('Please enter your password')
1104 vbox.addWidget(QLabel(msg))
1106 grid = QGridLayout()
1108 grid.addWidget(QLabel(_('Password')), 1, 0)
1109 grid.addWidget(pw, 1, 1)
1110 vbox.addLayout(grid)
1112 vbox.addLayout(ok_cancel_buttons(d))
1115 if not d.exec_(): return
1116 return unicode(pw.text())
1123 def change_password_dialog( wallet, parent=None ):
1126 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1134 new_pw = QLineEdit()
1135 new_pw.setEchoMode(2)
1136 conf_pw = QLineEdit()
1137 conf_pw.setEchoMode(2)
1139 vbox = QVBoxLayout()
1141 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'+_('To disable wallet encryption, enter an empty new password.')) if wallet.use_encryption else _('Your wallet keys are not encrypted')
1143 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'+_("Leave these fields empty if you want to disable encryption.")
1144 vbox.addWidget(QLabel(msg))
1146 grid = QGridLayout()
1149 if wallet.use_encryption:
1150 grid.addWidget(QLabel(_('Password')), 1, 0)
1151 grid.addWidget(pw, 1, 1)
1153 grid.addWidget(QLabel(_('New Password')), 2, 0)
1154 grid.addWidget(new_pw, 2, 1)
1156 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1157 grid.addWidget(conf_pw, 3, 1)
1158 vbox.addLayout(grid)
1160 vbox.addLayout(ok_cancel_buttons(d))
1163 if not d.exec_(): return
1165 password = unicode(pw.text()) if wallet.use_encryption else None
1166 new_password = unicode(new_pw.text())
1167 new_password2 = unicode(conf_pw.text())
1170 seed = wallet.pw_decode( wallet.seed, password)
1172 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1175 if new_password != new_password2:
1176 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1179 wallet.update_password(seed, password, new_password)
1182 def seed_dialog(wallet, parent=None):
1186 vbox = QVBoxLayout()
1187 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1188 vbox.addWidget(QLabel(msg))
1190 grid = QGridLayout()
1193 seed_e = QLineEdit()
1194 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1195 grid.addWidget(seed_e, 1, 1)
1199 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1200 grid.addWidget(gap_e, 2, 1)
1201 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1202 vbox.addLayout(grid)
1204 vbox.addLayout(ok_cancel_buttons(d))
1207 if not d.exec_(): return
1210 gap = int(unicode(gap_e.text()))
1212 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1216 seed = unicode(seed_e.text())
1219 print_error("Warning: Not hex, trying decode")
1221 seed = mnemonic.mn_decode( seed.split(' ') )
1223 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1226 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1229 wallet.seed = str(seed)
1230 #print repr(wallet.seed)
1231 wallet.gap_limit = gap
1236 def settings_dialog(self):
1239 vbox = QVBoxLayout()
1240 msg = _('Here are the settings of your wallet.') + '\n'\
1241 + _('For more explanations, click on the help buttons next to each field.')
1244 label.setFixedWidth(250)
1245 label.setWordWrap(True)
1246 label.setAlignment(Qt.AlignJustify)
1247 vbox.addWidget(label)
1249 grid = QGridLayout()
1251 vbox.addLayout(grid)
1253 fee_label = QLabel(_('Transaction fee'))
1254 grid.addWidget(fee_label, 2, 0)
1256 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1257 grid.addWidget(fee_e, 2, 1)
1258 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1259 + _('Recommended value') + ': 0.001'
1260 grid.addWidget(HelpButton(msg), 2, 2)
1261 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1262 if not self.config.is_modifiable('fee'):
1263 for w in [fee_e, fee_label]: w.setEnabled(False)
1265 nz_label = QLabel(_('Display zeros'))
1266 grid.addWidget(nz_label, 3, 0)
1268 nz_e.setText("%d"% self.wallet.num_zeros)
1269 grid.addWidget(nz_e, 3, 1)
1270 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1271 grid.addWidget(HelpButton(msg), 3, 2)
1272 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1273 if not self.config.is_modifiable('num_zeros'):
1274 for w in [nz_e, nz_label]: w.setEnabled(False)
1276 usechange_cb = QCheckBox(_('Use change addresses'))
1277 grid.addWidget(usechange_cb, 5, 0)
1278 usechange_cb.setChecked(self.wallet.use_change)
1279 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1280 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1282 gap_label = QLabel(_('Gap limit'))
1283 grid.addWidget(gap_label, 6, 0)
1285 gap_e.setText("%d"% self.wallet.gap_limit)
1286 grid.addWidget(gap_e, 6, 1)
1287 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1288 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1289 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1290 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1291 + _('Warning') + ': ' \
1292 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1293 + _('Do not modify it if you do not understand what you are doing, or if you expect to recover your wallet without knowing it!') + '\n\n'
1294 grid.addWidget(HelpButton(msg), 6, 2)
1295 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1296 if not self.config.is_modifiable('gap_limit'):
1297 for w in [gap_e, gap_label]: w.setEnabled(False)
1299 gui_label=QLabel(_('Default GUI') + ':')
1300 grid.addWidget(gui_label , 7, 0)
1301 gui_combo = QComboBox()
1302 gui_combo.addItems(['Lite', 'Classic', 'Gtk'])
1303 gui_combo.setCurrentIndex(gui_combo.findText(self.config.get("gui","classic").capitalize()))
1304 grid.addWidget(gui_combo, 7, 1)
1305 grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1306 if not self.config.is_modifiable('gui'):
1307 for w in [gui_combo, gui_label]: w.setEnabled(False)
1309 vbox.addLayout(ok_cancel_buttons(d))
1313 if not d.exec_(): return
1315 fee = unicode(fee_e.text())
1317 fee = int( 100000000 * Decimal(fee) )
1319 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1322 if self.wallet.fee != fee:
1323 self.wallet.fee = fee
1326 nz = unicode(nz_e.text())
1331 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1334 if self.wallet.num_zeros != nz:
1335 self.wallet.num_zeros = nz
1336 self.config.set_key('num_zeros', nz, True)
1337 self.update_history_tab()
1338 self.update_receive_tab()
1340 if self.wallet.use_change != usechange_cb.isChecked():
1341 self.wallet.use_change = usechange_cb.isChecked()
1342 self.config.set_key('use_change', self.wallet.use_change, True)
1345 n = int(gap_e.text())
1347 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1350 if self.wallet.gap_limit != n:
1351 r = self.wallet.change_gap_limit(n)
1353 self.update_receive_tab()
1354 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1356 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1358 self.config.set_key("gui", str(gui_combo.currentText()).lower(), True)
1363 def network_dialog(wallet, parent=None):
1364 interface = wallet.interface
1366 if interface.is_connected:
1367 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.blocks)
1369 status = _("Not connected")
1372 status = _("Please choose a server.")
1374 server = interface.server
1376 if not wallet.interface.servers:
1378 for x in DEFAULT_SERVERS:
1379 h,port,protocol = x.split(':')
1380 servers_list.append( (h,[(protocol,port)] ) )
1382 servers_list = wallet.interface.servers
1385 for item in servers_list:
1389 _protocol, _port = item2
1390 z[_protocol] = _port
1395 d.setWindowTitle(_('Server'))
1396 d.setMinimumSize(375, 20)
1398 vbox = QVBoxLayout()
1401 hbox = QHBoxLayout()
1403 l.setPixmap(QPixmap(":icons/network.png"))
1406 hbox.addWidget(QLabel(status))
1408 vbox.addLayout(hbox)
1412 grid = QGridLayout()
1414 vbox.addLayout(grid)
1417 server_protocol = QComboBox()
1418 server_host = QLineEdit()
1419 server_host.setFixedWidth(200)
1420 server_port = QLineEdit()
1421 server_port.setFixedWidth(60)
1423 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1424 protocol_letters = 'thsg'
1425 server_protocol.addItems(protocol_names)
1427 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1428 grid.addWidget(server_protocol, 0, 1)
1429 grid.addWidget(server_host, 0, 2)
1430 grid.addWidget(server_port, 0, 3)
1432 host, port, protocol = server.split(':')
1434 def change_protocol(p):
1435 protocol = protocol_letters[p]
1436 host = unicode(server_host.text())
1438 if protocol not in pp.keys():
1439 protocol = pp.keys()[0]
1441 server_host.setText( host )
1442 server_port.setText( port )
1444 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1446 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1447 servers_list_widget = QTreeWidget(parent)
1448 servers_list_widget.setHeaderLabels( [ label ] )
1449 servers_list_widget.setMaximumHeight(150)
1450 for _host in plist.keys():
1451 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host ] ))
1454 def change_server(host, protocol=None):
1455 pp = plist.get(host,{})
1457 port = pp.get(protocol)
1458 if not port: protocol = None
1464 elif 't' in pp.keys():
1466 port = pp.get(protocol)
1468 protocol = pp.keys()[0]
1469 port = pp.get(protocol)
1472 server_host.setText( host )
1473 server_port.setText( port )
1474 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
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)
1484 change_server(host,protocol)
1487 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1488 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1490 if not wallet.config.is_modifiable('server'):
1491 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1494 proxy_mode = QComboBox()
1495 proxy_host = QLineEdit()
1496 proxy_host.setFixedWidth(200)
1497 proxy_port = QLineEdit()
1498 proxy_port.setFixedWidth(60)
1499 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1501 def check_for_disable(index = False):
1502 if proxy_mode.currentText() != 'NONE':
1503 proxy_host.setEnabled(True)
1504 proxy_port.setEnabled(True)
1506 proxy_host.setEnabled(False)
1507 proxy_port.setEnabled(False)
1510 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1512 if not wallet.config.is_modifiable('proxy'):
1513 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1515 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1516 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1517 proxy_host.setText(proxy_config.get("host"))
1518 proxy_port.setText(proxy_config.get("port"))
1520 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1521 grid.addWidget(proxy_mode, 2, 1)
1522 grid.addWidget(proxy_host, 2, 2)
1523 grid.addWidget(proxy_port, 2, 3)
1526 vbox.addLayout(ok_cancel_buttons(d))
1529 if not d.exec_(): return
1531 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1532 if proxy_mode.currentText() != 'NONE':
1533 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1537 wallet.config.set_key("proxy", proxy, True)
1538 wallet.config.set_key("server", server, True)
1539 interface.set_server(server, proxy)
1543 def closeEvent(self, event):
1545 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1551 def __init__(self, wallet, config, app=None):
1552 self.wallet = wallet
1553 self.config = config
1555 self.app = QApplication(sys.argv)
1557 def server_list_changed(self):
1561 def restore_or_create(self):
1563 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1564 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1565 if r==2: return False
1567 is_recovery = (r==1)
1568 wallet = self.wallet
1569 # ask for the server.
1570 if not ElectrumWindow.network_dialog( wallet, parent=None ): return False
1572 waiting = lambda: False if wallet.up_to_date else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1573 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1576 wallet.new_seed(None)
1577 wallet.init_mpk( wallet.seed )
1578 wallet.up_to_date_event.clear()
1579 wallet.up_to_date = False
1580 wallet.interface.poke()
1581 waiting_dialog(waiting)
1582 # run a dialog indicating the seed, ask the user to remember it
1583 ElectrumWindow.show_seed_dialog(wallet)
1585 ElectrumWindow.change_password_dialog(wallet)
1587 # ask for seed and gap.
1588 if not ElectrumWindow.seed_dialog( wallet ): return False
1589 wallet.init_mpk( wallet.seed )
1590 wallet.up_to_date_event.clear()
1591 wallet.up_to_date = False
1592 wallet.interface.poke()
1593 waiting_dialog(waiting)
1594 if wallet.is_found():
1595 # history and addressbook
1596 wallet.update_tx_history()
1597 wallet.fill_addressbook()
1598 print "Recovery successful"
1601 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1609 w = ElectrumWindow(self.wallet, self.config)
1610 if url: w.set_url(url)