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)
214 self.detailed_view = config.get('qt_detailed_view', False)
216 self.funds_error = False
217 self.completions = QStringListModel()
219 self.tabs = tabs = QTabWidget(self)
220 tabs.addTab(self.create_history_tab(), _('History') )
222 tabs.addTab(self.create_send_tab(), _('Send') )
223 tabs.addTab(self.create_receive_tab(), _('Receive') )
224 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
225 tabs.addTab(self.create_wall_tab(), _('Wall') )
226 tabs.setMinimumSize(600, 400)
227 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
228 self.setCentralWidget(tabs)
229 self.create_status_bar()
231 g = self.config.get("winpos-qt",[100, 100, 840, 400])
232 self.setGeometry(g[0], g[1], g[2], g[3])
233 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
234 if not self.wallet.seed: title += ' [seedless]'
235 self.setWindowTitle( title )
237 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
238 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
239 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
240 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
242 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
243 self.history_list.setFocus(True)
245 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
246 if platform.system() == 'Windows':
247 n = 3 if self.wallet.seed else 2
248 tabs.setCurrentIndex (n)
249 tabs.setCurrentIndex (0)
252 def connect_slots(self, sender):
254 self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient)
255 self.previous_payto_e=''
257 def check_recipient(self):
258 if self.payto_e.hasFocus():
260 r = unicode( self.payto_e.text() )
261 if r != self.previous_payto_e:
262 self.previous_payto_e = r
264 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
266 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
270 s = r + ' <' + to_address + '>'
271 self.payto_e.setText(s)
274 def update_callback(self):
275 self.emit(QtCore.SIGNAL('updatesignal'))
277 def update_wallet(self):
278 if self.wallet.interface and self.wallet.interface.is_connected:
279 if self.wallet.blocks == -1:
280 text = _( "Connecting..." )
281 icon = QIcon(":icons/status_disconnected.png")
282 elif self.wallet.blocks == 0:
283 text = _( "Server not ready" )
284 icon = QIcon(":icons/status_disconnected.png")
285 elif not self.wallet.up_to_date:
286 text = _( "Synchronizing..." )
287 icon = QIcon(":icons/status_waiting.png")
289 c, u = self.wallet.get_balance()
290 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
291 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
292 icon = QIcon(":icons/status_connected.png")
294 text = _( "Not connected" )
295 icon = QIcon(":icons/status_disconnected.png")
298 text = _( "Not enough funds" )
300 self.statusBar().showMessage(text)
301 self.status_button.setIcon( icon )
303 if self.wallet.up_to_date:
304 self.textbox.setText( self.wallet.banner )
305 self.update_history_tab()
306 self.update_receive_tab()
307 self.update_contacts_tab()
308 self.update_completions()
311 def create_history_tab(self):
312 self.history_list = l = MyTreeWidget(self)
314 l.setColumnWidth(0, 40)
315 l.setColumnWidth(1, 140)
316 l.setColumnWidth(2, 350)
317 l.setColumnWidth(3, 140)
318 l.setColumnWidth(4, 140)
319 l.setHeaderLabels( [ '', _( 'Date' ), _( 'To / From' ) , _('Amount'), _('Balance')] )
320 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
321 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
322 l.setContextMenuPolicy(Qt.CustomContextMenu)
323 l.customContextMenuRequested.connect(self.create_history_menu)
326 def create_history_menu(self, position):
327 self.history_list.selectedIndexes()
328 item = self.history_list.currentItem()
330 tx_hash = str(item.toolTip(0))
332 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
333 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
334 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
335 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
337 def tx_details(self, tx_hash):
338 tx = self.wallet.tx_history.get(tx_hash)
341 conf = self.wallet.blocks - tx['height'] + 1
342 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
347 tx_details = _("Transaction Details") +"\n\n" \
348 + "Transaction ID:\n" + tx_hash + "\n\n" \
349 + "Status: %d confirmations\n\n"%conf \
350 + "Date: %s\n\n"%time_str \
351 + "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" \
352 + "Outputs:\n-"+ '\n-'.join(tx['outputs'])
354 r = self.wallet.receipts.get(tx_hash)
356 tx_details += "\n_______________________________________" \
357 + '\n\nSigned URI: ' + r[2] \
358 + "\n\nSigned by: " + r[0] \
359 + '\n\nSignature: ' + r[1]
361 QMessageBox.information(self, 'Details', tx_details, 'OK')
364 def tx_label_clicked(self, item, column):
365 if column==2 and item.isSelected():
366 tx_hash = str(item.toolTip(0))
368 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
369 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
370 self.history_list.editItem( item, column )
371 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
374 def tx_label_changed(self, item, column):
378 tx_hash = str(item.toolTip(0))
379 tx = self.wallet.tx_history.get(tx_hash)
380 s = self.wallet.labels.get(tx_hash)
381 text = unicode( item.text(2) )
383 self.wallet.labels[tx_hash] = text
384 item.setForeground(2, QBrush(QColor('black')))
386 if s: self.wallet.labels.pop(tx_hash)
387 text = tx['default_label']
388 item.setText(2, text)
389 item.setForeground(2, QBrush(QColor('gray')))
392 def edit_label(self, is_recv):
393 l = self.receive_list if is_recv else self.contacts_list
394 c = 2 if is_recv else 1
395 item = l.currentItem()
396 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
397 l.editItem( item, c )
398 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
400 def address_label_clicked(self, item, column, l, column_addr, column_label):
401 if column==column_label and item.isSelected():
402 addr = unicode( item.text(column_addr) )
403 label = unicode( item.text(column_label) )
404 if label in self.wallet.aliases.keys():
406 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
407 l.editItem( item, column )
408 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
410 def address_label_changed(self, item, column, l, column_addr, column_label):
411 addr = unicode( item.text(column_addr) )
412 text = unicode( item.text(column_label) )
416 if text not in self.wallet.aliases.keys():
417 old_addr = self.wallet.labels.get(text)
419 self.wallet.labels[addr] = text
422 print_error("Error: This is one of your aliases")
423 label = self.wallet.labels.get(addr,'')
424 item.setText(column_label, QString(label))
426 s = self.wallet.labels.get(addr)
428 self.wallet.labels.pop(addr)
432 self.wallet.update_tx_labels()
433 self.update_history_tab()
434 self.update_completions()
437 def update_history_tab(self):
438 self.history_list.clear()
440 for tx in self.wallet.get_tx_history():
441 tx_hash = tx['tx_hash']
443 conf = self.wallet.blocks - tx['height'] + 1
444 time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
446 icon = QIcon(":icons/clock%d.png"%conf)
448 icon = QIcon(":icons/confirmed.png")
452 icon = QIcon(":icons/unconfirmed.png")
455 label = self.wallet.labels.get(tx_hash)
456 is_default_label = (label == '') or (label is None)
457 if is_default_label: label = tx['default_label']
459 item = QTreeWidgetItem( [ '', time_str, label, format_satoshis(v,True,self.wallet.num_zeros), format_satoshis(balance,False,self.wallet.num_zeros)] )
460 item.setFont(2, QFont(MONOSPACE_FONT))
461 item.setFont(3, QFont(MONOSPACE_FONT))
462 item.setFont(4, QFont(MONOSPACE_FONT))
463 item.setToolTip(0, tx_hash)
465 item.setForeground(2, QBrush(QColor('grey')))
467 item.setIcon(0, icon)
468 self.history_list.insertTopLevelItem(0,item)
470 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
473 def create_send_tab(self):
478 grid.setColumnMinimumWidth(3,300)
479 grid.setColumnStretch(5,1)
481 self.payto_e = QLineEdit()
482 grid.addWidget(QLabel(_('Pay to')), 1, 0)
483 grid.addWidget(self.payto_e, 1, 1, 1, 3)
486 qrcode = qrscanner.scan_qr()
487 if 'address' in qrcode:
488 self.payto_e.setText(qrcode['address'])
489 if 'amount' in qrcode:
490 self.amount_e.setText(str(qrcode['amount']))
491 if 'label' in qrcode:
492 self.message_e.setText(qrcode['label'])
493 if 'message' in qrcode:
494 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
497 if qrscanner.is_available():
498 b = QPushButton(_("Scan QR code"))
499 b.clicked.connect(fill_from_qr)
500 grid.addWidget(b, 1, 5)
502 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)
504 completer = QCompleter()
505 completer.setCaseSensitivity(False)
506 self.payto_e.setCompleter(completer)
507 completer.setModel(self.completions)
509 self.message_e = QLineEdit()
510 grid.addWidget(QLabel(_('Description')), 2, 0)
511 grid.addWidget(self.message_e, 2, 1, 1, 3)
512 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)
514 self.amount_e = QLineEdit()
515 grid.addWidget(QLabel(_('Amount')), 3, 0)
516 grid.addWidget(self.amount_e, 3, 1, 1, 2)
517 grid.addWidget(HelpButton(
518 _('Amount to be sent.') + '\n\n' \
519 + _('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)
521 self.fee_e = QLineEdit()
522 grid.addWidget(QLabel(_('Fee')), 4, 0)
523 grid.addWidget(self.fee_e, 4, 1, 1, 2)
524 grid.addWidget(HelpButton(
525 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
526 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
527 + _('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)
529 b = EnterButton(_("Send"), self.do_send)
530 grid.addWidget(b, 6, 1)
532 b = EnterButton(_("Clear"),self.do_clear)
533 grid.addWidget(b, 6, 2)
535 self.payto_sig = QLabel('')
536 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
538 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
539 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
548 def entry_changed( is_fee ):
549 self.funds_error = False
550 amount = numbify(self.amount_e)
551 fee = numbify(self.fee_e)
552 if not is_fee: fee = None
555 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
557 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
560 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
563 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
564 self.funds_error = True
565 self.amount_e.setPalette(palette)
566 self.fee_e.setPalette(palette)
568 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
569 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
574 def update_completions(self):
576 for addr,label in self.wallet.labels.items():
577 if addr in self.wallet.addressbook:
578 l.append( label + ' <' + addr + '>')
579 l = l + self.wallet.aliases.keys()
581 self.completions.setStringList(l)
587 label = unicode( self.message_e.text() )
588 r = unicode( self.payto_e.text() )
592 m1 = re.match(ALIAS_REGEXP, r)
593 # label or alias, with address in brackets
594 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
597 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
601 to_address = m2.group(2)
605 if not self.wallet.is_valid(to_address):
606 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
610 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
612 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
615 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
617 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
620 if self.wallet.use_encryption:
621 password = self.password_dialog()
628 tx = self.wallet.mktx( to_address, amount, label, password, fee)
629 except BaseException, e:
630 self.show_message(str(e))
633 h = self.wallet.send_tx(tx)
634 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
635 status, msg = self.wallet.receive_tx( h )
638 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
640 self.update_contacts_tab()
642 QMessageBox.warning(self, _('Error'), msg, _('OK'))
645 def set_url(self, url):
646 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
647 self.tabs.setCurrentIndex(1)
648 label = self.wallet.labels.get(payto)
649 m_addr = label + ' <'+ payto+'>' if label else payto
650 self.payto_e.setText(m_addr)
652 self.message_e.setText(message)
653 self.amount_e.setText(amount)
655 self.set_frozen(self.payto_e,True)
656 self.set_frozen(self.amount_e,True)
657 self.set_frozen(self.message_e,True)
658 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
660 self.payto_sig.setVisible(False)
663 self.payto_sig.setVisible(False)
664 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
666 self.set_frozen(e,False)
668 def set_frozen(self,entry,frozen):
670 entry.setReadOnly(True)
671 entry.setFrame(False)
673 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
674 entry.setPalette(palette)
676 entry.setReadOnly(False)
679 palette.setColor(entry.backgroundRole(), QColor('white'))
680 entry.setPalette(palette)
683 def toggle_freeze(self,addr):
685 if addr in self.wallet.frozen_addresses:
686 self.wallet.unfreeze(addr)
688 self.wallet.freeze(addr)
689 self.update_receive_tab()
691 def toggle_priority(self,addr):
693 if addr in self.wallet.prioritized_addresses:
694 self.wallet.unprioritize(addr)
696 self.wallet.prioritize(addr)
697 self.update_receive_tab()
700 def create_list_tab(self, headers):
701 "generic tab creation method"
702 l = MyTreeWidget(self)
703 l.setColumnCount( len(headers) )
704 l.setHeaderLabels( headers )
714 vbox.addWidget(buttons)
719 buttons.setLayout(hbox)
724 def create_receive_tab(self):
725 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Balance'), _('Tx')])
726 l.setContextMenuPolicy(Qt.CustomContextMenu)
727 l.customContextMenuRequested.connect(self.create_receive_menu)
728 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
729 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
730 self.receive_list = l
731 self.receive_buttons_hbox = hbox
732 self.details_button = EnterButton(self.details_button_text(), self.toggle_detailed_view)
733 hbox.addWidget(self.details_button)
737 def details_button_text(self):
738 return _('Hide details') if self.detailed_view else _('Show details')
740 def toggle_detailed_view(self):
741 self.detailed_view = not self.detailed_view
742 self.config.set_key('qt_detailed_view', self.detailed_view, True)
744 self.details_button.setText(self.details_button_text())
746 self.update_receive_tab()
747 self.update_contacts_tab()
750 def create_contacts_tab(self):
751 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
752 l.setContextMenuPolicy(Qt.CustomContextMenu)
753 l.customContextMenuRequested.connect(self.create_contact_menu)
754 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
755 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
756 self.contacts_list = l
757 self.contacts_buttons_hbox = hbox
758 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
763 def create_receive_menu(self, position):
764 # fixme: this function apparently has a side effect.
765 # if it is not called the menu pops up several times
766 #self.receive_list.selectedIndexes()
768 item = self.receive_list.itemAt(position)
770 addr = unicode(item.text(1))
772 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
773 menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
774 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
776 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
777 menu.addAction(t, lambda: self.toggle_freeze(addr))
778 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
779 menu.addAction(t, lambda: self.toggle_priority(addr))
780 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
783 def payto(self, x, is_alias):
790 label = self.wallet.labels.get(addr)
791 m_addr = label + ' <' + addr + '>' if label else addr
792 self.tabs.setCurrentIndex(1)
793 self.payto_e.setText(m_addr)
794 self.amount_e.setFocus()
796 def delete_contact(self, x, is_alias):
797 if self.question("Do you want to remove %s from your list of contacts?"%x):
798 if not is_alias and x in self.wallet.addressbook:
799 self.wallet.addressbook.remove(x)
800 if x in self.wallet.labels.keys():
801 self.wallet.labels.pop(x)
802 elif is_alias and x in self.wallet.aliases:
803 self.wallet.aliases.pop(x)
804 self.update_history_tab()
805 self.update_contacts_tab()
806 self.update_completions()
808 def create_contact_menu(self, position):
809 # fixme: this function apparently has a side effect.
810 # if it is not called the menu pops up several times
811 #self.contacts_list.selectedIndexes()
813 item = self.contacts_list.itemAt(position)
815 addr = unicode(item.text(0))
816 label = unicode(item.text(1))
817 is_alias = label in self.wallet.aliases.keys()
818 x = label if is_alias else addr
820 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
821 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
822 menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
824 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
826 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
827 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
828 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
831 def update_receive_tab(self):
832 l = self.receive_list
834 l.setColumnHidden(0,not self.detailed_view)
835 l.setColumnHidden(3,not self.detailed_view)
836 l.setColumnHidden(4,not self.detailed_view)
837 l.setColumnWidth(0, 50)
838 l.setColumnWidth(1, 310)
839 l.setColumnWidth(2, 250)
840 l.setColumnWidth(3, 130)
841 l.setColumnWidth(4, 10)
845 for address in self.wallet.all_addresses():
847 if self.wallet.is_change(address) and not self.detailed_view:
850 label = self.wallet.labels.get(address,'')
852 h = self.wallet.history.get(address,[])
854 if not item['is_input'] : n=n+1
858 if address in self.wallet.addresses:
860 if gap > self.wallet.gap_limit:
863 if address in self.wallet.addresses:
866 c, u = self.wallet.get_addr_balance(address)
867 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
868 flags = self.wallet.get_address_flags(address)
869 item = QTreeWidgetItem( [ flags, address, label, balance, tx] )
871 item.setFont(0, QFont(MONOSPACE_FONT))
872 item.setFont(1, QFont(MONOSPACE_FONT))
873 item.setFont(3, QFont(MONOSPACE_FONT))
874 if address in self.wallet.frozen_addresses:
875 item.setBackgroundColor(1, QColor('lightblue'))
876 elif address in self.wallet.prioritized_addresses:
877 item.setBackgroundColor(1, QColor('lightgreen'))
878 if is_red and address in self.wallet.addresses:
879 item.setBackgroundColor(1, QColor('red'))
880 l.addTopLevelItem(item)
882 # we use column 1 because column 0 may be hidden
883 l.setCurrentItem(l.topLevelItem(0),1)
885 def show_contact_details(self, m):
886 a = self.wallet.aliases.get(m)
888 if a[0] in self.wallet.authorities.keys():
889 s = self.wallet.authorities.get(a[0])
892 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
893 QMessageBox.information(self, 'Alias', msg, 'OK')
895 def update_contacts_tab(self):
897 l = self.contacts_list
899 l.setColumnHidden(2, not self.detailed_view)
900 l.setColumnWidth(0, 350)
901 l.setColumnWidth(1, 330)
902 l.setColumnWidth(2, 100)
905 for alias, v in self.wallet.aliases.items():
907 alias_targets.append(target)
908 item = QTreeWidgetItem( [ target, alias, '-'] )
909 item.setBackgroundColor(0, QColor('lightgray'))
910 l.addTopLevelItem(item)
912 for address in self.wallet.addressbook:
913 if address in alias_targets: continue
914 label = self.wallet.labels.get(address,'')
916 for item in self.wallet.tx_history.values():
917 if address in item['outputs'] : n=n+1
919 item = QTreeWidgetItem( [ address, label, tx] )
920 item.setFont(0, QFont(MONOSPACE_FONT))
921 l.addTopLevelItem(item)
923 l.setCurrentItem(l.topLevelItem(0))
925 def create_wall_tab(self):
926 self.textbox = textbox = QTextEdit(self)
927 textbox.setFont(QFont(MONOSPACE_FONT))
928 textbox.setReadOnly(True)
931 def create_status_bar(self):
933 sb.setFixedHeight(35)
935 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
936 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
938 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
939 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
940 sb.addPermanentWidget( self.status_button )
941 self.setStatusBar(sb)
943 def new_contact_dialog(self):
944 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
945 address = unicode(text)
947 if self.wallet.is_valid(address):
948 self.wallet.addressbook.append(address)
950 self.update_contacts_tab()
951 self.update_history_tab()
952 self.update_completions()
954 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
957 def show_seed_dialog(wallet, parent=None):
959 QMessageBox.information(parent, _('Message'),
960 _('No seed'), _('OK'))
963 if wallet.use_encryption:
964 password = parent.password_dialog()
971 seed = wallet.pw_decode(wallet.seed, password)
973 QMessageBox.warning(parent, _('Error'),
974 _('Incorrect Password'), _('OK'))
977 dialog = QDialog(None)
979 dialog.setWindowTitle(_("Seed"))
981 brainwallet = ' '.join(mnemonic.mn_encode(seed))
983 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
984 + _("Please write down or memorize these 12 words (order is important).") + " " \
985 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
986 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
988 main_text = QLabel(msg)
989 main_text.setWordWrap(True)
992 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
999 copy_function = lambda: app.clipboard().setText(brainwallet)
1000 copy_button = QPushButton(_("Copy to Clipboard"))
1001 copy_button.clicked.connect(copy_function)
1003 show_qr_function = lambda: ElectrumWindow.show_seed_qrcode(seed)
1004 qr_button = QPushButton(_("View as QR Code"))
1005 qr_button.clicked.connect(show_qr_function)
1007 ok_button = QPushButton(_("OK"))
1008 ok_button.setDefault(True)
1009 ok_button.clicked.connect(dialog.accept)
1011 main_layout = QGridLayout()
1012 main_layout.addWidget(logo, 0, 0)
1013 main_layout.addWidget(main_text, 0, 1, 1, -1)
1014 main_layout.addWidget(copy_button, 1, 1)
1015 main_layout.addWidget(qr_button, 1, 2)
1016 main_layout.addWidget(ok_button, 1, 3)
1017 dialog.setLayout(main_layout)
1022 def show_seed_qrcode(seed):
1026 d.setWindowTitle(_("Seed"))
1027 d.setMinimumSize(270, 300)
1028 vbox = QVBoxLayout()
1029 vbox.addWidget(QRCodeWidget(seed))
1030 hbox = QHBoxLayout()
1032 b = QPushButton(_("OK"))
1034 b.clicked.connect(d.accept)
1036 vbox.addLayout(hbox)
1041 def show_address_qrcode(self,address):
1042 if not address: return
1045 d.setWindowTitle(address)
1046 d.setMinimumSize(270, 350)
1047 vbox = QVBoxLayout()
1048 qrw = QRCodeWidget(address)
1051 hbox = QHBoxLayout()
1052 amount_e = QLineEdit()
1053 hbox.addWidget(QLabel(_('Amount')))
1054 hbox.addWidget(amount_e)
1055 vbox.addLayout(hbox)
1057 #hbox = QHBoxLayout()
1058 #label_e = QLineEdit()
1059 #hbox.addWidget(QLabel('Label'))
1060 #hbox.addWidget(label_e)
1061 #vbox.addLayout(hbox)
1063 def amount_changed():
1064 amount = numbify(amount_e)
1065 #label = str( label_e.getText() )
1066 if amount is not None:
1067 qrw.set_addr('bitcoin:%s?amount=%s'%(address,str( Decimal(amount) /100000000)))
1069 qrw.set_addr( address )
1073 bmp.save_qrcode(qrw.qr, "qrcode.bmp")
1074 self.show_message(_("QR code saved to file") + " 'qrcode.bmp'")
1076 amount_e.textChanged.connect( amount_changed )
1078 hbox = QHBoxLayout()
1080 b = QPushButton(_("Save"))
1081 b.clicked.connect(do_save)
1083 b = QPushButton(_("Close"))
1085 b.clicked.connect(d.accept)
1087 vbox.addLayout(hbox)
1091 def question(self, msg):
1092 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1094 def show_message(self, msg):
1095 QMessageBox.information(self, _('Message'), msg, _('OK'))
1097 def password_dialog(self ):
1104 vbox = QVBoxLayout()
1105 msg = _('Please enter your password')
1106 vbox.addWidget(QLabel(msg))
1108 grid = QGridLayout()
1110 grid.addWidget(QLabel(_('Password')), 1, 0)
1111 grid.addWidget(pw, 1, 1)
1112 vbox.addLayout(grid)
1114 vbox.addLayout(ok_cancel_buttons(d))
1117 if not d.exec_(): return
1118 return unicode(pw.text())
1125 def change_password_dialog( wallet, parent=None ):
1128 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1136 new_pw = QLineEdit()
1137 new_pw.setEchoMode(2)
1138 conf_pw = QLineEdit()
1139 conf_pw.setEchoMode(2)
1141 vbox = QVBoxLayout()
1143 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')
1145 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'+_("Leave these fields empty if you want to disable encryption.")
1146 vbox.addWidget(QLabel(msg))
1148 grid = QGridLayout()
1151 if wallet.use_encryption:
1152 grid.addWidget(QLabel(_('Password')), 1, 0)
1153 grid.addWidget(pw, 1, 1)
1155 grid.addWidget(QLabel(_('New Password')), 2, 0)
1156 grid.addWidget(new_pw, 2, 1)
1158 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1159 grid.addWidget(conf_pw, 3, 1)
1160 vbox.addLayout(grid)
1162 vbox.addLayout(ok_cancel_buttons(d))
1165 if not d.exec_(): return
1167 password = unicode(pw.text()) if wallet.use_encryption else None
1168 new_password = unicode(new_pw.text())
1169 new_password2 = unicode(conf_pw.text())
1172 seed = wallet.pw_decode( wallet.seed, password)
1174 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1177 if new_password != new_password2:
1178 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1181 wallet.update_password(seed, password, new_password)
1184 def seed_dialog(wallet, parent=None):
1188 vbox = QVBoxLayout()
1189 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1190 vbox.addWidget(QLabel(msg))
1192 grid = QGridLayout()
1195 seed_e = QLineEdit()
1196 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1197 grid.addWidget(seed_e, 1, 1)
1201 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1202 grid.addWidget(gap_e, 2, 1)
1203 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1204 vbox.addLayout(grid)
1206 vbox.addLayout(ok_cancel_buttons(d))
1209 if not d.exec_(): return
1212 gap = int(unicode(gap_e.text()))
1214 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1218 seed = unicode(seed_e.text())
1221 print_error("Warning: Not hex, trying decode")
1223 seed = mnemonic.mn_decode( seed.split(' ') )
1225 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1228 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1231 wallet.seed = str(seed)
1232 #print repr(wallet.seed)
1233 wallet.gap_limit = gap
1238 def settings_dialog(self):
1241 vbox = QVBoxLayout()
1242 msg = _('Here are the settings of your wallet.') + '\n'\
1243 + _('For more explanations, click on the help buttons next to each field.')
1246 label.setFixedWidth(250)
1247 label.setWordWrap(True)
1248 label.setAlignment(Qt.AlignJustify)
1249 vbox.addWidget(label)
1251 grid = QGridLayout()
1253 vbox.addLayout(grid)
1255 fee_label = QLabel(_('Transaction fee'))
1256 grid.addWidget(fee_label, 2, 0)
1258 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1259 grid.addWidget(fee_e, 2, 1)
1260 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1261 + _('Recommended value') + ': 0.001'
1262 grid.addWidget(HelpButton(msg), 2, 2)
1263 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1264 if not self.config.is_modifiable('fee'):
1265 for w in [fee_e, fee_label]: w.setEnabled(False)
1267 nz_label = QLabel(_('Display zeros'))
1268 grid.addWidget(nz_label, 3, 0)
1270 nz_e.setText("%d"% self.wallet.num_zeros)
1271 grid.addWidget(nz_e, 3, 1)
1272 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1273 grid.addWidget(HelpButton(msg), 3, 2)
1274 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1275 if not self.config.is_modifiable('num_zeros'):
1276 for w in [nz_e, nz_label]: w.setEnabled(False)
1278 usechange_cb = QCheckBox(_('Use change addresses'))
1279 grid.addWidget(usechange_cb, 5, 0)
1280 usechange_cb.setChecked(self.wallet.use_change)
1281 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1282 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1284 gap_label = QLabel(_('Gap limit'))
1285 grid.addWidget(gap_label, 6, 0)
1287 gap_e.setText("%d"% self.wallet.gap_limit)
1288 grid.addWidget(gap_e, 6, 1)
1289 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1290 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1291 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1292 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1293 + _('Warning') + ': ' \
1294 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1295 + _('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'
1296 grid.addWidget(HelpButton(msg), 6, 2)
1297 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1298 if not self.config.is_modifiable('gap_limit'):
1299 for w in [gap_e, gap_label]: w.setEnabled(False)
1301 gui_label=QLabel(_('Default GUI') + ':')
1302 grid.addWidget(gui_label , 7, 0)
1303 gui_combo = QComboBox()
1304 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1305 gui_combo.setCurrentIndex(gui_combo.findText(self.config.get("gui","classic").capitalize()))
1306 grid.addWidget(gui_combo, 7, 1)
1307 grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1308 if not self.config.is_modifiable('gui'):
1309 for w in [gui_combo, gui_label]: w.setEnabled(False)
1311 vbox.addLayout(ok_cancel_buttons(d))
1315 if not d.exec_(): return
1317 fee = unicode(fee_e.text())
1319 fee = int( 100000000 * Decimal(fee) )
1321 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1324 if self.wallet.fee != fee:
1325 self.wallet.fee = fee
1328 nz = unicode(nz_e.text())
1333 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1336 if self.wallet.num_zeros != nz:
1337 self.wallet.num_zeros = nz
1338 self.config.set_key('num_zeros', nz, True)
1339 self.update_history_tab()
1340 self.update_receive_tab()
1342 if self.wallet.use_change != usechange_cb.isChecked():
1343 self.wallet.use_change = usechange_cb.isChecked()
1344 self.config.set_key('use_change', self.wallet.use_change, True)
1347 n = int(gap_e.text())
1349 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1352 if self.wallet.gap_limit != n:
1353 r = self.wallet.change_gap_limit(n)
1355 self.update_receive_tab()
1356 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1358 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1360 self.config.set_key("gui", str(gui_combo.currentText()).lower(), True)
1365 def network_dialog(wallet, parent=None):
1366 interface = wallet.interface
1368 if interface.is_connected:
1369 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.blocks)
1371 status = _("Not connected")
1374 status = _("Please choose a server.")
1376 server = interface.server
1378 if not wallet.interface.servers:
1380 for x in DEFAULT_SERVERS:
1381 h,port,protocol = x.split(':')
1382 servers_list.append( (h,[(protocol,port)] ) )
1384 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 server_protocol.addItems(protocol_names)
1429 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1430 grid.addWidget(server_protocol, 0, 1)
1431 grid.addWidget(server_host, 0, 2)
1432 grid.addWidget(server_port, 0, 3)
1434 host, port, protocol = server.split(':')
1436 def change_protocol(p):
1437 protocol = protocol_letters[p]
1438 host = unicode(server_host.text())
1440 if protocol not in pp.keys():
1441 protocol = pp.keys()[0]
1443 server_host.setText( host )
1444 server_port.setText( port )
1446 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1448 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1449 servers_list_widget = QTreeWidget(parent)
1450 servers_list_widget.setHeaderLabels( [ label ] )
1451 servers_list_widget.setMaximumHeight(150)
1452 for _host in plist.keys():
1453 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host ] ))
1456 def change_server(host, protocol=None):
1457 pp = plist.get(host,{})
1459 port = pp.get(protocol)
1460 if not port: protocol = None
1466 elif 't' in pp.keys():
1468 port = pp.get(protocol)
1470 protocol = pp.keys()[0]
1471 port = pp.get(protocol)
1474 server_host.setText( host )
1475 server_port.setText( port )
1476 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1478 for p in protocol_letters:
1479 i = protocol_letters.index(p)
1480 j = server_protocol.model().index(i,0)
1481 if p not in pp.keys():
1482 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1484 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1486 change_server(host,protocol)
1489 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1490 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1492 if not wallet.config.is_modifiable('server'):
1493 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1496 proxy_mode = QComboBox()
1497 proxy_host = QLineEdit()
1498 proxy_host.setFixedWidth(200)
1499 proxy_port = QLineEdit()
1500 proxy_port.setFixedWidth(60)
1501 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1503 def check_for_disable(index = False):
1504 if proxy_mode.currentText() != 'NONE':
1505 proxy_host.setEnabled(True)
1506 proxy_port.setEnabled(True)
1508 proxy_host.setEnabled(False)
1509 proxy_port.setEnabled(False)
1512 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1514 if not wallet.config.is_modifiable('proxy'):
1515 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1517 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1518 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1519 proxy_host.setText(proxy_config.get("host"))
1520 proxy_port.setText(proxy_config.get("port"))
1522 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1523 grid.addWidget(proxy_mode, 2, 1)
1524 grid.addWidget(proxy_host, 2, 2)
1525 grid.addWidget(proxy_port, 2, 3)
1528 vbox.addLayout(ok_cancel_buttons(d))
1531 if not d.exec_(): return
1533 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1534 if proxy_mode.currentText() != 'NONE':
1535 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1539 wallet.config.set_key("proxy", proxy, True)
1540 wallet.config.set_key("server", server, True)
1541 interface.set_server(server, proxy)
1545 def closeEvent(self, event):
1547 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1553 def __init__(self, wallet, config, app=None):
1554 self.wallet = wallet
1555 self.config = config
1557 self.app = QApplication(sys.argv)
1559 def server_list_changed(self):
1563 def restore_or_create(self):
1565 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1566 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1567 if r==2: return False
1569 is_recovery = (r==1)
1570 wallet = self.wallet
1571 # ask for the server.
1572 if not ElectrumWindow.network_dialog( wallet, parent=None ): return False
1574 waiting = lambda: False if wallet.up_to_date else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1575 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1578 wallet.new_seed(None)
1579 wallet.init_mpk( wallet.seed )
1580 wallet.up_to_date_event.clear()
1581 wallet.up_to_date = False
1582 wallet.interface.poke('synchronizer')
1583 waiting_dialog(waiting)
1584 # run a dialog indicating the seed, ask the user to remember it
1585 ElectrumWindow.show_seed_dialog(wallet)
1587 ElectrumWindow.change_password_dialog(wallet)
1589 # ask for seed and gap.
1590 if not ElectrumWindow.seed_dialog( wallet ): return False
1591 wallet.init_mpk( wallet.seed )
1592 wallet.up_to_date_event.clear()
1593 wallet.up_to_date = False
1594 wallet.interface.poke('synchronizer')
1595 waiting_dialog(waiting)
1596 if wallet.is_found():
1597 # history and addressbook
1598 wallet.update_tx_history()
1599 wallet.fill_addressbook()
1600 print "Recovery successful"
1603 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1611 w = ElectrumWindow(self.wallet, self.config)
1612 if url: w.set_url(url)