3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re
21 from util import print_error
26 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
28 from PyQt4.QtGui import *
29 from PyQt4.QtCore import *
30 import PyQt4.QtCore as QtCore
31 import PyQt4.QtGui as QtGui
32 from interface import DEFAULT_SERVERS
37 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o lib/icons_rc.py'")
39 from wallet import format_satoshis
40 import bmp, mnemonic, pyqrnative, qrscanner
42 from decimal import Decimal
46 if platform.system() == 'Windows':
47 MONOSPACE_FONT = 'Lucida Console'
48 elif platform.system() == 'Darwin':
49 MONOSPACE_FONT = 'Monaco'
51 MONOSPACE_FONT = 'monospace'
53 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
55 def numbify(entry, is_int = False):
56 text = unicode(entry.text()).strip()
57 pos = entry.cursorPosition()
59 if not is_int: chars +='.'
60 s = ''.join([i for i in text if i in chars])
65 s = s[:p] + '.' + s[p:p+8]
67 amount = int( Decimal(s) * 100000000 )
76 entry.setCursorPosition(pos)
80 class Timer(QtCore.QThread):
83 self.emit(QtCore.SIGNAL('timersignal'))
86 class HelpButton(QPushButton):
87 def __init__(self, text):
88 QPushButton.__init__(self, '?')
89 self.setFocusPolicy(Qt.NoFocus)
90 self.setFixedWidth(20)
91 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
94 class EnterButton(QPushButton):
95 def __init__(self, text, func):
96 QPushButton.__init__(self, text)
98 self.clicked.connect(func)
100 def keyPressEvent(self, e):
101 if e.key() == QtCore.Qt.Key_Return:
104 class MyTreeWidget(QTreeWidget):
105 def __init__(self, parent):
106 QTreeWidget.__init__(self, parent)
109 for i in range(0,self.viewport().height()/5):
110 if self.itemAt(QPoint(0,i*5)) == item:
114 for j in range(0,30):
115 if self.itemAt(QPoint(0,i*5 + j)) != item:
117 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
119 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
124 class StatusBarButton(QPushButton):
125 def __init__(self, icon, tooltip, func):
126 QPushButton.__init__(self, icon, '')
127 self.setToolTip(tooltip)
129 self.setMaximumWidth(25)
130 self.clicked.connect(func)
133 def keyPressEvent(self, e):
134 if e.key() == QtCore.Qt.Key_Return:
138 class QRCodeWidget(QWidget):
140 def __init__(self, addr):
141 super(QRCodeWidget, self).__init__()
142 self.setMinimumSize(210, 210)
145 def set_addr(self, addr):
147 self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
148 self.qr.addData(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 self.address_label.setFont(QFont(MONOSPACE_FONT))
197 vbox.addWidget(self.address_label)
199 self.label_label = QLabel("")
200 vbox.addWidget(self.label_label)
202 self.amount_label = QLabel("")
203 vbox.addWidget(self.amount_label)
207 self.setLayout(main_box)
210 self.filename = "qrcode.bmp"
211 bmp.save_qrcode(self.qrw.qr, self.filename)
213 def set_content(self, addr, label, amount):
215 address_text = "<span style='font-size: 21pt'>%s</span>" % addr if addr else ""
216 self.address_label.setText(address_text)
219 amount_text = "<span style='font-size: 21pt'>%s</span> <span style='font-size: 16pt'>BTC</span> " % format_satoshis(amount) if amount else ""
220 self.amount_label.setText(amount_text)
223 label_text = "<span style='font-size: 21pt'>%s</span>" % label if label else ""
224 self.label_label.setText(label_text)
228 msg = 'bitcoin:'+self.address
229 if self.amount is not None:
230 msg += '?amount=%s'%(str( Decimal(self.amount) /100000000))
231 if self.label is not None:
232 msg += '&label=%s'%(self.label)
233 elif self.label is not None:
234 msg += '?label=%s'%(self.label)
236 self.qrw.set_addr( msg )
242 def waiting_dialog(f):
248 w.setWindowTitle('Electrum')
258 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
263 def ok_cancel_buttons(dialog):
266 b = QPushButton("OK")
268 b.clicked.connect(dialog.accept)
269 b = QPushButton("Cancel")
271 b.clicked.connect(dialog.reject)
275 class ElectrumWindow(QMainWindow):
277 def __init__(self, wallet, config):
278 QMainWindow.__init__(self)
281 self.wallet.interface.register_callback('updated', self.update_callback)
282 self.wallet.interface.register_callback('connected', self.update_callback)
283 self.wallet.interface.register_callback('disconnected', self.update_callback)
284 self.wallet.interface.register_callback('disconnecting', self.update_callback)
286 self.detailed_view = config.get('qt_detailed_view', False)
288 self.qr_window = None
289 self.funds_error = False
290 self.completions = QStringListModel()
292 self.tabs = tabs = QTabWidget(self)
293 tabs.addTab(self.create_history_tab(), _('History') )
295 tabs.addTab(self.create_send_tab(), _('Send') )
296 tabs.addTab(self.create_receive_tab(), _('Receive') )
297 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
298 tabs.addTab(self.create_wall_tab(), _('Wall') )
299 tabs.setMinimumSize(600, 400)
300 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
301 self.setCentralWidget(tabs)
302 self.create_status_bar()
304 g = self.config.get("winpos-qt",[100, 100, 840, 400])
305 self.setGeometry(g[0], g[1], g[2], g[3])
306 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
307 if not self.wallet.seed: title += ' [seedless]'
308 self.setWindowTitle( title )
310 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
311 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
312 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
313 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
315 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
316 #self.connect(self, SIGNAL('editamount'), self.edit_amount)
317 self.history_list.setFocus(True)
319 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
320 if platform.system() == 'Windows':
321 n = 3 if self.wallet.seed else 2
322 tabs.setCurrentIndex (n)
323 tabs.setCurrentIndex (0)
326 def connect_slots(self, sender):
328 self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient)
329 self.previous_payto_e=''
331 def check_recipient(self):
332 if self.payto_e.hasFocus():
334 r = unicode( self.payto_e.text() )
335 if r != self.previous_payto_e:
336 self.previous_payto_e = r
338 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
340 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
344 s = r + ' <' + to_address + '>'
345 self.payto_e.setText(s)
348 def update_callback(self):
349 self.emit(QtCore.SIGNAL('updatesignal'))
351 def update_wallet(self):
352 if self.wallet.interface and self.wallet.interface.is_connected:
353 if not self.wallet.up_to_date:
354 text = _( "Synchronizing..." )
355 icon = QIcon(":icons/status_waiting.png")
357 c, u = self.wallet.get_balance()
358 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
359 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
360 icon = QIcon(":icons/status_connected.png")
362 text = _( "Not connected" )
363 icon = QIcon(":icons/status_disconnected.png")
366 text = _( "Not enough funds" )
368 self.statusBar().showMessage(text)
369 self.status_button.setIcon( icon )
371 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
372 self.textbox.setText( self.wallet.banner )
373 self.update_history_tab()
374 self.update_receive_tab()
375 self.update_contacts_tab()
376 self.update_completions()
379 def create_history_tab(self):
380 self.history_list = l = MyTreeWidget(self)
382 l.setColumnWidth(0, 40)
383 l.setColumnWidth(1, 140)
384 l.setColumnWidth(2, 350)
385 l.setColumnWidth(3, 140)
386 l.setColumnWidth(4, 140)
387 l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
388 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
389 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
391 l.setContextMenuPolicy(Qt.CustomContextMenu)
392 l.customContextMenuRequested.connect(self.create_history_menu)
396 def create_history_menu(self, position):
397 self.history_list.selectedIndexes()
398 item = self.history_list.currentItem()
400 tx_hash = str(item.toolTip(0))
401 if not tx_hash: return
403 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
404 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
405 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
406 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
409 def tx_details(self, tx_hash):
410 tx_details = self.wallet.get_tx_details(tx_hash)
411 QMessageBox.information(self, 'Details', tx_details, 'OK')
414 def tx_label_clicked(self, item, column):
415 if column==2 and item.isSelected():
416 tx_hash = str(item.toolTip(0))
418 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
419 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
420 self.history_list.editItem( item, column )
421 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
424 def tx_label_changed(self, item, column):
428 tx_hash = str(item.toolTip(0))
429 tx = self.wallet.transactions.get(tx_hash)
430 s = self.wallet.labels.get(tx_hash)
431 text = unicode( item.text(2) )
433 self.wallet.labels[tx_hash] = text
434 item.setForeground(2, QBrush(QColor('black')))
436 if s: self.wallet.labels.pop(tx_hash)
437 text = self.wallet.get_default_label(tx_hash)
438 item.setText(2, text)
439 item.setForeground(2, QBrush(QColor('gray')))
443 def edit_label(self, is_recv):
444 l = self.receive_list if is_recv else self.contacts_list
445 c = 2 if is_recv else 1
446 item = l.currentItem()
447 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
448 l.editItem( item, c )
449 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
453 def address_label_clicked(self, item, column, l, column_addr, column_label):
454 if column == column_label and item.isSelected():
455 addr = unicode( item.text(column_addr) )
456 label = unicode( item.text(column_label) )
457 if label in self.wallet.aliases.keys():
459 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
460 l.editItem( item, column )
461 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
464 def address_label_changed(self, item, column, l, column_addr, column_label):
466 if column == column_label:
467 addr = unicode( item.text(column_addr) )
468 text = unicode( item.text(column_label) )
472 if text not in self.wallet.aliases.keys():
473 old_addr = self.wallet.labels.get(text)
475 self.wallet.labels[addr] = text
478 print_error("Error: This is one of your aliases")
479 label = self.wallet.labels.get(addr,'')
480 item.setText(column_label, QString(label))
482 s = self.wallet.labels.get(addr)
484 self.wallet.labels.pop(addr)
488 self.update_history_tab()
489 self.update_completions()
491 self.recv_changed(item)
494 def recv_changed(self, a):
495 "current item changed"
496 if a is not None and self.qr_window:
497 address = str(a.text(1))
498 label = self.wallet.labels.get(address)
499 amount = self.wallet.requested_amounts.get(address)
500 self.qr_window.set_content( address, label, amount )
503 def update_history_tab(self):
505 self.history_list.clear()
506 for item in self.wallet.get_tx_history():
507 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
510 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
516 icon = QIcon(":icons/unconfirmed.png")
518 icon = QIcon(":icons/clock%d.png"%conf)
520 icon = QIcon(":icons/confirmed.png")
523 icon = QIcon(":icons/unconfirmed.png")
525 if value is not None:
526 v_str = format_satoshis(value, True, self.wallet.num_zeros)
530 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
533 label, is_default_label = self.wallet.get_label(tx_hash)
535 label = _('Pruned transaction outputs')
536 is_default_label = False
538 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
539 item.setFont(2, QFont(MONOSPACE_FONT))
540 item.setFont(3, QFont(MONOSPACE_FONT))
541 item.setFont(4, QFont(MONOSPACE_FONT))
543 item.setToolTip(0, tx_hash)
545 item.setForeground(2, QBrush(QColor('grey')))
547 item.setIcon(0, icon)
548 self.history_list.insertTopLevelItem(0,item)
551 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
554 def create_send_tab(self):
559 grid.setColumnMinimumWidth(3,300)
560 grid.setColumnStretch(5,1)
562 self.payto_e = QLineEdit()
563 grid.addWidget(QLabel(_('Pay to')), 1, 0)
564 grid.addWidget(self.payto_e, 1, 1, 1, 3)
567 qrcode = qrscanner.scan_qr()
568 if 'address' in qrcode:
569 self.payto_e.setText(qrcode['address'])
570 if 'amount' in qrcode:
571 self.amount_e.setText(str(qrcode['amount']))
572 if 'label' in qrcode:
573 self.message_e.setText(qrcode['label'])
574 if 'message' in qrcode:
575 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
578 if qrscanner.is_available():
579 b = QPushButton(_("Scan QR code"))
580 b.clicked.connect(fill_from_qr)
581 grid.addWidget(b, 1, 5)
583 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)
585 completer = QCompleter()
586 completer.setCaseSensitivity(False)
587 self.payto_e.setCompleter(completer)
588 completer.setModel(self.completions)
590 self.message_e = QLineEdit()
591 grid.addWidget(QLabel(_('Description')), 2, 0)
592 grid.addWidget(self.message_e, 2, 1, 1, 3)
593 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)
595 self.amount_e = QLineEdit()
596 grid.addWidget(QLabel(_('Amount')), 3, 0)
597 grid.addWidget(self.amount_e, 3, 1, 1, 2)
598 grid.addWidget(HelpButton(
599 _('Amount to be sent.') + '\n\n' \
600 + _('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)
602 self.fee_e = QLineEdit()
603 grid.addWidget(QLabel(_('Fee')), 4, 0)
604 grid.addWidget(self.fee_e, 4, 1, 1, 2)
605 grid.addWidget(HelpButton(
606 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
607 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
608 + _('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)
610 b = EnterButton(_("Send"), self.do_send)
611 grid.addWidget(b, 6, 1)
613 b = EnterButton(_("Clear"),self.do_clear)
614 grid.addWidget(b, 6, 2)
616 self.payto_sig = QLabel('')
617 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
619 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
620 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
629 def entry_changed( is_fee ):
630 self.funds_error = False
631 amount = numbify(self.amount_e)
632 fee = numbify(self.fee_e)
633 if not is_fee: fee = None
636 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
638 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
641 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
644 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
645 self.funds_error = True
646 self.amount_e.setPalette(palette)
647 self.fee_e.setPalette(palette)
649 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
650 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
655 def update_completions(self):
657 for addr,label in self.wallet.labels.items():
658 if addr in self.wallet.addressbook:
659 l.append( label + ' <' + addr + '>')
660 l = l + self.wallet.aliases.keys()
662 self.completions.setStringList(l)
668 label = unicode( self.message_e.text() )
669 r = unicode( self.payto_e.text() )
673 m1 = re.match(ALIAS_REGEXP, r)
674 # label or alias, with address in brackets
675 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
678 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
682 to_address = m2.group(2)
686 if not self.wallet.is_valid(to_address):
687 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
691 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
693 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
696 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
698 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
701 if self.wallet.use_encryption:
702 password = self.password_dialog()
709 tx = self.wallet.mktx( to_address, amount, label, password, fee)
710 except BaseException, e:
711 self.show_message(str(e))
714 h = self.wallet.send_tx(tx)
715 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
716 status, msg = self.wallet.receive_tx( h )
719 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
721 self.update_contacts_tab()
723 QMessageBox.warning(self, _('Error'), msg, _('OK'))
726 def set_url(self, url):
727 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
728 self.tabs.setCurrentIndex(1)
729 label = self.wallet.labels.get(payto)
730 m_addr = label + ' <'+ payto+'>' if label else payto
731 self.payto_e.setText(m_addr)
733 self.message_e.setText(message)
734 self.amount_e.setText(amount)
736 self.set_frozen(self.payto_e,True)
737 self.set_frozen(self.amount_e,True)
738 self.set_frozen(self.message_e,True)
739 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
741 self.payto_sig.setVisible(False)
744 self.payto_sig.setVisible(False)
745 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
747 self.set_frozen(e,False)
749 def set_frozen(self,entry,frozen):
751 entry.setReadOnly(True)
752 entry.setFrame(False)
754 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
755 entry.setPalette(palette)
757 entry.setReadOnly(False)
760 palette.setColor(entry.backgroundRole(), QColor('white'))
761 entry.setPalette(palette)
764 def toggle_freeze(self,addr):
766 if addr in self.wallet.frozen_addresses:
767 self.wallet.unfreeze(addr)
769 self.wallet.freeze(addr)
770 self.update_receive_tab()
772 def toggle_priority(self,addr):
774 if addr in self.wallet.prioritized_addresses:
775 self.wallet.unprioritize(addr)
777 self.wallet.prioritize(addr)
778 self.update_receive_tab()
781 def create_list_tab(self, headers):
782 "generic tab creation method"
783 l = MyTreeWidget(self)
784 l.setColumnCount( len(headers) )
785 l.setHeaderLabels( headers )
795 vbox.addWidget(buttons)
800 buttons.setLayout(hbox)
805 def create_receive_tab(self):
806 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
807 l.setContextMenuPolicy(Qt.CustomContextMenu)
808 l.customContextMenuRequested.connect(self.create_receive_menu)
809 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
810 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
811 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
812 self.receive_list = l
813 self.receive_buttons_hbox = hbox
814 self.qr_button = EnterButton(self.qr_button_text(), self.toggle_QR_window)
815 hbox.addWidget(self.qr_button)
816 self.print_button = EnterButton(_("Print QR"), self.print_qr)
817 self.print_button.setHidden(True)
818 hbox.addWidget(self.print_button)
820 self.details_button = EnterButton(self.details_button_text(), self.toggle_detailed_view)
821 hbox.addWidget(self.details_button)
827 self.qr_window.do_save()
828 self.show_message(_("QR code saved to file") + " " + self.qr_window.filename)
831 def request_amount_dialog(self, address):
832 # pick the first unused address
833 # print "please wait" in qr window
834 # enter amount in usd
837 d.setWindowTitle('Request payment')
840 vbox.addWidget(QLabel(address))
847 index = self.wallet.addresses.index(address)
851 label = self.wallet.labels.get(address,'invoice %04d'%(index+1))
852 grid.addWidget(QLabel(_('Label')), 0, 0)
853 label_e = QLineEdit()
854 label_e.setText(label)
855 grid.addWidget(label_e, 0, 1)
857 amount_e = QLineEdit()
858 amount_e.textChanged.connect(lambda: numbify(amount_e))
860 grid.addWidget(QLabel(_('Amount')), 1, 0)
861 grid.addWidget(amount_e, 1, 1, 1, 3)
863 vbox.addLayout(ok_cancel_buttons(d))
867 if not d.exec_(): return
869 amount = int( Decimal( unicode( amount_e.text())) * 100000000 )
872 self.wallet.requested_amounts[address] = amount
874 label = unicode(label_e.text())
876 self.wallet.labels[address] = label
878 self.update_receive_item(self.receive_list.currentItem())
881 self.qr_window.set_content( address, label, amount )
884 #self.receive_list.currentItem().setFocus(True)
887 def details_button_text(self):
888 return _('Hide details') if self.detailed_view else _('Show details')
890 def qr_button_text(self):
891 return _('Hide QR') if self.qr_window else _('Show QR')
894 def toggle_detailed_view(self):
895 self.detailed_view = not self.detailed_view
896 self.config.set_key('qt_detailed_view', self.detailed_view, True)
898 self.details_button.setText(self.details_button_text())
900 self.update_receive_tab()
901 self.update_contacts_tab()
904 def create_contacts_tab(self):
905 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
906 l.setContextMenuPolicy(Qt.CustomContextMenu)
907 l.customContextMenuRequested.connect(self.create_contact_menu)
908 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
909 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
910 self.contacts_list = l
911 self.contacts_buttons_hbox = hbox
912 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
917 def create_receive_menu(self, position):
918 # fixme: this function apparently has a side effect.
919 # if it is not called the menu pops up several times
920 #self.receive_list.selectedIndexes()
922 item = self.receive_list.itemAt(position)
924 addr = unicode(item.text(1))
926 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
927 if self.qr_window: menu.addAction(_("Request payment"), lambda: self.request_amount_dialog(addr))
928 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
929 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
931 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
932 menu.addAction(t, lambda: self.toggle_freeze(addr))
933 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
934 menu.addAction(t, lambda: self.toggle_priority(addr))
935 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
938 def payto(self, x, is_alias):
945 label = self.wallet.labels.get(addr)
946 m_addr = label + ' <' + addr + '>' if label else addr
947 self.tabs.setCurrentIndex(1)
948 self.payto_e.setText(m_addr)
949 self.amount_e.setFocus()
951 def delete_contact(self, x, is_alias):
952 if self.question("Do you want to remove %s from your list of contacts?"%x):
953 if not is_alias and x in self.wallet.addressbook:
954 self.wallet.addressbook.remove(x)
955 if x in self.wallet.labels.keys():
956 self.wallet.labels.pop(x)
957 elif is_alias and x in self.wallet.aliases:
958 self.wallet.aliases.pop(x)
959 self.update_history_tab()
960 self.update_contacts_tab()
961 self.update_completions()
963 def create_contact_menu(self, position):
964 # fixme: this function apparently has a side effect.
965 # if it is not called the menu pops up several times
966 #self.contacts_list.selectedIndexes()
968 item = self.contacts_list.itemAt(position)
970 addr = unicode(item.text(0))
971 label = unicode(item.text(1))
972 is_alias = label in self.wallet.aliases.keys()
973 x = label if is_alias else addr
975 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
976 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
977 menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
979 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
981 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
982 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
983 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
986 def update_receive_item(self, item):
987 address = str( item.data(1,0).toString() )
989 flags = self.wallet.get_address_flags(address)
990 item.setData(0,0,flags)
992 label = self.wallet.labels.get(address,'')
993 item.setData(2,0,label)
995 amount_str = format_satoshis( self.wallet.requested_amounts.get(address,0) )
996 item.setData(3,0,amount_str)
998 c, u = self.wallet.get_addr_balance(address)
999 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1000 item.setData(4,0,balance)
1002 if address in self.wallet.frozen_addresses:
1003 item.setBackgroundColor(1, QColor('lightblue'))
1004 elif address in self.wallet.prioritized_addresses:
1005 item.setBackgroundColor(1, QColor('lightgreen'))
1008 def update_receive_tab(self):
1009 l = self.receive_list
1012 l.setColumnHidden(0, not self.detailed_view)
1013 l.setColumnHidden(3, self.qr_window is None)
1014 l.setColumnHidden(4, not self.detailed_view)
1015 l.setColumnHidden(5, not self.detailed_view)
1016 l.setColumnWidth(0, 50)
1017 l.setColumnWidth(1, 310)
1018 l.setColumnWidth(2, 200)
1019 l.setColumnWidth(3, 130)
1020 l.setColumnWidth(4, 130)
1021 l.setColumnWidth(5, 10)
1025 for address in self.wallet.all_addresses():
1027 if self.wallet.is_change(address) and not self.detailed_view:
1031 h = self.wallet.history.get(address,[])
1034 for tx_hash, tx_height in h:
1035 tx = self.wallet.transactions.get(tx_hash)
1043 if address in self.wallet.addresses:
1045 if gap > self.wallet.gap_limit:
1048 if address in self.wallet.addresses:
1051 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1052 item.setFont(0, QFont(MONOSPACE_FONT))
1053 item.setFont(1, QFont(MONOSPACE_FONT))
1054 item.setFont(3, QFont(MONOSPACE_FONT))
1055 self.update_receive_item(item)
1056 if is_red and address in self.wallet.addresses:
1057 item.setBackgroundColor(1, QColor('red'))
1058 l.addTopLevelItem(item)
1060 # we use column 1 because column 0 may be hidden
1061 l.setCurrentItem(l.topLevelItem(0),1)
1063 def show_contact_details(self, m):
1064 a = self.wallet.aliases.get(m)
1066 if a[0] in self.wallet.authorities.keys():
1067 s = self.wallet.authorities.get(a[0])
1070 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1071 QMessageBox.information(self, 'Alias', msg, 'OK')
1073 def update_contacts_tab(self):
1075 l = self.contacts_list
1077 l.setColumnHidden(2, not self.detailed_view)
1078 l.setColumnWidth(0, 350)
1079 l.setColumnWidth(1, 330)
1080 l.setColumnWidth(2, 100)
1083 for alias, v in self.wallet.aliases.items():
1085 alias_targets.append(target)
1086 item = QTreeWidgetItem( [ target, alias, '-'] )
1087 item.setBackgroundColor(0, QColor('lightgray'))
1088 l.addTopLevelItem(item)
1090 for address in self.wallet.addressbook:
1091 if address in alias_targets: continue
1092 label = self.wallet.labels.get(address,'')
1094 for item in self.wallet.transactions.values():
1095 if address in item['outputs'] : n=n+1
1097 item = QTreeWidgetItem( [ address, label, tx] )
1098 item.setFont(0, QFont(MONOSPACE_FONT))
1099 l.addTopLevelItem(item)
1101 l.setCurrentItem(l.topLevelItem(0))
1103 def create_wall_tab(self):
1104 self.textbox = textbox = QTextEdit(self)
1105 textbox.setFont(QFont(MONOSPACE_FONT))
1106 textbox.setReadOnly(True)
1109 def create_status_bar(self):
1111 sb.setFixedHeight(35)
1112 if self.wallet.seed:
1113 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1114 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1115 if self.wallet.seed:
1116 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1117 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
1118 sb.addPermanentWidget( self.status_button )
1119 self.setStatusBar(sb)
1121 def new_contact_dialog(self):
1122 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1123 address = unicode(text)
1125 if self.wallet.is_valid(address):
1126 self.wallet.addressbook.append(address)
1128 self.update_contacts_tab()
1129 self.update_history_tab()
1130 self.update_completions()
1132 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1135 def show_seed_dialog(wallet, parent=None):
1137 QMessageBox.information(parent, _('Message'),
1138 _('No seed'), _('OK'))
1141 if wallet.use_encryption:
1142 password = parent.password_dialog()
1149 seed = wallet.pw_decode(wallet.seed, password)
1151 QMessageBox.warning(parent, _('Error'),
1152 _('Incorrect Password'), _('OK'))
1155 dialog = QDialog(None)
1157 dialog.setWindowTitle("Electrum")
1159 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1161 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1162 + _("Please write down or memorize these 12 words (order is important).") + " " \
1163 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1164 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1166 main_text = QLabel(msg)
1167 main_text.setWordWrap(True)
1170 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1177 copy_function = lambda: app.clipboard().setText(brainwallet)
1178 copy_button = QPushButton(_("Copy to Clipboard"))
1179 copy_button.clicked.connect(copy_function)
1181 show_qr_function = lambda: ElectrumWindow.show_seed_qrcode(seed)
1182 qr_button = QPushButton(_("View as QR Code"))
1183 qr_button.clicked.connect(show_qr_function)
1185 ok_button = QPushButton(_("OK"))
1186 ok_button.setDefault(True)
1187 ok_button.clicked.connect(dialog.accept)
1189 main_layout = QGridLayout()
1190 main_layout.addWidget(logo, 0, 0)
1191 main_layout.addWidget(main_text, 0, 1, 1, -1)
1192 main_layout.addWidget(copy_button, 1, 1)
1193 main_layout.addWidget(qr_button, 1, 2)
1194 main_layout.addWidget(ok_button, 1, 3)
1195 dialog.setLayout(main_layout)
1200 def show_seed_qrcode(seed):
1204 d.setWindowTitle(_("Seed"))
1205 d.setMinimumSize(270, 300)
1206 vbox = QVBoxLayout()
1207 vbox.addWidget(QRCodeWidget(seed))
1208 hbox = QHBoxLayout()
1210 b = QPushButton(_("OK"))
1212 b.clicked.connect(d.accept)
1214 vbox.addLayout(hbox)
1218 def sign_message(self,address):
1219 if not address: return
1222 d.setWindowTitle('Sign Message')
1223 d.setMinimumSize(270, 350)
1225 tab_widget = QTabWidget()
1227 layout = QGridLayout(tab)
1229 sign_address = QLineEdit()
1230 sign_address.setText(address)
1231 layout.addWidget(QLabel(_('Address')), 1, 0)
1232 layout.addWidget(sign_address, 1, 1)
1234 sign_message = QTextEdit()
1235 layout.addWidget(QLabel(_('Message')), 2, 0)
1236 layout.addWidget(sign_message, 2, 1, 2, 1)
1238 sign_signature = QLineEdit()
1239 layout.addWidget(QLabel(_('Signature')), 3, 0)
1240 layout.addWidget(sign_signature, 3, 1)
1243 if self.wallet.use_encryption:
1244 password = self.password_dialog()
1251 signature = self.wallet.sign_message(sign_address.text(), sign_message.toPlainText(), password)
1252 sign_signature.setText(signature)
1253 except BaseException, e:
1254 self.show_message(str(e))
1257 hbox = QHBoxLayout()
1258 b = QPushButton(_("Sign"))
1260 b.clicked.connect(do_sign)
1261 b = QPushButton(_("Close"))
1262 b.clicked.connect(d.accept)
1264 layout.addLayout(hbox, 4, 1)
1265 tab_widget.addTab(tab, "Sign")
1269 layout = QGridLayout(tab)
1271 verify_address = QLineEdit()
1272 layout.addWidget(QLabel(_('Address')), 1, 0)
1273 layout.addWidget(verify_address, 1, 1)
1275 verify_message = QTextEdit()
1276 layout.addWidget(QLabel(_('Message')), 2, 0)
1277 layout.addWidget(verify_message, 2, 1, 2, 1)
1279 verify_signature = QLineEdit()
1280 layout.addWidget(QLabel(_('Signature')), 3, 0)
1281 layout.addWidget(verify_signature, 3, 1)
1285 self.wallet.verify_message(verify_address.text(), verify_signature.text(), verify_message.toPlainText())
1286 self.show_message("Signature verified")
1287 except BaseException, e:
1288 self.show_message(str(e))
1291 hbox = QHBoxLayout()
1292 b = QPushButton(_("Verify"))
1293 b.clicked.connect(do_verify)
1295 b = QPushButton(_("Close"))
1296 b.clicked.connect(d.accept)
1298 layout.addLayout(hbox, 4, 1)
1299 tab_widget.addTab(tab, "Verify")
1301 vbox = QVBoxLayout()
1302 vbox.addWidget(tab_widget)
1307 def toggle_QR_window(self):
1309 self.qr_window.close()
1310 self.qr_window = None
1312 self.qr_window = QR_Window()
1313 item = self.receive_list.currentItem()
1315 address = str(item.text(1))
1316 label = self.wallet.labels.get(address)
1317 amount = self.wallet.requested_amounts.get(address)
1318 self.qr_window.set_content( address, label, amount )
1319 self.qr_window.show()
1321 self.qr_button.setText(self.qr_button_text())
1322 self.print_button.setHidden(self.qr_window is None)
1323 self.update_receive_tab()
1325 def question(self, msg):
1326 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1328 def show_message(self, msg):
1329 QMessageBox.information(self, _('Message'), msg, _('OK'))
1331 def password_dialog(self ):
1338 vbox = QVBoxLayout()
1339 msg = _('Please enter your password')
1340 vbox.addWidget(QLabel(msg))
1342 grid = QGridLayout()
1344 grid.addWidget(QLabel(_('Password')), 1, 0)
1345 grid.addWidget(pw, 1, 1)
1346 vbox.addLayout(grid)
1348 vbox.addLayout(ok_cancel_buttons(d))
1351 if not d.exec_(): return
1352 return unicode(pw.text())
1359 def change_password_dialog( wallet, parent=None ):
1362 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1370 new_pw = QLineEdit()
1371 new_pw.setEchoMode(2)
1372 conf_pw = QLineEdit()
1373 conf_pw.setEchoMode(2)
1375 vbox = QVBoxLayout()
1377 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1378 +_('To disable wallet encryption, enter an empty new password.')) \
1379 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1381 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1382 +_("Leave these fields empty if you want to disable encryption.")
1383 vbox.addWidget(QLabel(msg))
1385 grid = QGridLayout()
1388 if wallet.use_encryption:
1389 grid.addWidget(QLabel(_('Password')), 1, 0)
1390 grid.addWidget(pw, 1, 1)
1392 grid.addWidget(QLabel(_('New Password')), 2, 0)
1393 grid.addWidget(new_pw, 2, 1)
1395 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1396 grid.addWidget(conf_pw, 3, 1)
1397 vbox.addLayout(grid)
1399 vbox.addLayout(ok_cancel_buttons(d))
1402 if not d.exec_(): return
1404 password = unicode(pw.text()) if wallet.use_encryption else None
1405 new_password = unicode(new_pw.text())
1406 new_password2 = unicode(conf_pw.text())
1409 seed = wallet.pw_decode( wallet.seed, password)
1411 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1414 if new_password != new_password2:
1415 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1418 wallet.update_password(seed, password, new_password)
1421 def seed_dialog(wallet, parent=None):
1425 vbox = QVBoxLayout()
1426 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1427 vbox.addWidget(QLabel(msg))
1429 grid = QGridLayout()
1432 seed_e = QLineEdit()
1433 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1434 grid.addWidget(seed_e, 1, 1)
1438 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1439 grid.addWidget(gap_e, 2, 1)
1440 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1441 vbox.addLayout(grid)
1443 vbox.addLayout(ok_cancel_buttons(d))
1446 if not d.exec_(): return
1449 gap = int(unicode(gap_e.text()))
1451 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1455 seed = unicode(seed_e.text())
1458 print_error("Warning: Not hex, trying decode")
1460 seed = mnemonic.mn_decode( seed.split(' ') )
1462 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1465 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1468 wallet.seed = str(seed)
1469 #print repr(wallet.seed)
1470 wallet.gap_limit = gap
1475 def settings_dialog(self):
1478 vbox = QVBoxLayout()
1479 msg = _('Here are the settings of your wallet.') + '\n'\
1480 + _('For more explanations, click on the help buttons next to each field.')
1483 label.setFixedWidth(250)
1484 label.setWordWrap(True)
1485 label.setAlignment(Qt.AlignJustify)
1486 vbox.addWidget(label)
1488 grid = QGridLayout()
1490 vbox.addLayout(grid)
1492 fee_label = QLabel(_('Transaction fee'))
1493 grid.addWidget(fee_label, 2, 0)
1495 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1496 grid.addWidget(fee_e, 2, 1)
1497 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1498 + _('Recommended value') + ': 0.001'
1499 grid.addWidget(HelpButton(msg), 2, 2)
1500 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1501 if not self.config.is_modifiable('fee'):
1502 for w in [fee_e, fee_label]: w.setEnabled(False)
1504 nz_label = QLabel(_('Display zeros'))
1505 grid.addWidget(nz_label, 3, 0)
1507 nz_e.setText("%d"% self.wallet.num_zeros)
1508 grid.addWidget(nz_e, 3, 1)
1509 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1510 grid.addWidget(HelpButton(msg), 3, 2)
1511 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1512 if not self.config.is_modifiable('num_zeros'):
1513 for w in [nz_e, nz_label]: w.setEnabled(False)
1515 usechange_cb = QCheckBox(_('Use change addresses'))
1516 grid.addWidget(usechange_cb, 5, 0)
1517 usechange_cb.setChecked(self.wallet.use_change)
1518 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1519 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1521 gap_label = QLabel(_('Gap limit'))
1522 grid.addWidget(gap_label, 6, 0)
1524 gap_e.setText("%d"% self.wallet.gap_limit)
1525 grid.addWidget(gap_e, 6, 1)
1526 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1527 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1528 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1529 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1530 + _('Warning') + ': ' \
1531 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1532 + _('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'
1533 grid.addWidget(HelpButton(msg), 6, 2)
1534 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1535 if not self.config.is_modifiable('gap_limit'):
1536 for w in [gap_e, gap_label]: w.setEnabled(False)
1538 gui_label=QLabel(_('Default GUI') + ':')
1539 grid.addWidget(gui_label , 7, 0)
1540 gui_combo = QComboBox()
1541 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1542 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1543 if index==-1: index = 1
1544 gui_combo.setCurrentIndex(index)
1545 grid.addWidget(gui_combo, 7, 1)
1546 grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1547 if not self.config.is_modifiable('gui'):
1548 for w in [gui_combo, gui_label]: w.setEnabled(False)
1550 vbox.addLayout(ok_cancel_buttons(d))
1554 if not d.exec_(): return
1556 fee = unicode(fee_e.text())
1558 fee = int( 100000000 * Decimal(fee) )
1560 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1563 if self.wallet.fee != fee:
1564 self.wallet.fee = fee
1567 nz = unicode(nz_e.text())
1572 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1575 if self.wallet.num_zeros != nz:
1576 self.wallet.num_zeros = nz
1577 self.config.set_key('num_zeros', nz, True)
1578 self.update_history_tab()
1579 self.update_receive_tab()
1581 if self.wallet.use_change != usechange_cb.isChecked():
1582 self.wallet.use_change = usechange_cb.isChecked()
1583 self.config.set_key('use_change', self.wallet.use_change, True)
1586 n = int(gap_e.text())
1588 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1591 if self.wallet.gap_limit != n:
1592 r = self.wallet.change_gap_limit(n)
1594 self.update_receive_tab()
1595 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1597 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1599 self.config.set_key("gui", str(gui_combo.currentText()).lower(), True)
1604 def network_dialog(wallet, parent=None):
1605 interface = wallet.interface
1607 if interface.is_connected:
1608 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1610 status = _("Not connected")
1611 server = interface.server
1614 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1615 server = interface.server
1617 plist, servers_list = interface.get_servers_list()
1621 d.setWindowTitle(_('Server'))
1622 d.setMinimumSize(375, 20)
1624 vbox = QVBoxLayout()
1627 hbox = QHBoxLayout()
1629 l.setPixmap(QPixmap(":icons/network.png"))
1632 hbox.addWidget(QLabel(status))
1634 vbox.addLayout(hbox)
1638 grid = QGridLayout()
1640 vbox.addLayout(grid)
1643 server_protocol = QComboBox()
1644 server_host = QLineEdit()
1645 server_host.setFixedWidth(200)
1646 server_port = QLineEdit()
1647 server_port.setFixedWidth(60)
1649 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1650 protocol_letters = 'thsg'
1651 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1652 server_protocol.addItems(protocol_names)
1654 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1655 grid.addWidget(server_protocol, 0, 1)
1656 grid.addWidget(server_host, 0, 2)
1657 grid.addWidget(server_port, 0, 3)
1659 def change_protocol(p):
1660 protocol = protocol_letters[p]
1661 host = unicode(server_host.text())
1662 pp = plist.get(host,DEFAULT_PORTS)
1663 if protocol not in pp.keys():
1664 protocol = pp.keys()[0]
1666 server_host.setText( host )
1667 server_port.setText( port )
1669 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1671 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1672 servers_list_widget = QTreeWidget(parent)
1673 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1674 servers_list_widget.setMaximumHeight(150)
1675 servers_list_widget.setColumnWidth(0, 240)
1676 for _host in servers_list.keys():
1677 _type = 'pruning' if servers_list[_host].get('pruning') else 'full'
1678 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1680 def change_server(host, protocol=None):
1681 pp = plist.get(host,DEFAULT_PORTS)
1683 port = pp.get(protocol)
1684 if not port: protocol = None
1687 if 't' in pp.keys():
1689 port = pp.get(protocol)
1691 protocol = pp.keys()[0]
1692 port = pp.get(protocol)
1694 server_host.setText( host )
1695 server_port.setText( port )
1696 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1698 if not plist: return
1699 for p in protocol_letters:
1700 i = protocol_letters.index(p)
1701 j = server_protocol.model().index(i,0)
1702 if p not in pp.keys():
1703 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1705 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1709 host, port, protocol = server.split(':')
1710 change_server(host,protocol)
1712 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1713 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1715 if not wallet.config.is_modifiable('server'):
1716 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1719 proxy_mode = QComboBox()
1720 proxy_host = QLineEdit()
1721 proxy_host.setFixedWidth(200)
1722 proxy_port = QLineEdit()
1723 proxy_port.setFixedWidth(60)
1724 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1726 def check_for_disable(index = False):
1727 if proxy_mode.currentText() != 'NONE':
1728 proxy_host.setEnabled(True)
1729 proxy_port.setEnabled(True)
1731 proxy_host.setEnabled(False)
1732 proxy_port.setEnabled(False)
1735 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1737 if not wallet.config.is_modifiable('proxy'):
1738 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1740 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1741 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1742 proxy_host.setText(proxy_config.get("host"))
1743 proxy_port.setText(proxy_config.get("port"))
1745 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1746 grid.addWidget(proxy_mode, 2, 1)
1747 grid.addWidget(proxy_host, 2, 2)
1748 grid.addWidget(proxy_port, 2, 3)
1751 vbox.addLayout(ok_cancel_buttons(d))
1754 if not d.exec_(): return
1756 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1757 if proxy_mode.currentText() != 'NONE':
1758 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1762 wallet.config.set_key("proxy", proxy, True)
1763 wallet.config.set_key("server", server, True)
1764 interface.set_server(server, proxy)
1768 def closeEvent(self, event):
1770 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1776 def __init__(self, wallet, config, app=None):
1777 self.wallet = wallet
1778 self.config = config
1780 self.app = QApplication(sys.argv)
1783 def restore_or_create(self):
1784 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1785 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1786 if r==2: return None
1787 return 'restore' if r==1 else 'create'
1789 def seed_dialog(self):
1790 return ElectrumWindow.seed_dialog( self.wallet )
1792 def network_dialog(self):
1793 return ElectrumWindow.network_dialog( self.wallet, parent=None )
1796 def show_seed(self):
1797 ElectrumWindow.show_seed_dialog(self.wallet)
1800 def password_dialog(self):
1801 ElectrumWindow.change_password_dialog(self.wallet)
1804 def restore_wallet(self):
1805 wallet = self.wallet
1806 # wait until we are connected, because the user might have selected another server
1807 if not wallet.interface.is_connected:
1808 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1809 waiting_dialog(waiting)
1811 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1812 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1814 wallet.set_up_to_date(False)
1815 wallet.interface.poke('synchronizer')
1816 waiting_dialog(waiting)
1817 if wallet.is_found():
1818 print_error( "Recovery successful" )
1820 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1827 w = ElectrumWindow(self.wallet, self.config)
1828 if url: w.set_url(url)