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)
233 self.qrw.set_addr( msg )
239 def waiting_dialog(f):
245 w.setWindowTitle('Electrum')
255 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
260 def ok_cancel_buttons(dialog):
263 b = QPushButton("OK")
265 b.clicked.connect(dialog.accept)
266 b = QPushButton("Cancel")
268 b.clicked.connect(dialog.reject)
272 class ElectrumWindow(QMainWindow):
274 def __init__(self, wallet, config):
275 QMainWindow.__init__(self)
278 self.wallet.interface.register_callback('updated', self.update_callback)
279 self.wallet.interface.register_callback('connected', self.update_callback)
280 self.wallet.interface.register_callback('disconnected', self.update_callback)
281 self.wallet.interface.register_callback('disconnecting', self.update_callback)
283 self.detailed_view = config.get('qt_detailed_view', False)
285 self.qr_window = None
286 self.funds_error = False
287 self.completions = QStringListModel()
289 self.tabs = tabs = QTabWidget(self)
290 tabs.addTab(self.create_history_tab(), _('History') )
292 tabs.addTab(self.create_send_tab(), _('Send') )
293 tabs.addTab(self.create_receive_tab(), _('Receive') )
294 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
295 tabs.addTab(self.create_wall_tab(), _('Wall') )
296 tabs.setMinimumSize(600, 400)
297 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
298 self.setCentralWidget(tabs)
299 self.create_status_bar()
301 g = self.config.get("winpos-qt",[100, 100, 840, 400])
302 self.setGeometry(g[0], g[1], g[2], g[3])
303 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
304 if not self.wallet.seed: title += ' [seedless]'
305 self.setWindowTitle( title )
307 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
308 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
309 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
310 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
312 self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
313 #self.connect(self, SIGNAL('editamount'), self.edit_amount)
314 self.history_list.setFocus(True)
316 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
317 if platform.system() == 'Windows':
318 n = 3 if self.wallet.seed else 2
319 tabs.setCurrentIndex (n)
320 tabs.setCurrentIndex (0)
323 def connect_slots(self, sender):
325 self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient)
326 self.previous_payto_e=''
328 def check_recipient(self):
329 if self.payto_e.hasFocus():
331 r = unicode( self.payto_e.text() )
332 if r != self.previous_payto_e:
333 self.previous_payto_e = r
335 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
337 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
341 s = r + ' <' + to_address + '>'
342 self.payto_e.setText(s)
345 def update_callback(self):
346 self.emit(QtCore.SIGNAL('updatesignal'))
348 def update_wallet(self):
349 if self.wallet.interface and self.wallet.interface.is_connected:
350 if not self.wallet.up_to_date:
351 text = _( "Synchronizing..." )
352 icon = QIcon(":icons/status_waiting.png")
354 c, u = self.wallet.get_balance()
355 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
356 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
357 icon = QIcon(":icons/status_connected.png")
359 text = _( "Not connected" )
360 icon = QIcon(":icons/status_disconnected.png")
363 text = _( "Not enough funds" )
365 self.statusBar().showMessage(text)
366 self.status_button.setIcon( icon )
368 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
369 self.textbox.setText( self.wallet.banner )
370 self.update_history_tab()
371 self.update_receive_tab()
372 self.update_contacts_tab()
373 self.update_completions()
376 def create_history_tab(self):
377 self.history_list = l = MyTreeWidget(self)
379 l.setColumnWidth(0, 40)
380 l.setColumnWidth(1, 140)
381 l.setColumnWidth(2, 350)
382 l.setColumnWidth(3, 140)
383 l.setColumnWidth(4, 140)
384 l.setHeaderLabels( [ '', _( 'Date' ), _( 'Description' ) , _('Amount'), _('Balance')] )
385 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
386 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
388 l.setContextMenuPolicy(Qt.CustomContextMenu)
389 l.customContextMenuRequested.connect(self.create_history_menu)
393 def create_history_menu(self, position):
394 self.history_list.selectedIndexes()
395 item = self.history_list.currentItem()
397 tx_hash = str(item.toolTip(0))
398 if not tx_hash: return
400 menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
401 menu.addAction(_("Details"), lambda: self.tx_details(tx_hash))
402 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
403 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
406 def tx_details(self, tx_hash):
407 tx_details = self.wallet.get_tx_details(tx_hash)
408 QMessageBox.information(self, 'Details', tx_details, 'OK')
411 def tx_label_clicked(self, item, column):
412 if column==2 and item.isSelected():
413 tx_hash = str(item.toolTip(0))
415 #if not self.wallet.labels.get(tx_hash): item.setText(2,'')
416 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
417 self.history_list.editItem( item, column )
418 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
421 def tx_label_changed(self, item, column):
425 tx_hash = str(item.toolTip(0))
426 tx = self.wallet.transactions.get(tx_hash)
427 s = self.wallet.labels.get(tx_hash)
428 text = unicode( item.text(2) )
430 self.wallet.labels[tx_hash] = text
431 item.setForeground(2, QBrush(QColor('black')))
433 if s: self.wallet.labels.pop(tx_hash)
434 text = self.wallet.get_default_label(tx_hash)
435 item.setText(2, text)
436 item.setForeground(2, QBrush(QColor('gray')))
440 def edit_label(self, is_recv):
441 l = self.receive_list if is_recv else self.contacts_list
442 c = 2 if is_recv else 1
443 item = l.currentItem()
444 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
445 l.editItem( item, c )
446 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
450 def address_label_clicked(self, item, column, l, column_addr, column_label):
451 if column == column_label and item.isSelected():
452 addr = unicode( item.text(column_addr) )
453 label = unicode( item.text(column_label) )
454 if label in self.wallet.aliases.keys():
456 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
457 l.editItem( item, column )
458 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
461 def address_label_changed(self, item, column, l, column_addr, column_label):
463 if column == column_label:
464 addr = unicode( item.text(column_addr) )
465 text = unicode( item.text(column_label) )
469 if text not in self.wallet.aliases.keys():
470 old_addr = self.wallet.labels.get(text)
472 self.wallet.labels[addr] = text
475 print_error("Error: This is one of your aliases")
476 label = self.wallet.labels.get(addr,'')
477 item.setText(column_label, QString(label))
479 s = self.wallet.labels.get(addr)
481 self.wallet.labels.pop(addr)
485 self.update_history_tab()
486 self.update_completions()
488 self.recv_changed(item)
491 def recv_changed(self, a):
492 "current item changed"
493 if a is not None and self.qr_window:
494 address = str(a.text(1))
495 label = self.wallet.labels.get(address)
496 amount = self.wallet.requested_amounts.get(address)
497 self.qr_window.set_content( address, label, amount )
500 def update_history_tab(self):
502 self.history_list.clear()
503 for item in self.wallet.get_tx_history():
504 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
507 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
513 icon = QIcon(":icons/unconfirmed.png")
515 icon = QIcon(":icons/clock%d.png"%conf)
517 icon = QIcon(":icons/confirmed.png")
520 icon = QIcon(":icons/unconfirmed.png")
522 if value is not None:
523 v_str = format_satoshis(value, True, self.wallet.num_zeros)
527 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
530 label, is_default_label = self.wallet.get_label(tx_hash)
532 label = _('Pruned transaction outputs')
533 is_default_label = False
535 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
536 item.setFont(2, QFont(MONOSPACE_FONT))
537 item.setFont(3, QFont(MONOSPACE_FONT))
538 item.setFont(4, QFont(MONOSPACE_FONT))
540 item.setToolTip(0, tx_hash)
542 item.setForeground(2, QBrush(QColor('grey')))
544 item.setIcon(0, icon)
545 self.history_list.insertTopLevelItem(0,item)
548 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
551 def create_send_tab(self):
556 grid.setColumnMinimumWidth(3,300)
557 grid.setColumnStretch(5,1)
559 self.payto_e = QLineEdit()
560 grid.addWidget(QLabel(_('Pay to')), 1, 0)
561 grid.addWidget(self.payto_e, 1, 1, 1, 3)
564 qrcode = qrscanner.scan_qr()
565 if 'address' in qrcode:
566 self.payto_e.setText(qrcode['address'])
567 if 'amount' in qrcode:
568 self.amount_e.setText(str(qrcode['amount']))
569 if 'label' in qrcode:
570 self.message_e.setText(qrcode['label'])
571 if 'message' in qrcode:
572 self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
575 if qrscanner.is_available():
576 b = QPushButton(_("Scan QR code"))
577 b.clicked.connect(fill_from_qr)
578 grid.addWidget(b, 1, 5)
580 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)
582 completer = QCompleter()
583 completer.setCaseSensitivity(False)
584 self.payto_e.setCompleter(completer)
585 completer.setModel(self.completions)
587 self.message_e = QLineEdit()
588 grid.addWidget(QLabel(_('Description')), 2, 0)
589 grid.addWidget(self.message_e, 2, 1, 1, 3)
590 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)
592 self.amount_e = QLineEdit()
593 grid.addWidget(QLabel(_('Amount')), 3, 0)
594 grid.addWidget(self.amount_e, 3, 1, 1, 2)
595 grid.addWidget(HelpButton(
596 _('Amount to be sent.') + '\n\n' \
597 + _('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)
599 self.fee_e = QLineEdit()
600 grid.addWidget(QLabel(_('Fee')), 4, 0)
601 grid.addWidget(self.fee_e, 4, 1, 1, 2)
602 grid.addWidget(HelpButton(
603 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
604 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
605 + _('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)
607 b = EnterButton(_("Send"), self.do_send)
608 grid.addWidget(b, 6, 1)
610 b = EnterButton(_("Clear"),self.do_clear)
611 grid.addWidget(b, 6, 2)
613 self.payto_sig = QLabel('')
614 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
616 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
617 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
626 def entry_changed( is_fee ):
627 self.funds_error = False
628 amount = numbify(self.amount_e)
629 fee = numbify(self.fee_e)
630 if not is_fee: fee = None
633 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
635 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
638 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
641 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
642 self.funds_error = True
643 self.amount_e.setPalette(palette)
644 self.fee_e.setPalette(palette)
646 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
647 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
652 def update_completions(self):
654 for addr,label in self.wallet.labels.items():
655 if addr in self.wallet.addressbook:
656 l.append( label + ' <' + addr + '>')
657 l = l + self.wallet.aliases.keys()
659 self.completions.setStringList(l)
665 label = unicode( self.message_e.text() )
666 r = unicode( self.payto_e.text() )
670 m1 = re.match(ALIAS_REGEXP, r)
671 # label or alias, with address in brackets
672 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
675 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
679 to_address = m2.group(2)
683 if not self.wallet.is_valid(to_address):
684 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
688 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
690 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
693 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
695 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
698 if self.wallet.use_encryption:
699 password = self.password_dialog()
706 tx = self.wallet.mktx( to_address, amount, label, password, fee)
707 except BaseException, e:
708 self.show_message(str(e))
711 h = self.wallet.send_tx(tx)
712 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
713 status, msg = self.wallet.receive_tx( h )
716 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
718 self.update_contacts_tab()
720 QMessageBox.warning(self, _('Error'), msg, _('OK'))
723 def set_url(self, url):
724 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
725 self.tabs.setCurrentIndex(1)
726 label = self.wallet.labels.get(payto)
727 m_addr = label + ' <'+ payto+'>' if label else payto
728 self.payto_e.setText(m_addr)
730 self.message_e.setText(message)
731 self.amount_e.setText(amount)
733 self.set_frozen(self.payto_e,True)
734 self.set_frozen(self.amount_e,True)
735 self.set_frozen(self.message_e,True)
736 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
738 self.payto_sig.setVisible(False)
741 self.payto_sig.setVisible(False)
742 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
744 self.set_frozen(e,False)
746 def set_frozen(self,entry,frozen):
748 entry.setReadOnly(True)
749 entry.setFrame(False)
751 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
752 entry.setPalette(palette)
754 entry.setReadOnly(False)
757 palette.setColor(entry.backgroundRole(), QColor('white'))
758 entry.setPalette(palette)
761 def toggle_freeze(self,addr):
763 if addr in self.wallet.frozen_addresses:
764 self.wallet.unfreeze(addr)
766 self.wallet.freeze(addr)
767 self.update_receive_tab()
769 def toggle_priority(self,addr):
771 if addr in self.wallet.prioritized_addresses:
772 self.wallet.unprioritize(addr)
774 self.wallet.prioritize(addr)
775 self.update_receive_tab()
778 def create_list_tab(self, headers):
779 "generic tab creation method"
780 l = MyTreeWidget(self)
781 l.setColumnCount( len(headers) )
782 l.setHeaderLabels( headers )
792 vbox.addWidget(buttons)
797 buttons.setLayout(hbox)
802 def create_receive_tab(self):
803 l,w,hbox = self.create_list_tab([_('Flags'), _('Address'), _('Label'), _('Requested'), _('Balance'), _('Tx')])
804 l.setContextMenuPolicy(Qt.CustomContextMenu)
805 l.customContextMenuRequested.connect(self.create_receive_menu)
806 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,1,2))
807 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,1,2))
808 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.recv_changed(a))
809 self.receive_list = l
810 self.receive_buttons_hbox = hbox
811 self.qr_button = EnterButton(self.qr_button_text(), self.toggle_QR_window)
812 hbox.addWidget(self.qr_button)
813 self.print_button = EnterButton(_("Print QR"), self.print_qr)
814 self.print_button.setHidden(True)
815 hbox.addWidget(self.print_button)
817 self.details_button = EnterButton(self.details_button_text(), self.toggle_detailed_view)
818 hbox.addWidget(self.details_button)
824 self.qr_window.do_save()
825 self.show_message(_("QR code saved to file") + " " + self.qr_window.filename)
828 def request_amount_dialog(self, address):
829 # pick the first unused address
830 # print "please wait" in qr window
831 # enter amount in usd
834 d.setWindowTitle('Request payment')
837 vbox.addWidget(QLabel(address))
844 index = self.wallet.addresses.index(address)
848 label = self.wallet.labels.get(address,'invoice %04d'%(index+1))
849 grid.addWidget(QLabel(_('Label')), 0, 0)
850 label_e = QLineEdit()
851 label_e.setText(label)
852 grid.addWidget(label_e, 0, 1)
854 amount_e = QLineEdit()
855 amount_e.textChanged.connect(lambda: numbify(amount_e))
857 grid.addWidget(QLabel(_('Amount')), 1, 0)
858 grid.addWidget(amount_e, 1, 1, 1, 3)
860 vbox.addLayout(ok_cancel_buttons(d))
864 if not d.exec_(): return
866 amount = int( Decimal( unicode( amount_e.text())) * 100000000 )
869 self.wallet.requested_amounts[address] = amount
871 label = unicode(label_e.text())
873 self.wallet.labels[address] = label
875 self.update_receive_item(self.receive_list.currentItem())
878 self.qr_window.set_content( address, label, amount )
881 #self.receive_list.currentItem().setFocus(True)
884 def details_button_text(self):
885 return _('Hide details') if self.detailed_view else _('Show details')
887 def qr_button_text(self):
888 return _('Hide QR') if self.qr_window else _('Show QR')
891 def toggle_detailed_view(self):
892 self.detailed_view = not self.detailed_view
893 self.config.set_key('qt_detailed_view', self.detailed_view, True)
895 self.details_button.setText(self.details_button_text())
897 self.update_receive_tab()
898 self.update_contacts_tab()
901 def create_contacts_tab(self):
902 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
903 l.setContextMenuPolicy(Qt.CustomContextMenu)
904 l.customContextMenuRequested.connect(self.create_contact_menu)
905 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
906 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
907 self.contacts_list = l
908 self.contacts_buttons_hbox = hbox
909 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
914 def create_receive_menu(self, position):
915 # fixme: this function apparently has a side effect.
916 # if it is not called the menu pops up several times
917 #self.receive_list.selectedIndexes()
919 item = self.receive_list.itemAt(position)
921 addr = unicode(item.text(1))
923 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
924 if self.qr_window: menu.addAction(_("Request payment"), lambda: self.request_amount_dialog(addr))
925 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
926 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
928 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
929 menu.addAction(t, lambda: self.toggle_freeze(addr))
930 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
931 menu.addAction(t, lambda: self.toggle_priority(addr))
932 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
935 def payto(self, x, is_alias):
942 label = self.wallet.labels.get(addr)
943 m_addr = label + ' <' + addr + '>' if label else addr
944 self.tabs.setCurrentIndex(1)
945 self.payto_e.setText(m_addr)
946 self.amount_e.setFocus()
948 def delete_contact(self, x, is_alias):
949 if self.question("Do you want to remove %s from your list of contacts?"%x):
950 if not is_alias and x in self.wallet.addressbook:
951 self.wallet.addressbook.remove(x)
952 if x in self.wallet.labels.keys():
953 self.wallet.labels.pop(x)
954 elif is_alias and x in self.wallet.aliases:
955 self.wallet.aliases.pop(x)
956 self.update_history_tab()
957 self.update_contacts_tab()
958 self.update_completions()
960 def create_contact_menu(self, position):
961 # fixme: this function apparently has a side effect.
962 # if it is not called the menu pops up several times
963 #self.contacts_list.selectedIndexes()
965 item = self.contacts_list.itemAt(position)
967 addr = unicode(item.text(0))
968 label = unicode(item.text(1))
969 is_alias = label in self.wallet.aliases.keys()
970 x = label if is_alias else addr
972 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
973 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
974 menu.addAction(_("View QR code"),lambda: self.show_address_qrcode(addr))
976 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
978 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
979 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
980 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
983 def update_receive_item(self, item):
984 address = str( item.data(1,0).toString() )
986 flags = self.wallet.get_address_flags(address)
987 item.setData(0,0,flags)
989 label = self.wallet.labels.get(address,'')
990 item.setData(2,0,label)
992 amount_str = format_satoshis( self.wallet.requested_amounts.get(address,0) )
993 item.setData(3,0,amount_str)
995 c, u = self.wallet.get_addr_balance(address)
996 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
997 item.setData(4,0,balance)
999 if address in self.wallet.frozen_addresses:
1000 item.setBackgroundColor(1, QColor('lightblue'))
1001 elif address in self.wallet.prioritized_addresses:
1002 item.setBackgroundColor(1, QColor('lightgreen'))
1005 def update_receive_tab(self):
1006 l = self.receive_list
1009 l.setColumnHidden(0, not self.detailed_view)
1010 l.setColumnHidden(3, self.qr_window is None)
1011 l.setColumnHidden(4, not self.detailed_view)
1012 l.setColumnHidden(5, not self.detailed_view)
1013 l.setColumnWidth(0, 50)
1014 l.setColumnWidth(1, 310)
1015 l.setColumnWidth(2, 200)
1016 l.setColumnWidth(3, 130)
1017 l.setColumnWidth(4, 130)
1018 l.setColumnWidth(5, 10)
1022 for address in self.wallet.all_addresses():
1024 if self.wallet.is_change(address) and not self.detailed_view:
1028 h = self.wallet.history.get(address,[])
1031 for tx_hash, tx_height in h:
1032 tx = self.wallet.transactions.get(tx_hash)
1040 if address in self.wallet.addresses:
1042 if gap > self.wallet.gap_limit:
1045 if address in self.wallet.addresses:
1048 item = QTreeWidgetItem( [ '', address, '', '', '', num_tx] )
1049 item.setFont(0, QFont(MONOSPACE_FONT))
1050 item.setFont(1, QFont(MONOSPACE_FONT))
1051 item.setFont(3, QFont(MONOSPACE_FONT))
1052 self.update_receive_item(item)
1053 if is_red and address in self.wallet.addresses:
1054 item.setBackgroundColor(1, QColor('red'))
1055 l.addTopLevelItem(item)
1057 # we use column 1 because column 0 may be hidden
1058 l.setCurrentItem(l.topLevelItem(0),1)
1060 def show_contact_details(self, m):
1061 a = self.wallet.aliases.get(m)
1063 if a[0] in self.wallet.authorities.keys():
1064 s = self.wallet.authorities.get(a[0])
1067 msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
1068 QMessageBox.information(self, 'Alias', msg, 'OK')
1070 def update_contacts_tab(self):
1072 l = self.contacts_list
1074 l.setColumnHidden(2, not self.detailed_view)
1075 l.setColumnWidth(0, 350)
1076 l.setColumnWidth(1, 330)
1077 l.setColumnWidth(2, 100)
1080 for alias, v in self.wallet.aliases.items():
1082 alias_targets.append(target)
1083 item = QTreeWidgetItem( [ target, alias, '-'] )
1084 item.setBackgroundColor(0, QColor('lightgray'))
1085 l.addTopLevelItem(item)
1087 for address in self.wallet.addressbook:
1088 if address in alias_targets: continue
1089 label = self.wallet.labels.get(address,'')
1091 for item in self.wallet.transactions.values():
1092 if address in item['outputs'] : n=n+1
1094 item = QTreeWidgetItem( [ address, label, tx] )
1095 item.setFont(0, QFont(MONOSPACE_FONT))
1096 l.addTopLevelItem(item)
1098 l.setCurrentItem(l.topLevelItem(0))
1100 def create_wall_tab(self):
1101 self.textbox = textbox = QTextEdit(self)
1102 textbox.setFont(QFont(MONOSPACE_FONT))
1103 textbox.setReadOnly(True)
1106 def create_status_bar(self):
1108 sb.setFixedHeight(35)
1109 if self.wallet.seed:
1110 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
1111 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), "Preferences", self.settings_dialog ) )
1112 if self.wallet.seed:
1113 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
1114 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) )
1115 sb.addPermanentWidget( self.status_button )
1116 self.setStatusBar(sb)
1118 def new_contact_dialog(self):
1119 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1120 address = unicode(text)
1122 if self.wallet.is_valid(address):
1123 self.wallet.addressbook.append(address)
1125 self.update_contacts_tab()
1126 self.update_history_tab()
1127 self.update_completions()
1129 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1132 def show_seed_dialog(wallet, parent=None):
1134 QMessageBox.information(parent, _('Message'),
1135 _('No seed'), _('OK'))
1138 if wallet.use_encryption:
1139 password = parent.password_dialog()
1146 seed = wallet.pw_decode(wallet.seed, password)
1148 QMessageBox.warning(parent, _('Error'),
1149 _('Incorrect Password'), _('OK'))
1152 dialog = QDialog(None)
1154 dialog.setWindowTitle("Electrum")
1156 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1158 msg = _("Your wallet generation seed is") +":<p>\"" + brainwallet + "\"<p>" \
1159 + _("Please write down or memorize these 12 words (order is important).") + " " \
1160 + _("This seed will allow you to recover your wallet in case of computer failure.") + "<p>" \
1161 + _("WARNING: Never disclose your seed. Never type it on a website.") + "<p>"
1163 main_text = QLabel(msg)
1164 main_text.setWordWrap(True)
1167 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1174 copy_function = lambda: app.clipboard().setText(brainwallet)
1175 copy_button = QPushButton(_("Copy to Clipboard"))
1176 copy_button.clicked.connect(copy_function)
1178 show_qr_function = lambda: ElectrumWindow.show_seed_qrcode(seed)
1179 qr_button = QPushButton(_("View as QR Code"))
1180 qr_button.clicked.connect(show_qr_function)
1182 ok_button = QPushButton(_("OK"))
1183 ok_button.setDefault(True)
1184 ok_button.clicked.connect(dialog.accept)
1186 main_layout = QGridLayout()
1187 main_layout.addWidget(logo, 0, 0)
1188 main_layout.addWidget(main_text, 0, 1, 1, -1)
1189 main_layout.addWidget(copy_button, 1, 1)
1190 main_layout.addWidget(qr_button, 1, 2)
1191 main_layout.addWidget(ok_button, 1, 3)
1192 dialog.setLayout(main_layout)
1197 def show_seed_qrcode(seed):
1201 d.setWindowTitle(_("Seed"))
1202 d.setMinimumSize(270, 300)
1203 vbox = QVBoxLayout()
1204 vbox.addWidget(QRCodeWidget(seed))
1205 hbox = QHBoxLayout()
1207 b = QPushButton(_("OK"))
1209 b.clicked.connect(d.accept)
1211 vbox.addLayout(hbox)
1215 def sign_message(self,address):
1216 if not address: return
1219 d.setWindowTitle('Sign Message')
1220 d.setMinimumSize(270, 350)
1222 tab_widget = QTabWidget()
1224 layout = QGridLayout(tab)
1226 sign_address = QLineEdit()
1227 sign_address.setText(address)
1228 layout.addWidget(QLabel(_('Address')), 1, 0)
1229 layout.addWidget(sign_address, 1, 1)
1231 sign_message = QTextEdit()
1232 layout.addWidget(QLabel(_('Message')), 2, 0)
1233 layout.addWidget(sign_message, 2, 1, 2, 1)
1235 sign_signature = QLineEdit()
1236 layout.addWidget(QLabel(_('Signature')), 3, 0)
1237 layout.addWidget(sign_signature, 3, 1)
1240 if self.wallet.use_encryption:
1241 password = self.password_dialog()
1248 signature = self.wallet.sign_message(sign_address.text(), sign_message.toPlainText(), password)
1249 sign_signature.setText(signature)
1250 except BaseException, e:
1251 self.show_message(str(e))
1254 hbox = QHBoxLayout()
1255 b = QPushButton(_("Sign"))
1257 b.clicked.connect(do_sign)
1258 b = QPushButton(_("Close"))
1259 b.clicked.connect(d.accept)
1261 layout.addLayout(hbox, 4, 1)
1262 tab_widget.addTab(tab, "Sign")
1266 layout = QGridLayout(tab)
1268 verify_address = QLineEdit()
1269 layout.addWidget(QLabel(_('Address')), 1, 0)
1270 layout.addWidget(verify_address, 1, 1)
1272 verify_message = QTextEdit()
1273 layout.addWidget(QLabel(_('Message')), 2, 0)
1274 layout.addWidget(verify_message, 2, 1, 2, 1)
1276 verify_signature = QLineEdit()
1277 layout.addWidget(QLabel(_('Signature')), 3, 0)
1278 layout.addWidget(verify_signature, 3, 1)
1282 self.wallet.verify_message(verify_address.text(), verify_signature.text(), verify_message.toPlainText())
1283 self.show_message("Signature verified")
1284 except BaseException, e:
1285 self.show_message(str(e))
1288 hbox = QHBoxLayout()
1289 b = QPushButton(_("Verify"))
1290 b.clicked.connect(do_verify)
1292 b = QPushButton(_("Close"))
1293 b.clicked.connect(d.accept)
1295 layout.addLayout(hbox, 4, 1)
1296 tab_widget.addTab(tab, "Verify")
1298 vbox = QVBoxLayout()
1299 vbox.addWidget(tab_widget)
1304 def toggle_QR_window(self):
1306 self.qr_window.close()
1307 self.qr_window = None
1309 self.qr_window = QR_Window()
1310 item = self.receive_list.currentItem()
1312 address = str(item.text(1))
1313 label = self.wallet.labels.get(address)
1314 amount = self.wallet.requested_amounts.get(address)
1315 self.qr_window.set_content( address, label, amount )
1316 self.qr_window.show()
1318 self.qr_button.setText(self.qr_button_text())
1319 self.print_button.setHidden(self.qr_window is None)
1320 self.update_receive_tab()
1322 def question(self, msg):
1323 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1325 def show_message(self, msg):
1326 QMessageBox.information(self, _('Message'), msg, _('OK'))
1328 def password_dialog(self ):
1335 vbox = QVBoxLayout()
1336 msg = _('Please enter your password')
1337 vbox.addWidget(QLabel(msg))
1339 grid = QGridLayout()
1341 grid.addWidget(QLabel(_('Password')), 1, 0)
1342 grid.addWidget(pw, 1, 1)
1343 vbox.addLayout(grid)
1345 vbox.addLayout(ok_cancel_buttons(d))
1348 if not d.exec_(): return
1349 return unicode(pw.text())
1356 def change_password_dialog( wallet, parent=None ):
1359 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1367 new_pw = QLineEdit()
1368 new_pw.setEchoMode(2)
1369 conf_pw = QLineEdit()
1370 conf_pw.setEchoMode(2)
1372 vbox = QVBoxLayout()
1374 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1375 +_('To disable wallet encryption, enter an empty new password.')) \
1376 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1378 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1379 +_("Leave these fields empty if you want to disable encryption.")
1380 vbox.addWidget(QLabel(msg))
1382 grid = QGridLayout()
1385 if wallet.use_encryption:
1386 grid.addWidget(QLabel(_('Password')), 1, 0)
1387 grid.addWidget(pw, 1, 1)
1389 grid.addWidget(QLabel(_('New Password')), 2, 0)
1390 grid.addWidget(new_pw, 2, 1)
1392 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1393 grid.addWidget(conf_pw, 3, 1)
1394 vbox.addLayout(grid)
1396 vbox.addLayout(ok_cancel_buttons(d))
1399 if not d.exec_(): return
1401 password = unicode(pw.text()) if wallet.use_encryption else None
1402 new_password = unicode(new_pw.text())
1403 new_password2 = unicode(conf_pw.text())
1406 seed = wallet.pw_decode( wallet.seed, password)
1408 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1411 if new_password != new_password2:
1412 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1415 wallet.update_password(seed, password, new_password)
1418 def seed_dialog(wallet, parent=None):
1422 vbox = QVBoxLayout()
1423 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1424 vbox.addWidget(QLabel(msg))
1426 grid = QGridLayout()
1429 seed_e = QLineEdit()
1430 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1431 grid.addWidget(seed_e, 1, 1)
1435 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1436 grid.addWidget(gap_e, 2, 1)
1437 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1438 vbox.addLayout(grid)
1440 vbox.addLayout(ok_cancel_buttons(d))
1443 if not d.exec_(): return
1446 gap = int(unicode(gap_e.text()))
1448 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1452 seed = unicode(seed_e.text())
1455 print_error("Warning: Not hex, trying decode")
1457 seed = mnemonic.mn_decode( seed.split(' ') )
1459 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1462 QMessageBox.warning(None, _('Error'), _('No seed'), 'OK')
1465 wallet.seed = str(seed)
1466 #print repr(wallet.seed)
1467 wallet.gap_limit = gap
1472 def settings_dialog(self):
1475 vbox = QVBoxLayout()
1476 msg = _('Here are the settings of your wallet.') + '\n'\
1477 + _('For more explanations, click on the help buttons next to each field.')
1480 label.setFixedWidth(250)
1481 label.setWordWrap(True)
1482 label.setAlignment(Qt.AlignJustify)
1483 vbox.addWidget(label)
1485 grid = QGridLayout()
1487 vbox.addLayout(grid)
1489 fee_label = QLabel(_('Transaction fee'))
1490 grid.addWidget(fee_label, 2, 0)
1492 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1493 grid.addWidget(fee_e, 2, 1)
1494 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1495 + _('Recommended value') + ': 0.001'
1496 grid.addWidget(HelpButton(msg), 2, 2)
1497 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1498 if not self.config.is_modifiable('fee'):
1499 for w in [fee_e, fee_label]: w.setEnabled(False)
1501 nz_label = QLabel(_('Display zeros'))
1502 grid.addWidget(nz_label, 3, 0)
1504 nz_e.setText("%d"% self.wallet.num_zeros)
1505 grid.addWidget(nz_e, 3, 1)
1506 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1507 grid.addWidget(HelpButton(msg), 3, 2)
1508 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1509 if not self.config.is_modifiable('num_zeros'):
1510 for w in [nz_e, nz_label]: w.setEnabled(False)
1512 usechange_cb = QCheckBox(_('Use change addresses'))
1513 grid.addWidget(usechange_cb, 5, 0)
1514 usechange_cb.setChecked(self.wallet.use_change)
1515 grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
1516 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1518 gap_label = QLabel(_('Gap limit'))
1519 grid.addWidget(gap_label, 6, 0)
1521 gap_e.setText("%d"% self.wallet.gap_limit)
1522 grid.addWidget(gap_e, 6, 1)
1523 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1524 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1525 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1526 + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1527 + _('Warning') + ': ' \
1528 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1529 + _('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'
1530 grid.addWidget(HelpButton(msg), 6, 2)
1531 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1532 if not self.config.is_modifiable('gap_limit'):
1533 for w in [gap_e, gap_label]: w.setEnabled(False)
1535 gui_label=QLabel(_('Default GUI') + ':')
1536 grid.addWidget(gui_label , 7, 0)
1537 gui_combo = QComboBox()
1538 gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
1539 index = gui_combo.findText(self.config.get("gui","classic").capitalize())
1540 if index==-1: index = 1
1541 gui_combo.setCurrentIndex(index)
1542 grid.addWidget(gui_combo, 7, 1)
1543 grid.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
1544 if not self.config.is_modifiable('gui'):
1545 for w in [gui_combo, gui_label]: w.setEnabled(False)
1547 vbox.addLayout(ok_cancel_buttons(d))
1551 if not d.exec_(): return
1553 fee = unicode(fee_e.text())
1555 fee = int( 100000000 * Decimal(fee) )
1557 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1560 if self.wallet.fee != fee:
1561 self.wallet.fee = fee
1564 nz = unicode(nz_e.text())
1569 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
1572 if self.wallet.num_zeros != nz:
1573 self.wallet.num_zeros = nz
1574 self.config.set_key('num_zeros', nz, True)
1575 self.update_history_tab()
1576 self.update_receive_tab()
1578 if self.wallet.use_change != usechange_cb.isChecked():
1579 self.wallet.use_change = usechange_cb.isChecked()
1580 self.config.set_key('use_change', self.wallet.use_change, True)
1583 n = int(gap_e.text())
1585 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1588 if self.wallet.gap_limit != n:
1589 r = self.wallet.change_gap_limit(n)
1591 self.update_receive_tab()
1592 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
1594 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
1596 self.config.set_key("gui", str(gui_combo.currentText()).lower(), True)
1601 def network_dialog(wallet, parent=None):
1602 interface = wallet.interface
1604 if interface.is_connected:
1605 status = _("Connected to")+" %s\n%d blocks"%(interface.host, wallet.verifier.height)
1607 status = _("Not connected")
1608 server = interface.server
1611 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
1612 server = interface.server
1614 plist, servers_list = interface.get_servers_list()
1618 d.setWindowTitle(_('Server'))
1619 d.setMinimumSize(375, 20)
1621 vbox = QVBoxLayout()
1624 hbox = QHBoxLayout()
1626 l.setPixmap(QPixmap(":icons/network.png"))
1629 hbox.addWidget(QLabel(status))
1631 vbox.addLayout(hbox)
1635 grid = QGridLayout()
1637 vbox.addLayout(grid)
1640 server_protocol = QComboBox()
1641 server_host = QLineEdit()
1642 server_host.setFixedWidth(200)
1643 server_port = QLineEdit()
1644 server_port.setFixedWidth(60)
1646 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
1647 protocol_letters = 'thsg'
1648 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
1649 server_protocol.addItems(protocol_names)
1651 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
1652 grid.addWidget(server_protocol, 0, 1)
1653 grid.addWidget(server_host, 0, 2)
1654 grid.addWidget(server_port, 0, 3)
1656 def change_protocol(p):
1657 protocol = protocol_letters[p]
1658 host = unicode(server_host.text())
1659 pp = plist.get(host,DEFAULT_PORTS)
1660 if protocol not in pp.keys():
1661 protocol = pp.keys()[0]
1663 server_host.setText( host )
1664 server_port.setText( port )
1666 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
1668 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
1669 servers_list_widget = QTreeWidget(parent)
1670 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
1671 servers_list_widget.setMaximumHeight(150)
1672 servers_list_widget.setColumnWidth(0, 240)
1673 for _host in servers_list.keys():
1674 _type = 'pruning' if servers_list[_host].get('pruning') else 'full'
1675 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
1677 def change_server(host, protocol=None):
1678 pp = plist.get(host,DEFAULT_PORTS)
1680 port = pp.get(protocol)
1681 if not port: protocol = None
1684 if 't' in pp.keys():
1686 port = pp.get(protocol)
1688 protocol = pp.keys()[0]
1689 port = pp.get(protocol)
1691 server_host.setText( host )
1692 server_port.setText( port )
1693 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
1695 if not plist: return
1696 for p in protocol_letters:
1697 i = protocol_letters.index(p)
1698 j = server_protocol.model().index(i,0)
1699 if p not in pp.keys():
1700 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
1702 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
1706 host, port, protocol = server.split(':')
1707 change_server(host,protocol)
1709 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
1710 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
1712 if not wallet.config.is_modifiable('server'):
1713 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
1716 proxy_mode = QComboBox()
1717 proxy_host = QLineEdit()
1718 proxy_host.setFixedWidth(200)
1719 proxy_port = QLineEdit()
1720 proxy_port.setFixedWidth(60)
1721 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
1723 def check_for_disable(index = False):
1724 if proxy_mode.currentText() != 'NONE':
1725 proxy_host.setEnabled(True)
1726 proxy_port.setEnabled(True)
1728 proxy_host.setEnabled(False)
1729 proxy_port.setEnabled(False)
1732 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
1734 if not wallet.config.is_modifiable('proxy'):
1735 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
1737 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
1738 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
1739 proxy_host.setText(proxy_config.get("host"))
1740 proxy_port.setText(proxy_config.get("port"))
1742 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
1743 grid.addWidget(proxy_mode, 2, 1)
1744 grid.addWidget(proxy_host, 2, 2)
1745 grid.addWidget(proxy_port, 2, 3)
1748 vbox.addLayout(ok_cancel_buttons(d))
1751 if not d.exec_(): return
1753 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
1754 if proxy_mode.currentText() != 'NONE':
1755 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
1759 wallet.config.set_key("proxy", proxy, True)
1760 wallet.config.set_key("server", server, True)
1761 interface.set_server(server, proxy)
1765 def closeEvent(self, event):
1767 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
1773 def __init__(self, wallet, config, app=None):
1774 self.wallet = wallet
1775 self.config = config
1777 self.app = QApplication(sys.argv)
1780 def restore_or_create(self):
1781 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
1782 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
1783 if r==2: return None
1784 return 'restore' if r==1 else 'create'
1786 def seed_dialog(self):
1787 return ElectrumWindow.seed_dialog( self.wallet )
1789 def network_dialog(self):
1790 return ElectrumWindow.network_dialog( self.wallet, parent=None )
1793 def show_seed(self):
1794 ElectrumWindow.show_seed_dialog(self.wallet)
1797 def password_dialog(self):
1798 ElectrumWindow.change_password_dialog(self.wallet)
1801 def restore_wallet(self):
1802 wallet = self.wallet
1803 # wait until we are connected, because the user might have selected another server
1804 if not wallet.interface.is_connected:
1805 waiting = lambda: False if wallet.interface.is_connected else "connecting...\n"
1806 waiting_dialog(waiting)
1808 waiting = lambda: False if wallet.is_up_to_date() else "Please wait...\nAddresses generated: %d\nKilobytes received: %.1f"\
1809 %(len(wallet.all_addresses()), wallet.interface.bytes_received/1024.)
1811 wallet.set_up_to_date(False)
1812 wallet.interface.poke('synchronizer')
1813 waiting_dialog(waiting)
1814 if wallet.is_found():
1815 print_error( "Recovery successful" )
1817 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
1824 w = ElectrumWindow(self.wallet, self.config)
1825 if url: w.set_url(url)