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, data = None):
141 QWidget.__init__(self)
142 self.setMinimumSize(210, 210)
149 def set_addr(self, addr):
150 if self.addr != addr:
156 if self.addr and not self.qr:
157 self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
158 self.qr.addData(self.addr)
162 def paintEvent(self, e):
167 black = QColor(0, 0, 0, 255)
168 white = QColor(255, 255, 255, 255)
172 qp = QtGui.QPainter()
176 qp.drawRect(0, 0, 198, 198)
180 size = self.qr.getModuleCount()*boxsize
181 k = self.qr.getModuleCount()
182 qp = QtGui.QPainter()
186 if self.qr.isDark(r, c):
192 qp.drawRect(c*boxsize, r*boxsize, boxsize, boxsize)
197 class QR_Window(QWidget):
200 QWidget.__init__(self)
201 self.setWindowTitle('Electrum - Invoice')
202 self.setMinimumSize(800, 250)
206 self.setFocusPolicy(QtCore.Qt.NoFocus)
208 main_box = QHBoxLayout()
210 self.qrw = QRCodeWidget()
211 main_box.addWidget(self.qrw)
214 main_box.addLayout(vbox)
216 main_box.addStretch(1)
218 self.address_label = QLabel("")
219 self.address_label.setFont(QFont(MONOSPACE_FONT))
220 vbox.addWidget(self.address_label)
222 self.label_label = QLabel("")
223 vbox.addWidget(self.label_label)
225 self.amount_label = QLabel("")
226 vbox.addWidget(self.amount_label)
229 self.setLayout(main_box)
232 def set_content(self, addr, label, amount):
234 address_text = "<span style='font-size: 18pt'>%s</span>" % addr if addr else ""
235 self.address_label.setText(address_text)
238 amount_text = "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % format_satoshis(amount) if amount else ""
239 self.amount_label.setText(amount_text)
242 label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
243 self.label_label.setText(label_text)
245 msg = 'bitcoin:'+self.address
246 if self.amount is not None:
247 msg += '?amount=%s'%(str( Decimal(self.amount) /100000000))
248 if self.label is not None:
249 msg += '&label=%s'%(self.label)
250 elif self.label is not None:
251 msg += '?label=%s'%(self.label)
253 self.qrw.set_addr( msg )
258 def waiting_dialog(f):
264 w.setWindowTitle('Electrum')
274 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
279 def ok_cancel_buttons(dialog):
282 b = QPushButton("OK")
284 b.clicked.connect(dialog.accept)
285 b = QPushButton("Cancel")
287 b.clicked.connect(dialog.reject)
291 class ElectrumWindow(QMainWindow):
293 def __init__(self, wallet, config):
294 QMainWindow.__init__(self)
297 self.wallet.interface.register_callback('updated', self.update_callback)
298 self.wallet.interface.register_callback('connected', self.update_callback)
299 self.wallet.interface.register_callback('disconnected', self.update_callback)
300 self.wallet.interface.register_callback('disconnecting', self.update_callback)
302 self.receive_tab_mode = config.get('qt_receive_tab_mode', 0)
303 self.merchant_name = config.get('merchant_name', 'Invoice')
305 self.qr_window = None
306 self.funds_error = False
307 self.completions = QStringListModel()
309 self.tabs = tabs = QTabWidget(self)
310 tabs.addTab(self.create_history_tab(), _('History') )
311 tabs.addTab(self.create_send_tab(), _('Send') )
312 tabs.addTab(self.create_receive_tab(), _('Receive') )
313 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
314 tabs.addTab(self.create_wall_tab(), _('Wall') )
315 tabs.setMinimumSize(600, 400)
316 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
317 self.setCentralWidget(tabs)
318 self.create_status_bar()
319 self.toggle_QR_window(self.receive_tab_mode == 2)
321 g = self.config.get("winpos-qt",[100, 100, 840, 400])
322 self.setGeometry(g[0], g[1], g[2], g[3])
323 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
324 if not self.wallet.seed: title += ' [seedless]'
325 self.setWindowTitle( title )
327 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
328 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
329 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
330 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
332 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
333 #self.connect(self, SIGNAL('editamount'), self.edit_amount)
334 self.history_list.setFocus(True)
336 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
337 if platform.system() == 'Windows':
338 n = 3 if self.wallet.seed else 2
339 tabs.setCurrentIndex (n)
340 tabs.setCurrentIndex (0)
343 QMainWindow.close(self)
345 self.qr_window.close()
346 self.qr_window = None
348 def connect_slots(self, sender):
350 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
351 self.previous_payto_e=''
353 def timer_actions(self):
355 self.qr_window.qrw.update_qr()
357 if self.payto_e.hasFocus():
359 r = unicode( self.payto_e.text() )
360 if r != self.previous_payto_e:
361 self.previous_payto_e = r
363 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
365 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
369 s = r + ' <' + to_address + '>'
370 self.payto_e.setText(s)
373 def update_callback(self):
374 self.emit(QtCore.SIGNAL('updatesignal'))
376 def update_wallet(self):
377 if self.wallet.interface and self.wallet.interface.is_connected:
378 if not self.wallet.up_to_date:
379 text = _( "Synchronizing..." )
380 icon = QIcon(":icons/status_waiting.png")
382 c, u = self.wallet.get_balance()
383 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
384 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
385 icon = QIcon(":icons/status_connected.png")
387 text = _( "Not connected" )
388 icon = QIcon(":icons/status_disconnected.png")
391 text = _( "Not enough funds" )
393 self.statusBar().showMessage(text)
394 self.status_button.setIcon( icon )
396 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
397 self.textbox.setText( self.wallet.banner )
398 self.update_history_tab()
399 self.update_receive_tab()
400 self.update_contacts_tab()
401 self.update_completions()
404 def create_history_tab(self):
405 self.history_list = l = MyTreeWidget(self)
407 l.setColumnWidth(0, 40)
408 l.setColumnWidth(1, 140)
409 l.setColumnWidth(2, 350)
410 l.setColumnWidth(3, 140)
411 l.setColumnWidth(4, 140)
412 l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
413 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
414 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
416 l.setContextMenuPolicy(Qt.CustomContextMenu)
417 l.customContextMenuRequested.connect(self.create_history_menu)
421 def create_history_menu(self, position):
422 self.history_list.selectedIndexes()
423 item = self.history_list.currentItem()
425 tx_hash = str(item.toolTip(0))
426 if not tx_hash: return
428 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
429 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
430 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
431 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
434 def tx_details(self, tx_hash):
435 tx_details = self.wallet.get_tx_details(tx_hash)
436 QMessageBox.information(self, 'Details', tx_details, 'OK')
439 def tx_label_clicked(self, item, column):
440 if column==2 and item.isSelected():
441 tx_hash = str(item.toolTip(0))
443 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
444 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
445 self.history_list.editItem( item, column )
446 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
449 def tx_label_changed(self, item, column):
453 tx_hash = str(item.toolTip(0))
454 tx = self.wallet.transactions.get(tx_hash)
455 s = self.wallet.labels.get(tx_hash)
456 text = unicode( item.text(2) )
458 self.wallet.labels[tx_hash] = text
459 item.setForeground(2, QBrush(QColor('black')))
461 if s: self.wallet.labels.pop(tx_hash)
462 text = self.wallet.get_default_label(tx_hash)
463 item.setText(2, text)
464 item.setForeground(2, QBrush(QColor('gray')))
468 def edit_label(self, is_recv):
469 l = self.receive_list if is_recv else self.contacts_list
470 c = 2 if is_recv else 1
471 item = l.currentItem()
472 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
473 l.editItem( item, c )
474 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
476 def edit_amount(self):
477 l = self.receive_list
478 item = l.currentItem()
479 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
480 l.editItem( item, 3 )
481 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
484 def address_label_clicked(self, item, column, l, column_addr, column_label):
485 if column == column_label and item.isSelected():
486 addr = unicode( item.text(column_addr) )
487 label = unicode( item.text(column_label) )
488 if label in self.wallet.aliases.keys():
490 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
491 l.editItem( item, column )
492 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
495 def address_label_changed(self, item, column, l, column_addr, column_label):
497 if column == column_label:
498 addr = unicode( item.text(column_addr) )
499 text = unicode( item.text(column_label) )
503 if text not in self.wallet.aliases.keys():
504 old_addr = self.wallet.labels.get(text)
506 self.wallet.labels[addr] = text
509 print_error("Error: This is one of your aliases")
510 label = self.wallet.labels.get(addr,'')
511 item.setText(column_label, QString(label))
513 s = self.wallet.labels.get(addr)
515 self.wallet.labels.pop(addr)
519 self.update_history_tab()
520 self.update_completions()
522 self.recv_changed(item)
525 address = unicode( item.text(column_addr) )
526 text = unicode( item.text(3) )
528 index = self.wallet.addresses.index(address)
533 amount = int( Decimal(text) * 100000000 )
534 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
536 amount = self.wallet.requested_amounts.get(address)
538 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
543 self.wallet.requested_amounts[address] = amount
545 label = self.wallet.labels.get(address)
547 label = self.merchant_name + ' - %04d'%(index+1)
548 self.wallet.labels[address] = label
550 self.update_receive_item(self.receive_list.currentItem())
552 self.qr_window.set_content( address, label, amount )
555 def recv_changed(self, a):
556 "current item changed"
557 if a is not None and self.qr_window and self.qr_window.isVisible():
558 address = str(a.text(1))
559 label = self.wallet.labels.get(address)
560 amount = self.wallet.requested_amounts.get(address)
561 self.qr_window.set_content( address, label, amount )
564 def update_history_tab(self):
566 self.history_list.clear()
567 for item in self.wallet.get_tx_history():
568 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
571 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
577 icon = QIcon(":icons/unconfirmed.png")
579 icon = QIcon(":icons/clock%d.png"%conf)
581 icon = QIcon(":icons/confirmed.png")
584 icon = QIcon(":icons/unconfirmed.png")
586 if value is not None:
587 v_str = format_satoshis(value, True, self.wallet.num_zeros)
591 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
594 label, is_default_label = self.wallet.get_label(tx_hash)
596 label = _('Pruned transaction outputs')
597 is_default_label = False
599 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
600 item.setFont(2, QFont(MONOSPACE_FONT))
601 item.setFont(3, QFont(MONOSPACE_FONT))
602 item.setFont(4, QFont(MONOSPACE_FONT))
604 item.setToolTip(0, tx_hash)
606 item.setForeground(2, QBrush(QColor('grey')))
608 item.setIcon(0, icon)
609 self.history_list.insertTopLevelItem(0,item)
612 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
615 def create_send_tab(self):
620 grid.setColumnMinimumWidth(3,300)
621 grid.setColumnStretch(5,1)
623 self.payto_e = QLineEdit()
624 grid.addWidget(QLabel(_('Pay to')), 1, 0)
625 grid.addWidget(self.payto_e, 1, 1, 1, 3)
628 qrcode = qrscanner.scan_qr()
629 if 'address' in qrcode:
630 self.payto_e.setText(qrcode['address'])
631 if 'amount' in qrcode:
632 self.amount_e.setText(str(qrcode['amount']))
633 if 'label' in qrcode:
634 self.message_e.setText(qrcode['label'])
635 if 'message' in qrcode:
636 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
639 if qrscanner.is_available():
640 b = QPushButton(_("Scan QR code"))
641 b.clicked.connect(fill_from_qr)
642 grid.addWidget(b, 1, 5)
644 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)
646 completer = QCompleter()
647 completer.setCaseSensitivity(False)
648 self.payto_e.setCompleter(completer)
649 completer.setModel(self.completions)
651 self.message_e = QLineEdit()
652 grid.addWidget(QLabel(_('Description')), 2, 0)
653 grid.addWidget(self.message_e, 2, 1, 1, 3)
654 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)
656 self.amount_e = QLineEdit()
657 grid.addWidget(QLabel(_('Amount')), 3, 0)
658 grid.addWidget(self.amount_e, 3, 1, 1, 2)
659 grid.addWidget(HelpButton(
660 _('Amount to be sent.') + '\n\n' \
661 + _('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)
663 self.fee_e = QLineEdit()
664 grid.addWidget(QLabel(_('Fee')), 4, 0)
665 grid.addWidget(self.fee_e, 4, 1, 1, 2)
666 grid.addWidget(HelpButton(
667 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
668 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
669 + _('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)
671 b = EnterButton(_("Send"), self.do_send)
672 grid.addWidget(b, 6, 1)
674 b = EnterButton(_("Clear"),self.do_clear)
675 grid.addWidget(b, 6, 2)
677 self.payto_sig = QLabel('')
678 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
680 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
681 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
690 def entry_changed( is_fee ):
691 self.funds_error = False
692 amount = numbify(self.amount_e)
693 fee = numbify(self.fee_e)
694 if not is_fee: fee = None
697 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
699 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
702 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
705 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
706 self.funds_error = True
707 self.amount_e.setPalette(palette)
708 self.fee_e.setPalette(palette)
710 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
711 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
716 def update_completions(self):
718 for addr,label in self.wallet.labels.items():
719 if addr in self.wallet.addressbook:
720 l.append( label + ' <' + addr + '>')
721 l = l + self.wallet.aliases.keys()
723 self.completions.setStringList(l)
729 label = unicode( self.message_e.text() )
730 r = unicode( self.payto_e.text() )
734 m1 = re.match(ALIAS_REGEXP, r)
735 # label or alias, with address in brackets
736 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
739 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
743 to_address = m2.group(2)
747 if not self.wallet.is_valid(to_address):
748 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
752 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
754 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
757 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
759 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
762 if self.wallet.use_encryption:
763 password = self.password_dialog()
770 tx = self.wallet.mktx( [(to_address, amount)], label, password, fee)
771 except BaseException, e:
772 self.show_message(str(e))
776 h = self.wallet.send_tx(tx)
777 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
778 status, msg = self.wallet.receive_tx( h )
780 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
782 self.update_contacts_tab()
784 QMessageBox.warning(self, _('Error'), msg, _('OK'))
786 filename = 'unsigned_tx'
787 f = open(filename,'w')
790 QMessageBox.information(self, _('Unsigned transaction'), _("Unsigned transaction was saved to file:") + " " +filename, _('OK'))
793 def set_url(self, url):
794 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
795 self.tabs.setCurrentIndex(1)
796 label = self.wallet.labels.get(payto)
797 m_addr = label + ' <'+ payto+'>' if label else payto
798 self.payto_e.setText(m_addr)
800 self.message_e.setText(message)
801 self.amount_e.setText(amount)
803 self.set_frozen(self.payto_e,True)
804 self.set_frozen(self.amount_e,True)
805 self.set_frozen(self.message_e,True)
806 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
808 self.payto_sig.setVisible(False)
811 self.payto_sig.setVisible(False)
812 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
814 self.set_frozen(e,False)
816 def set_frozen(self,entry,frozen):
818 entry.setReadOnly(True)
819 entry.setFrame(False)
821 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
822 entry.setPalette(palette)
824 entry.setReadOnly(False)
827 palette.setColor(entry.backgroundRole(), QColor('white'))
828 entry.setPalette(palette)
831 def toggle_freeze(self,addr):
833 if addr in self.wallet.frozen_addresses:
834 self.wallet.unfreeze(addr)
836 self.wallet.freeze(addr)
837 self.update_receive_tab()
839 def toggle_priority(self,addr):
841 if addr in self.wallet.prioritized_addresses:
842 self.wallet.unprioritize(addr)
844 self.wallet.prioritize(addr)
845 self.update_receive_tab()
848 def create_list_tab(self, headers):
849 "generic tab creation method"
850 l = MyTreeWidget(self)
851 l.setColumnCount( len(headers) )
852 l.setHeaderLabels( headers )
862 vbox.addWidget(buttons)
867 buttons.setLayout(hbox)
872 def create_receive_tab(self):
873 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
874 l.setContextMenuPolicy(Qt.CustomContextMenu)
875 l.customContextMenuRequested.connect(self.create_receive_menu)
876 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
877 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
878 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
879 self.receive_list = l
880 self.receive_buttons_hbox = hbox
881 view_combo = QComboBox()
882 view_combo.addItems([_('Simple View'), _('Detailed View'), _('Point of Sale')])
883 view_combo.setCurrentIndex(self.receive_tab_mode)
884 hbox.addWidget(view_combo)
885 view_combo.currentIndexChanged.connect(self.receive_tab_set_mode)
891 def receive_tab_set_mode(self, i):
892 self.receive_tab_mode = i
893 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
895 self.update_receive_tab()
896 self.toggle_QR_window(self.receive_tab_mode == 2)
899 def create_contacts_tab(self):
900 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
901 l.setContextMenuPolicy(Qt.CustomContextMenu)
902 l.customContextMenuRequested.connect(self.create_contact_menu)
903 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
904 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
905 self.contacts_list = l
906 self.contacts_buttons_hbox = hbox
907 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
912 def create_receive_menu(self, position):
913 # fixme: this function apparently has a side effect.
914 # if it is not called the menu pops up several times
915 #self.receive_list.selectedIndexes()
917 item = self.receive_list.itemAt(position)
919 addr = unicode(item.text(1))
921 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
922 if self.receive_tab_mode == 2:
923 menu.addAction(_("Request amount"), lambda: self.edit_amount())
924 menu.addAction(_("View QR"), lambda: ElectrumWindow.show_qrcode("Address","bitcoin:"+addr) )
925 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
926 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
928 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
929 menu.addAction(t, lambda: self.toggle_freeze(addr))
930 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
931 menu.addAction(t, lambda: self.toggle_priority(addr))
932 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
935 def payto(self, x, is_alias):
942 label = self.wallet.labels.get(addr)
943 m_addr = label + ' <' + addr + '>' if label else addr
944 self.tabs.setCurrentIndex(1)
945 self.payto_e.setText(m_addr)
946 self.amount_e.setFocus()
948 def delete_contact(self, x, is_alias):
949 if self.question("Do you want to remove %s from your list of contacts?"%x):
950 if not is_alias and x in self.wallet.addressbook:
951 self.wallet.addressbook.remove(x)
952 if x in self.wallet.labels.keys():
953 self.wallet.labels.pop(x)
954 elif is_alias and x in self.wallet.aliases:
955 self.wallet.aliases.pop(x)
956 self.update_history_tab()
957 self.update_contacts_tab()
958 self.update_completions()
960 def create_contact_menu(self, position):
961 # fixme: this function apparently has a side effect.
962 # if it is not called the menu pops up several times
963 #self.contacts_list.selectedIndexes()
965 item = self.contacts_list.itemAt(position)
967 addr = unicode(item.text(0))
968 label = unicode(item.text(1))
969 is_alias = label in self.wallet.aliases.keys()
970 x = label if is_alias else addr
972 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
973 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
974 menu.addAction(_("View QR code"),lambda: self.show_qrcode("Address","bitcoin:"+addr))
976 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
978 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
979 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
980 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
983 def update_receive_item(self, item):
984 address = str( item.data(1,0).toString() )
986 flags = self.wallet.get_address_flags(address)
987 item.setData(0,0,flags)
989 label = self.wallet.labels.get(address,'')
990 item.setData(2,0,label)
992 amount = self.wallet.requested_amounts.get(address,None)
993 amount_str = format_satoshis( amount, False, self.wallet.num_zeros ) if amount is not None else ""
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.receive_tab_mode == 1)
1011 l.setColumnHidden(3, not self.receive_tab_mode == 2)
1012 l.setColumnHidden(4, self.receive_tab_mode == 0)
1013 l.setColumnHidden(5, not self.receive_tab_mode == 1)
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 self.receive_tab_mode != 1:
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.setColumnWidth(0, 350)
1076 l.setColumnWidth(1, 330)
1077 l.setColumnWidth(2, 100)
1080 for alias, v in self.wallet.aliases.items():
1082 alias_targets.append(target)
1083 item = QTreeWidgetItem( [ target, alias, '-'] )
1084 item.setBackgroundColor(0, QColor('lightgray'))
1085 l.addTopLevelItem(item)
1087 for address in self.wallet.addressbook:
1088 if address in alias_targets: continue
1089 label = self.wallet.labels.get(address,'')
1091 for item in self.wallet.transactions.values():
1092 if address in item['outputs'] : n=n+1
1094 item = QTreeWidgetItem( [ address, label, tx] )
1095 item.setFont(0, QFont(MONOSPACE_FONT))
1096 l.addTopLevelItem(item)
1098 l.setCurrentItem(l.topLevelItem(0))
1100 def create_wall_tab(self):
1101 self.textbox = textbox = QTextEdit(self)
1102 textbox.setFont(QFont(MONOSPACE_FONT))
1103 textbox.setReadOnly(True)
1106 def create_status_bar(self):
1108 sb.setFixedHeight(35)
1109 if self.wallet.seed:
1110 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1111 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1112 if self.wallet.seed:
1113 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1114 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
1115 sb.addPermanentWidget( self.status_button )
1116 self.setStatusBar(sb)
1118 def new_contact_dialog(self):
1119 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1120 address = unicode(text)
1122 if self.wallet.is_valid(address):
1123 self.wallet.addressbook.append(address)
1125 self.update_contacts_tab()
1126 self.update_history_tab()
1127 self.update_completions()
1129 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1132 def show_seed_dialog(wallet, parent=None):
1134 QMessageBox.information(parent, _('Message'),
1135 _('No seed'), _('OK'))
1138 if wallet.use_encryption:
1139 password = parent.password_dialog()
1146 seed = wallet.pw_decode(wallet.seed, password)
1148 QMessageBox.warning(parent, _('Error'),
1149 _('Incorrect Password'), _('OK'))
1152 dialog = QDialog(None)
1154 dialog.setWindowTitle("Electrum")
1156 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1158 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1159 + _("Please write down or memorize these 12 words (order is important).") + " " \
1160 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1161 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1163 main_text = QLabel(msg)
1164 main_text.setWordWrap(True)
1167 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1174 copy_function = lambda: app.clipboard().setText(brainwallet)
1175 copy_button = QPushButton(_("Copy to Clipboard"))
1176 copy_button.clicked.connect(copy_function)
1178 show_qr_function = lambda: ElectrumWindow.show_qrcode(_("Seed"), seed)
1179 qr_button = QPushButton(_("View as QR Code"))
1180 qr_button.clicked.connect(show_qr_function)
1182 ok_button = QPushButton(_("OK"))
1183 ok_button.setDefault(True)
1184 ok_button.clicked.connect(dialog.accept)
1186 main_layout = QGridLayout()
1187 main_layout.addWidget(logo, 0, 0)
1188 main_layout.addWidget(main_text, 0, 1, 1, -1)
1189 main_layout.addWidget(copy_button, 1, 1)
1190 main_layout.addWidget(qr_button, 1, 2)
1191 main_layout.addWidget(ok_button, 1, 3)
1192 dialog.setLayout(main_layout)
1197 def show_qrcode(title, data):
1201 d.setWindowTitle(title)
1202 d.setMinimumSize(270, 300)
1203 vbox = QVBoxLayout()
1204 qrw = QRCodeWidget(data)
1206 vbox.addWidget(QLabel(data))
1207 hbox = QHBoxLayout()
1211 filename = "qrcode.bmp"
1212 bmp.save_qrcode(qrw.qr, filename)
1213 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1215 b = QPushButton(_("Print"))
1217 b.clicked.connect(print_qr)
1219 b = QPushButton(_("Close"))
1221 b.clicked.connect(d.accept)
1223 vbox.addLayout(hbox)
1227 def sign_message(self,address):
1228 if not address: return
1231 d.setWindowTitle('Sign Message')
1232 d.setMinimumSize(270, 350)
1234 tab_widget = QTabWidget()
1236 layout = QGridLayout(tab)
1238 sign_address = QLineEdit()
1239 sign_address.setText(address)
1240 layout.addWidget(QLabel(_('Address')), 1, 0)
1241 layout.addWidget(sign_address, 1, 1)
1243 sign_message = QTextEdit()
1244 layout.addWidget(QLabel(_('Message')), 2, 0)
1245 layout.addWidget(sign_message, 2, 1, 2, 1)
1247 sign_signature = QLineEdit()
1248 layout.addWidget(QLabel(_('Signature')), 3, 0)
1249 layout.addWidget(sign_signature, 3, 1)
1252 if self.wallet.use_encryption:
1253 password = self.password_dialog()
1260 signature = self.wallet.sign_message(sign_address.text(), str(sign_message.toPlainText()), password)
1261 sign_signature.setText(signature)
1262 except BaseException, e:
1263 self.show_message(str(e))
1266 hbox = QHBoxLayout()
1267 b = QPushButton(_("Sign"))
1269 b.clicked.connect(do_sign)
1270 b = QPushButton(_("Close"))
1271 b.clicked.connect(d.accept)
1273 layout.addLayout(hbox, 4, 1)
1274 tab_widget.addTab(tab, "Sign")
1278 layout = QGridLayout(tab)
1280 verify_address = QLineEdit()
1281 layout.addWidget(QLabel(_('Address')), 1, 0)
1282 layout.addWidget(verify_address, 1, 1)
1284 verify_message = QTextEdit()
1285 layout.addWidget(QLabel(_('Message')), 2, 0)
1286 layout.addWidget(verify_message, 2, 1, 2, 1)
1288 verify_signature = QLineEdit()
1289 layout.addWidget(QLabel(_('Signature')), 3, 0)
1290 layout.addWidget(verify_signature, 3, 1)
1294 self.wallet.verify_message(verify_address.text(), verify_signature.text(), str(verify_message.toPlainText()))
1295 self.show_message("Signature verified")
1296 except BaseException, e:
1297 self.show_message(str(e))
1300 hbox = QHBoxLayout()
1301 b = QPushButton(_("Verify"))
1302 b.clicked.connect(do_verify)
1304 b = QPushButton(_("Close"))
1305 b.clicked.connect(d.accept)
1307 layout.addLayout(hbox, 4, 1)
1308 tab_widget.addTab(tab, "Verify")
1310 vbox = QVBoxLayout()
1311 vbox.addWidget(tab_widget)
1316 def toggle_QR_window(self, show):
1317 if show and not self.qr_window:
1318 self.qr_window = QR_Window()
1319 self.qr_window.setVisible(True)
1320 self.qr_window_geometry = self.qr_window.geometry()
1321 item = self.receive_list.currentItem()
1323 address = str(item.text(1))
1324 label = self.wallet.labels.get(address)
1325 amount = self.wallet.requested_amounts.get(address)
1326 self.qr_window.set_content( address, label, amount )
1328 elif show and self.qr_window and not self.qr_window.isVisible():
1329 self.qr_window.setVisible(True)
1330 self.qr_window.setGeometry(self.qr_window_geometry)
1332 elif not show and self.qr_window and self.qr_window.isVisible():
1333 self.qr_window_geometry = self.qr_window.geometry()
1334 self.qr_window.setVisible(False)
1336 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1337 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1338 self.receive_list.setColumnWidth(2, 200)
1341 def question(self, msg):
1342 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1344 def show_message(self, msg):
1345 QMessageBox.information(self, _('Message'), msg, _('OK'))
1347 def password_dialog(self ):
1354 vbox = QVBoxLayout()
1355 msg = _('Please enter your password')
1356 vbox.addWidget(QLabel(msg))
1358 grid = QGridLayout()
1360 grid.addWidget(QLabel(_('Password')), 1, 0)
1361 grid.addWidget(pw, 1, 1)
1362 vbox.addLayout(grid)
1364 vbox.addLayout(ok_cancel_buttons(d))
1367 if not d.exec_(): return
1368 return unicode(pw.text())
1375 def change_password_dialog( wallet, parent=None ):
1378 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1386 new_pw = QLineEdit()
1387 new_pw.setEchoMode(2)
1388 conf_pw = QLineEdit()
1389 conf_pw.setEchoMode(2)
1391 vbox = QVBoxLayout()
1393 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1394 +_('To disable wallet encryption, enter an empty new password.')) \
1395 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1397 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1398 +_("Leave these fields empty if you want to disable encryption.")
1399 vbox.addWidget(QLabel(msg))
1401 grid = QGridLayout()
1404 if wallet.use_encryption:
1405 grid.addWidget(QLabel(_('Password')), 1, 0)
1406 grid.addWidget(pw, 1, 1)
1408 grid.addWidget(QLabel(_('New Password')), 2, 0)
1409 grid.addWidget(new_pw, 2, 1)
1411 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1412 grid.addWidget(conf_pw, 3, 1)
1413 vbox.addLayout(grid)
1415 vbox.addLayout(ok_cancel_buttons(d))
1418 if not d.exec_(): return
1420 password = unicode(pw.text()) if wallet.use_encryption else None
1421 new_password = unicode(new_pw.text())
1422 new_password2 = unicode(conf_pw.text())
1425 seed = wallet.pw_decode( wallet.seed, password)
1427 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1430 if new_password != new_password2:
1431 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1432 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1434 wallet.update_password(seed, password, new_password)
1437 def seed_dialog(wallet, parent=None):
1441 vbox = QVBoxLayout()
1442 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1443 vbox.addWidget(QLabel(msg))
1445 grid = QGridLayout()
1448 seed_e = QLineEdit()
1449 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1450 grid.addWidget(seed_e, 1, 1)
1454 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1455 grid.addWidget(gap_e, 2, 1)
1456 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1457 vbox.addLayout(grid)
1459 vbox.addLayout(ok_cancel_buttons(d))
1462 if not d.exec_(): return
1465 gap = int(unicode(gap_e.text()))
1467 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1471 seed = unicode(seed_e.text())
1474 print_error("Warning: Not hex, trying decode")
1476 seed = mnemonic.mn_decode( seed.split(' ') )
1478 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1481 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1484 wallet.seed = str(seed)
1485 #print repr(wallet.seed)
1486 wallet.gap_limit = gap
1491 def settings_dialog(self):
1494 vbox = QVBoxLayout()
1495 msg = _('Here are the settings of your wallet.') + '\n'\
1496 + _('For more explanations, click on the help buttons next to each field.')
1499 label.setFixedWidth(250)
1500 label.setWordWrap(True)
1501 label.setAlignment(Qt.AlignJustify)
1502 vbox.addWidget(label)
1504 grid = QGridLayout()
1506 vbox.addLayout(grid)
1508 fee_label = QLabel(_('Transaction fee'))
1509 grid.addWidget(fee_label, 2, 0)
1511 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1512 grid.addWidget(fee_e, 2, 1)
1513 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1514 + _('Recommended value') + ': 0.001'
1515 grid.addWidget(HelpButton(msg), 2, 2)
1516 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1517 if not self.config.is_modifiable('fee'):
1518 for w in [fee_e, fee_label]: w.setEnabled(False)
1520 nz_label = QLabel(_('Display zeros'))
1521 grid.addWidget(nz_label, 3, 0)
1523 nz_e.setText("%d"% self.wallet.num_zeros)
1524 grid.addWidget(nz_e, 3, 1)
1525 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1526 grid.addWidget(HelpButton(msg), 3, 2)
1527 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1528 if not self.config.is_modifiable('num_zeros'):
1529 for w in [nz_e, nz_label]: w.setEnabled(False)
1531 usechange_cb = QCheckBox(_('Use change addresses'))
1532 grid.addWidget(usechange_cb, 5, 0)
1533 usechange_cb.setChecked(self.wallet.use_change)
1534 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1535 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1537 gap_label = QLabel(_('Gap limit'))
1538 grid.addWidget(gap_label, 6, 0)
1540 gap_e.setText("%d"% self.wallet.gap_limit)
1541 grid.addWidget(gap_e, 6, 1)
1542 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1543 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1544 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1545 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1546 + _('Warning') + ': ' \
1547 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1548 + _('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'
1549 grid.addWidget(HelpButton(msg), 6, 2)
1550 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1551 if not self.config.is_modifiable('gap_limit'):
1552 for w in [gap_e, gap_label]: w.setEnabled(False)
1554 gui_label=QLabel(_('Default GUI') + ':')
1555 grid.addWidget(gui_label , 7, 0)
1556 gui_combo = QComboBox()
1557 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1558 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1559 if index==-1: index = 1
1560 gui_combo.setCurrentIndex(index)
1561 grid.addWidget(gui_combo, 7, 1)
1562 grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1563 if not self.config.is_modifiable('gui'):
1564 for w in [gui_combo, gui_label]: w.setEnabled(False)
1566 vbox.addLayout(ok_cancel_buttons(d))
1570 if not d.exec_(): return
1572 fee = unicode(fee_e.text())
1574 fee = int( 100000000 * Decimal(fee) )
1576 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1579 if self.wallet.fee != fee:
1580 self.wallet.fee = fee
1583 nz = unicode(nz_e.text())
1588 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1591 if self.wallet.num_zeros != nz:
1592 self.wallet.num_zeros = nz
1593 self.config.set_key('num_zeros', nz, True)
1594 self.update_history_tab()
1595 self.update_receive_tab()
1597 if self.wallet.use_change != usechange_cb.isChecked():
1598 self.wallet.use_change = usechange_cb.isChecked()
1599 self.config.set_key('use_change', self.wallet.use_change, True)
1602 n = int(gap_e.text())
1604 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1607 if self.wallet.gap_limit != n:
1608 r = self.wallet.change_gap_limit(n)
1610 self.update_receive_tab()
1611 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1613 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1615 self.config.set_key("gui", str(gui_combo.currentText()).lower(), True)
1620 def network_dialog(wallet, parent=None):
1621 interface = wallet.interface
1623 if interface.is_connected:
1624 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1626 status = _("Not connected")
1627 server = interface.server
1630 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1631 server = interface.server
1633 plist, servers_list = interface.get_servers_list()
1637 d.setWindowTitle(_('Server'))
1638 d.setMinimumSize(375, 20)
1640 vbox = QVBoxLayout()
1643 hbox = QHBoxLayout()
1645 l.setPixmap(QPixmap(":icons/network.png"))
1648 hbox.addWidget(QLabel(status))
1650 vbox.addLayout(hbox)
1654 grid = QGridLayout()
1656 vbox.addLayout(grid)
1659 server_protocol = QComboBox()
1660 server_host = QLineEdit()
1661 server_host.setFixedWidth(200)
1662 server_port = QLineEdit()
1663 server_port.setFixedWidth(60)
1665 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1666 protocol_letters = 'thsg'
1667 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1668 server_protocol.addItems(protocol_names)
1670 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1671 grid.addWidget(server_protocol, 0, 1)
1672 grid.addWidget(server_host, 0, 2)
1673 grid.addWidget(server_port, 0, 3)
1675 def change_protocol(p):
1676 protocol = protocol_letters[p]
1677 host = unicode(server_host.text())
1678 pp = plist.get(host,DEFAULT_PORTS)
1679 if protocol not in pp.keys():
1680 protocol = pp.keys()[0]
1682 server_host.setText( host )
1683 server_port.setText( port )
1685 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1687 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1688 servers_list_widget = QTreeWidget(parent)
1689 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1690 servers_list_widget.setMaximumHeight(150)
1691 servers_list_widget.setColumnWidth(0, 240)
1692 for _host in servers_list.keys():
1693 _type = 'pruning' if servers_list[_host].get('pruning') else 'full'
1694 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1696 def change_server(host, protocol=None):
1697 pp = plist.get(host,DEFAULT_PORTS)
1699 port = pp.get(protocol)
1700 if not port: protocol = None
1703 if 't' in pp.keys():
1705 port = pp.get(protocol)
1707 protocol = pp.keys()[0]
1708 port = pp.get(protocol)
1710 server_host.setText( host )
1711 server_port.setText( port )
1712 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1714 if not plist: return
1715 for p in protocol_letters:
1716 i = protocol_letters.index(p)
1717 j = server_protocol.model().index(i,0)
1718 if p not in pp.keys():
1719 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1721 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1725 host, port, protocol = server.split(':')
1726 change_server(host,protocol)
1728 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1729 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1731 if not wallet.config.is_modifiable('server'):
1732 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1735 proxy_mode = QComboBox()
1736 proxy_host = QLineEdit()
1737 proxy_host.setFixedWidth(200)
1738 proxy_port = QLineEdit()
1739 proxy_port.setFixedWidth(60)
1740 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1742 def check_for_disable(index = False):
1743 if proxy_mode.currentText() != 'NONE':
1744 proxy_host.setEnabled(True)
1745 proxy_port.setEnabled(True)
1747 proxy_host.setEnabled(False)
1748 proxy_port.setEnabled(False)
1751 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1753 if not wallet.config.is_modifiable('proxy'):
1754 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1756 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1757 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1758 proxy_host.setText(proxy_config.get("host"))
1759 proxy_port.setText(proxy_config.get("port"))
1761 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1762 grid.addWidget(proxy_mode, 2, 1)
1763 grid.addWidget(proxy_host, 2, 2)
1764 grid.addWidget(proxy_port, 2, 3)
1767 vbox.addLayout(ok_cancel_buttons(d))
1770 if not d.exec_(): return
1772 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1773 if proxy_mode.currentText() != 'NONE':
1774 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1778 wallet.config.set_key("proxy", proxy, True)
1779 wallet.config.set_key("server", server, True)
1780 interface.set_server(server, proxy)
1784 def closeEvent(self, event):
1786 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1792 def __init__(self, wallet, config, app=None):
1793 self.wallet = wallet
1794 self.config = config
1796 self.app = QApplication(sys.argv)
1799 def restore_or_create(self):
1800 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1801 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1802 if r==2: return None
1803 return 'restore' if r==1 else 'create'
1805 def seed_dialog(self):
1806 return ElectrumWindow.seed_dialog( self.wallet )
1808 def network_dialog(self):
1809 return ElectrumWindow.network_dialog( self.wallet, parent=None )
1812 def show_seed(self):
1813 ElectrumWindow.show_seed_dialog(self.wallet)
1816 def password_dialog(self):
1817 ElectrumWindow.change_password_dialog(self.wallet)
1820 def restore_wallet(self):
1821 wallet = self.wallet
1822 # wait until we are connected, because the user might have selected another server
1823 if not wallet.interface.is_connected:
1824 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1825 waiting_dialog(waiting)
1827 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1828 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1830 wallet.set_up_to_date(False)
1831 wallet.interface.poke('synchronizer')
1832 waiting_dialog(waiting)
1833 if wallet.is_found():
1834 print_error( "Recovery successful" )
1836 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1843 w = ElectrumWindow(self.wallet, self.config)
1844 if url: w.set_url(url)