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.setMinimumSize(210, 210)
145 def set_addr(self, addr):
147 self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
148 self.qr.addData(self.addr)
151 def paintEvent(self, e):
152 if not self.addr: return
154 qp = QtGui.QPainter()
157 size = self.qr.getModuleCount()*boxsize
158 k = self.qr.getModuleCount()
159 black = QColor(0, 0, 0, 255)
160 white = QColor(255, 255, 255, 255)
163 if self.qr.isDark(r, c):
169 qp.drawRect(c*boxsize, r*boxsize, boxsize, boxsize)
174 class QR_Window(QWidget):
177 QWidget.__init__(self)
178 self.setWindowTitle('Electrum - Invoice')
179 self.setMinimumSize(800, 250)
183 self.setFocusPolicy(QtCore.Qt.NoFocus)
185 main_box = QHBoxLayout()
187 self.qrw = QRCodeWidget('')
188 main_box.addWidget(self.qrw)
191 main_box.addLayout(vbox)
193 main_box.addStretch(1)
195 self.address_label = QLabel("")
196 self.address_label.setFont(QFont(MONOSPACE_FONT))
197 vbox.addWidget(self.address_label)
199 self.label_label = QLabel("")
200 vbox.addWidget(self.label_label)
202 self.amount_label = QLabel("")
203 vbox.addWidget(self.amount_label)
207 self.setLayout(main_box)
210 self.filename = "qrcode.bmp"
211 bmp.save_qrcode(self.qrw.qr, self.filename)
213 def set_content(self, addr, label, amount):
215 address_text = "<span style='font-size: 21pt'>%s</span>" % addr if addr else ""
216 self.address_label.setText(address_text)
219 amount_text = "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % format_satoshis(amount) if amount else ""
220 self.amount_label.setText(amount_text)
223 label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
224 self.label_label.setText(label_text)
226 msg = 'bitcoin:'+self.address
227 if self.amount is not None:
228 msg += '?amount=%s'%(str( Decimal(self.amount) /100000000))
229 if self.label is not None:
230 msg += '&label=%s'%(self.label)
231 elif self.label is not None:
232 msg += '?label=%s'%(self.label)
234 self.qrw.set_addr( msg )
240 def waiting_dialog(f):
246 w.setWindowTitle('Electrum')
256 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
261 def ok_cancel_buttons(dialog):
264 b = QPushButton("OK")
266 b.clicked.connect(dialog.accept)
267 b = QPushButton("Cancel")
269 b.clicked.connect(dialog.reject)
273 class ElectrumWindow(QMainWindow):
275 def __init__(self, wallet, config):
276 QMainWindow.__init__(self)
279 self.wallet.interface.register_callback('updated', self.update_callback)
280 self.wallet.interface.register_callback('connected', self.update_callback)
281 self.wallet.interface.register_callback('disconnected', self.update_callback)
282 self.wallet.interface.register_callback('disconnecting', self.update_callback)
284 self.detailed_view = config.get('qt_detailed_view', False)
286 self.qr_window = None
287 self.funds_error = False
288 self.completions = QStringListModel()
290 self.tabs = tabs = QTabWidget(self)
291 tabs.addTab(self.create_history_tab(), _('History') )
293 tabs.addTab(self.create_send_tab(), _('Send') )
294 tabs.addTab(self.create_receive_tab(), _('Receive') )
295 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
296 tabs.addTab(self.create_wall_tab(), _('Wall') )
297 tabs.setMinimumSize(600, 400)
298 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
299 self.setCentralWidget(tabs)
300 self.create_status_bar()
302 g = self.config.get("winpos-qt",[100, 100, 840, 400])
303 self.setGeometry(g[0], g[1], g[2], g[3])
304 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
305 if not self.wallet.seed: title += ' [seedless]'
306 self.setWindowTitle( title )
308 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
309 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
310 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
311 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
313 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
314 #self.connect(self, SIGNAL('editamount'), self.edit_amount)
315 self.history_list.setFocus(True)
317 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
318 if platform.system() == 'Windows':
319 n = 3 if self.wallet.seed else 2
320 tabs.setCurrentIndex (n)
321 tabs.setCurrentIndex (0)
324 def connect_slots(self, sender):
326 self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient)
327 self.previous_payto_e=''
329 def check_recipient(self):
330 if self.payto_e.hasFocus():
332 r = unicode( self.payto_e.text() )
333 if r != self.previous_payto_e:
334 self.previous_payto_e = r
336 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
338 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
342 s = r + ' <' + to_address + '>'
343 self.payto_e.setText(s)
346 def update_callback(self):
347 self.emit(QtCore.SIGNAL('updatesignal'))
349 def update_wallet(self):
350 if self.wallet.interface and self.wallet.interface.is_connected:
351 if not self.wallet.up_to_date:
352 text = _( "Synchronizing..." )
353 icon = QIcon(":icons/status_waiting.png")
355 c, u = self.wallet.get_balance()
356 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
357 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
358 icon = QIcon(":icons/status_connected.png")
360 text = _( "Not connected" )
361 icon = QIcon(":icons/status_disconnected.png")
364 text = _( "Not enough funds" )
366 self.statusBar().showMessage(text)
367 self.status_button.setIcon( icon )
369 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
370 self.textbox.setText( self.wallet.banner )
371 self.update_history_tab()
372 self.update_receive_tab()
373 self.update_contacts_tab()
374 self.update_completions()
377 def create_history_tab(self):
378 self.history_list = l = MyTreeWidget(self)
380 l.setColumnWidth(0, 40)
381 l.setColumnWidth(1, 140)
382 l.setColumnWidth(2, 350)
383 l.setColumnWidth(3, 140)
384 l.setColumnWidth(4, 140)
385 l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
386 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
387 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
389 l.setContextMenuPolicy(Qt.CustomContextMenu)
390 l.customContextMenuRequested.connect(self.create_history_menu)
394 def create_history_menu(self, position):
395 self.history_list.selectedIndexes()
396 item = self.history_list.currentItem()
398 tx_hash = str(item.toolTip(0))
399 if not tx_hash: return
401 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
402 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
403 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
404 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
407 def tx_details(self, tx_hash):
408 tx_details = self.wallet.get_tx_details(tx_hash)
409 QMessageBox.information(self, 'Details', tx_details, 'OK')
412 def tx_label_clicked(self, item, column):
413 if column==2 and item.isSelected():
414 tx_hash = str(item.toolTip(0))
416 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
417 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
418 self.history_list.editItem( item, column )
419 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
422 def tx_label_changed(self, item, column):
426 tx_hash = str(item.toolTip(0))
427 tx = self.wallet.transactions.get(tx_hash)
428 s = self.wallet.labels.get(tx_hash)
429 text = unicode( item.text(2) )
431 self.wallet.labels[tx_hash] = text
432 item.setForeground(2, QBrush(QColor('black')))
434 if s: self.wallet.labels.pop(tx_hash)
435 text = self.wallet.get_default_label(tx_hash)
436 item.setText(2, text)
437 item.setForeground(2, QBrush(QColor('gray')))
441 def edit_label(self, is_recv):
442 l = self.receive_list if is_recv else self.contacts_list
443 c = 2 if is_recv else 1
444 item = l.currentItem()
445 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
446 l.editItem( item, c )
447 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
451 def address_label_clicked(self, item, column, l, column_addr, column_label):
452 if column == column_label and item.isSelected():
453 addr = unicode( item.text(column_addr) )
454 label = unicode( item.text(column_label) )
455 if label in self.wallet.aliases.keys():
457 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
458 l.editItem( item, column )
459 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
462 def address_label_changed(self, item, column, l, column_addr, column_label):
464 if column == column_label:
465 addr = unicode( item.text(column_addr) )
466 text = unicode( item.text(column_label) )
470 if text not in self.wallet.aliases.keys():
471 old_addr = self.wallet.labels.get(text)
473 self.wallet.labels[addr] = text
476 print_error("Error: This is one of your aliases")
477 label = self.wallet.labels.get(addr,'')
478 item.setText(column_label, QString(label))
480 s = self.wallet.labels.get(addr)
482 self.wallet.labels.pop(addr)
486 self.update_history_tab()
487 self.update_completions()
489 self.recv_changed(item)
492 def recv_changed(self, a):
493 "current item changed"
494 if a is not None and self.qr_window and self.qr_window.isVisible():
495 address = str(a.text(1))
496 label = self.wallet.labels.get(address)
497 amount = self.wallet.requested_amounts.get(address)
498 self.qr_window.set_content( address, label, amount )
501 def update_history_tab(self):
503 self.history_list.clear()
504 for item in self.wallet.get_tx_history():
505 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
508 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
514 icon = QIcon(":icons/unconfirmed.png")
516 icon = QIcon(":icons/clock%d.png"%conf)
518 icon = QIcon(":icons/confirmed.png")
521 icon = QIcon(":icons/unconfirmed.png")
523 if value is not None:
524 v_str = format_satoshis(value, True, self.wallet.num_zeros)
528 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
531 label, is_default_label = self.wallet.get_label(tx_hash)
533 label = _('Pruned transaction outputs')
534 is_default_label = False
536 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
537 item.setFont(2, QFont(MONOSPACE_FONT))
538 item.setFont(3, QFont(MONOSPACE_FONT))
539 item.setFont(4, QFont(MONOSPACE_FONT))
541 item.setToolTip(0, tx_hash)
543 item.setForeground(2, QBrush(QColor('grey')))
545 item.setIcon(0, icon)
546 self.history_list.insertTopLevelItem(0,item)
549 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
552 def create_send_tab(self):
557 grid.setColumnMinimumWidth(3,300)
558 grid.setColumnStretch(5,1)
560 self.payto_e = QLineEdit()
561 grid.addWidget(QLabel(_('Pay to')), 1, 0)
562 grid.addWidget(self.payto_e, 1, 1, 1, 3)
565 qrcode = qrscanner.scan_qr()
566 if 'address' in qrcode:
567 self.payto_e.setText(qrcode['address'])
568 if 'amount' in qrcode:
569 self.amount_e.setText(str(qrcode['amount']))
570 if 'label' in qrcode:
571 self.message_e.setText(qrcode['label'])
572 if 'message' in qrcode:
573 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
576 if qrscanner.is_available():
577 b = QPushButton(_("Scan QR code"))
578 b.clicked.connect(fill_from_qr)
579 grid.addWidget(b, 1, 5)
581 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)
583 completer = QCompleter()
584 completer.setCaseSensitivity(False)
585 self.payto_e.setCompleter(completer)
586 completer.setModel(self.completions)
588 self.message_e = QLineEdit()
589 grid.addWidget(QLabel(_('Description')), 2, 0)
590 grid.addWidget(self.message_e, 2, 1, 1, 3)
591 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)
593 self.amount_e = QLineEdit()
594 grid.addWidget(QLabel(_('Amount')), 3, 0)
595 grid.addWidget(self.amount_e, 3, 1, 1, 2)
596 grid.addWidget(HelpButton(
597 _('Amount to be sent.') + '\n\n' \
598 + _('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)
600 self.fee_e = QLineEdit()
601 grid.addWidget(QLabel(_('Fee')), 4, 0)
602 grid.addWidget(self.fee_e, 4, 1, 1, 2)
603 grid.addWidget(HelpButton(
604 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
605 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
606 + _('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)
608 b = EnterButton(_("Send"), self.do_send)
609 grid.addWidget(b, 6, 1)
611 b = EnterButton(_("Clear"),self.do_clear)
612 grid.addWidget(b, 6, 2)
614 self.payto_sig = QLabel('')
615 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
617 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
618 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
627 def entry_changed( is_fee ):
628 self.funds_error = False
629 amount = numbify(self.amount_e)
630 fee = numbify(self.fee_e)
631 if not is_fee: fee = None
634 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
636 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
639 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
642 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
643 self.funds_error = True
644 self.amount_e.setPalette(palette)
645 self.fee_e.setPalette(palette)
647 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
648 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
653 def update_completions(self):
655 for addr,label in self.wallet.labels.items():
656 if addr in self.wallet.addressbook:
657 l.append( label + ' <' + addr + '>')
658 l = l + self.wallet.aliases.keys()
660 self.completions.setStringList(l)
666 label = unicode( self.message_e.text() )
667 r = unicode( self.payto_e.text() )
671 m1 = re.match(ALIAS_REGEXP, r)
672 # label or alias, with address in brackets
673 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
676 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
680 to_address = m2.group(2)
684 if not self.wallet.is_valid(to_address):
685 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
689 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
691 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
694 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
696 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
699 if self.wallet.use_encryption:
700 password = self.password_dialog()
707 tx = self.wallet.mktx( to_address, amount, label, password, fee)
708 except BaseException, e:
709 self.show_message(str(e))
712 h = self.wallet.send_tx(tx)
713 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
714 status, msg = self.wallet.receive_tx( h )
717 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
719 self.update_contacts_tab()
721 QMessageBox.warning(self, _('Error'), msg, _('OK'))
724 def set_url(self, url):
725 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
726 self.tabs.setCurrentIndex(1)
727 label = self.wallet.labels.get(payto)
728 m_addr = label + ' <'+ payto+'>' if label else payto
729 self.payto_e.setText(m_addr)
731 self.message_e.setText(message)
732 self.amount_e.setText(amount)
734 self.set_frozen(self.payto_e,True)
735 self.set_frozen(self.amount_e,True)
736 self.set_frozen(self.message_e,True)
737 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
739 self.payto_sig.setVisible(False)
742 self.payto_sig.setVisible(False)
743 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
745 self.set_frozen(e,False)
747 def set_frozen(self,entry,frozen):
749 entry.setReadOnly(True)
750 entry.setFrame(False)
752 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
753 entry.setPalette(palette)
755 entry.setReadOnly(False)
758 palette.setColor(entry.backgroundRole(), QColor('white'))
759 entry.setPalette(palette)
762 def toggle_freeze(self,addr):
764 if addr in self.wallet.frozen_addresses:
765 self.wallet.unfreeze(addr)
767 self.wallet.freeze(addr)
768 self.update_receive_tab()
770 def toggle_priority(self,addr):
772 if addr in self.wallet.prioritized_addresses:
773 self.wallet.unprioritize(addr)
775 self.wallet.prioritize(addr)
776 self.update_receive_tab()
779 def create_list_tab(self, headers):
780 "generic tab creation method"
781 l = MyTreeWidget(self)
782 l.setColumnCount( len(headers) )
783 l.setHeaderLabels( headers )
793 vbox.addWidget(buttons)
798 buttons.setLayout(hbox)
803 def create_receive_tab(self):
804 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
805 l.setContextMenuPolicy(Qt.CustomContextMenu)
806 l.customContextMenuRequested.connect(self.create_receive_menu)
807 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
808 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
809 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
810 self.receive_list = l
811 self.receive_buttons_hbox = hbox
812 self.qr_button = EnterButton(self.qr_button_text(), self.toggle_QR_window)
813 hbox.addWidget(self.qr_button)
814 self.print_button = EnterButton(_("Print QR"), self.print_qr)
815 self.print_button.setHidden(True)
816 hbox.addWidget(self.print_button)
818 self.details_button = EnterButton(self.details_button_text(), self.toggle_detailed_view)
819 hbox.addWidget(self.details_button)
825 self.qr_window.do_save()
826 self.show_message(_("QR code saved to file") + " " + self.qr_window.filename)
829 def request_amount_dialog(self, address):
830 # pick the first unused address
831 # print "please wait" in qr window
832 # enter amount in usd
835 d.setWindowTitle('Request payment')
838 vbox.addWidget(QLabel(address))
845 index = self.wallet.addresses.index(address)
849 label = self.wallet.labels.get(address,'invoice %04d'%(index+1))
850 grid.addWidget(QLabel(_('Label')), 0, 0)
851 label_e = QLineEdit()
852 label_e.setText(label)
853 grid.addWidget(label_e, 0, 1)
855 amount_e = QLineEdit()
856 amount_e.textChanged.connect(lambda: numbify(amount_e))
858 grid.addWidget(QLabel(_('Amount')), 1, 0)
859 grid.addWidget(amount_e, 1, 1, 1, 3)
861 vbox.addLayout(ok_cancel_buttons(d))
865 if not d.exec_(): return
867 amount = int( Decimal( unicode( amount_e.text())) * 100000000 )
870 self.wallet.requested_amounts[address] = amount
872 label = unicode(label_e.text())
874 self.wallet.labels[address] = label
876 self.update_receive_item(self.receive_list.currentItem())
879 self.qr_window.set_content( address, label, amount )
882 #self.receive_list.currentItem().setFocus(True)
885 def details_button_text(self):
886 return _('Hide details') if self.detailed_view else _('Show details')
888 def qr_button_text(self):
889 return _('Hide QR') if self.qr_window and self.qr_window.isVisible() else _('Show QR')
892 def toggle_detailed_view(self):
893 self.detailed_view = not self.detailed_view
894 self.config.set_key('qt_detailed_view', self.detailed_view, True)
896 self.details_button.setText(self.details_button_text())
898 self.update_receive_tab()
899 self.update_contacts_tab()
902 def create_contacts_tab(self):
903 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
904 l.setContextMenuPolicy(Qt.CustomContextMenu)
905 l.customContextMenuRequested.connect(self.create_contact_menu)
906 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
907 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
908 self.contacts_list = l
909 self.contacts_buttons_hbox = hbox
910 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
915 def create_receive_menu(self, position):
916 # fixme: this function apparently has a side effect.
917 # if it is not called the menu pops up several times
918 #self.receive_list.selectedIndexes()
920 item = self.receive_list.itemAt(position)
922 addr = unicode(item.text(1))
924 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
925 menu.addAction(_("Request payment"), lambda: self.request_amount_dialog(addr))
926 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
927 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
929 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
930 menu.addAction(t, lambda: self.toggle_freeze(addr))
931 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
932 menu.addAction(t, lambda: self.toggle_priority(addr))
933 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
936 def payto(self, x, is_alias):
943 label = self.wallet.labels.get(addr)
944 m_addr = label + ' <' + addr + '>' if label else addr
945 self.tabs.setCurrentIndex(1)
946 self.payto_e.setText(m_addr)
947 self.amount_e.setFocus()
949 def delete_contact(self, x, is_alias):
950 if self.question("Do you want to remove %s from your list of contacts?"%x):
951 if not is_alias and x in self.wallet.addressbook:
952 self.wallet.addressbook.remove(x)
953 if x in self.wallet.labels.keys():
954 self.wallet.labels.pop(x)
955 elif is_alias and x in self.wallet.aliases:
956 self.wallet.aliases.pop(x)
957 self.update_history_tab()
958 self.update_contacts_tab()
959 self.update_completions()
961 def create_contact_menu(self, position):
962 # fixme: this function apparently has a side effect.
963 # if it is not called the menu pops up several times
964 #self.contacts_list.selectedIndexes()
966 item = self.contacts_list.itemAt(position)
968 addr = unicode(item.text(0))
969 label = unicode(item.text(1))
970 is_alias = label in self.wallet.aliases.keys()
971 x = label if is_alias else addr
973 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
974 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
975 menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
977 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
979 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
980 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
981 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
984 def update_receive_item(self, item):
985 address = str( item.data(1,0).toString() )
987 flags = self.wallet.get_address_flags(address)
988 item.setData(0,0,flags)
990 label = self.wallet.labels.get(address,'')
991 item.setData(2,0,label)
993 amount_str = format_satoshis( self.wallet.requested_amounts.get(address,0) )
994 item.setData(3,0,amount_str)
996 c, u = self.wallet.get_addr_balance(address)
997 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
998 item.setData(4,0,balance)
1000 if address in self.wallet.frozen_addresses:
1001 item.setBackgroundColor(1, QColor('lightblue'))
1002 elif address in self.wallet.prioritized_addresses:
1003 item.setBackgroundColor(1, QColor('lightgreen'))
1006 def update_receive_tab(self):
1007 l = self.receive_list
1010 l.setColumnHidden(0, not self.detailed_view)
1011 l.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1012 l.setColumnHidden(4, not self.detailed_view)
1013 l.setColumnHidden(5, not self.detailed_view)
1014 l.setColumnWidth(0, 50)
1015 l.setColumnWidth(1, 310)
1016 l.setColumnWidth(2, 200)
1017 l.setColumnWidth(3, 130)
1018 l.setColumnWidth(4, 130)
1019 l.setColumnWidth(5, 10)
1023 for address in self.wallet.all_addresses():
1025 if self.wallet.is_change(address) and not self.detailed_view:
1029 h = self.wallet.history.get(address,[])
1032 for tx_hash, tx_height in h:
1033 tx = self.wallet.transactions.get(tx_hash)
1041 if address in self.wallet.addresses:
1043 if gap > self.wallet.gap_limit:
1046 if address in self.wallet.addresses:
1049 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1050 item.setFont(0, QFont(MONOSPACE_FONT))
1051 item.setFont(1, QFont(MONOSPACE_FONT))
1052 item.setFont(3, QFont(MONOSPACE_FONT))
1053 self.update_receive_item(item)
1054 if is_red and address in self.wallet.addresses:
1055 item.setBackgroundColor(1, QColor('red'))
1056 l.addTopLevelItem(item)
1058 # we use column 1 because column 0 may be hidden
1059 l.setCurrentItem(l.topLevelItem(0),1)
1061 def show_contact_details(self, m):
1062 a = self.wallet.aliases.get(m)
1064 if a[0] in self.wallet.authorities.keys():
1065 s = self.wallet.authorities.get(a[0])
1068 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1069 QMessageBox.information(self, 'Alias', msg, 'OK')
1071 def update_contacts_tab(self):
1073 l = self.contacts_list
1075 l.setColumnHidden(2, not self.detailed_view)
1076 l.setColumnWidth(0, 350)
1077 l.setColumnWidth(1, 330)
1078 l.setColumnWidth(2, 100)
1081 for alias, v in self.wallet.aliases.items():
1083 alias_targets.append(target)
1084 item = QTreeWidgetItem( [ target, alias, '-'] )
1085 item.setBackgroundColor(0, QColor('lightgray'))
1086 l.addTopLevelItem(item)
1088 for address in self.wallet.addressbook:
1089 if address in alias_targets: continue
1090 label = self.wallet.labels.get(address,'')
1092 for item in self.wallet.transactions.values():
1093 if address in item['outputs'] : n=n+1
1095 item = QTreeWidgetItem( [ address, label, tx] )
1096 item.setFont(0, QFont(MONOSPACE_FONT))
1097 l.addTopLevelItem(item)
1099 l.setCurrentItem(l.topLevelItem(0))
1101 def create_wall_tab(self):
1102 self.textbox = textbox = QTextEdit(self)
1103 textbox.setFont(QFont(MONOSPACE_FONT))
1104 textbox.setReadOnly(True)
1107 def create_status_bar(self):
1109 sb.setFixedHeight(35)
1110 if self.wallet.seed:
1111 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1112 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1113 if self.wallet.seed:
1114 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1115 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
1116 sb.addPermanentWidget( self.status_button )
1117 self.setStatusBar(sb)
1119 def new_contact_dialog(self):
1120 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1121 address = unicode(text)
1123 if self.wallet.is_valid(address):
1124 self.wallet.addressbook.append(address)
1126 self.update_contacts_tab()
1127 self.update_history_tab()
1128 self.update_completions()
1130 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1133 def show_seed_dialog(wallet, parent=None):
1135 QMessageBox.information(parent, _('Message'),
1136 _('No seed'), _('OK'))
1139 if wallet.use_encryption:
1140 password = parent.password_dialog()
1147 seed = wallet.pw_decode(wallet.seed, password)
1149 QMessageBox.warning(parent, _('Error'),
1150 _('Incorrect Password'), _('OK'))
1153 dialog = QDialog(None)
1155 dialog.setWindowTitle("Electrum")
1157 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1159 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1160 + _("Please write down or memorize these 12 words (order is important).") + " " \
1161 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1162 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1164 main_text = QLabel(msg)
1165 main_text.setWordWrap(True)
1168 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1175 copy_function = lambda: app.clipboard().setText(brainwallet)
1176 copy_button = QPushButton(_("Copy to Clipboard"))
1177 copy_button.clicked.connect(copy_function)
1179 show_qr_function = lambda: ElectrumWindow.show_seed_qrcode(seed)
1180 qr_button = QPushButton(_("View as QR Code"))
1181 qr_button.clicked.connect(show_qr_function)
1183 ok_button = QPushButton(_("OK"))
1184 ok_button.setDefault(True)
1185 ok_button.clicked.connect(dialog.accept)
1187 main_layout = QGridLayout()
1188 main_layout.addWidget(logo, 0, 0)
1189 main_layout.addWidget(main_text, 0, 1, 1, -1)
1190 main_layout.addWidget(copy_button, 1, 1)
1191 main_layout.addWidget(qr_button, 1, 2)
1192 main_layout.addWidget(ok_button, 1, 3)
1193 dialog.setLayout(main_layout)
1198 def show_seed_qrcode(seed):
1202 d.setWindowTitle(_("Seed"))
1203 d.setMinimumSize(270, 300)
1204 vbox = QVBoxLayout()
1205 vbox.addWidget(QRCodeWidget(seed))
1206 hbox = QHBoxLayout()
1208 b = QPushButton(_("OK"))
1210 b.clicked.connect(d.accept)
1212 vbox.addLayout(hbox)
1216 def sign_message(self,address):
1217 if not address: return
1220 d.setWindowTitle('Sign Message')
1221 d.setMinimumSize(270, 350)
1223 tab_widget = QTabWidget()
1225 layout = QGridLayout(tab)
1227 sign_address = QLineEdit()
1228 sign_address.setText(address)
1229 layout.addWidget(QLabel(_('Address')), 1, 0)
1230 layout.addWidget(sign_address, 1, 1)
1232 sign_message = QTextEdit()
1233 layout.addWidget(QLabel(_('Message')), 2, 0)
1234 layout.addWidget(sign_message, 2, 1, 2, 1)
1236 sign_signature = QLineEdit()
1237 layout.addWidget(QLabel(_('Signature')), 3, 0)
1238 layout.addWidget(sign_signature, 3, 1)
1241 if self.wallet.use_encryption:
1242 password = self.password_dialog()
1249 signature = self.wallet.sign_message(sign_address.text(), sign_message.toPlainText(), password)
1250 sign_signature.setText(signature)
1251 except BaseException, e:
1252 self.show_message(str(e))
1255 hbox = QHBoxLayout()
1256 b = QPushButton(_("Sign"))
1258 b.clicked.connect(do_sign)
1259 b = QPushButton(_("Close"))
1260 b.clicked.connect(d.accept)
1262 layout.addLayout(hbox, 4, 1)
1263 tab_widget.addTab(tab, "Sign")
1267 layout = QGridLayout(tab)
1269 verify_address = QLineEdit()
1270 layout.addWidget(QLabel(_('Address')), 1, 0)
1271 layout.addWidget(verify_address, 1, 1)
1273 verify_message = QTextEdit()
1274 layout.addWidget(QLabel(_('Message')), 2, 0)
1275 layout.addWidget(verify_message, 2, 1, 2, 1)
1277 verify_signature = QLineEdit()
1278 layout.addWidget(QLabel(_('Signature')), 3, 0)
1279 layout.addWidget(verify_signature, 3, 1)
1283 self.wallet.verify_message(verify_address.text(), verify_signature.text(), verify_message.toPlainText())
1284 self.show_message("Signature verified")
1285 except BaseException, e:
1286 self.show_message(str(e))
1289 hbox = QHBoxLayout()
1290 b = QPushButton(_("Verify"))
1291 b.clicked.connect(do_verify)
1293 b = QPushButton(_("Close"))
1294 b.clicked.connect(d.accept)
1296 layout.addLayout(hbox, 4, 1)
1297 tab_widget.addTab(tab, "Verify")
1299 vbox = QVBoxLayout()
1300 vbox.addWidget(tab_widget)
1305 def toggle_QR_window(self):
1306 if not self.qr_window:
1307 self.qr_window = QR_Window()
1308 self.qr_window.setVisible(True)
1309 #print self.qr_window.isVisible()
1310 self.qr_window_geometry = self.qr_window.geometry()
1311 item = self.receive_list.currentItem()
1313 address = str(item.text(1))
1314 label = self.wallet.labels.get(address)
1315 amount = self.wallet.requested_amounts.get(address)
1316 self.qr_window.set_content( address, label, amount )
1317 self.update_receive_tab()
1319 if self.qr_window.isVisible():
1320 self.qr_window_geometry = self.qr_window.geometry()
1321 self.qr_window.setVisible(False)
1323 self.qr_window.setVisible(True)
1324 self.qr_window.setGeometry(self.qr_window_geometry)
1326 self.qr_button.setText(self.qr_button_text())
1327 self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1328 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1329 self.receive_list.setColumnWidth(2, 200)
1332 def question(self, msg):
1333 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1335 def show_message(self, msg):
1336 QMessageBox.information(self, _('Message'), msg, _('OK'))
1338 def password_dialog(self ):
1345 vbox = QVBoxLayout()
1346 msg = _('Please enter your password')
1347 vbox.addWidget(QLabel(msg))
1349 grid = QGridLayout()
1351 grid.addWidget(QLabel(_('Password')), 1, 0)
1352 grid.addWidget(pw, 1, 1)
1353 vbox.addLayout(grid)
1355 vbox.addLayout(ok_cancel_buttons(d))
1358 if not d.exec_(): return
1359 return unicode(pw.text())
1366 def change_password_dialog( wallet, parent=None ):
1369 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1377 new_pw = QLineEdit()
1378 new_pw.setEchoMode(2)
1379 conf_pw = QLineEdit()
1380 conf_pw.setEchoMode(2)
1382 vbox = QVBoxLayout()
1384 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1385 +_('To disable wallet encryption, enter an empty new password.')) \
1386 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1388 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1389 +_("Leave these fields empty if you want to disable encryption.")
1390 vbox.addWidget(QLabel(msg))
1392 grid = QGridLayout()
1395 if wallet.use_encryption:
1396 grid.addWidget(QLabel(_('Password')), 1, 0)
1397 grid.addWidget(pw, 1, 1)
1399 grid.addWidget(QLabel(_('New Password')), 2, 0)
1400 grid.addWidget(new_pw, 2, 1)
1402 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1403 grid.addWidget(conf_pw, 3, 1)
1404 vbox.addLayout(grid)
1406 vbox.addLayout(ok_cancel_buttons(d))
1409 if not d.exec_(): return
1411 password = unicode(pw.text()) if wallet.use_encryption else None
1412 new_password = unicode(new_pw.text())
1413 new_password2 = unicode(conf_pw.text())
1416 seed = wallet.pw_decode( wallet.seed, password)
1418 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1421 if new_password != new_password2:
1422 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1425 wallet.update_password(seed, password, new_password)
1428 def seed_dialog(wallet, parent=None):
1432 vbox = QVBoxLayout()
1433 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1434 vbox.addWidget(QLabel(msg))
1436 grid = QGridLayout()
1439 seed_e = QLineEdit()
1440 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1441 grid.addWidget(seed_e, 1, 1)
1445 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1446 grid.addWidget(gap_e, 2, 1)
1447 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1448 vbox.addLayout(grid)
1450 vbox.addLayout(ok_cancel_buttons(d))
1453 if not d.exec_(): return
1456 gap = int(unicode(gap_e.text()))
1458 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1462 seed = unicode(seed_e.text())
1465 print_error("Warning: Not hex, trying decode")
1467 seed = mnemonic.mn_decode( seed.split(' ') )
1469 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1472 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1475 wallet.seed = str(seed)
1476 #print repr(wallet.seed)
1477 wallet.gap_limit = gap
1482 def settings_dialog(self):
1485 vbox = QVBoxLayout()
1486 msg = _('Here are the settings of your wallet.') + '\n'\
1487 + _('For more explanations, click on the help buttons next to each field.')
1490 label.setFixedWidth(250)
1491 label.setWordWrap(True)
1492 label.setAlignment(Qt.AlignJustify)
1493 vbox.addWidget(label)
1495 grid = QGridLayout()
1497 vbox.addLayout(grid)
1499 fee_label = QLabel(_('Transaction fee'))
1500 grid.addWidget(fee_label, 2, 0)
1502 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1503 grid.addWidget(fee_e, 2, 1)
1504 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1505 + _('Recommended value') + ': 0.001'
1506 grid.addWidget(HelpButton(msg), 2, 2)
1507 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1508 if not self.config.is_modifiable('fee'):
1509 for w in [fee_e, fee_label]: w.setEnabled(False)
1511 nz_label = QLabel(_('Display zeros'))
1512 grid.addWidget(nz_label, 3, 0)
1514 nz_e.setText("%d"% self.wallet.num_zeros)
1515 grid.addWidget(nz_e, 3, 1)
1516 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1517 grid.addWidget(HelpButton(msg), 3, 2)
1518 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1519 if not self.config.is_modifiable('num_zeros'):
1520 for w in [nz_e, nz_label]: w.setEnabled(False)
1522 usechange_cb = QCheckBox(_('Use change addresses'))
1523 grid.addWidget(usechange_cb, 5, 0)
1524 usechange_cb.setChecked(self.wallet.use_change)
1525 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1526 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1528 gap_label = QLabel(_('Gap limit'))
1529 grid.addWidget(gap_label, 6, 0)
1531 gap_e.setText("%d"% self.wallet.gap_limit)
1532 grid.addWidget(gap_e, 6, 1)
1533 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1534 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1535 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1536 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1537 + _('Warning') + ': ' \
1538 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1539 + _('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'
1540 grid.addWidget(HelpButton(msg), 6, 2)
1541 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1542 if not self.config.is_modifiable('gap_limit'):
1543 for w in [gap_e, gap_label]: w.setEnabled(False)
1545 gui_label=QLabel(_('Default GUI') + ':')
1546 grid.addWidget(gui_label , 7, 0)
1547 gui_combo = QComboBox()
1548 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1549 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1550 if index==-1: index = 1
1551 gui_combo.setCurrentIndex(index)
1552 grid.addWidget(gui_combo, 7, 1)
1553 grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1554 if not self.config.is_modifiable('gui'):
1555 for w in [gui_combo, gui_label]: w.setEnabled(False)
1557 vbox.addLayout(ok_cancel_buttons(d))
1561 if not d.exec_(): return
1563 fee = unicode(fee_e.text())
1565 fee = int( 100000000 * Decimal(fee) )
1567 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1570 if self.wallet.fee != fee:
1571 self.wallet.fee = fee
1574 nz = unicode(nz_e.text())
1579 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1582 if self.wallet.num_zeros != nz:
1583 self.wallet.num_zeros = nz
1584 self.config.set_key('num_zeros', nz, True)
1585 self.update_history_tab()
1586 self.update_receive_tab()
1588 if self.wallet.use_change != usechange_cb.isChecked():
1589 self.wallet.use_change = usechange_cb.isChecked()
1590 self.config.set_key('use_change', self.wallet.use_change, True)
1593 n = int(gap_e.text())
1595 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1598 if self.wallet.gap_limit != n:
1599 r = self.wallet.change_gap_limit(n)
1601 self.update_receive_tab()
1602 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1604 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1606 self.config.set_key("gui", str(gui_combo.currentText()).lower(), True)
1611 def network_dialog(wallet, parent=None):
1612 interface = wallet.interface
1614 if interface.is_connected:
1615 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1617 status = _("Not connected")
1618 server = interface.server
1621 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1622 server = interface.server
1624 plist, servers_list = interface.get_servers_list()
1628 d.setWindowTitle(_('Server'))
1629 d.setMinimumSize(375, 20)
1631 vbox = QVBoxLayout()
1634 hbox = QHBoxLayout()
1636 l.setPixmap(QPixmap(":icons/network.png"))
1639 hbox.addWidget(QLabel(status))
1641 vbox.addLayout(hbox)
1645 grid = QGridLayout()
1647 vbox.addLayout(grid)
1650 server_protocol = QComboBox()
1651 server_host = QLineEdit()
1652 server_host.setFixedWidth(200)
1653 server_port = QLineEdit()
1654 server_port.setFixedWidth(60)
1656 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1657 protocol_letters = 'thsg'
1658 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1659 server_protocol.addItems(protocol_names)
1661 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1662 grid.addWidget(server_protocol, 0, 1)
1663 grid.addWidget(server_host, 0, 2)
1664 grid.addWidget(server_port, 0, 3)
1666 def change_protocol(p):
1667 protocol = protocol_letters[p]
1668 host = unicode(server_host.text())
1669 pp = plist.get(host,DEFAULT_PORTS)
1670 if protocol not in pp.keys():
1671 protocol = pp.keys()[0]
1673 server_host.setText( host )
1674 server_port.setText( port )
1676 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1678 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1679 servers_list_widget = QTreeWidget(parent)
1680 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1681 servers_list_widget.setMaximumHeight(150)
1682 servers_list_widget.setColumnWidth(0, 240)
1683 for _host in servers_list.keys():
1684 _type = 'pruning' if servers_list[_host].get('pruning') else 'full'
1685 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1687 def change_server(host, protocol=None):
1688 pp = plist.get(host,DEFAULT_PORTS)
1690 port = pp.get(protocol)
1691 if not port: protocol = None
1694 if 't' in pp.keys():
1696 port = pp.get(protocol)
1698 protocol = pp.keys()[0]
1699 port = pp.get(protocol)
1701 server_host.setText( host )
1702 server_port.setText( port )
1703 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1705 if not plist: return
1706 for p in protocol_letters:
1707 i = protocol_letters.index(p)
1708 j = server_protocol.model().index(i,0)
1709 if p not in pp.keys():
1710 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1712 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1716 host, port, protocol = server.split(':')
1717 change_server(host,protocol)
1719 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1720 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1722 if not wallet.config.is_modifiable('server'):
1723 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1726 proxy_mode = QComboBox()
1727 proxy_host = QLineEdit()
1728 proxy_host.setFixedWidth(200)
1729 proxy_port = QLineEdit()
1730 proxy_port.setFixedWidth(60)
1731 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1733 def check_for_disable(index = False):
1734 if proxy_mode.currentText() != 'NONE':
1735 proxy_host.setEnabled(True)
1736 proxy_port.setEnabled(True)
1738 proxy_host.setEnabled(False)
1739 proxy_port.setEnabled(False)
1742 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1744 if not wallet.config.is_modifiable('proxy'):
1745 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1747 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1748 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1749 proxy_host.setText(proxy_config.get("host"))
1750 proxy_port.setText(proxy_config.get("port"))
1752 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1753 grid.addWidget(proxy_mode, 2, 1)
1754 grid.addWidget(proxy_host, 2, 2)
1755 grid.addWidget(proxy_port, 2, 3)
1758 vbox.addLayout(ok_cancel_buttons(d))
1761 if not d.exec_(): return
1763 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1764 if proxy_mode.currentText() != 'NONE':
1765 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1769 wallet.config.set_key("proxy", proxy, True)
1770 wallet.config.set_key("server", server, True)
1771 interface.set_server(server, proxy)
1775 def closeEvent(self, event):
1777 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1783 def __init__(self, wallet, config, app=None):
1784 self.wallet = wallet
1785 self.config = config
1787 self.app = QApplication(sys.argv)
1790 def restore_or_create(self):
1791 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1792 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1793 if r==2: return None
1794 return 'restore' if r==1 else 'create'
1796 def seed_dialog(self):
1797 return ElectrumWindow.seed_dialog( self.wallet )
1799 def network_dialog(self):
1800 return ElectrumWindow.network_dialog( self.wallet, parent=None )
1803 def show_seed(self):
1804 ElectrumWindow.show_seed_dialog(self.wallet)
1807 def password_dialog(self):
1808 ElectrumWindow.change_password_dialog(self.wallet)
1811 def restore_wallet(self):
1812 wallet = self.wallet
1813 # wait until we are connected, because the user might have selected another server
1814 if not wallet.interface.is_connected:
1815 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1816 waiting_dialog(waiting)
1818 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1819 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1821 wallet.set_up_to_date(False)
1822 wallet.interface.poke('synchronizer')
1823 waiting_dialog(waiting)
1824 if wallet.is_found():
1825 print_error( "Recovery successful" )
1827 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1834 w = ElectrumWindow(self.wallet, self.config)
1835 if url: w.set_url(url)