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
20 from i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
23 from qrcodewidget import QRCodeWidget
28 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
30 from PyQt4.QtGui import *
31 from PyQt4.QtCore import *
32 import PyQt4.QtCore as QtCore
33 import PyQt4.QtGui as QtGui
34 from electrum.interface import DEFAULT_SERVERS
39 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'")
41 from electrum.wallet import format_satoshis
42 from electrum.bitcoin import Transaction, is_valid
43 from electrum import mnemonic
44 from electrum import util, bitcoin, commands
46 import bmp, pyqrnative
49 from decimal import Decimal
57 if platform.system() == 'Windows':
58 MONOSPACE_FONT = 'Lucida Console'
59 elif platform.system() == 'Darwin':
60 MONOSPACE_FONT = 'Monaco'
62 MONOSPACE_FONT = 'monospace'
64 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
66 from electrum import ELECTRUM_VERSION
69 class UpdateLabel(QtGui.QLabel):
70 def __init__(self, config, parent=None):
71 QtGui.QLabel.__init__(self, parent)
72 self.new_version = False
75 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
76 con.request("GET", "/version")
77 res = con.getresponse()
78 except socket.error as msg:
79 print_error("Could not retrieve version information")
83 self.latest_version = res.read()
84 self.latest_version = self.latest_version.replace("\n","")
85 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
87 self.current_version = ELECTRUM_VERSION
88 if(self.compare_versions(self.latest_version, self.current_version) == 1):
89 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
90 if(self.compare_versions(self.latest_version, latest_seen) == 1):
91 self.new_version = True
92 self.setText(_("New version available") + ": " + self.latest_version)
95 def compare_versions(self, version1, version2):
97 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
98 return cmp(normalize(version1), normalize(version2))
100 def ignore_this_version(self):
102 self.config.set_key("last_seen_version", self.latest_version, True)
103 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
106 def ignore_all_version(self):
108 self.config.set_key("last_seen_version", "9.9.9", True)
109 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
112 def open_website(self):
113 webbrowser.open("http://electrum.org/download.html")
116 def mouseReleaseEvent(self, event):
117 dialog = QDialog(self)
118 dialog.setWindowTitle(_('Electrum update'))
121 main_layout = QGridLayout()
122 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
124 ignore_version = QPushButton(_("Ignore this version"))
125 ignore_version.clicked.connect(self.ignore_this_version)
127 ignore_all_versions = QPushButton(_("Ignore all versions"))
128 ignore_all_versions.clicked.connect(self.ignore_all_version)
130 open_website = QPushButton(_("Goto download page"))
131 open_website.clicked.connect(self.open_website)
133 main_layout.addWidget(ignore_version, 1, 0)
134 main_layout.addWidget(ignore_all_versions, 1, 1)
135 main_layout.addWidget(open_website, 1, 2)
137 dialog.setLayout(main_layout)
141 if not dialog.exec_(): return
143 def numbify(entry, is_int = False):
144 text = unicode(entry.text()).strip()
145 pos = entry.cursorPosition()
147 if not is_int: chars +='.'
148 s = ''.join([i for i in text if i in chars])
152 s = s.replace('.','')
153 s = s[:p] + '.' + s[p:p+8]
155 amount = int( Decimal(s) * 100000000 )
164 entry.setCursorPosition(pos)
168 class Timer(QtCore.QThread):
171 self.emit(QtCore.SIGNAL('timersignal'))
174 class HelpButton(QPushButton):
175 def __init__(self, text):
176 QPushButton.__init__(self, '?')
177 self.setFocusPolicy(Qt.NoFocus)
178 self.setFixedWidth(20)
179 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
182 class EnterButton(QPushButton):
183 def __init__(self, text, func):
184 QPushButton.__init__(self, text)
186 self.clicked.connect(func)
188 def keyPressEvent(self, e):
189 if e.key() == QtCore.Qt.Key_Return:
192 class MyTreeWidget(QTreeWidget):
193 def __init__(self, parent):
194 QTreeWidget.__init__(self, parent)
197 for i in range(0,self.viewport().height()/5):
198 if self.itemAt(QPoint(0,i*5)) == item:
202 for j in range(0,30):
203 if self.itemAt(QPoint(0,i*5 + j)) != item:
205 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
207 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
212 class StatusBarButton(QPushButton):
213 def __init__(self, icon, tooltip, func):
214 QPushButton.__init__(self, icon, '')
215 self.setToolTip(tooltip)
217 self.setMaximumWidth(25)
218 self.clicked.connect(func)
221 def keyPressEvent(self, e):
222 if e.key() == QtCore.Qt.Key_Return:
230 def waiting_dialog(f):
236 w.setWindowTitle('Electrum')
246 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
251 def ok_cancel_buttons(dialog):
254 b = QPushButton("Cancel")
256 b.clicked.connect(dialog.reject)
257 b = QPushButton("OK")
259 b.clicked.connect(dialog.accept)
264 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
265 "receive":[[370],[370,200,130]] }
267 class ElectrumWindow(QMainWindow):
269 def __init__(self, wallet, config):
270 QMainWindow.__init__(self)
276 self.create_status_bar()
278 self.wallet.interface.register_callback('updated', lambda: self.emit(QtCore.SIGNAL('update_wallet')))
279 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
280 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
281 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
283 self.expert_mode = config.get('classic_expert_mode', False)
285 set_language(config.get('language'))
287 self.funds_error = False
288 self.completions = QStringListModel()
290 self.tabs = tabs = QTabWidget(self)
291 self.column_widths = self.config.get("column-widths", default_column_widths )
292 tabs.addTab(self.create_history_tab(), _('History') )
293 tabs.addTab(self.create_send_tab(), _('Send') )
294 tabs.addTab(self.create_receive_tab(), _('Receive') )
295 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
296 tabs.addTab(self.create_console_tab(), _('Console') )
297 tabs.setMinimumSize(600, 400)
298 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
299 self.setCentralWidget(tabs)
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 += ' [%s]' % (_('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('update_wallet'), self.update_wallet)
313 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
314 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.banner) )
315 self.history_list.setFocus(True)
317 self.exchanger = exchange_rate.Exchanger(self)
318 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
320 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
321 if platform.system() == 'Windows':
322 n = 3 if self.wallet.seed else 2
323 tabs.setCurrentIndex (n)
324 tabs.setCurrentIndex (0)
326 # set initial message
327 self.console.showMessage(self.wallet.banner)
331 def init_plugins(self):
333 if os.path.exists("plugins"):
334 fp, pathname, description = imp.find_module('plugins')
335 imp.load_module('electrum_plugins', fp, pathname, description)
336 plugin_names = [name for a, name, b in pkgutil.iter_modules(['plugins'])]
337 self.plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
339 import electrum_plugins
340 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
341 self.plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
343 self.plugin_hooks = {}
344 for p in self.plugins:
348 print_msg("Error:cannot initialize plugin",p)
349 traceback.print_exc(file=sys.stdout)
351 def set_hook(self, name, callback):
352 h = self.plugin_hooks.get(name, [])
354 self.plugin_hooks[name] = h
356 def unset_hook(self, name, callback):
357 h = self.plugin_hooks.get(name,[])
358 if callback in h: h.remove(callback)
359 self.plugin_hooks[name] = h
361 def run_hook(self, name, args):
362 for cb in self.plugin_hooks.get(name,[]):
366 def set_label(self, name, text = None):
368 old_text = self.wallet.labels.get(name)
371 self.wallet.labels[name] = text
375 self.wallet.labels.pop(name)
377 run_hook('set_label', name, text, changed)
381 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
382 def getOpenFileName(self, title, filter = None):
383 directory = self.config.get('io_dir', os.path.expanduser('~'))
384 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
385 if fileName and directory != os.path.dirname(fileName):
386 self.config.set_key('io_dir', os.path.dirname(fileName), True)
389 def getSaveFileName(self, title, filename, filter = None):
390 directory = self.config.get('io_dir', os.path.expanduser('~'))
391 path = os.path.join( directory, filename )
392 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
393 if fileName and directory != os.path.dirname(fileName):
394 self.config.set_key('io_dir', os.path.dirname(fileName), True)
400 QMainWindow.close(self)
401 self.run_hook('close_main_window', (self,))
403 def connect_slots(self, sender):
404 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
405 self.previous_payto_e=''
407 def timer_actions(self):
408 self.run_hook('timer_actions', (self,))
410 if self.payto_e.hasFocus():
412 r = unicode( self.payto_e.text() )
413 if r != self.previous_payto_e:
414 self.previous_payto_e = r
416 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
418 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
422 s = r + ' <' + to_address + '>'
423 self.payto_e.setText(s)
427 def update_status(self):
428 if self.wallet.interface and self.wallet.interface.is_connected:
429 if not self.wallet.up_to_date:
430 text = _("Synchronizing...")
431 icon = QIcon(":icons/status_waiting.png")
433 c, u = self.wallet.get_balance()
434 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
435 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
436 text += self.create_quote_text(Decimal(c+u)/100000000)
437 icon = QIcon(":icons/status_connected.png")
439 text = _("Not connected")
440 icon = QIcon(":icons/status_disconnected.png")
442 self.status_text = text
443 self.statusBar().showMessage(text)
444 self.status_button.setIcon( icon )
446 def update_wallet(self):
448 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
449 self.update_history_tab()
450 self.update_receive_tab()
451 self.update_contacts_tab()
452 self.update_completions()
455 def create_quote_text(self, btc_balance):
456 quote_currency = self.config.get("currency", "None")
457 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
458 if quote_balance is None:
461 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
464 def create_history_tab(self):
465 self.history_list = l = MyTreeWidget(self)
467 for i,width in enumerate(self.column_widths['history']):
468 l.setColumnWidth(i, width)
469 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
470 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
471 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
473 l.setContextMenuPolicy(Qt.CustomContextMenu)
474 l.customContextMenuRequested.connect(self.create_history_menu)
478 def create_history_menu(self, position):
479 self.history_list.selectedIndexes()
480 item = self.history_list.currentItem()
482 tx_hash = str(item.data(0, Qt.UserRole).toString())
483 if not tx_hash: return
485 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
486 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
487 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
488 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
491 def show_tx_details(self, tx):
492 dialog = QDialog(self)
494 dialog.setWindowTitle(_("Transaction Details"))
496 dialog.setLayout(vbox)
497 dialog.setMinimumSize(600,300)
500 if tx_hash in self.wallet.transactions.keys():
501 is_mine, v, fee = self.wallet.get_tx_value(tx)
502 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
504 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
510 vbox.addWidget(QLabel("Transaction ID:"))
511 e = QLineEdit(tx_hash)
515 vbox.addWidget(QLabel("Date: %s"%time_str))
516 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
519 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
520 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
522 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
523 vbox.addWidget(QLabel("Transaction fee: unknown"))
525 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
527 vbox.addWidget( self.generate_transaction_information_widget(tx) )
529 ok_button = QPushButton(_("Close"))
530 ok_button.setDefault(True)
531 ok_button.clicked.connect(dialog.accept)
535 hbox.addWidget(ok_button)
539 def tx_label_clicked(self, item, column):
540 if column==2 and item.isSelected():
542 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
543 self.history_list.editItem( item, column )
544 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
547 def tx_label_changed(self, item, column):
551 tx_hash = str(item.data(0, Qt.UserRole).toString())
552 tx = self.wallet.transactions.get(tx_hash)
553 text = unicode( item.text(2) )
554 self.set_label(tx_hash, text)
556 item.setForeground(2, QBrush(QColor('black')))
558 text = self.wallet.get_default_label(tx_hash)
559 item.setText(2, text)
560 item.setForeground(2, QBrush(QColor('gray')))
564 def edit_label(self, is_recv):
565 l = self.receive_list if is_recv else self.contacts_list
566 item = l.currentItem()
567 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
568 l.editItem( item, 1 )
569 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
573 def address_label_clicked(self, item, column, l, column_addr, column_label):
574 if column == column_label and item.isSelected():
575 addr = unicode( item.text(column_addr) )
576 label = unicode( item.text(column_label) )
577 if label in self.wallet.aliases.keys():
579 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
580 l.editItem( item, column )
581 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
584 def address_label_changed(self, item, column, l, column_addr, column_label):
585 if column == column_label:
586 addr = unicode( item.text(column_addr) )
587 text = unicode( item.text(column_label) )
590 if text in self.wallet.aliases.keys():
591 print_error("Error: This is one of your aliases")
592 label = self.wallet.labels.get(addr,'')
593 item.setText(column_label, QString(label))
596 changed = self.set_label(addr, text)
598 self.update_history_tab()
599 self.update_completions()
601 self.current_item_changed(item)
603 self.run_hook('item_changed',(self, item, column))
606 def current_item_changed(self, a):
607 self.run_hook('current_item_changed',(self, a))
611 def update_history_tab(self):
613 self.history_list.clear()
614 for item in self.wallet.get_tx_history():
615 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
618 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
624 icon = QIcon(":icons/unconfirmed.png")
626 icon = QIcon(":icons/clock%d.png"%conf)
628 icon = QIcon(":icons/confirmed.png")
631 icon = QIcon(":icons/unconfirmed.png")
633 if value is not None:
634 v_str = format_satoshis(value, True, self.wallet.num_zeros)
638 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
641 label, is_default_label = self.wallet.get_label(tx_hash)
643 label = _('Pruned transaction outputs')
644 is_default_label = False
646 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
647 item.setFont(2, QFont(MONOSPACE_FONT))
648 item.setFont(3, QFont(MONOSPACE_FONT))
649 item.setFont(4, QFont(MONOSPACE_FONT))
651 item.setForeground(3, QBrush(QColor("#BC1E1E")))
653 item.setData(0, Qt.UserRole, tx_hash)
654 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
656 item.setForeground(2, QBrush(QColor('grey')))
658 item.setIcon(0, icon)
659 self.history_list.insertTopLevelItem(0,item)
662 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
665 def create_send_tab(self):
670 grid.setColumnMinimumWidth(3,300)
671 grid.setColumnStretch(5,1)
673 self.payto_e = QLineEdit()
674 grid.addWidget(QLabel(_('Pay to')), 1, 0)
675 grid.addWidget(self.payto_e, 1, 1, 1, 3)
677 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)
679 completer = QCompleter()
680 completer.setCaseSensitivity(False)
681 self.payto_e.setCompleter(completer)
682 completer.setModel(self.completions)
684 self.message_e = QLineEdit()
685 grid.addWidget(QLabel(_('Description')), 2, 0)
686 grid.addWidget(self.message_e, 2, 1, 1, 3)
687 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)
689 self.amount_e = QLineEdit()
690 grid.addWidget(QLabel(_('Amount')), 3, 0)
691 grid.addWidget(self.amount_e, 3, 1, 1, 2)
692 grid.addWidget(HelpButton(
693 _('Amount to be sent.') + '\n\n' \
694 + _('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)
696 self.fee_e = QLineEdit()
697 grid.addWidget(QLabel(_('Fee')), 4, 0)
698 grid.addWidget(self.fee_e, 4, 1, 1, 2)
699 grid.addWidget(HelpButton(
700 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
701 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
702 + _('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)
705 b = EnterButton(_("Send"), self.do_send)
707 b = EnterButton(_("Create unsigned transaction"), self.do_send)
708 grid.addWidget(b, 6, 1)
710 b = EnterButton(_("Clear"),self.do_clear)
711 grid.addWidget(b, 6, 2)
713 self.payto_sig = QLabel('')
714 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
716 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
717 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
726 def entry_changed( is_fee ):
727 self.funds_error = False
728 amount = numbify(self.amount_e)
729 fee = numbify(self.fee_e)
730 if not is_fee: fee = None
733 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
735 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
738 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
739 text = self.status_text
742 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
743 self.funds_error = True
744 text = _( "Not enough funds" )
746 self.statusBar().showMessage(text)
747 self.amount_e.setPalette(palette)
748 self.fee_e.setPalette(palette)
750 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
751 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
753 self.run_hook('create_send_tab',(self,grid))
757 def update_completions(self):
759 for addr,label in self.wallet.labels.items():
760 if addr in self.wallet.addressbook:
761 l.append( label + ' <' + addr + '>')
762 l = l + self.wallet.aliases.keys()
764 self.completions.setStringList(l)
768 return lambda s, *args: s.do_protect(func, args)
772 def do_send(self, password):
774 label = unicode( self.message_e.text() )
775 r = unicode( self.payto_e.text() )
779 m1 = re.match(ALIAS_REGEXP, r)
780 # label or alias, with address in brackets
781 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
784 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
788 to_address = m2.group(2)
792 if not is_valid(to_address):
793 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
797 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
799 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
802 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
804 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
808 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
809 except BaseException, e:
810 self.show_message(str(e))
813 self.run_hook('send_tx', (self.wallet, self, tx))
816 self.set_label(tx.hash(), label)
819 h = self.wallet.send_tx(tx)
820 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
821 status, msg = self.wallet.receive_tx( h )
823 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
825 self.update_contacts_tab()
827 QMessageBox.warning(self, _('Error'), msg, _('OK'))
829 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
831 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
832 with open(fileName,'w') as f:
833 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
834 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
836 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
841 def set_url(self, url):
842 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
843 self.tabs.setCurrentIndex(1)
844 label = self.wallet.labels.get(payto)
845 m_addr = label + ' <'+ payto+'>' if label else payto
846 self.payto_e.setText(m_addr)
848 self.message_e.setText(message)
849 self.amount_e.setText(amount)
851 self.set_frozen(self.payto_e,True)
852 self.set_frozen(self.amount_e,True)
853 self.set_frozen(self.message_e,True)
854 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
856 self.payto_sig.setVisible(False)
859 self.payto_sig.setVisible(False)
860 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
862 self.set_frozen(e,False)
864 def set_frozen(self,entry,frozen):
866 entry.setReadOnly(True)
867 entry.setFrame(False)
869 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
870 entry.setPalette(palette)
872 entry.setReadOnly(False)
875 palette.setColor(entry.backgroundRole(), QColor('white'))
876 entry.setPalette(palette)
879 def toggle_freeze(self,addr):
881 if addr in self.wallet.frozen_addresses:
882 self.wallet.unfreeze(addr)
884 self.wallet.freeze(addr)
885 self.update_receive_tab()
887 def toggle_priority(self,addr):
889 if addr in self.wallet.prioritized_addresses:
890 self.wallet.unprioritize(addr)
892 self.wallet.prioritize(addr)
893 self.update_receive_tab()
896 def create_list_tab(self, headers):
897 "generic tab creation method"
898 l = MyTreeWidget(self)
899 l.setColumnCount( len(headers) )
900 l.setHeaderLabels( headers )
910 vbox.addWidget(buttons)
915 buttons.setLayout(hbox)
920 def create_receive_tab(self):
921 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
922 l.setContextMenuPolicy(Qt.CustomContextMenu)
923 l.customContextMenuRequested.connect(self.create_receive_menu)
924 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
925 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
926 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
927 self.receive_list = l
928 self.receive_buttons_hbox = hbox
933 def receive_tab_set_mode(self, i):
934 self.save_column_widths()
935 self.expert_mode = (i == 1)
936 self.config.set_key('classic_expert_mode', self.expert_mode, True)
938 self.update_receive_tab()
941 def save_column_widths(self):
942 if not self.expert_mode:
943 widths = [ self.receive_list.columnWidth(0) ]
946 for i in range(self.receive_list.columnCount() -1):
947 widths.append(self.receive_list.columnWidth(i))
948 self.column_widths["receive"][self.expert_mode] = widths
950 self.column_widths["history"] = []
951 for i in range(self.history_list.columnCount() - 1):
952 self.column_widths["history"].append(self.history_list.columnWidth(i))
954 self.column_widths["contacts"] = []
955 for i in range(self.contacts_list.columnCount() - 1):
956 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
959 def create_contacts_tab(self):
960 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
961 l.setContextMenuPolicy(Qt.CustomContextMenu)
962 l.customContextMenuRequested.connect(self.create_contact_menu)
963 for i,width in enumerate(self.column_widths['contacts']):
964 l.setColumnWidth(i, width)
966 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
967 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
968 self.contacts_list = l
969 self.contacts_buttons_hbox = hbox
970 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
975 def delete_imported_key(self, addr):
976 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
977 self.wallet.imported_keys.pop(addr)
978 self.update_receive_tab()
979 self.update_history_tab()
983 def create_receive_menu(self, position):
984 # fixme: this function apparently has a side effect.
985 # if it is not called the menu pops up several times
986 #self.receive_list.selectedIndexes()
988 item = self.receive_list.itemAt(position)
990 addr = unicode(item.text(0))
991 if not is_valid(addr):
992 item.setExpanded(not item.isExpanded())
995 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
996 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
997 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
998 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
999 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1000 if addr in self.wallet.imported_keys:
1001 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1003 if self.expert_mode:
1004 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1005 menu.addAction(t, lambda: self.toggle_freeze(addr))
1006 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1007 menu.addAction(t, lambda: self.toggle_priority(addr))
1009 self.run_hook('receive_menu', (self, menu,))
1010 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1013 def payto(self, x, is_alias):
1020 label = self.wallet.labels.get(addr)
1021 m_addr = label + ' <' + addr + '>' if label else addr
1022 self.tabs.setCurrentIndex(1)
1023 self.payto_e.setText(m_addr)
1024 self.amount_e.setFocus()
1026 def delete_contact(self, x, is_alias):
1027 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1028 if not is_alias and x in self.wallet.addressbook:
1029 self.wallet.addressbook.remove(x)
1030 self.set_label(x, None)
1031 elif is_alias and x in self.wallet.aliases:
1032 self.wallet.aliases.pop(x)
1033 self.update_history_tab()
1034 self.update_contacts_tab()
1035 self.update_completions()
1037 def create_contact_menu(self, position):
1038 # fixme: this function apparently has a side effect.
1039 # if it is not called the menu pops up several times
1040 #self.contacts_list.selectedIndexes()
1042 item = self.contacts_list.itemAt(position)
1044 addr = unicode(item.text(0))
1045 label = unicode(item.text(1))
1046 is_alias = label in self.wallet.aliases.keys()
1047 x = label if is_alias else addr
1049 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1050 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1051 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1053 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1055 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1056 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1057 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1060 def update_receive_item(self, item):
1061 item.setFont(0, QFont(MONOSPACE_FONT))
1062 address = str(item.data(0,0).toString())
1063 label = self.wallet.labels.get(address,'')
1064 item.setData(1,0,label)
1066 self.run_hook('update_receive_item', (self, address, item))
1068 c, u = self.wallet.get_addr_balance(address)
1069 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1070 item.setData(2,0,balance)
1072 if self.expert_mode:
1073 if address in self.wallet.frozen_addresses:
1074 item.setBackgroundColor(0, QColor('lightblue'))
1075 elif address in self.wallet.prioritized_addresses:
1076 item.setBackgroundColor(0, QColor('lightgreen'))
1079 def update_receive_tab(self):
1080 l = self.receive_list
1083 l.setColumnHidden(2, not self.expert_mode)
1084 l.setColumnHidden(3, not self.expert_mode)
1085 if not self.expert_mode:
1086 width = self.column_widths['receive'][0][0]
1087 l.setColumnWidth(0, width)
1089 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1090 l.setColumnWidth(i, width)
1093 for k, account in self.wallet.accounts.items():
1094 name = account.get('name',str(k))
1095 c,u = self.wallet.get_account_balance(k)
1096 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1097 l.addTopLevelItem(account_item)
1098 account_item.setExpanded(True)
1101 for is_change in [0,1]:
1102 name = "Receiving" if not is_change else "Change"
1103 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1104 account_item.addChild(seq_item)
1105 if not is_change: seq_item.setExpanded(True)
1109 for address in account[is_change]:
1110 h = self.wallet.history.get(address,[])
1115 if gap > self.wallet.gap_limit:
1120 num_tx = '*' if h == ['*'] else "%d"%len(h)
1121 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1122 self.update_receive_item(item)
1124 item.setBackgroundColor(1, QColor('red'))
1125 seq_item.addChild(item)
1127 if self.wallet.imported_keys:
1128 c,u = self.wallet.get_imported_balance()
1129 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1130 l.addTopLevelItem(account_item)
1131 account_item.setExpanded(True)
1132 for address in self.wallet.imported_keys.keys():
1133 item = QTreeWidgetItem( [ address, '', '', ''] )
1134 self.update_receive_item(item)
1135 account_item.addChild(item)
1138 # we use column 1 because column 0 may be hidden
1139 l.setCurrentItem(l.topLevelItem(0),1)
1141 def show_contact_details(self, m):
1142 a = self.wallet.aliases.get(m)
1144 if a[0] in self.wallet.authorities.keys():
1145 s = self.wallet.authorities.get(a[0])
1148 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1149 QMessageBox.information(self, 'Alias', msg, 'OK')
1151 def update_contacts_tab(self):
1153 l = self.contacts_list
1157 for alias, v in self.wallet.aliases.items():
1159 alias_targets.append(target)
1160 item = QTreeWidgetItem( [ target, alias, '-'] )
1161 item.setBackgroundColor(0, QColor('lightgray'))
1162 l.addTopLevelItem(item)
1164 for address in self.wallet.addressbook:
1165 if address in alias_targets: continue
1166 label = self.wallet.labels.get(address,'')
1168 for tx in self.wallet.transactions.values():
1169 if address in map(lambda x: x[0], tx.outputs): n += 1
1171 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1172 item.setFont(0, QFont(MONOSPACE_FONT))
1173 l.addTopLevelItem(item)
1175 l.setCurrentItem(l.topLevelItem(0))
1178 def create_console_tab(self):
1179 from qt_console import Console
1180 self.console = console = Console()
1181 self.console.history = self.config.get("console-history",[])
1182 self.console.history_index = len(self.console.history)
1184 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1185 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1187 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1189 def mkfunc(f, method):
1190 return lambda *args: apply( f, (method, args, self.password_dialog ))
1192 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1193 methods[m] = mkfunc(c._run, m)
1195 console.updateNamespace(methods)
1199 def create_status_bar(self):
1200 self.status_text = ""
1202 sb.setFixedHeight(35)
1203 qtVersion = qVersion()
1205 update_notification = UpdateLabel(self.config)
1206 if(update_notification.new_version):
1207 sb.addPermanentWidget(update_notification)
1209 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1210 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1211 if self.wallet.seed:
1212 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1213 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1214 if self.wallet.seed:
1215 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1216 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1217 sb.addPermanentWidget( self.status_button )
1219 self.run_hook('create_status_bar', (sb,))
1221 self.setStatusBar(sb)
1225 self.config.set_key('gui', 'lite', True)
1228 self.lite.mini.show()
1230 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1231 self.lite.main(None)
1233 def new_contact_dialog(self):
1234 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1235 address = unicode(text)
1237 if is_valid(address):
1238 self.wallet.addressbook.append(address)
1240 self.update_contacts_tab()
1241 self.update_history_tab()
1242 self.update_completions()
1244 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1246 def show_master_public_key(self):
1247 dialog = QDialog(self)
1249 dialog.setWindowTitle(_("Master Public Key"))
1251 main_text = QTextEdit()
1252 main_text.setText(self.wallet.get_master_public_key())
1253 main_text.setReadOnly(True)
1254 main_text.setMaximumHeight(170)
1255 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1257 ok_button = QPushButton(_("OK"))
1258 ok_button.setDefault(True)
1259 ok_button.clicked.connect(dialog.accept)
1261 main_layout = QGridLayout()
1262 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1264 main_layout.addWidget(main_text, 1, 0)
1265 main_layout.addWidget(qrw, 1, 1 )
1267 vbox = QVBoxLayout()
1268 vbox.addLayout(main_layout)
1269 hbox = QHBoxLayout()
1271 hbox.addWidget(ok_button)
1272 vbox.addLayout(hbox)
1274 dialog.setLayout(vbox)
1279 def show_seed_dialog(self, password):
1280 if not self.wallet.seed:
1281 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1284 seed = self.wallet.decode_seed(password)
1286 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1288 self.show_seed(seed, self)
1292 def show_seed(self, seed, parent=None):
1293 dialog = QDialog(parent)
1295 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1297 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1299 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1301 seed_text = QTextEdit(brainwallet)
1302 seed_text.setReadOnly(True)
1303 seed_text.setMaximumHeight(130)
1305 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1306 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1307 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1308 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1309 label2 = QLabel(msg2)
1310 label2.setWordWrap(True)
1313 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1314 logo.setMaximumWidth(60)
1316 qrw = QRCodeWidget(seed)
1318 ok_button = QPushButton(_("OK"))
1319 ok_button.setDefault(True)
1320 ok_button.clicked.connect(dialog.accept)
1322 grid = QGridLayout()
1323 #main_layout.addWidget(logo, 0, 0)
1325 grid.addWidget(logo, 0, 0)
1326 grid.addWidget(label1, 0, 1)
1328 grid.addWidget(seed_text, 1, 0, 1, 2)
1330 grid.addWidget(qrw, 0, 2, 2, 1)
1332 vbox = QVBoxLayout()
1333 vbox.addLayout(grid)
1334 vbox.addWidget(label2)
1336 hbox = QHBoxLayout()
1338 hbox.addWidget(ok_button)
1339 vbox.addLayout(hbox)
1341 dialog.setLayout(vbox)
1344 def show_qrcode(self, data, title = "QR code"):
1348 d.setWindowTitle(title)
1349 d.setMinimumSize(270, 300)
1350 vbox = QVBoxLayout()
1351 qrw = QRCodeWidget(data)
1352 vbox.addWidget(qrw, 1)
1353 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1354 hbox = QHBoxLayout()
1358 filename = "qrcode.bmp"
1359 bmp.save_qrcode(qrw.qr, filename)
1360 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1362 b = QPushButton(_("Save"))
1364 b.clicked.connect(print_qr)
1366 b = QPushButton(_("Close"))
1368 b.clicked.connect(d.accept)
1371 vbox.addLayout(hbox)
1376 def do_protect(self, func, args):
1377 if self.wallet.use_encryption:
1378 password = self.password_dialog()
1384 if args != (False,):
1385 args = (self,) + args + (password,)
1387 args = (self,password)
1392 def show_private_key(self, address, password):
1393 if not address: return
1395 pk = self.wallet.get_private_key(address, password)
1396 except BaseException, e:
1397 self.show_message(str(e))
1399 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1403 def do_sign(self, address, message, signature, password):
1405 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1406 signature.setText(sig)
1407 except BaseException, e:
1408 self.show_message(str(e))
1410 def sign_message(self, address):
1411 if not address: return
1414 d.setWindowTitle(_('Sign Message'))
1415 d.setMinimumSize(410, 290)
1417 tab_widget = QTabWidget()
1419 layout = QGridLayout(tab)
1421 sign_address = QLineEdit()
1423 sign_address.setText(address)
1424 layout.addWidget(QLabel(_('Address')), 1, 0)
1425 layout.addWidget(sign_address, 1, 1)
1427 sign_message = QTextEdit()
1428 layout.addWidget(QLabel(_('Message')), 2, 0)
1429 layout.addWidget(sign_message, 2, 1)
1430 layout.setRowStretch(2,3)
1432 sign_signature = QTextEdit()
1433 layout.addWidget(QLabel(_('Signature')), 3, 0)
1434 layout.addWidget(sign_signature, 3, 1)
1435 layout.setRowStretch(3,1)
1438 hbox = QHBoxLayout()
1439 b = QPushButton(_("Sign"))
1441 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1442 b = QPushButton(_("Close"))
1443 b.clicked.connect(d.accept)
1445 layout.addLayout(hbox, 4, 1)
1446 tab_widget.addTab(tab, _("Sign"))
1450 layout = QGridLayout(tab)
1452 verify_address = QLineEdit()
1453 layout.addWidget(QLabel(_('Address')), 1, 0)
1454 layout.addWidget(verify_address, 1, 1)
1456 verify_message = QTextEdit()
1457 layout.addWidget(QLabel(_('Message')), 2, 0)
1458 layout.addWidget(verify_message, 2, 1)
1459 layout.setRowStretch(2,3)
1461 verify_signature = QTextEdit()
1462 layout.addWidget(QLabel(_('Signature')), 3, 0)
1463 layout.addWidget(verify_signature, 3, 1)
1464 layout.setRowStretch(3,1)
1468 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1469 self.show_message(_("Signature verified"))
1470 except BaseException, e:
1471 self.show_message(str(e))
1474 hbox = QHBoxLayout()
1475 b = QPushButton(_("Verify"))
1476 b.clicked.connect(do_verify)
1478 b = QPushButton(_("Close"))
1479 b.clicked.connect(d.accept)
1481 layout.addLayout(hbox, 4, 1)
1482 tab_widget.addTab(tab, _("Verify"))
1484 vbox = QVBoxLayout()
1485 vbox.addWidget(tab_widget)
1492 def question(self, msg):
1493 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1495 def show_message(self, msg):
1496 QMessageBox.information(self, _('Message'), msg, _('OK'))
1498 def password_dialog(self ):
1505 vbox = QVBoxLayout()
1506 msg = _('Please enter your password')
1507 vbox.addWidget(QLabel(msg))
1509 grid = QGridLayout()
1511 grid.addWidget(QLabel(_('Password')), 1, 0)
1512 grid.addWidget(pw, 1, 1)
1513 vbox.addLayout(grid)
1515 vbox.addLayout(ok_cancel_buttons(d))
1518 if not d.exec_(): return
1519 return unicode(pw.text())
1526 def change_password_dialog( wallet, parent=None ):
1529 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1537 new_pw = QLineEdit()
1538 new_pw.setEchoMode(2)
1539 conf_pw = QLineEdit()
1540 conf_pw.setEchoMode(2)
1542 vbox = QVBoxLayout()
1544 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1545 +_('To disable wallet encryption, enter an empty new password.')) \
1546 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1548 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1549 +_("Leave these fields empty if you want to disable encryption.")
1550 vbox.addWidget(QLabel(msg))
1552 grid = QGridLayout()
1555 if wallet.use_encryption:
1556 grid.addWidget(QLabel(_('Password')), 1, 0)
1557 grid.addWidget(pw, 1, 1)
1559 grid.addWidget(QLabel(_('New Password')), 2, 0)
1560 grid.addWidget(new_pw, 2, 1)
1562 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1563 grid.addWidget(conf_pw, 3, 1)
1564 vbox.addLayout(grid)
1566 vbox.addLayout(ok_cancel_buttons(d))
1569 if not d.exec_(): return
1571 password = unicode(pw.text()) if wallet.use_encryption else None
1572 new_password = unicode(new_pw.text())
1573 new_password2 = unicode(conf_pw.text())
1576 seed = wallet.decode_seed(password)
1578 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1581 if new_password != new_password2:
1582 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1583 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1585 wallet.update_password(seed, password, new_password)
1588 def seed_dialog(wallet, parent=None):
1592 vbox = QVBoxLayout()
1593 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1594 vbox.addWidget(QLabel(msg))
1596 grid = QGridLayout()
1599 seed_e = QLineEdit()
1600 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1601 grid.addWidget(seed_e, 1, 1)
1605 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1606 grid.addWidget(gap_e, 2, 1)
1607 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1608 vbox.addLayout(grid)
1610 vbox.addLayout(ok_cancel_buttons(d))
1613 if not d.exec_(): return
1616 gap = int(unicode(gap_e.text()))
1618 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1622 seed = str(seed_e.text())
1625 print_error("Warning: Not hex, trying decode")
1627 seed = mnemonic.mn_decode( seed.split(' ') )
1629 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1633 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1638 def generate_transaction_information_widget(self, tx):
1639 tabs = QTabWidget(self)
1642 grid_ui = QGridLayout(tab1)
1643 grid_ui.setColumnStretch(0,1)
1644 tabs.addTab(tab1, _('Outputs') )
1646 tree_widget = MyTreeWidget(self)
1647 tree_widget.setColumnCount(2)
1648 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1649 tree_widget.setColumnWidth(0, 300)
1650 tree_widget.setColumnWidth(1, 50)
1652 for address, value in tx.outputs:
1653 item = QTreeWidgetItem( [address, "%s" % ( format_satoshis(value))] )
1654 tree_widget.addTopLevelItem(item)
1656 tree_widget.setMaximumHeight(100)
1658 grid_ui.addWidget(tree_widget)
1661 grid_ui = QGridLayout(tab2)
1662 grid_ui.setColumnStretch(0,1)
1663 tabs.addTab(tab2, _('Inputs') )
1665 tree_widget = MyTreeWidget(self)
1666 tree_widget.setColumnCount(2)
1667 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1669 for input_line in tx.inputs:
1670 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1671 tree_widget.addTopLevelItem(item)
1673 tree_widget.setMaximumHeight(100)
1675 grid_ui.addWidget(tree_widget)
1679 def tx_dict_from_text(self, txt):
1681 tx_dict = json.loads(str(txt))
1682 assert "hex" in tx_dict.keys()
1683 assert "complete" in tx_dict.keys()
1684 if not tx_dict["complete"]:
1685 assert "input_info" in tx_dict.keys()
1687 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1692 def read_tx_from_file(self):
1693 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1697 with open(fileName, "r") as f:
1698 file_content = f.read()
1699 except (ValueError, IOError, os.error), reason:
1700 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1702 return self.tx_dict_from_text(file_content)
1706 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1708 self.wallet.signrawtransaction(tx, input_info, [], password)
1710 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1712 with open(fileName, "w+") as f:
1713 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1714 self.show_message(_("Transaction saved successfully"))
1717 except BaseException, e:
1718 self.show_message(str(e))
1721 def send_raw_transaction(self, raw_tx, dialog = ""):
1722 result, result_message = self.wallet.sendtx( raw_tx )
1724 self.show_message("Transaction successfully sent: %s" % (result_message))
1728 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1730 def do_process_from_text(self):
1731 dialog = QDialog(self)
1732 dialog.setMinimumWidth(500)
1733 dialog.setWindowTitle(_('Input raw transaction'))
1737 l.addWidget(QLabel(_("Transaction:")))
1741 ok_button = QPushButton(_("Load transaction"))
1742 ok_button.setDefault(True)
1743 ok_button.clicked.connect(dialog.accept)
1744 l.addWidget(ok_button)
1747 tx_dict = self.tx_dict_from_text(unicode(txt.toPlainText()))
1749 self.create_process_transaction_window(tx_dict)
1751 def do_process_from_file(self):
1752 tx_dict = self.read_tx_from_file()
1754 self.create_process_transaction_window(tx_dict)
1756 def create_process_transaction_window(self, tx_dict):
1757 tx = Transaction(tx_dict["hex"])
1759 dialog = QDialog(self)
1760 dialog.setMinimumWidth(500)
1761 dialog.setWindowTitle(_('Process raw transaction'))
1767 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1768 l.addWidget(QLabel(_("Actions")), 4,0)
1770 if tx_dict["complete"] == False:
1771 l.addWidget(QLabel(_("Unsigned")), 3,1)
1772 if self.wallet.seed :
1773 b = QPushButton("Sign transaction")
1774 input_info = json.loads(tx_dict["input_info"])
1775 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1776 l.addWidget(b, 4, 1)
1778 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1780 l.addWidget(QLabel(_("Signed")), 3,1)
1781 b = QPushButton("Broadcast transaction")
1782 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1785 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1786 cancelButton = QPushButton(_("Cancel"))
1787 cancelButton.clicked.connect(lambda: dialog.done(0))
1788 l.addWidget(cancelButton, 4,2)
1794 def do_export_privkeys(self, password):
1795 self.show_message("%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."), _("Exposing a single private key can compromise your entire wallet!"), _("In particular, DO NOT use 'redeem private key' services proposed by third parties.")))
1798 select_export = _('Select file to export your private keys to')
1799 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1801 with open(fileName, "w+") as csvfile:
1802 transaction = csv.writer(csvfile)
1803 transaction.writerow(["address", "private_key"])
1806 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1807 transaction.writerow(["%34s"%addr,pk])
1809 self.show_message(_("Private keys exported."))
1811 except (IOError, os.error), reason:
1812 export_error_label = _("Electrum was unable to produce a private key-export.")
1813 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1815 except BaseException, e:
1816 self.show_message(str(e))
1820 def do_import_labels(self):
1821 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1822 if not labelsFile: return
1824 f = open(labelsFile, 'r')
1827 for key, value in json.loads(data).items():
1828 self.wallet.labels[key] = value
1830 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
1831 except (IOError, os.error), reason:
1832 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1835 def do_export_labels(self):
1836 labels = self.wallet.labels
1838 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1840 with open(fileName, 'w+') as f:
1841 json.dump(labels, f)
1842 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1843 except (IOError, os.error), reason:
1844 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1847 def do_export_history(self):
1848 from gui_lite import csv_transaction
1849 csv_transaction(self.wallet)
1853 def do_import_privkey(self, password):
1854 if not self.wallet.imported_keys:
1855 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1856 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1857 + _('Are you sure you understand what you are doing?'), 3, 4)
1860 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1862 sec = str(text).strip()
1864 addr = self.wallet.import_key(sec, password)
1866 QMessageBox.critical(None, _("Unable to import key"), "error")
1868 QMessageBox.information(None, _("Key imported"), addr)
1869 self.update_receive_tab()
1870 self.update_history_tab()
1871 except BaseException as e:
1872 QMessageBox.critical(None, _("Unable to import key"), str(e))
1875 def settings_dialog(self):
1877 d.setWindowTitle(_('Electrum Settings'))
1879 vbox = QVBoxLayout()
1881 tabs = QTabWidget(self)
1882 self.settings_tab = tabs
1883 vbox.addWidget(tabs)
1886 grid_ui = QGridLayout(tab1)
1887 grid_ui.setColumnStretch(0,1)
1888 tabs.addTab(tab1, _('Display') )
1890 nz_label = QLabel(_('Display zeros'))
1891 grid_ui.addWidget(nz_label, 0, 0)
1893 nz_e.setText("%d"% self.wallet.num_zeros)
1894 grid_ui.addWidget(nz_e, 0, 1)
1895 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1896 grid_ui.addWidget(HelpButton(msg), 0, 2)
1897 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1898 if not self.config.is_modifiable('num_zeros'):
1899 for w in [nz_e, nz_label]: w.setEnabled(False)
1901 lang_label=QLabel(_('Language') + ':')
1902 grid_ui.addWidget(lang_label, 1, 0)
1903 lang_combo = QComboBox()
1904 from i18n import languages
1905 lang_combo.addItems(languages.values())
1907 index = languages.keys().index(self.config.get("language",''))
1910 lang_combo.setCurrentIndex(index)
1911 grid_ui.addWidget(lang_combo, 1, 1)
1912 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1913 if not self.config.is_modifiable('language'):
1914 for w in [lang_combo, lang_label]: w.setEnabled(False)
1916 currencies = self.exchanger.get_currencies()
1917 currencies.insert(0, "None")
1919 cur_label=QLabel(_('Currency') + ':')
1920 grid_ui.addWidget(cur_label , 2, 0)
1921 cur_combo = QComboBox()
1922 cur_combo.addItems(currencies)
1924 index = currencies.index(self.config.get('currency', "None"))
1927 cur_combo.setCurrentIndex(index)
1928 grid_ui.addWidget(cur_combo, 2, 1)
1929 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1931 view_label=QLabel(_('Receive Tab') + ':')
1932 grid_ui.addWidget(view_label , 3, 0)
1933 view_combo = QComboBox()
1934 view_combo.addItems([_('Simple'), _('Advanced')])
1935 view_combo.setCurrentIndex(self.expert_mode)
1936 grid_ui.addWidget(view_combo, 3, 1)
1937 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1938 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1939 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n'
1941 grid_ui.addWidget(HelpButton(hh), 3, 2)
1942 grid_ui.setRowStretch(4,1)
1946 grid_wallet = QGridLayout(tab2)
1947 grid_wallet.setColumnStretch(0,1)
1948 tabs.addTab(tab2, _('Wallet') )
1950 fee_label = QLabel(_('Transaction fee'))
1951 grid_wallet.addWidget(fee_label, 0, 0)
1953 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1954 grid_wallet.addWidget(fee_e, 0, 2)
1955 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1956 + _('Recommended value') + ': 0.001'
1957 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1958 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1959 if not self.config.is_modifiable('fee'):
1960 for w in [fee_e, fee_label]: w.setEnabled(False)
1962 usechange_label = QLabel(_('Use change addresses'))
1963 grid_wallet.addWidget(usechange_label, 1, 0)
1964 usechange_combo = QComboBox()
1965 usechange_combo.addItems([_('Yes'), _('No')])
1966 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1967 grid_wallet.addWidget(usechange_combo, 1, 2)
1968 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1969 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1971 gap_label = QLabel(_('Gap limit'))
1972 grid_wallet.addWidget(gap_label, 2, 0)
1974 gap_e.setText("%d"% self.wallet.gap_limit)
1975 grid_wallet.addWidget(gap_e, 2, 2)
1976 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1977 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1978 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1979 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1980 + _('Warning') + ': ' \
1981 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1982 + _('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'
1983 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1984 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1985 if not self.config.is_modifiable('gap_limit'):
1986 for w in [gap_e, gap_label]: w.setEnabled(False)
1988 grid_wallet.setRowStretch(3,1)
1993 grid_io = QGridLayout(tab3)
1994 grid_io.setColumnStretch(0,1)
1995 tabs.addTab(tab3, _('Import/Export') )
1997 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1998 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1999 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2000 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2002 grid_io.addWidget(QLabel(_('History')), 2, 0)
2003 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2004 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2006 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2008 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2009 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2010 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2012 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2013 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2014 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2015 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2016 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2019 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
2020 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
2021 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
2022 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
2024 grid_io.setRowStretch(5,1)
2029 tab5 = QScrollArea()
2030 grid_plugins = QGridLayout(tab5)
2031 grid_plugins.setColumnStretch(0,1)
2032 tabs.addTab(tab5, _('Plugins') )
2033 def mk_toggle(cb, p):
2034 return lambda: cb.setChecked(p.toggle(self))
2035 for i, p in enumerate(self.plugins):
2037 name, description = p.get_info()
2038 cb = QCheckBox(name)
2039 cb.setDisabled(not p.is_available())
2040 cb.setChecked(p.is_enabled())
2041 cb.clicked.connect(mk_toggle(cb,p))
2042 grid_plugins.addWidget(cb, i, 0)
2043 grid_plugins.addWidget(HelpButton(description), i, 2)
2045 print_msg("Error: cannot display plugin", p)
2046 traceback.print_exc(file=sys.stdout)
2047 grid_plugins.setRowStretch(i+1,1)
2049 self.run_hook('create_settings_tab', (self,tabs,))
2051 vbox.addLayout(ok_cancel_buttons(d))
2055 if not d.exec_(): return
2057 fee = unicode(fee_e.text())
2059 fee = int( 100000000 * Decimal(fee) )
2061 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2064 if self.wallet.fee != fee:
2065 self.wallet.fee = fee
2068 nz = unicode(nz_e.text())
2073 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2076 if self.wallet.num_zeros != nz:
2077 self.wallet.num_zeros = nz
2078 self.config.set_key('num_zeros', nz, True)
2079 self.update_history_tab()
2080 self.update_receive_tab()
2082 usechange_result = usechange_combo.currentIndex() == 0
2083 if self.wallet.use_change != usechange_result:
2084 self.wallet.use_change = usechange_result
2085 self.config.set_key('use_change', self.wallet.use_change, True)
2088 n = int(gap_e.text())
2090 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2093 if self.wallet.gap_limit != n:
2094 r = self.wallet.change_gap_limit(n)
2096 self.update_receive_tab()
2097 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2099 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2101 need_restart = False
2103 lang_request = languages.keys()[lang_combo.currentIndex()]
2104 if lang_request != self.config.get('language'):
2105 self.config.set_key("language", lang_request, True)
2108 cur_request = str(currencies[cur_combo.currentIndex()])
2109 if cur_request != self.config.get('currency', "None"):
2110 self.config.set_key('currency', cur_request, True)
2111 self.update_wallet()
2113 self.run_hook('close_setting_dialog', (self,))
2116 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2118 self.receive_tab_set_mode(view_combo.currentIndex())
2122 def network_dialog(wallet, parent=None):
2123 interface = wallet.interface
2125 if interface.is_connected:
2126 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2128 status = _("Not connected")
2129 server = interface.server
2132 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2133 server = interface.server
2135 plist, servers_list = interface.get_servers_list()
2139 d.setWindowTitle(_('Server'))
2140 d.setMinimumSize(375, 20)
2142 vbox = QVBoxLayout()
2145 hbox = QHBoxLayout()
2147 l.setPixmap(QPixmap(":icons/network.png"))
2150 hbox.addWidget(QLabel(status))
2152 vbox.addLayout(hbox)
2156 grid = QGridLayout()
2158 vbox.addLayout(grid)
2161 server_protocol = QComboBox()
2162 server_host = QLineEdit()
2163 server_host.setFixedWidth(200)
2164 server_port = QLineEdit()
2165 server_port.setFixedWidth(60)
2167 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2168 protocol_letters = 'thsg'
2169 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2170 server_protocol.addItems(protocol_names)
2172 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2173 grid.addWidget(server_protocol, 0, 1)
2174 grid.addWidget(server_host, 0, 2)
2175 grid.addWidget(server_port, 0, 3)
2177 def change_protocol(p):
2178 protocol = protocol_letters[p]
2179 host = unicode(server_host.text())
2180 pp = plist.get(host,DEFAULT_PORTS)
2181 if protocol not in pp.keys():
2182 protocol = pp.keys()[0]
2184 server_host.setText( host )
2185 server_port.setText( port )
2187 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2189 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2190 servers_list_widget = QTreeWidget(parent)
2191 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2192 servers_list_widget.setMaximumHeight(150)
2193 servers_list_widget.setColumnWidth(0, 240)
2194 for _host in servers_list.keys():
2195 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2196 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2198 def change_server(host, protocol=None):
2199 pp = plist.get(host,DEFAULT_PORTS)
2201 port = pp.get(protocol)
2202 if not port: protocol = None
2205 if 't' in pp.keys():
2207 port = pp.get(protocol)
2209 protocol = pp.keys()[0]
2210 port = pp.get(protocol)
2212 server_host.setText( host )
2213 server_port.setText( port )
2214 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2216 if not plist: return
2217 for p in protocol_letters:
2218 i = protocol_letters.index(p)
2219 j = server_protocol.model().index(i,0)
2220 if p not in pp.keys():
2221 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2223 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2227 host, port, protocol = server.split(':')
2228 change_server(host,protocol)
2230 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2231 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2233 if not wallet.config.is_modifiable('server'):
2234 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2237 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2238 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2239 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2240 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2243 proxy_mode = QComboBox()
2244 proxy_host = QLineEdit()
2245 proxy_host.setFixedWidth(200)
2246 proxy_port = QLineEdit()
2247 proxy_port.setFixedWidth(60)
2248 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2250 def check_for_disable(index = False):
2251 if proxy_mode.currentText() != 'NONE':
2252 proxy_host.setEnabled(True)
2253 proxy_port.setEnabled(True)
2255 proxy_host.setEnabled(False)
2256 proxy_port.setEnabled(False)
2259 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2261 if not wallet.config.is_modifiable('proxy'):
2262 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2264 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2265 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2266 proxy_host.setText(proxy_config.get("host"))
2267 proxy_port.setText(proxy_config.get("port"))
2269 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2270 grid.addWidget(proxy_mode, 2, 1)
2271 grid.addWidget(proxy_host, 2, 2)
2272 grid.addWidget(proxy_port, 2, 3)
2275 vbox.addLayout(ok_cancel_buttons(d))
2278 if not d.exec_(): return
2280 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2281 if proxy_mode.currentText() != 'NONE':
2282 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2286 wallet.config.set_key("proxy", proxy, True)
2287 wallet.config.set_key("server", server, True)
2288 interface.set_server(server, proxy)
2289 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2292 def closeEvent(self, event):
2294 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2295 self.save_column_widths()
2296 self.config.set_key("column-widths", self.column_widths, True)
2297 self.config.set_key("console-history",self.console.history[-50:])
2303 def __init__(self, wallet, config, app=None):
2304 self.wallet = wallet
2305 self.config = config
2307 self.app = QApplication(sys.argv)
2310 def restore_or_create(self):
2311 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2312 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2313 if r==2: return None
2314 return 'restore' if r==1 else 'create'
2316 def seed_dialog(self):
2317 return ElectrumWindow.seed_dialog( self.wallet )
2319 def network_dialog(self):
2320 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2323 def show_seed(self):
2324 ElectrumWindow.show_seed(self.wallet.seed)
2327 def password_dialog(self):
2328 if self.wallet.seed:
2329 ElectrumWindow.change_password_dialog(self.wallet)
2332 def restore_wallet(self):
2333 wallet = self.wallet
2334 # wait until we are connected, because the user might have selected another server
2335 if not wallet.interface.is_connected:
2336 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2337 waiting_dialog(waiting)
2339 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2340 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2342 wallet.set_up_to_date(False)
2343 wallet.interface.poke('synchronizer')
2344 waiting_dialog(waiting)
2345 if wallet.is_found():
2346 print_error( "Recovery successful" )
2348 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2355 w = ElectrumWindow(self.wallet, self.config)
2356 if url: w.set_url(url)