3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re
21 from util import print_error
26 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
28 from PyQt4.QtGui import *
29 from PyQt4.QtCore import *
30 import PyQt4.QtCore as QtCore
31 import PyQt4.QtGui as QtGui
32 from interface import DEFAULT_SERVERS
37 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o lib/icons_rc.py'")
39 from wallet import format_satoshis
40 import bmp, mnemonic, pyqrnative, qrscanner
42 from decimal import Decimal
46 if platform.system() == 'Windows':
47 MONOSPACE_FONT = 'Lucida Console'
48 elif platform.system() == 'Darwin':
49 MONOSPACE_FONT = 'Monaco'
51 MONOSPACE_FONT = 'monospace'
53 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
55 def numbify(entry, is_int = False):
56 text = unicode(entry.text()).strip()
57 pos = entry.cursorPosition()
59 if not is_int: chars +='.'
60 s = ''.join([i for i in text if i in chars])
65 s = s[:p] + '.' + s[p:p+8]
67 amount = int( Decimal(s) * 100000000 )
76 entry.setCursorPosition(pos)
80 class Timer(QtCore.QThread):
83 self.emit(QtCore.SIGNAL('timersignal'))
86 class HelpButton(QPushButton):
87 def __init__(self, text):
88 QPushButton.__init__(self, '?')
89 self.setFocusPolicy(Qt.NoFocus)
90 self.setFixedWidth(20)
91 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
94 class EnterButton(QPushButton):
95 def __init__(self, text, func):
96 QPushButton.__init__(self, text)
98 self.clicked.connect(func)
100 def keyPressEvent(self, e):
101 if e.key() == QtCore.Qt.Key_Return:
104 class MyTreeWidget(QTreeWidget):
105 def __init__(self, parent):
106 QTreeWidget.__init__(self, parent)
109 for i in range(0,self.viewport().height()/5):
110 if self.itemAt(QPoint(0,i*5)) == item:
114 for j in range(0,30):
115 if self.itemAt(QPoint(0,i*5 + j)) != item:
117 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
119 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
124 class StatusBarButton(QPushButton):
125 def __init__(self, icon, tooltip, func):
126 QPushButton.__init__(self, icon, '')
127 self.setToolTip(tooltip)
129 self.setMaximumWidth(25)
130 self.clicked.connect(func)
133 def keyPressEvent(self, e):
134 if e.key() == QtCore.Qt.Key_Return:
138 class QRCodeWidget(QWidget):
140 def __init__(self, addr):
141 super(QRCodeWidget, self).__init__()
142 self.setMinimumSize(250, 250)
145 def set_addr(self, addr):
147 self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
148 self.qr.addData(addr)
151 def paintEvent(self, e):
152 if not self.addr: return
154 qp = QtGui.QPainter()
157 size = self.qr.getModuleCount()*boxsize
158 k = self.qr.getModuleCount()
159 black = QColor(0, 0, 0, 255)
160 white = QColor(255, 255, 255, 255)
163 if self.qr.isDark(r, c):
169 qp.drawRect(c*boxsize, r*boxsize, boxsize, boxsize)
174 class QR_Window(QWidget):
177 QWidget.__init__(self)
178 self.setWindowTitle('Bitcoin Electrum')
179 self.setMinimumSize(800, 250)
183 self.setFocusPolicy(QtCore.Qt.NoFocus)
185 main_box = QHBoxLayout()
187 self.qrw = QRCodeWidget('')
188 main_box.addWidget(self.qrw)
191 main_box.addLayout(vbox)
193 main_box.addStretch(1)
195 self.address_label = QLabel("")
196 vbox.addWidget(self.address_label)
198 self.label_label = QLabel("")
199 vbox.addWidget(self.label_label)
201 self.amount_label = QLabel("")
202 vbox.addWidget(self.amount_label)
206 self.setLayout(main_box)
209 self.filename = "qrcode.bmp"
210 bmp.save_qrcode(self.qrw.qr, self.filename)
212 def set_content(self, addr, label, amount):
214 address_text = "<span style='font-size: 18pt'>%s</span>" % addr if addr else ""
215 self.address_label.setText(address_text)
218 amount_text = "<span style='font-size: 18pt'>%s</span> <span style='font-size: 10pt'>BTC</span> " % format_satoshis(amount) if amount else ""
219 self.amount_label.setText(amount_text)
222 label_text = "<span style='font-size: 18pt'>%s</span>" % label if label else ""
223 self.label_label.setText(label_text)
227 msg = 'bitcoin:'+self.address
228 if self.amount is not None:
229 msg += '?amount=%s'%(str( Decimal(self.amount) /100000000))
230 if self.label is not None:
231 msg += '&label=%s'%(self.label)
232 elif self.label is not None:
233 msg += '?label=%s'%(self.label)
235 self.qrw.set_addr( msg )
241 def waiting_dialog(f):
247 w.setWindowTitle('Electrum')
257 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
262 def ok_cancel_buttons(dialog):
265 b = QPushButton("OK")
267 b.clicked.connect(dialog.accept)
268 b = QPushButton("Cancel")
270 b.clicked.connect(dialog.reject)
274 class ElectrumWindow(QMainWindow):
276 def __init__(self, wallet, config):
277 QMainWindow.__init__(self)
280 self.wallet.interface.register_callback('updated', self.update_callback)
281 self.wallet.interface.register_callback('connected', self.update_callback)
282 self.wallet.interface.register_callback('disconnected', self.update_callback)
283 self.wallet.interface.register_callback('disconnecting', self.update_callback)
285 self.detailed_view = config.get('qt_detailed_view', False)
287 self.qr_window = None
288 self.funds_error = False
289 self.completions = QStringListModel()
291 self.tabs = tabs = QTabWidget(self)
292 tabs.addTab(self.create_history_tab(), _('History') )
294 tabs.addTab(self.create_send_tab(), _('Send') )
295 tabs.addTab(self.create_receive_tab(), _('Receive') )
296 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
297 tabs.addTab(self.create_wall_tab(), _('Wall') )
298 tabs.setMinimumSize(600, 400)
299 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
300 self.setCentralWidget(tabs)
301 self.create_status_bar()
303 g = self.config.get("winpos-qt",[100, 100, 840, 400])
304 self.setGeometry(g[0], g[1], g[2], g[3])
305 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
306 if not self.wallet.seed: title += ' [seedless]'
307 self.setWindowTitle( title )
309 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
310 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
311 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
312 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
314 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
315 #self.connect(self, SIGNAL('editamount'), self.edit_amount)
316 self.history_list.setFocus(True)
318 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
319 if platform.system() == 'Windows':
320 n = 3 if self.wallet.seed else 2
321 tabs.setCurrentIndex (n)
322 tabs.setCurrentIndex (0)
325 def connect_slots(self, sender):
327 self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient)
328 self.previous_payto_e=''
330 def check_recipient(self):
331 if self.payto_e.hasFocus():
333 r = unicode( self.payto_e.text() )
334 if r != self.previous_payto_e:
335 self.previous_payto_e = r
337 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
339 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
343 s = r + ' <' + to_address + '>'
344 self.payto_e.setText(s)
347 def update_callback(self):
348 self.emit(QtCore.SIGNAL('updatesignal'))
350 def update_wallet(self):
351 if self.wallet.interface and self.wallet.interface.is_connected:
352 if not self.wallet.up_to_date:
353 text = _( "Synchronizing..." )
354 icon = QIcon(":icons/status_waiting.png")
356 c, u = self.wallet.get_balance()
357 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
358 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
359 icon = QIcon(":icons/status_connected.png")
361 text = _( "Not connected" )
362 icon = QIcon(":icons/status_disconnected.png")
365 text = _( "Not enough funds" )
367 self.statusBar().showMessage(text)
368 self.status_button.setIcon( icon )
370 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
371 self.textbox.setText( self.wallet.banner )
372 self.update_history_tab()
373 self.update_receive_tab()
374 self.update_contacts_tab()
375 self.update_completions()
378 def create_history_tab(self):
379 self.history_list = l = MyTreeWidget(self)
381 l.setColumnWidth(0, 40)
382 l.setColumnWidth(1, 140)
383 l.setColumnWidth(2, 350)
384 l.setColumnWidth(3, 140)
385 l.setColumnWidth(4, 140)
386 l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
387 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
388 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
390 l.setContextMenuPolicy(Qt.CustomContextMenu)
391 l.customContextMenuRequested.connect(self.create_history_menu)
395 def create_history_menu(self, position):
396 self.history_list.selectedIndexes()
397 item = self.history_list.currentItem()
399 tx_hash = str(item.toolTip(0))
400 if not tx_hash: return
402 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
403 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
404 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
405 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
408 def tx_details(self, tx_hash):
409 tx_details = self.wallet.get_tx_details(tx_hash)
410 QMessageBox.information(self, 'Details', tx_details, 'OK')
413 def tx_label_clicked(self, item, column):
414 if column==2 and item.isSelected():
415 tx_hash = str(item.toolTip(0))
417 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
418 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
419 self.history_list.editItem( item, column )
420 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
423 def tx_label_changed(self, item, column):
427 tx_hash = str(item.toolTip(0))
428 tx = self.wallet.transactions.get(tx_hash)
429 s = self.wallet.labels.get(tx_hash)
430 text = unicode( item.text(2) )
432 self.wallet.labels[tx_hash] = text
433 item.setForeground(2, QBrush(QColor('black')))
435 if s: self.wallet.labels.pop(tx_hash)
436 text = self.wallet.get_default_label(tx_hash)
437 item.setText(2, text)
438 item.setForeground(2, QBrush(QColor('gray')))
442 def edit_label(self, is_recv):
443 l = self.receive_list if is_recv else self.contacts_list
444 c = 2 if is_recv else 1
445 item = l.currentItem()
446 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
447 l.editItem( item, c )
448 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
452 def address_label_clicked(self, item, column, l, column_addr, column_label):
453 if column == column_label and item.isSelected():
454 addr = unicode( item.text(column_addr) )
455 label = unicode( item.text(column_label) )
456 if label in self.wallet.aliases.keys():
458 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
459 l.editItem( item, column )
460 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
463 def address_label_changed(self, item, column, l, column_addr, column_label):
465 if column == column_label:
466 addr = unicode( item.text(column_addr) )
467 text = unicode( item.text(column_label) )
471 if text not in self.wallet.aliases.keys():
472 old_addr = self.wallet.labels.get(text)
474 self.wallet.labels[addr] = text
477 print_error("Error: This is one of your aliases")
478 label = self.wallet.labels.get(addr,'')
479 item.setText(column_label, QString(label))
481 s = self.wallet.labels.get(addr)
483 self.wallet.labels.pop(addr)
487 self.update_history_tab()
488 self.update_completions()
490 self.recv_changed(item)
493 def recv_changed(self, a):
494 "current item changed"
495 if a is not None and self.qr_window:
496 address = str(a.text(1))
497 label = self.wallet.labels.get(address)
498 amount = self.wallet.requested_amounts.get(address)
499 self.qr_window.set_content( address, label, amount )
502 def update_history_tab(self):
504 self.history_list.clear()
505 for item in self.wallet.get_tx_history():
506 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
509 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
515 icon = QIcon(":icons/unconfirmed.png")
517 icon = QIcon(":icons/clock%d.png"%conf)
519 icon = QIcon(":icons/confirmed.png")
522 icon = QIcon(":icons/unconfirmed.png")
524 if value is not None:
525 v_str = format_satoshis(value, True, self.wallet.num_zeros)
529 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
532 label, is_default_label = self.wallet.get_label(tx_hash)
534 label = _('Pruned transaction outputs')
535 is_default_label = False
537 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
538 item.setFont(2, QFont(MONOSPACE_FONT))
539 item.setFont(3, QFont(MONOSPACE_FONT))
540 item.setFont(4, QFont(MONOSPACE_FONT))
542 item.setToolTip(0, tx_hash)
544 item.setForeground(2, QBrush(QColor('grey')))
546 item.setIcon(0, icon)
547 self.history_list.insertTopLevelItem(0,item)
550 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
553 def create_send_tab(self):
558 grid.setColumnMinimumWidth(3,300)
559 grid.setColumnStretch(5,1)
561 self.payto_e = QLineEdit()
562 grid.addWidget(QLabel(_('Pay to')), 1, 0)
563 grid.addWidget(self.payto_e, 1, 1, 1, 3)
566 qrcode = qrscanner.scan_qr()
567 if 'address' in qrcode:
568 self.payto_e.setText(qrcode['address'])
569 if 'amount' in qrcode:
570 self.amount_e.setText(str(qrcode['amount']))
571 if 'label' in qrcode:
572 self.message_e.setText(qrcode['label'])
573 if 'message' in qrcode:
574 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
577 if qrscanner.is_available():
578 b = QPushButton(_("Scan QR code"))
579 b.clicked.connect(fill_from_qr)
580 grid.addWidget(b, 1, 5)
582 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)
584 completer = QCompleter()
585 completer.setCaseSensitivity(False)
586 self.payto_e.setCompleter(completer)
587 completer.setModel(self.completions)
589 self.message_e = QLineEdit()
590 grid.addWidget(QLabel(_('Description')), 2, 0)
591 grid.addWidget(self.message_e, 2, 1, 1, 3)
592 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)
594 self.amount_e = QLineEdit()
595 grid.addWidget(QLabel(_('Amount')), 3, 0)
596 grid.addWidget(self.amount_e, 3, 1, 1, 2)
597 grid.addWidget(HelpButton(
598 _('Amount to be sent.') + '\n\n' \
599 + _('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)
601 self.fee_e = QLineEdit()
602 grid.addWidget(QLabel(_('Fee')), 4, 0)
603 grid.addWidget(self.fee_e, 4, 1, 1, 2)
604 grid.addWidget(HelpButton(
605 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
606 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
607 + _('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)
609 b = EnterButton(_("Send"), self.do_send)
610 grid.addWidget(b, 6, 1)
612 b = EnterButton(_("Clear"),self.do_clear)
613 grid.addWidget(b, 6, 2)
615 self.payto_sig = QLabel('')
616 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
618 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
619 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
628 def entry_changed( is_fee ):
629 self.funds_error = False
630 amount = numbify(self.amount_e)
631 fee = numbify(self.fee_e)
632 if not is_fee: fee = None
635 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
637 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
640 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
643 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
644 self.funds_error = True
645 self.amount_e.setPalette(palette)
646 self.fee_e.setPalette(palette)
648 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
649 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
654 def update_completions(self):
656 for addr,label in self.wallet.labels.items():
657 if addr in self.wallet.addressbook:
658 l.append( label + ' <' + addr + '>')
659 l = l + self.wallet.aliases.keys()
661 self.completions.setStringList(l)
667 label = unicode( self.message_e.text() )
668 r = unicode( self.payto_e.text() )
672 m1 = re.match(ALIAS_REGEXP, r)
673 # label or alias, with address in brackets
674 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
677 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
681 to_address = m2.group(2)
685 if not self.wallet.is_valid(to_address):
686 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
690 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
692 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
695 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
697 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
700 if self.wallet.use_encryption:
701 password = self.password_dialog()
708 tx = self.wallet.mktx( to_address, amount, label, password, fee)
709 except BaseException, e:
710 self.show_message(str(e))
713 h = self.wallet.send_tx(tx)
714 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
715 status, msg = self.wallet.receive_tx( h )
718 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
720 self.update_contacts_tab()
722 QMessageBox.warning(self, _('Error'), msg, _('OK'))
725 def set_url(self, url):
726 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
727 self.tabs.setCurrentIndex(1)
728 label = self.wallet.labels.get(payto)
729 m_addr = label + ' <'+ payto+'>' if label else payto
730 self.payto_e.setText(m_addr)
732 self.message_e.setText(message)
733 self.amount_e.setText(amount)
735 self.set_frozen(self.payto_e,True)
736 self.set_frozen(self.amount_e,True)
737 self.set_frozen(self.message_e,True)
738 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
740 self.payto_sig.setVisible(False)
743 self.payto_sig.setVisible(False)
744 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
746 self.set_frozen(e,False)
748 def set_frozen(self,entry,frozen):
750 entry.setReadOnly(True)
751 entry.setFrame(False)
753 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
754 entry.setPalette(palette)
756 entry.setReadOnly(False)
759 palette.setColor(entry.backgroundRole(), QColor('white'))
760 entry.setPalette(palette)
763 def toggle_freeze(self,addr):
765 if addr in self.wallet.frozen_addresses:
766 self.wallet.unfreeze(addr)
768 self.wallet.freeze(addr)
769 self.update_receive_tab()
771 def toggle_priority(self,addr):
773 if addr in self.wallet.prioritized_addresses:
774 self.wallet.unprioritize(addr)
776 self.wallet.prioritize(addr)
777 self.update_receive_tab()
780 def create_list_tab(self, headers):
781 "generic tab creation method"
782 l = MyTreeWidget(self)
783 l.setColumnCount( len(headers) )
784 l.setHeaderLabels( headers )
794 vbox.addWidget(buttons)
799 buttons.setLayout(hbox)
804 def create_receive_tab(self):
805 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
806 l.setContextMenuPolicy(Qt.CustomContextMenu)
807 l.customContextMenuRequested.connect(self.create_receive_menu)
808 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
809 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
810 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
811 self.receive_list = l
812 self.receive_buttons_hbox = hbox
813 self.qr_button = EnterButton(self.qr_button_text(), self.toggle_QR_window)
814 hbox.addWidget(self.qr_button)
815 self.print_button = EnterButton(_("Print QR"), self.print_qr)
816 self.print_button.setHidden(True)
817 hbox.addWidget(self.print_button)
819 self.details_button = EnterButton(self.details_button_text(), self.toggle_detailed_view)
820 hbox.addWidget(self.details_button)
826 self.qr_window.do_save()
827 self.show_message(_("QR code saved to file") + " " + self.qr_window.filename)
830 def request_amount_dialog(self, address):
831 # pick the first unused address
832 # print "please wait" in qr window
833 # enter amount in usd
836 d.setWindowTitle('Request payment')
839 vbox.addWidget(QLabel(address))
846 index = self.wallet.addresses.index(address)
850 label = self.wallet.labels.get(address,'invoice %04d'%(index+1))
851 grid.addWidget(QLabel(_('Label')), 0, 0)
852 label_e = QLineEdit()
853 label_e.setText(label)
854 grid.addWidget(label_e, 0, 1)
856 amount_e = QLineEdit()
857 amount_e.textChanged.connect(lambda: numbify(amount_e))
859 grid.addWidget(QLabel(_('Amount')), 1, 0)
860 grid.addWidget(amount_e, 1, 1, 1, 3)
862 vbox.addLayout(ok_cancel_buttons(d))
866 if not d.exec_(): return
868 amount = int( Decimal( unicode( amount_e.text())) * 100000000 )
871 self.wallet.requested_amounts[address] = amount
873 label = unicode(label_e.text())
875 self.wallet.labels[address] = label
877 self.update_receive_item(self.receive_list.currentItem())
880 self.qr_window.set_content( address, label, amount )
883 #self.receive_list.currentItem().setFocus(True)
886 def details_button_text(self):
887 return _('Hide details') if self.detailed_view else _('Show details')
889 def qr_button_text(self):
890 return _('Hide QR') if self.qr_window else _('Show QR')
893 def toggle_detailed_view(self):
894 self.detailed_view = not self.detailed_view
895 self.config.set_key('qt_detailed_view', self.detailed_view, True)
897 self.details_button.setText(self.details_button_text())
899 self.update_receive_tab()
900 self.update_contacts_tab()
903 def create_contacts_tab(self):
904 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
905 l.setContextMenuPolicy(Qt.CustomContextMenu)
906 l.customContextMenuRequested.connect(self.create_contact_menu)
907 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
908 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
909 self.contacts_list = l
910 self.contacts_buttons_hbox = hbox
911 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
916 def create_receive_menu(self, position):
917 # fixme: this function apparently has a side effect.
918 # if it is not called the menu pops up several times
919 #self.receive_list.selectedIndexes()
921 item = self.receive_list.itemAt(position)
923 addr = unicode(item.text(1))
925 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
926 if self.qr_window: menu.addAction(_("Request payment"), lambda: self.request_amount_dialog(addr))
927 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
928 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
930 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
931 menu.addAction(t, lambda: self.toggle_freeze(addr))
932 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
933 menu.addAction(t, lambda: self.toggle_priority(addr))
934 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
937 def payto(self, x, is_alias):
944 label = self.wallet.labels.get(addr)
945 m_addr = label + ' <' + addr + '>' if label else addr
946 self.tabs.setCurrentIndex(1)
947 self.payto_e.setText(m_addr)
948 self.amount_e.setFocus()
950 def delete_contact(self, x, is_alias):
951 if self.question("Do you want to remove %s from your list of contacts?"%x):
952 if not is_alias and x in self.wallet.addressbook:
953 self.wallet.addressbook.remove(x)
954 if x in self.wallet.labels.keys():
955 self.wallet.labels.pop(x)
956 elif is_alias and x in self.wallet.aliases:
957 self.wallet.aliases.pop(x)
958 self.update_history_tab()
959 self.update_contacts_tab()
960 self.update_completions()
962 def create_contact_menu(self, position):
963 # fixme: this function apparently has a side effect.
964 # if it is not called the menu pops up several times
965 #self.contacts_list.selectedIndexes()
967 item = self.contacts_list.itemAt(position)
969 addr = unicode(item.text(0))
970 label = unicode(item.text(1))
971 is_alias = label in self.wallet.aliases.keys()
972 x = label if is_alias else addr
974 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
975 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
976 menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
978 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
980 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
981 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
982 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
985 def update_receive_item(self, item):
986 address = str( item.data(1,0).toString() )
988 flags = self.wallet.get_address_flags(address)
989 item.setData(0,0,flags)
991 label = self.wallet.labels.get(address,'')
992 item.setData(2,0,label)
994 amount_str = format_satoshis( self.wallet.requested_amounts.get(address,0) )
995 item.setData(3,0,amount_str)
997 c, u = self.wallet.get_addr_balance(address)
998 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
999 item.setData(4,0,balance)
1001 if address in self.wallet.frozen_addresses:
1002 item.setBackgroundColor(1, QColor('lightblue'))
1003 elif address in self.wallet.prioritized_addresses:
1004 item.setBackgroundColor(1, QColor('lightgreen'))
1007 def update_receive_tab(self):
1008 l = self.receive_list
1011 l.setColumnHidden(0, not self.detailed_view)
1012 l.setColumnHidden(3, self.qr_window is None)
1013 l.setColumnHidden(4, not self.detailed_view)
1014 l.setColumnHidden(5, not self.detailed_view)
1015 l.setColumnWidth(0, 50)
1016 l.setColumnWidth(1, 310)
1017 l.setColumnWidth(2, 200)
1018 l.setColumnWidth(3, 130)
1019 l.setColumnWidth(4, 130)
1020 l.setColumnWidth(5, 10)
1024 for address in self.wallet.all_addresses():
1026 if self.wallet.is_change(address) and not self.detailed_view:
1030 h = self.wallet.history.get(address,[])
1033 for tx_hash, tx_height in h:
1034 tx = self.wallet.transactions.get(tx_hash)
1042 if address in self.wallet.addresses:
1044 if gap > self.wallet.gap_limit:
1047 if address in self.wallet.addresses:
1050 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1051 item.setFont(0, QFont(MONOSPACE_FONT))
1052 item.setFont(1, QFont(MONOSPACE_FONT))
1053 item.setFont(3, QFont(MONOSPACE_FONT))
1054 self.update_receive_item(item)
1055 if is_red and address in self.wallet.addresses:
1056 item.setBackgroundColor(1, QColor('red'))
1057 l.addTopLevelItem(item)
1059 # we use column 1 because column 0 may be hidden
1060 l.setCurrentItem(l.topLevelItem(0),1)
1062 def show_contact_details(self, m):
1063 a = self.wallet.aliases.get(m)
1065 if a[0] in self.wallet.authorities.keys():
1066 s = self.wallet.authorities.get(a[0])
1069 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1070 QMessageBox.information(self, 'Alias', msg, 'OK')
1072 def update_contacts_tab(self):
1074 l = self.contacts_list
1076 l.setColumnHidden(2, not self.detailed_view)
1077 l.setColumnWidth(0, 350)
1078 l.setColumnWidth(1, 330)
1079 l.setColumnWidth(2, 100)
1082 for alias, v in self.wallet.aliases.items():
1084 alias_targets.append(target)
1085 item = QTreeWidgetItem( [ target, alias, '-'] )
1086 item.setBackgroundColor(0, QColor('lightgray'))
1087 l.addTopLevelItem(item)
1089 for address in self.wallet.addressbook:
1090 if address in alias_targets: continue
1091 label = self.wallet.labels.get(address,'')
1093 for item in self.wallet.transactions.values():
1094 if address in item['outputs'] : n=n+1
1096 item = QTreeWidgetItem( [ address, label, tx] )
1097 item.setFont(0, QFont(MONOSPACE_FONT))
1098 l.addTopLevelItem(item)
1100 l.setCurrentItem(l.topLevelItem(0))
1102 def create_wall_tab(self):
1103 self.textbox = textbox = QTextEdit(self)
1104 textbox.setFont(QFont(MONOSPACE_FONT))
1105 textbox.setReadOnly(True)
1108 def create_status_bar(self):
1110 sb.setFixedHeight(35)
1111 if self.wallet.seed:
1112 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1113 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1114 if self.wallet.seed:
1115 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1116 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
1117 sb.addPermanentWidget( self.status_button )
1118 self.setStatusBar(sb)
1120 def new_contact_dialog(self):
1121 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1122 address = unicode(text)
1124 if self.wallet.is_valid(address):
1125 self.wallet.addressbook.append(address)
1127 self.update_contacts_tab()
1128 self.update_history_tab()
1129 self.update_completions()
1131 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1134 def show_seed_dialog(wallet, parent=None):
1136 QMessageBox.information(parent, _('Message'),
1137 _('No seed'), _('OK'))
1140 if wallet.use_encryption:
1141 password = parent.password_dialog()
1148 seed = wallet.pw_decode(wallet.seed, password)
1150 QMessageBox.warning(parent, _('Error'),
1151 _('Incorrect Password'), _('OK'))
1154 dialog = QDialog(None)
1156 dialog.setWindowTitle("Electrum")
1158 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1160 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1161 + _("Please write down or memorize these 12 words (order is important).") + " " \
1162 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1163 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1165 main_text = QLabel(msg)
1166 main_text.setWordWrap(True)
1169 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1176 copy_function = lambda: app.clipboard().setText(brainwallet)
1177 copy_button = QPushButton(_("Copy to Clipboard"))
1178 copy_button.clicked.connect(copy_function)
1180 show_qr_function = lambda: ElectrumWindow.show_seed_qrcode(seed)
1181 qr_button = QPushButton(_("View as QR Code"))
1182 qr_button.clicked.connect(show_qr_function)
1184 ok_button = QPushButton(_("OK"))
1185 ok_button.setDefault(True)
1186 ok_button.clicked.connect(dialog.accept)
1188 main_layout = QGridLayout()
1189 main_layout.addWidget(logo, 0, 0)
1190 main_layout.addWidget(main_text, 0, 1, 1, -1)
1191 main_layout.addWidget(copy_button, 1, 1)
1192 main_layout.addWidget(qr_button, 1, 2)
1193 main_layout.addWidget(ok_button, 1, 3)
1194 dialog.setLayout(main_layout)
1199 def show_seed_qrcode(seed):
1203 d.setWindowTitle(_("Seed"))
1204 d.setMinimumSize(270, 300)
1205 vbox = QVBoxLayout()
1206 vbox.addWidget(QRCodeWidget(seed))
1207 hbox = QHBoxLayout()
1209 b = QPushButton(_("OK"))
1211 b.clicked.connect(d.accept)
1213 vbox.addLayout(hbox)
1217 def sign_message(self,address):
1218 if not address: return
1221 d.setWindowTitle('Sign Message')
1222 d.setMinimumSize(270, 350)
1224 tab_widget = QTabWidget()
1226 layout = QGridLayout(tab)
1228 sign_address = QLineEdit()
1229 sign_address.setText(address)
1230 layout.addWidget(QLabel(_('Address')), 1, 0)
1231 layout.addWidget(sign_address, 1, 1)
1233 sign_message = QTextEdit()
1234 layout.addWidget(QLabel(_('Message')), 2, 0)
1235 layout.addWidget(sign_message, 2, 1, 2, 1)
1237 sign_signature = QLineEdit()
1238 layout.addWidget(QLabel(_('Signature')), 3, 0)
1239 layout.addWidget(sign_signature, 3, 1)
1242 if self.wallet.use_encryption:
1243 password = self.password_dialog()
1250 signature = self.wallet.sign_message(sign_address.text(), sign_message.toPlainText(), password)
1251 sign_signature.setText(signature)
1252 except BaseException, e:
1253 self.show_message(str(e))
1256 hbox = QHBoxLayout()
1257 b = QPushButton(_("Sign"))
1259 b.clicked.connect(do_sign)
1260 b = QPushButton(_("Close"))
1261 b.clicked.connect(d.accept)
1263 layout.addLayout(hbox, 4, 1)
1264 tab_widget.addTab(tab, "Sign")
1268 layout = QGridLayout(tab)
1270 verify_address = QLineEdit()
1271 layout.addWidget(QLabel(_('Address')), 1, 0)
1272 layout.addWidget(verify_address, 1, 1)
1274 verify_message = QTextEdit()
1275 layout.addWidget(QLabel(_('Message')), 2, 0)
1276 layout.addWidget(verify_message, 2, 1, 2, 1)
1278 verify_signature = QLineEdit()
1279 layout.addWidget(QLabel(_('Signature')), 3, 0)
1280 layout.addWidget(verify_signature, 3, 1)
1284 self.wallet.verify_message(verify_address.text(), verify_signature.text(), verify_message.toPlainText())
1285 self.show_message("Signature verified")
1286 except BaseException, e:
1287 self.show_message(str(e))
1290 hbox = QHBoxLayout()
1291 b = QPushButton(_("Verify"))
1292 b.clicked.connect(do_verify)
1294 b = QPushButton(_("Close"))
1295 b.clicked.connect(d.accept)
1297 layout.addLayout(hbox, 4, 1)
1298 tab_widget.addTab(tab, "Verify")
1300 vbox = QVBoxLayout()
1301 vbox.addWidget(tab_widget)
1306 def toggle_QR_window(self):
1308 self.qr_window.close()
1309 self.qr_window = None
1311 self.qr_window = QR_Window()
1312 item = self.receive_list.currentItem()
1314 address = str(item.text(1))
1315 label = self.wallet.labels.get(address)
1316 amount = self.wallet.requested_amounts.get(address)
1317 self.qr_window.set_content( address, label, amount )
1318 self.qr_window.show()
1320 self.qr_button.setText(self.qr_button_text())
1321 self.print_button.setHidden(self.qr_window is None)
1322 self.update_receive_tab()
1324 def question(self, msg):
1325 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1327 def show_message(self, msg):
1328 QMessageBox.information(self, _('Message'), msg, _('OK'))
1330 def password_dialog(self ):
1337 vbox = QVBoxLayout()
1338 msg = _('Please enter your password')
1339 vbox.addWidget(QLabel(msg))
1341 grid = QGridLayout()
1343 grid.addWidget(QLabel(_('Password')), 1, 0)
1344 grid.addWidget(pw, 1, 1)
1345 vbox.addLayout(grid)
1347 vbox.addLayout(ok_cancel_buttons(d))
1350 if not d.exec_(): return
1351 return unicode(pw.text())
1358 def change_password_dialog( wallet, parent=None ):
1361 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1369 new_pw = QLineEdit()
1370 new_pw.setEchoMode(2)
1371 conf_pw = QLineEdit()
1372 conf_pw.setEchoMode(2)
1374 vbox = QVBoxLayout()
1376 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1377 +_('To disable wallet encryption, enter an empty new password.')) \
1378 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1380 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1381 +_("Leave these fields empty if you want to disable encryption.")
1382 vbox.addWidget(QLabel(msg))
1384 grid = QGridLayout()
1387 if wallet.use_encryption:
1388 grid.addWidget(QLabel(_('Password')), 1, 0)
1389 grid.addWidget(pw, 1, 1)
1391 grid.addWidget(QLabel(_('New Password')), 2, 0)
1392 grid.addWidget(new_pw, 2, 1)
1394 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1395 grid.addWidget(conf_pw, 3, 1)
1396 vbox.addLayout(grid)
1398 vbox.addLayout(ok_cancel_buttons(d))
1401 if not d.exec_(): return
1403 password = unicode(pw.text()) if wallet.use_encryption else None
1404 new_password = unicode(new_pw.text())
1405 new_password2 = unicode(conf_pw.text())
1408 seed = wallet.pw_decode( wallet.seed, password)
1410 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1413 if new_password != new_password2:
1414 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1417 wallet.update_password(seed, password, new_password)
1420 def seed_dialog(wallet, parent=None):
1424 vbox = QVBoxLayout()
1425 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1426 vbox.addWidget(QLabel(msg))
1428 grid = QGridLayout()
1431 seed_e = QLineEdit()
1432 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1433 grid.addWidget(seed_e, 1, 1)
1437 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1438 grid.addWidget(gap_e, 2, 1)
1439 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1440 vbox.addLayout(grid)
1442 vbox.addLayout(ok_cancel_buttons(d))
1445 if not d.exec_(): return
1448 gap = int(unicode(gap_e.text()))
1450 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1454 seed = unicode(seed_e.text())
1457 print_error("Warning: Not hex, trying decode")
1459 seed = mnemonic.mn_decode( seed.split(' ') )
1461 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1464 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1467 wallet.seed = str(seed)
1468 #print repr(wallet.seed)
1469 wallet.gap_limit = gap
1474 def settings_dialog(self):
1477 vbox = QVBoxLayout()
1478 msg = _('Here are the settings of your wallet.') + '\n'\
1479 + _('For more explanations, click on the help buttons next to each field.')
1482 label.setFixedWidth(250)
1483 label.setWordWrap(True)
1484 label.setAlignment(Qt.AlignJustify)
1485 vbox.addWidget(label)
1487 grid = QGridLayout()
1489 vbox.addLayout(grid)
1491 fee_label = QLabel(_('Transaction fee'))
1492 grid.addWidget(fee_label, 2, 0)
1494 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1495 grid.addWidget(fee_e, 2, 1)
1496 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1497 + _('Recommended value') + ': 0.001'
1498 grid.addWidget(HelpButton(msg), 2, 2)
1499 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1500 if not self.config.is_modifiable('fee'):
1501 for w in [fee_e, fee_label]: w.setEnabled(False)
1503 nz_label = QLabel(_('Display zeros'))
1504 grid.addWidget(nz_label, 3, 0)
1506 nz_e.setText("%d"% self.wallet.num_zeros)
1507 grid.addWidget(nz_e, 3, 1)
1508 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1509 grid.addWidget(HelpButton(msg), 3, 2)
1510 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1511 if not self.config.is_modifiable('num_zeros'):
1512 for w in [nz_e, nz_label]: w.setEnabled(False)
1514 usechange_cb = QCheckBox(_('Use change addresses'))
1515 grid.addWidget(usechange_cb, 5, 0)
1516 usechange_cb.setChecked(self.wallet.use_change)
1517 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1518 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1520 gap_label = QLabel(_('Gap limit'))
1521 grid.addWidget(gap_label, 6, 0)
1523 gap_e.setText("%d"% self.wallet.gap_limit)
1524 grid.addWidget(gap_e, 6, 1)
1525 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1526 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1527 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1528 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1529 + _('Warning') + ': ' \
1530 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1531 + _('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'
1532 grid.addWidget(HelpButton(msg), 6, 2)
1533 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1534 if not self.config.is_modifiable('gap_limit'):
1535 for w in [gap_e, gap_label]: w.setEnabled(False)
1537 gui_label=QLabel(_('Default GUI') + ':')
1538 grid.addWidget(gui_label , 7, 0)
1539 gui_combo = QComboBox()
1540 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1541 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1542 if index==-1: index = 1
1543 gui_combo.setCurrentIndex(index)
1544 grid.addWidget(gui_combo, 7, 1)
1545 grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1546 if not self.config.is_modifiable('gui'):
1547 for w in [gui_combo, gui_label]: w.setEnabled(False)
1549 vbox.addLayout(ok_cancel_buttons(d))
1553 if not d.exec_(): return
1555 fee = unicode(fee_e.text())
1557 fee = int( 100000000 * Decimal(fee) )
1559 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1562 if self.wallet.fee != fee:
1563 self.wallet.fee = fee
1566 nz = unicode(nz_e.text())
1571 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1574 if self.wallet.num_zeros != nz:
1575 self.wallet.num_zeros = nz
1576 self.config.set_key('num_zeros', nz, True)
1577 self.update_history_tab()
1578 self.update_receive_tab()
1580 if self.wallet.use_change != usechange_cb.isChecked():
1581 self.wallet.use_change = usechange_cb.isChecked()
1582 self.config.set_key('use_change', self.wallet.use_change, True)
1585 n = int(gap_e.text())
1587 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1590 if self.wallet.gap_limit != n:
1591 r = self.wallet.change_gap_limit(n)
1593 self.update_receive_tab()
1594 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1596 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1598 self.config.set_key("gui", str(gui_combo.currentText()).lower(), True)
1603 def network_dialog(wallet, parent=None):
1604 interface = wallet.interface
1606 if interface.is_connected:
1607 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1609 status = _("Not connected")
1610 server = interface.server
1613 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1614 server = interface.server
1616 plist, servers_list = interface.get_servers_list()
1620 d.setWindowTitle(_('Server'))
1621 d.setMinimumSize(375, 20)
1623 vbox = QVBoxLayout()
1626 hbox = QHBoxLayout()
1628 l.setPixmap(QPixmap(":icons/network.png"))
1631 hbox.addWidget(QLabel(status))
1633 vbox.addLayout(hbox)
1637 grid = QGridLayout()
1639 vbox.addLayout(grid)
1642 server_protocol = QComboBox()
1643 server_host = QLineEdit()
1644 server_host.setFixedWidth(200)
1645 server_port = QLineEdit()
1646 server_port.setFixedWidth(60)
1648 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1649 protocol_letters = 'thsg'
1650 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1651 server_protocol.addItems(protocol_names)
1653 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1654 grid.addWidget(server_protocol, 0, 1)
1655 grid.addWidget(server_host, 0, 2)
1656 grid.addWidget(server_port, 0, 3)
1658 def change_protocol(p):
1659 protocol = protocol_letters[p]
1660 host = unicode(server_host.text())
1661 pp = plist.get(host,DEFAULT_PORTS)
1662 if protocol not in pp.keys():
1663 protocol = pp.keys()[0]
1665 server_host.setText( host )
1666 server_port.setText( port )
1668 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1670 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1671 servers_list_widget = QTreeWidget(parent)
1672 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1673 servers_list_widget.setMaximumHeight(150)
1674 servers_list_widget.setColumnWidth(0, 240)
1675 for _host in servers_list.keys():
1676 _type = 'pruning' if servers_list[_host].get('pruning') else 'full'
1677 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1679 def change_server(host, protocol=None):
1680 pp = plist.get(host,DEFAULT_PORTS)
1682 port = pp.get(protocol)
1683 if not port: protocol = None
1686 if 't' in pp.keys():
1688 port = pp.get(protocol)
1690 protocol = pp.keys()[0]
1691 port = pp.get(protocol)
1693 server_host.setText( host )
1694 server_port.setText( port )
1695 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1697 if not plist: return
1698 for p in protocol_letters:
1699 i = protocol_letters.index(p)
1700 j = server_protocol.model().index(i,0)
1701 if p not in pp.keys():
1702 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1704 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1708 host, port, protocol = server.split(':')
1709 change_server(host,protocol)
1711 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1712 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1714 if not wallet.config.is_modifiable('server'):
1715 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1718 proxy_mode = QComboBox()
1719 proxy_host = QLineEdit()
1720 proxy_host.setFixedWidth(200)
1721 proxy_port = QLineEdit()
1722 proxy_port.setFixedWidth(60)
1723 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1725 def check_for_disable(index = False):
1726 if proxy_mode.currentText() != 'NONE':
1727 proxy_host.setEnabled(True)
1728 proxy_port.setEnabled(True)
1730 proxy_host.setEnabled(False)
1731 proxy_port.setEnabled(False)
1734 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1736 if not wallet.config.is_modifiable('proxy'):
1737 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1739 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1740 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1741 proxy_host.setText(proxy_config.get("host"))
1742 proxy_port.setText(proxy_config.get("port"))
1744 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1745 grid.addWidget(proxy_mode, 2, 1)
1746 grid.addWidget(proxy_host, 2, 2)
1747 grid.addWidget(proxy_port, 2, 3)
1750 vbox.addLayout(ok_cancel_buttons(d))
1753 if not d.exec_(): return
1755 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1756 if proxy_mode.currentText() != 'NONE':
1757 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1761 wallet.config.set_key("proxy", proxy, True)
1762 wallet.config.set_key("server", server, True)
1763 interface.set_server(server, proxy)
1767 def closeEvent(self, event):
1769 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1775 def __init__(self, wallet, config, app=None):
1776 self.wallet = wallet
1777 self.config = config
1779 self.app = QApplication(sys.argv)
1782 def restore_or_create(self):
1783 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1784 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1785 if r==2: return None
1786 return 'restore' if r==1 else 'create'
1788 def seed_dialog(self):
1789 return ElectrumWindow.seed_dialog( self.wallet )
1791 def network_dialog(self):
1792 return ElectrumWindow.network_dialog( self.wallet, parent=None )
1795 def show_seed(self):
1796 ElectrumWindow.show_seed_dialog(self.wallet)
1799 def password_dialog(self):
1800 ElectrumWindow.change_password_dialog(self.wallet)
1803 def restore_wallet(self):
1804 wallet = self.wallet
1805 # wait until we are connected, because the user might have selected another server
1806 if not wallet.interface.is_connected:
1807 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1808 waiting_dialog(waiting)
1810 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1811 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1813 wallet.set_up_to_date(False)
1814 wallet.interface.poke('synchronizer')
1815 waiting_dialog(waiting)
1816 if wallet.is_found():
1817 print_error( "Recovery successful" )
1819 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1826 w = ElectrumWindow(self.wallet, self.config)
1827 if url: w.set_url(url)