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
43 from decimal import Decimal
47 if platform.system() == 'Windows':
48 MONOSPACE_FONT = 'Lucida Console'
49 elif platform.system() == 'Darwin':
50 MONOSPACE_FONT = 'Monaco'
52 MONOSPACE_FONT = 'monospace'
54 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
56 def numbify(entry, is_int = False):
57 text = unicode(entry.text()).strip()
58 pos = entry.cursorPosition()
60 if not is_int: chars +='.'
61 s = ''.join([i for i in text if i in chars])
66 s = s[:p] + '.' + s[p:p+8]
68 amount = int( Decimal(s) * 100000000 )
77 entry.setCursorPosition(pos)
81 class Timer(QtCore.QThread):
84 self.emit(QtCore.SIGNAL('timersignal'))
87 class HelpButton(QPushButton):
88 def __init__(self, text):
89 QPushButton.__init__(self, '?')
90 self.setFocusPolicy(Qt.NoFocus)
91 self.setFixedWidth(20)
92 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
95 class EnterButton(QPushButton):
96 def __init__(self, text, func):
97 QPushButton.__init__(self, text)
99 self.clicked.connect(func)
101 def keyPressEvent(self, e):
102 if e.key() == QtCore.Qt.Key_Return:
105 class MyTreeWidget(QTreeWidget):
106 def __init__(self, parent):
107 QTreeWidget.__init__(self, parent)
110 for i in range(0,self.viewport().height()/5):
111 if self.itemAt(QPoint(0,i*5)) == item:
115 for j in range(0,30):
116 if self.itemAt(QPoint(0,i*5 + j)) != item:
118 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
120 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
125 class StatusBarButton(QPushButton):
126 def __init__(self, icon, tooltip, func):
127 QPushButton.__init__(self, icon, '')
128 self.setToolTip(tooltip)
130 self.setMaximumWidth(25)
131 self.clicked.connect(func)
134 def keyPressEvent(self, e):
135 if e.key() == QtCore.Qt.Key_Return:
139 class QRCodeWidget(QWidget):
141 def __init__(self, data = None):
142 QWidget.__init__(self)
143 self.setMinimumSize(210, 210)
150 def set_addr(self, addr):
151 if self.addr != addr:
157 if self.addr and not self.qr:
158 self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
159 self.qr.addData(self.addr)
163 def paintEvent(self, e):
168 black = QColor(0, 0, 0, 255)
169 white = QColor(255, 255, 255, 255)
172 qp = QtGui.QPainter()
176 qp.drawRect(0, 0, 198, 198)
180 k = self.qr.getModuleCount()
181 qp = QtGui.QPainter()
184 boxsize = min(r.width(), r.height())*0.8/k
186 left = (r.width() - size)/2
187 top = (r.height() - size)/2
191 if self.qr.isDark(r, c):
197 qp.drawRect(left+c*boxsize, top+r*boxsize, boxsize, boxsize)
202 class QR_Window(QWidget):
205 QWidget.__init__(self)
206 self.setWindowTitle('Electrum - Invoice')
207 self.setMinimumSize(800, 250)
211 self.setFocusPolicy(QtCore.Qt.NoFocus)
213 main_box = QHBoxLayout()
215 self.qrw = QRCodeWidget()
216 main_box.addWidget(self.qrw, 1)
219 main_box.addLayout(vbox)
221 self.address_label = QLabel("")
222 self.address_label.setFont(QFont(MONOSPACE_FONT))
223 vbox.addWidget(self.address_label)
225 self.label_label = QLabel("")
226 vbox.addWidget(self.label_label)
228 self.amount_label = QLabel("")
229 vbox.addWidget(self.amount_label)
232 self.setLayout(main_box)
235 def set_content(self, addr, label, amount):
237 address_text = "<span style='font-size: 18pt'>%s</span>" % addr if addr else ""
238 self.address_label.setText(address_text)
241 amount_text = "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % format_satoshis(amount) if amount else ""
242 self.amount_label.setText(amount_text)
245 label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
246 self.label_label.setText(label_text)
248 msg = 'bitcoin:'+self.address
249 if self.amount is not None:
250 msg += '?amount=%s'%(str( Decimal(self.amount) /100000000))
251 if self.label is not None:
252 msg += '&label=%s'%(self.label)
253 elif self.label is not None:
254 msg += '?label=%s'%(self.label)
256 self.qrw.set_addr( msg )
261 def waiting_dialog(f):
267 w.setWindowTitle('Electrum')
277 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
282 def ok_cancel_buttons(dialog):
285 b = QPushButton("OK")
287 b.clicked.connect(dialog.accept)
288 b = QPushButton("Cancel")
290 b.clicked.connect(dialog.reject)
294 class ElectrumWindow(QMainWindow):
296 def __init__(self, wallet, config):
297 QMainWindow.__init__(self)
301 self.wallet.interface.register_callback('updated', self.update_callback)
302 self.wallet.interface.register_callback('connected', self.update_callback)
303 self.wallet.interface.register_callback('disconnected', self.update_callback)
304 self.wallet.interface.register_callback('disconnecting', self.update_callback)
306 self.receive_tab_mode = config.get('qt_receive_tab_mode', 0)
307 self.merchant_name = config.get('merchant_name', 'Invoice')
309 self.qr_window = None
310 self.funds_error = False
311 self.completions = QStringListModel()
313 self.tabs = tabs = QTabWidget(self)
314 tabs.addTab(self.create_history_tab(), _('History') )
315 tabs.addTab(self.create_send_tab(), _('Send') )
316 tabs.addTab(self.create_receive_tab(), _('Receive') )
317 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
318 tabs.addTab(self.create_wall_tab(), _('Wall') )
319 tabs.setMinimumSize(600, 400)
320 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
321 self.setCentralWidget(tabs)
322 self.create_status_bar()
323 self.toggle_QR_window(self.receive_tab_mode == 2)
325 g = self.config.get("winpos-qt",[100, 100, 840, 400])
326 self.setGeometry(g[0], g[1], g[2], g[3])
327 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
328 if not self.wallet.seed: title += ' [seedless]'
329 self.setWindowTitle( title )
331 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
332 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
333 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
334 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
336 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
337 #self.connect(self, SIGNAL('editamount'), self.edit_amount)
338 self.history_list.setFocus(True)
340 self.exchanger = exchange_rate.Exchanger(self)
341 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
343 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
344 if platform.system() == 'Windows':
345 n = 3 if self.wallet.seed else 2
346 tabs.setCurrentIndex (n)
347 tabs.setCurrentIndex (0)
350 QMainWindow.close(self)
352 self.qr_window.close()
353 self.qr_window = None
355 def connect_slots(self, sender):
356 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
357 self.previous_payto_e=''
359 def timer_actions(self):
361 self.qr_window.qrw.update_qr()
363 if self.payto_e.hasFocus():
365 r = unicode( self.payto_e.text() )
366 if r != self.previous_payto_e:
367 self.previous_payto_e = r
369 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
371 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
375 s = r + ' <' + to_address + '>'
376 self.payto_e.setText(s)
379 def update_callback(self):
380 self.emit(QtCore.SIGNAL('updatesignal'))
382 def update_wallet(self):
383 if self.wallet.interface and self.wallet.interface.is_connected:
384 if not self.wallet.up_to_date:
385 text = _( "Synchronizing..." )
386 icon = QIcon(":icons/status_waiting.png")
388 c, u = self.wallet.get_balance()
389 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
390 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
391 text += self.create_quote_text(Decimal(c+u)/100000000)
392 icon = QIcon(":icons/status_connected.png")
394 text = _( "Not connected" )
395 icon = QIcon(":icons/status_disconnected.png")
398 text = _( "Not enough funds" )
400 self.statusBar().showMessage(text)
401 self.status_button.setIcon( icon )
403 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
404 self.textbox.setText( self.wallet.banner )
405 self.update_history_tab()
406 self.update_receive_tab()
407 self.update_contacts_tab()
408 self.update_completions()
410 def create_quote_text(self, btc_balance):
411 quote_currency = self.config.get("currency", "None")
412 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
413 if quote_balance is None:
416 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
419 def create_history_tab(self):
420 self.history_list = l = MyTreeWidget(self)
422 l.setColumnWidth(0, 40)
423 l.setColumnWidth(1, 140)
424 l.setColumnWidth(2, 350)
425 l.setColumnWidth(3, 140)
426 l.setColumnWidth(4, 140)
427 l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
428 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
429 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
431 l.setContextMenuPolicy(Qt.CustomContextMenu)
432 l.customContextMenuRequested.connect(self.create_history_menu)
436 def create_history_menu(self, position):
437 self.history_list.selectedIndexes()
438 item = self.history_list.currentItem()
440 tx_hash = str(item.toolTip(0))
441 if not tx_hash: return
443 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
444 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
445 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
446 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
449 def tx_details(self, tx_hash):
450 tx_details = self.wallet.get_tx_details(tx_hash)
451 QMessageBox.information(self, 'Details', tx_details, 'OK')
454 def tx_label_clicked(self, item, column):
455 if column==2 and item.isSelected():
456 tx_hash = str(item.toolTip(0))
458 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
459 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
460 self.history_list.editItem( item, column )
461 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
464 def tx_label_changed(self, item, column):
468 tx_hash = str(item.toolTip(0))
469 tx = self.wallet.transactions.get(tx_hash)
470 s = self.wallet.labels.get(tx_hash)
471 text = unicode( item.text(2) )
473 self.wallet.labels[tx_hash] = text
474 item.setForeground(2, QBrush(QColor('black')))
476 if s: self.wallet.labels.pop(tx_hash)
477 text = self.wallet.get_default_label(tx_hash)
478 item.setText(2, text)
479 item.setForeground(2, QBrush(QColor('gray')))
483 def edit_label(self, is_recv):
484 l = self.receive_list if is_recv else self.contacts_list
485 c = 2 if is_recv else 1
486 item = l.currentItem()
487 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
488 l.editItem( item, c )
489 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
491 def edit_amount(self):
492 l = self.receive_list
493 item = l.currentItem()
494 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
495 l.editItem( item, 3 )
496 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
499 def address_label_clicked(self, item, column, l, column_addr, column_label):
500 if column == column_label and item.isSelected():
501 addr = unicode( item.text(column_addr) )
502 label = unicode( item.text(column_label) )
503 if label in self.wallet.aliases.keys():
505 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
506 l.editItem( item, column )
507 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
510 def address_label_changed(self, item, column, l, column_addr, column_label):
512 if column == column_label:
513 addr = unicode( item.text(column_addr) )
514 text = unicode( item.text(column_label) )
518 if text not in self.wallet.aliases.keys():
519 old_addr = self.wallet.labels.get(text)
521 self.wallet.labels[addr] = text
524 print_error("Error: This is one of your aliases")
525 label = self.wallet.labels.get(addr,'')
526 item.setText(column_label, QString(label))
528 s = self.wallet.labels.get(addr)
530 self.wallet.labels.pop(addr)
534 self.update_history_tab()
535 self.update_completions()
537 self.recv_changed(item)
540 address = unicode( item.text(column_addr) )
541 text = unicode( item.text(3) )
543 index = self.wallet.addresses.index(address)
548 amount = int( Decimal(text) * 100000000 )
549 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
551 amount = self.wallet.requested_amounts.get(address)
553 item.setText(3,format_satoshis(amount,False, self.wallet.num_zeros))
558 self.wallet.requested_amounts[address] = amount
560 label = self.wallet.labels.get(address)
562 label = self.merchant_name + ' - %04d'%(index+1)
563 self.wallet.labels[address] = label
565 self.update_receive_item(self.receive_list.currentItem())
567 self.qr_window.set_content( address, label, amount )
570 def recv_changed(self, a):
571 "current item changed"
572 if a is not None and self.qr_window and self.qr_window.isVisible():
573 address = str(a.text(1))
574 label = self.wallet.labels.get(address)
575 amount = self.wallet.requested_amounts.get(address)
576 self.qr_window.set_content( address, label, amount )
579 def update_history_tab(self):
581 self.history_list.clear()
582 for item in self.wallet.get_tx_history():
583 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
586 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
592 icon = QIcon(":icons/unconfirmed.png")
594 icon = QIcon(":icons/clock%d.png"%conf)
596 icon = QIcon(":icons/confirmed.png")
599 icon = QIcon(":icons/unconfirmed.png")
601 if value is not None:
602 v_str = format_satoshis(value, True, self.wallet.num_zeros)
606 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
609 label, is_default_label = self.wallet.get_label(tx_hash)
611 label = _('Pruned transaction outputs')
612 is_default_label = False
614 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
615 item.setFont(2, QFont(MONOSPACE_FONT))
616 item.setFont(3, QFont(MONOSPACE_FONT))
617 item.setFont(4, QFont(MONOSPACE_FONT))
619 item.setForeground(3, QBrush(QColor("#BC1E1E")))
621 item.setToolTip(0, tx_hash)
623 item.setForeground(2, QBrush(QColor('grey')))
625 item.setIcon(0, icon)
626 self.history_list.insertTopLevelItem(0,item)
629 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
632 def create_send_tab(self):
637 grid.setColumnMinimumWidth(3,300)
638 grid.setColumnStretch(5,1)
640 self.payto_e = QLineEdit()
641 grid.addWidget(QLabel(_('Pay to')), 1, 0)
642 grid.addWidget(self.payto_e, 1, 1, 1, 3)
645 qrcode = qrscanner.scan_qr()
646 if 'address' in qrcode:
647 self.payto_e.setText(qrcode['address'])
648 if 'amount' in qrcode:
649 self.amount_e.setText(str(qrcode['amount']))
650 if 'label' in qrcode:
651 self.message_e.setText(qrcode['label'])
652 if 'message' in qrcode:
653 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
656 if qrscanner.is_available():
657 b = QPushButton(_("Scan QR code"))
658 b.clicked.connect(fill_from_qr)
659 grid.addWidget(b, 1, 5)
661 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)
663 completer = QCompleter()
664 completer.setCaseSensitivity(False)
665 self.payto_e.setCompleter(completer)
666 completer.setModel(self.completions)
668 self.message_e = QLineEdit()
669 grid.addWidget(QLabel(_('Description')), 2, 0)
670 grid.addWidget(self.message_e, 2, 1, 1, 3)
671 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)
673 self.amount_e = QLineEdit()
674 grid.addWidget(QLabel(_('Amount')), 3, 0)
675 grid.addWidget(self.amount_e, 3, 1, 1, 2)
676 grid.addWidget(HelpButton(
677 _('Amount to be sent.') + '\n\n' \
678 + _('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)
680 self.fee_e = QLineEdit()
681 grid.addWidget(QLabel(_('Fee')), 4, 0)
682 grid.addWidget(self.fee_e, 4, 1, 1, 2)
683 grid.addWidget(HelpButton(
684 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
685 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
686 + _('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)
688 b = EnterButton(_("Send"), self.do_send)
689 grid.addWidget(b, 6, 1)
691 b = EnterButton(_("Clear"),self.do_clear)
692 grid.addWidget(b, 6, 2)
694 self.payto_sig = QLabel('')
695 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
697 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
698 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
707 def entry_changed( is_fee ):
708 self.funds_error = False
709 amount = numbify(self.amount_e)
710 fee = numbify(self.fee_e)
711 if not is_fee: fee = None
714 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
716 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
719 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
722 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
723 self.funds_error = True
724 self.amount_e.setPalette(palette)
725 self.fee_e.setPalette(palette)
728 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
729 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
734 def update_completions(self):
736 for addr,label in self.wallet.labels.items():
737 if addr in self.wallet.addressbook:
738 l.append( label + ' <' + addr + '>')
739 l = l + self.wallet.aliases.keys()
741 self.completions.setStringList(l)
747 label = unicode( self.message_e.text() )
748 r = unicode( self.payto_e.text() )
752 m1 = re.match(ALIAS_REGEXP, r)
753 # label or alias, with address in brackets
754 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
757 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
761 to_address = m2.group(2)
765 if not self.wallet.is_valid(to_address):
766 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
770 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
772 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
775 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
777 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
780 if self.wallet.use_encryption:
781 password = self.password_dialog()
788 tx = self.wallet.mktx( [(to_address, amount)], label, password, fee)
789 except BaseException, e:
790 self.show_message(str(e))
794 h = self.wallet.send_tx(tx)
795 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
796 status, msg = self.wallet.receive_tx( h )
798 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
800 self.update_contacts_tab()
802 QMessageBox.warning(self, _('Error'), msg, _('OK'))
804 filename = 'unsigned_tx'
805 f = open(filename,'w')
808 QMessageBox.information(self, _('Unsigned transaction'), _("Unsigned transaction was saved to file:") + " " +filename, _('OK'))
811 def set_url(self, url):
812 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
813 self.tabs.setCurrentIndex(1)
814 label = self.wallet.labels.get(payto)
815 m_addr = label + ' <'+ payto+'>' if label else payto
816 self.payto_e.setText(m_addr)
818 self.message_e.setText(message)
819 self.amount_e.setText(amount)
821 self.set_frozen(self.payto_e,True)
822 self.set_frozen(self.amount_e,True)
823 self.set_frozen(self.message_e,True)
824 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
826 self.payto_sig.setVisible(False)
829 self.payto_sig.setVisible(False)
830 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
832 self.set_frozen(e,False)
834 def set_frozen(self,entry,frozen):
836 entry.setReadOnly(True)
837 entry.setFrame(False)
839 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
840 entry.setPalette(palette)
842 entry.setReadOnly(False)
845 palette.setColor(entry.backgroundRole(), QColor('white'))
846 entry.setPalette(palette)
849 def toggle_freeze(self,addr):
851 if addr in self.wallet.frozen_addresses:
852 self.wallet.unfreeze(addr)
854 self.wallet.freeze(addr)
855 self.update_receive_tab()
857 def toggle_priority(self,addr):
859 if addr in self.wallet.prioritized_addresses:
860 self.wallet.unprioritize(addr)
862 self.wallet.prioritize(addr)
863 self.update_receive_tab()
866 def create_list_tab(self, headers):
867 "generic tab creation method"
868 l = MyTreeWidget(self)
869 l.setColumnCount( len(headers) )
870 l.setHeaderLabels( headers )
880 vbox.addWidget(buttons)
885 buttons.setLayout(hbox)
890 def create_receive_tab(self):
891 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
892 l.setContextMenuPolicy(Qt.CustomContextMenu)
893 l.customContextMenuRequested.connect(self.create_receive_menu)
894 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
895 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
896 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
897 self.receive_list = l
898 self.receive_buttons_hbox = hbox
904 def receive_tab_set_mode(self, i):
905 self.receive_tab_mode = i
906 self.config.set_key('qt_receive_tab_mode', self.receive_tab_mode, True)
908 self.update_receive_tab()
909 self.toggle_QR_window(self.receive_tab_mode == 2)
912 def create_contacts_tab(self):
913 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
914 l.setContextMenuPolicy(Qt.CustomContextMenu)
915 l.customContextMenuRequested.connect(self.create_contact_menu)
916 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
917 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
918 self.contacts_list = l
919 self.contacts_buttons_hbox = hbox
920 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
925 def create_receive_menu(self, position):
926 # fixme: this function apparently has a side effect.
927 # if it is not called the menu pops up several times
928 #self.receive_list.selectedIndexes()
930 item = self.receive_list.itemAt(position)
932 addr = unicode(item.text(1))
934 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
935 if self.receive_tab_mode == 2:
936 menu.addAction(_("Request amount"), lambda: self.edit_amount())
937 menu.addAction(_("View QR"), lambda: ElectrumWindow.show_qrcode("Address","bitcoin:"+addr) )
938 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
939 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
941 if self.receive_tab_mode == 1:
942 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
943 menu.addAction(t, lambda: self.toggle_freeze(addr))
944 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
945 menu.addAction(t, lambda: self.toggle_priority(addr))
947 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
950 def payto(self, x, is_alias):
957 label = self.wallet.labels.get(addr)
958 m_addr = label + ' <' + addr + '>' if label else addr
959 self.tabs.setCurrentIndex(1)
960 self.payto_e.setText(m_addr)
961 self.amount_e.setFocus()
963 def delete_contact(self, x, is_alias):
964 if self.question("Do you want to remove %s from your list of contacts?"%x):
965 if not is_alias and x in self.wallet.addressbook:
966 self.wallet.addressbook.remove(x)
967 if x in self.wallet.labels.keys():
968 self.wallet.labels.pop(x)
969 elif is_alias and x in self.wallet.aliases:
970 self.wallet.aliases.pop(x)
971 self.update_history_tab()
972 self.update_contacts_tab()
973 self.update_completions()
975 def create_contact_menu(self, position):
976 # fixme: this function apparently has a side effect.
977 # if it is not called the menu pops up several times
978 #self.contacts_list.selectedIndexes()
980 item = self.contacts_list.itemAt(position)
982 addr = unicode(item.text(0))
983 label = unicode(item.text(1))
984 is_alias = label in self.wallet.aliases.keys()
985 x = label if is_alias else addr
987 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
988 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
989 menu.addAction(_("View QR code"),lambda: self.show_qrcode("Address","bitcoin:"+addr))
991 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
993 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
994 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
995 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
998 def update_receive_item(self, item):
999 address = str( item.data(1,0).toString() )
1001 flags = self.wallet.get_address_flags(address)
1002 item.setData(0,0,flags)
1004 label = self.wallet.labels.get(address,'')
1005 item.setData(2,0,label)
1007 amount = self.wallet.requested_amounts.get(address,None)
1008 amount_str = format_satoshis( amount, False, self.wallet.num_zeros ) if amount is not None else ""
1009 item.setData(3,0,amount_str)
1011 c, u = self.wallet.get_addr_balance(address)
1012 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1013 item.setData(4,0,balance)
1015 if self.receive_tab_mode == 1:
1016 if address in self.wallet.frozen_addresses:
1017 item.setBackgroundColor(1, QColor('lightblue'))
1018 elif address in self.wallet.prioritized_addresses:
1019 item.setBackgroundColor(1, QColor('lightgreen'))
1022 def update_receive_tab(self):
1023 l = self.receive_list
1026 l.setColumnHidden(0, not self.receive_tab_mode == 1)
1027 l.setColumnHidden(3, not self.receive_tab_mode == 2)
1028 l.setColumnHidden(4, self.receive_tab_mode == 0)
1029 l.setColumnHidden(5, not self.receive_tab_mode == 1)
1030 l.setColumnWidth(0, 50)
1031 l.setColumnWidth(1, 310)
1032 l.setColumnWidth(2, 200)
1033 l.setColumnWidth(3, 130)
1034 l.setColumnWidth(4, 130)
1035 l.setColumnWidth(5, 10)
1039 for address in self.wallet.all_addresses():
1041 if self.wallet.is_change(address) and self.receive_tab_mode != 1:
1045 h = self.wallet.history.get(address,[])
1048 for tx_hash, tx_height in h:
1049 tx = self.wallet.transactions.get(tx_hash)
1057 if address in self.wallet.addresses:
1059 if gap > self.wallet.gap_limit:
1062 if address in self.wallet.addresses:
1065 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1066 item.setFont(0, QFont(MONOSPACE_FONT))
1067 item.setFont(1, QFont(MONOSPACE_FONT))
1068 item.setFont(3, QFont(MONOSPACE_FONT))
1069 self.update_receive_item(item)
1070 if is_red and address in self.wallet.addresses:
1071 item.setBackgroundColor(1, QColor('red'))
1072 l.addTopLevelItem(item)
1074 # we use column 1 because column 0 may be hidden
1075 l.setCurrentItem(l.topLevelItem(0),1)
1077 def show_contact_details(self, m):
1078 a = self.wallet.aliases.get(m)
1080 if a[0] in self.wallet.authorities.keys():
1081 s = self.wallet.authorities.get(a[0])
1084 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1085 QMessageBox.information(self, 'Alias', msg, 'OK')
1087 def update_contacts_tab(self):
1089 l = self.contacts_list
1091 l.setColumnWidth(0, 350)
1092 l.setColumnWidth(1, 330)
1093 l.setColumnWidth(2, 100)
1096 for alias, v in self.wallet.aliases.items():
1098 alias_targets.append(target)
1099 item = QTreeWidgetItem( [ target, alias, '-'] )
1100 item.setBackgroundColor(0, QColor('lightgray'))
1101 l.addTopLevelItem(item)
1103 for address in self.wallet.addressbook:
1104 if address in alias_targets: continue
1105 label = self.wallet.labels.get(address,'')
1107 for item in self.wallet.transactions.values():
1108 if address in item['outputs'] : n=n+1
1110 item = QTreeWidgetItem( [ address, label, tx] )
1111 item.setFont(0, QFont(MONOSPACE_FONT))
1112 l.addTopLevelItem(item)
1114 l.setCurrentItem(l.topLevelItem(0))
1116 def create_wall_tab(self):
1117 self.textbox = textbox = QTextEdit(self)
1118 textbox.setFont(QFont(MONOSPACE_FONT))
1119 textbox.setReadOnly(True)
1122 def create_status_bar(self):
1124 sb.setFixedHeight(35)
1125 qtVersion = qVersion()
1126 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1127 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), "Switch to Lite Mode", self.go_lite ) )
1128 if self.wallet.seed:
1129 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1130 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1131 if self.wallet.seed:
1132 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1133 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
1134 sb.addPermanentWidget( self.status_button )
1135 self.setStatusBar(sb)
1141 self.lite.mini.show()
1143 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1144 self.lite.main(None)
1146 def new_contact_dialog(self):
1147 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1148 address = unicode(text)
1150 if self.wallet.is_valid(address):
1151 self.wallet.addressbook.append(address)
1153 self.update_contacts_tab()
1154 self.update_history_tab()
1155 self.update_completions()
1157 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1160 def show_seed_dialog(wallet, parent=None):
1162 QMessageBox.information(parent, _('Message'),
1163 _('No seed'), _('OK'))
1166 if wallet.use_encryption:
1167 password = parent.password_dialog()
1174 seed = wallet.pw_decode(wallet.seed, password)
1176 QMessageBox.warning(parent, _('Error'),
1177 _('Incorrect Password'), _('OK'))
1180 dialog = QDialog(None)
1182 dialog.setWindowTitle("Electrum")
1184 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1186 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1187 + _("Please write down or memorize these 12 words (order is important).") + " " \
1188 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1189 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1191 main_text = QLabel(msg)
1192 main_text.setWordWrap(True)
1195 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1202 copy_function = lambda: app.clipboard().setText(brainwallet)
1203 copy_button = QPushButton(_("Copy to Clipboard"))
1204 copy_button.clicked.connect(copy_function)
1206 show_qr_function = lambda: ElectrumWindow.show_qrcode(_("Seed"), seed)
1207 qr_button = QPushButton(_("View as QR Code"))
1208 qr_button.clicked.connect(show_qr_function)
1210 ok_button = QPushButton(_("OK"))
1211 ok_button.setDefault(True)
1212 ok_button.clicked.connect(dialog.accept)
1214 main_layout = QGridLayout()
1215 main_layout.addWidget(logo, 0, 0)
1216 main_layout.addWidget(main_text, 0, 1, 1, -1)
1217 main_layout.addWidget(copy_button, 1, 1)
1218 main_layout.addWidget(qr_button, 1, 2)
1219 main_layout.addWidget(ok_button, 1, 3)
1220 dialog.setLayout(main_layout)
1225 def show_qrcode(title, data):
1229 d.setWindowTitle(title)
1230 d.setMinimumSize(270, 300)
1231 vbox = QVBoxLayout()
1232 qrw = QRCodeWidget(data)
1233 vbox.addWidget(qrw, 1)
1234 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1235 hbox = QHBoxLayout()
1239 filename = "qrcode.bmp"
1240 bmp.save_qrcode(qrw.qr, filename)
1241 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1243 b = QPushButton(_("Print"))
1245 b.clicked.connect(print_qr)
1247 b = QPushButton(_("Close"))
1249 b.clicked.connect(d.accept)
1251 vbox.addLayout(hbox)
1255 def sign_message(self,address):
1256 if not address: return
1259 d.setWindowTitle('Sign Message')
1260 d.setMinimumSize(270, 350)
1262 tab_widget = QTabWidget()
1264 layout = QGridLayout(tab)
1266 sign_address = QLineEdit()
1267 sign_address.setText(address)
1268 layout.addWidget(QLabel(_('Address')), 1, 0)
1269 layout.addWidget(sign_address, 1, 1)
1271 sign_message = QTextEdit()
1272 layout.addWidget(QLabel(_('Message')), 2, 0)
1273 layout.addWidget(sign_message, 2, 1, 2, 1)
1275 sign_signature = QLineEdit()
1276 layout.addWidget(QLabel(_('Signature')), 3, 0)
1277 layout.addWidget(sign_signature, 3, 1)
1280 if self.wallet.use_encryption:
1281 password = self.password_dialog()
1288 signature = self.wallet.sign_message(sign_address.text(), str(sign_message.toPlainText()), password)
1289 sign_signature.setText(signature)
1290 except BaseException, e:
1291 self.show_message(str(e))
1294 hbox = QHBoxLayout()
1295 b = QPushButton(_("Sign"))
1297 b.clicked.connect(do_sign)
1298 b = QPushButton(_("Close"))
1299 b.clicked.connect(d.accept)
1301 layout.addLayout(hbox, 4, 1)
1302 tab_widget.addTab(tab, "Sign")
1306 layout = QGridLayout(tab)
1308 verify_address = QLineEdit()
1309 layout.addWidget(QLabel(_('Address')), 1, 0)
1310 layout.addWidget(verify_address, 1, 1)
1312 verify_message = QTextEdit()
1313 layout.addWidget(QLabel(_('Message')), 2, 0)
1314 layout.addWidget(verify_message, 2, 1, 2, 1)
1316 verify_signature = QLineEdit()
1317 layout.addWidget(QLabel(_('Signature')), 3, 0)
1318 layout.addWidget(verify_signature, 3, 1)
1322 self.wallet.verify_message(verify_address.text(), verify_signature.text(), str(verify_message.toPlainText()))
1323 self.show_message("Signature verified")
1324 except BaseException, e:
1325 self.show_message(str(e))
1328 hbox = QHBoxLayout()
1329 b = QPushButton(_("Verify"))
1330 b.clicked.connect(do_verify)
1332 b = QPushButton(_("Close"))
1333 b.clicked.connect(d.accept)
1335 layout.addLayout(hbox, 4, 1)
1336 tab_widget.addTab(tab, "Verify")
1338 vbox = QVBoxLayout()
1339 vbox.addWidget(tab_widget)
1344 def toggle_QR_window(self, show):
1345 if show and not self.qr_window:
1346 self.qr_window = QR_Window()
1347 self.qr_window.setVisible(True)
1348 self.qr_window_geometry = self.qr_window.geometry()
1349 item = self.receive_list.currentItem()
1351 address = str(item.text(1))
1352 label = self.wallet.labels.get(address)
1353 amount = self.wallet.requested_amounts.get(address)
1354 self.qr_window.set_content( address, label, amount )
1356 elif show and self.qr_window and not self.qr_window.isVisible():
1357 self.qr_window.setVisible(True)
1358 self.qr_window.setGeometry(self.qr_window_geometry)
1360 elif not show and self.qr_window and self.qr_window.isVisible():
1361 self.qr_window_geometry = self.qr_window.geometry()
1362 self.qr_window.setVisible(False)
1364 #self.print_button.setHidden(self.qr_window is None or not self.qr_window.isVisible())
1365 self.receive_list.setColumnHidden(3, self.qr_window is None or not self.qr_window.isVisible())
1366 self.receive_list.setColumnWidth(2, 200)
1369 def question(self, msg):
1370 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1372 def show_message(self, msg):
1373 QMessageBox.information(self, _('Message'), msg, _('OK'))
1375 def password_dialog(self ):
1382 vbox = QVBoxLayout()
1383 msg = _('Please enter your password')
1384 vbox.addWidget(QLabel(msg))
1386 grid = QGridLayout()
1388 grid.addWidget(QLabel(_('Password')), 1, 0)
1389 grid.addWidget(pw, 1, 1)
1390 vbox.addLayout(grid)
1392 vbox.addLayout(ok_cancel_buttons(d))
1395 if not d.exec_(): return
1396 return unicode(pw.text())
1403 def change_password_dialog( wallet, parent=None ):
1406 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1414 new_pw = QLineEdit()
1415 new_pw.setEchoMode(2)
1416 conf_pw = QLineEdit()
1417 conf_pw.setEchoMode(2)
1419 vbox = QVBoxLayout()
1421 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1422 +_('To disable wallet encryption, enter an empty new password.')) \
1423 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1425 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1426 +_("Leave these fields empty if you want to disable encryption.")
1427 vbox.addWidget(QLabel(msg))
1429 grid = QGridLayout()
1432 if wallet.use_encryption:
1433 grid.addWidget(QLabel(_('Password')), 1, 0)
1434 grid.addWidget(pw, 1, 1)
1436 grid.addWidget(QLabel(_('New Password')), 2, 0)
1437 grid.addWidget(new_pw, 2, 1)
1439 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1440 grid.addWidget(conf_pw, 3, 1)
1441 vbox.addLayout(grid)
1443 vbox.addLayout(ok_cancel_buttons(d))
1446 if not d.exec_(): return
1448 password = unicode(pw.text()) if wallet.use_encryption else None
1449 new_password = unicode(new_pw.text())
1450 new_password2 = unicode(conf_pw.text())
1453 seed = wallet.pw_decode( wallet.seed, password)
1455 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1458 if new_password != new_password2:
1459 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1460 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1462 wallet.update_password(seed, password, new_password)
1465 def seed_dialog(wallet, parent=None):
1469 vbox = QVBoxLayout()
1470 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1471 vbox.addWidget(QLabel(msg))
1473 grid = QGridLayout()
1476 seed_e = QLineEdit()
1477 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1478 grid.addWidget(seed_e, 1, 1)
1482 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1483 grid.addWidget(gap_e, 2, 1)
1484 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1485 vbox.addLayout(grid)
1487 vbox.addLayout(ok_cancel_buttons(d))
1490 if not d.exec_(): return
1493 gap = int(unicode(gap_e.text()))
1495 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1499 seed = unicode(seed_e.text())
1502 print_error("Warning: Not hex, trying decode")
1504 seed = mnemonic.mn_decode( seed.split(' ') )
1506 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1509 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1512 wallet.seed = str(seed)
1513 #print repr(wallet.seed)
1514 wallet.gap_limit = gap
1519 def settings_dialog(self):
1521 d.setWindowTitle(_('Electrum Settings'))
1523 vbox = QVBoxLayout()
1525 tabs = QTabWidget(self)
1526 vbox.addWidget(tabs)
1529 grid_ui = QGridLayout(tab2)
1530 grid_ui.setColumnStretch(0,1)
1531 tabs.addTab(tab2, _('Display') )
1534 grid_wallet = QGridLayout(tab)
1535 grid_wallet.setColumnStretch(0,1)
1536 tabs.addTab(tab, _('Wallet') )
1538 fee_label = QLabel(_('Transaction fee'))
1539 grid_wallet.addWidget(fee_label, 2, 0)
1541 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1542 grid_wallet.addWidget(fee_e, 2, 1)
1543 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1544 + _('Recommended value') + ': 0.001'
1545 grid_wallet.addWidget(HelpButton(msg), 2, 2)
1546 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1547 if not self.config.is_modifiable('fee'):
1548 for w in [fee_e, fee_label]: w.setEnabled(False)
1550 nz_label = QLabel(_('Display zeros'))
1551 grid_ui.addWidget(nz_label, 3, 0)
1553 nz_e.setText("%d"% self.wallet.num_zeros)
1554 grid_ui.addWidget(nz_e, 3, 1)
1555 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1556 grid_ui.addWidget(HelpButton(msg), 3, 2)
1557 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1558 if not self.config.is_modifiable('num_zeros'):
1559 for w in [nz_e, nz_label]: w.setEnabled(False)
1562 usechange_label = QLabel(_('Use change addresses'))
1563 grid_wallet.addWidget(usechange_label, 5, 0)
1564 usechange_combo = QComboBox()
1565 usechange_combo.addItems(['Yes', 'No'])
1566 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1567 grid_wallet.addWidget(usechange_combo, 5, 1)
1568 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1569 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1571 gap_label = QLabel(_('Gap limit'))
1572 grid_wallet.addWidget(gap_label, 6, 0)
1574 gap_e.setText("%d"% self.wallet.gap_limit)
1575 grid_wallet.addWidget(gap_e, 6, 1)
1576 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1577 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1578 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1579 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1580 + _('Warning') + ': ' \
1581 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1582 + _('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'
1583 grid_wallet.addWidget(HelpButton(msg), 6, 2)
1584 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1585 if not self.config.is_modifiable('gap_limit'):
1586 for w in [gap_e, gap_label]: w.setEnabled(False)
1588 gui_label=QLabel(_('Default GUI') + ':')
1589 grid_ui.addWidget(gui_label , 7, 0)
1590 gui_combo = QComboBox()
1591 gui_combo.addItems(['Lite', 'Classic'])
1592 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1593 if index==-1: index = 1
1594 gui_combo.setCurrentIndex(index)
1595 grid_ui.addWidget(gui_combo, 7, 1)
1596 grid_ui.addWidget(HelpButton(_('Select which GUI mode to use at start up.'+'\n'+'Note: use the command line to access the "text" and "gtk" GUIs')), 7, 2)
1597 if not self.config.is_modifiable('gui'):
1598 for w in [gui_combo, gui_label]: w.setEnabled(False)
1600 lang_label=QLabel(_('Language') + ':')
1601 grid_ui.addWidget(lang_label , 8, 0)
1602 lang_combo = QComboBox()
1603 from i18n import languages
1604 lang_combo.addItems(languages.values())
1606 index = languages.keys().index(self.config.get("language",''))
1609 lang_combo.setCurrentIndex(index)
1610 grid_ui.addWidget(lang_combo, 8, 1)
1611 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart). ')), 8, 2)
1612 if not self.config.is_modifiable('language'):
1613 for w in [lang_combo, lang_label]: w.setEnabled(False)
1615 currencies = self.exchanger.get_currencies()
1616 currencies.insert(0, "None")
1618 cur_label=QLabel(_('Currency') + ':')
1619 grid_ui.addWidget(cur_label , 9, 0)
1620 cur_combo = QComboBox()
1621 cur_combo.addItems(currencies)
1623 index = currencies.index(self.config.get('currency', "None"))
1626 cur_combo.setCurrentIndex(index)
1627 grid_ui.addWidget(cur_combo, 9, 1)
1628 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes. ')), 9, 2)
1630 view_label=QLabel(_('Receive Tab') + ':')
1631 grid_ui.addWidget(view_label , 10, 0)
1632 view_combo = QComboBox()
1633 view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
1634 view_combo.setCurrentIndex(self.receive_tab_mode)
1635 grid_ui.addWidget(view_combo, 10, 1)
1636 hh = _('This selects the interaction mode of the "Receive" tab. ') + '\n\n' \
1637 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1638 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
1639 + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n'
1641 grid_ui.addWidget(HelpButton(hh), 10, 2)
1643 vbox.addLayout(ok_cancel_buttons(d))
1647 if not d.exec_(): return
1649 fee = unicode(fee_e.text())
1651 fee = int( 100000000 * Decimal(fee) )
1653 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1656 if self.wallet.fee != fee:
1657 self.wallet.fee = fee
1660 nz = unicode(nz_e.text())
1665 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1668 if self.wallet.num_zeros != nz:
1669 self.wallet.num_zeros = nz
1670 self.config.set_key('num_zeros', nz, True)
1671 self.update_history_tab()
1672 self.update_receive_tab()
1674 usechange_result = usechange_combo.currentIndex() == 0
1675 if self.wallet.use_change != usechange_result:
1676 self.wallet.use_change = usechange_result
1677 self.config.set_key('use_change', self.wallet.use_change, True)
1680 n = int(gap_e.text())
1682 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1685 if self.wallet.gap_limit != n:
1686 r = self.wallet.change_gap_limit(n)
1688 self.update_receive_tab()
1689 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1691 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1693 need_restart = False
1695 gui_request = str(gui_combo.currentText()).lower()
1696 if gui_request != self.config.get('gui'):
1697 self.config.set_key('gui', gui_request, True)
1700 lang_request = languages.keys()[lang_combo.currentIndex()]
1701 if lang_request != self.config.get('language'):
1702 self.config.set_key("language", lang_request, True)
1705 cur_request = str(currencies[cur_combo.currentIndex()])
1706 if cur_request != self.config.get('currency', "None"):
1707 self.config.set_key('currency', cur_request, True)
1708 self.update_wallet()
1711 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
1713 self.receive_tab_set_mode(view_combo.currentIndex())
1717 def network_dialog(wallet, parent=None):
1718 interface = wallet.interface
1720 if interface.is_connected:
1721 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1723 status = _("Not connected")
1724 server = interface.server
1727 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1728 server = interface.server
1730 plist, servers_list = interface.get_servers_list()
1734 d.setWindowTitle(_('Server'))
1735 d.setMinimumSize(375, 20)
1737 vbox = QVBoxLayout()
1740 hbox = QHBoxLayout()
1742 l.setPixmap(QPixmap(":icons/network.png"))
1745 hbox.addWidget(QLabel(status))
1747 vbox.addLayout(hbox)
1751 grid = QGridLayout()
1753 vbox.addLayout(grid)
1756 server_protocol = QComboBox()
1757 server_host = QLineEdit()
1758 server_host.setFixedWidth(200)
1759 server_port = QLineEdit()
1760 server_port.setFixedWidth(60)
1762 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1763 protocol_letters = 'thsg'
1764 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1765 server_protocol.addItems(protocol_names)
1767 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1768 grid.addWidget(server_protocol, 0, 1)
1769 grid.addWidget(server_host, 0, 2)
1770 grid.addWidget(server_port, 0, 3)
1772 def change_protocol(p):
1773 protocol = protocol_letters[p]
1774 host = unicode(server_host.text())
1775 pp = plist.get(host,DEFAULT_PORTS)
1776 if protocol not in pp.keys():
1777 protocol = pp.keys()[0]
1779 server_host.setText( host )
1780 server_port.setText( port )
1782 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1784 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1785 servers_list_widget = QTreeWidget(parent)
1786 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1787 servers_list_widget.setMaximumHeight(150)
1788 servers_list_widget.setColumnWidth(0, 240)
1789 for _host in servers_list.keys():
1790 _type = 'P' if servers_list[_host].get('pruning') else 'F'
1791 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1793 def change_server(host, protocol=None):
1794 pp = plist.get(host,DEFAULT_PORTS)
1796 port = pp.get(protocol)
1797 if not port: protocol = None
1800 if 't' in pp.keys():
1802 port = pp.get(protocol)
1804 protocol = pp.keys()[0]
1805 port = pp.get(protocol)
1807 server_host.setText( host )
1808 server_port.setText( port )
1809 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1811 if not plist: return
1812 for p in protocol_letters:
1813 i = protocol_letters.index(p)
1814 j = server_protocol.model().index(i,0)
1815 if p not in pp.keys():
1816 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1818 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1822 host, port, protocol = server.split(':')
1823 change_server(host,protocol)
1825 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1826 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1828 if not wallet.config.is_modifiable('server'):
1829 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1832 autocycle_cb = QCheckBox('Try random servers if disconnected')
1833 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
1834 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
1835 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
1838 proxy_mode = QComboBox()
1839 proxy_host = QLineEdit()
1840 proxy_host.setFixedWidth(200)
1841 proxy_port = QLineEdit()
1842 proxy_port.setFixedWidth(60)
1843 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1845 def check_for_disable(index = False):
1846 if proxy_mode.currentText() != 'NONE':
1847 proxy_host.setEnabled(True)
1848 proxy_port.setEnabled(True)
1850 proxy_host.setEnabled(False)
1851 proxy_port.setEnabled(False)
1854 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1856 if not wallet.config.is_modifiable('proxy'):
1857 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1859 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1860 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1861 proxy_host.setText(proxy_config.get("host"))
1862 proxy_port.setText(proxy_config.get("port"))
1864 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1865 grid.addWidget(proxy_mode, 2, 1)
1866 grid.addWidget(proxy_host, 2, 2)
1867 grid.addWidget(proxy_port, 2, 3)
1870 vbox.addLayout(ok_cancel_buttons(d))
1873 if not d.exec_(): return
1875 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1876 if proxy_mode.currentText() != 'NONE':
1877 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1881 wallet.config.set_key("proxy", proxy, True)
1882 wallet.config.set_key("server", server, True)
1883 interface.set_server(server, proxy)
1884 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
1887 def closeEvent(self, event):
1889 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1895 def __init__(self, wallet, config, app=None):
1896 self.wallet = wallet
1897 self.config = config
1899 self.app = QApplication(sys.argv)
1902 def restore_or_create(self):
1903 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1904 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1905 if r==2: return None
1906 return 'restore' if r==1 else 'create'
1908 def seed_dialog(self):
1909 return ElectrumWindow.seed_dialog( self.wallet )
1911 def network_dialog(self):
1912 return ElectrumWindow.network_dialog( self.wallet, parent=None )
1915 def show_seed(self):
1916 ElectrumWindow.show_seed_dialog(self.wallet)
1919 def password_dialog(self):
1920 ElectrumWindow.change_password_dialog(self.wallet)
1923 def restore_wallet(self):
1924 wallet = self.wallet
1925 # wait until we are connected, because the user might have selected another server
1926 if not wallet.interface.is_connected:
1927 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1928 waiting_dialog(waiting)
1930 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1931 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1933 wallet.set_up_to_date(False)
1934 wallet.interface.poke('synchronizer')
1935 waiting_dialog(waiting)
1936 if wallet.is_found():
1937 print_error( "Recovery successful" )
1939 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1946 w = ElectrumWindow(self.wallet, self.config)
1947 if url: w.set_url(url)