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)
329 # plugins that need to change the GUI do it here
330 self.run_hook('init')
334 def init_plugins(self):
336 if os.path.exists("plugins"):
337 fp, pathname, description = imp.find_module('plugins')
338 imp.load_module('electrum_plugins', fp, pathname, description)
339 plugin_names = [name for a, name, b in pkgutil.iter_modules(['plugins'])]
340 self.plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
342 import electrum_plugins
343 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
344 self.plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
346 self.plugin_hooks = {}
347 for p in self.plugins:
351 print_msg("Error:cannot initialize plugin",p)
352 traceback.print_exc(file=sys.stdout)
354 def set_hook(self, name, callback):
355 h = self.plugin_hooks.get(name, [])
357 self.plugin_hooks[name] = h
359 def unset_hook(self, name, callback):
360 h = self.plugin_hooks.get(name,[])
361 if callback in h: h.remove(callback)
362 self.plugin_hooks[name] = h
364 def run_hook(self, name, args = ()):
365 args = (self,) + args
366 for cb in self.plugin_hooks.get(name,[]):
370 def set_label(self, name, text = None):
372 old_text = self.wallet.labels.get(name)
375 self.wallet.labels[name] = text
379 self.wallet.labels.pop(name)
381 self.run_hook('set_label', (name, text, changed))
386 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
387 def getOpenFileName(self, title, filter = None):
388 directory = self.config.get('io_dir', os.path.expanduser('~'))
389 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
390 if fileName and directory != os.path.dirname(fileName):
391 self.config.set_key('io_dir', os.path.dirname(fileName), True)
394 def getSaveFileName(self, title, filename, filter = None):
395 directory = self.config.get('io_dir', os.path.expanduser('~'))
396 path = os.path.join( directory, filename )
397 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
398 if fileName and directory != os.path.dirname(fileName):
399 self.config.set_key('io_dir', os.path.dirname(fileName), True)
405 QMainWindow.close(self)
406 self.run_hook('close_main_window')
408 def connect_slots(self, sender):
409 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
410 self.previous_payto_e=''
412 def timer_actions(self):
413 self.run_hook('timer_actions')
415 if self.payto_e.hasFocus():
417 r = unicode( self.payto_e.text() )
418 if r != self.previous_payto_e:
419 self.previous_payto_e = r
421 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
423 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
427 s = r + ' <' + to_address + '>'
428 self.payto_e.setText(s)
432 def update_status(self):
433 if self.wallet.interface and self.wallet.interface.is_connected:
434 if not self.wallet.up_to_date:
435 text = _("Synchronizing...")
436 icon = QIcon(":icons/status_waiting.png")
438 c, u = self.wallet.get_balance()
439 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
440 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
441 text += self.create_quote_text(Decimal(c+u)/100000000)
442 icon = QIcon(":icons/status_connected.png")
444 text = _("Not connected")
445 icon = QIcon(":icons/status_disconnected.png")
447 self.status_text = text
448 self.statusBar().showMessage(text)
449 self.status_button.setIcon( icon )
451 def update_wallet(self):
453 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
454 self.update_history_tab()
455 self.update_receive_tab()
456 self.update_contacts_tab()
457 self.update_completions()
460 def create_quote_text(self, btc_balance):
461 quote_currency = self.config.get("currency", "None")
462 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
463 if quote_balance is None:
466 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
469 def create_history_tab(self):
470 self.history_list = l = MyTreeWidget(self)
472 for i,width in enumerate(self.column_widths['history']):
473 l.setColumnWidth(i, width)
474 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
475 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
476 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
478 l.setContextMenuPolicy(Qt.CustomContextMenu)
479 l.customContextMenuRequested.connect(self.create_history_menu)
483 def create_history_menu(self, position):
484 self.history_list.selectedIndexes()
485 item = self.history_list.currentItem()
487 tx_hash = str(item.data(0, Qt.UserRole).toString())
488 if not tx_hash: return
490 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
491 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
492 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
493 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
496 def show_tx_details(self, tx):
497 dialog = QDialog(self)
499 dialog.setWindowTitle(_("Transaction Details"))
501 dialog.setLayout(vbox)
502 dialog.setMinimumSize(600,300)
505 if tx_hash in self.wallet.transactions.keys():
506 is_mine, v, fee = self.wallet.get_tx_value(tx)
507 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
509 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
515 vbox.addWidget(QLabel("Transaction ID:"))
516 e = QLineEdit(tx_hash)
520 vbox.addWidget(QLabel("Date: %s"%time_str))
521 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
524 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
525 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
527 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
528 vbox.addWidget(QLabel("Transaction fee: unknown"))
530 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
532 vbox.addWidget( self.generate_transaction_information_widget(tx) )
534 ok_button = QPushButton(_("Close"))
535 ok_button.setDefault(True)
536 ok_button.clicked.connect(dialog.accept)
540 hbox.addWidget(ok_button)
544 def tx_label_clicked(self, item, column):
545 if column==2 and item.isSelected():
547 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
548 self.history_list.editItem( item, column )
549 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
552 def tx_label_changed(self, item, column):
556 tx_hash = str(item.data(0, Qt.UserRole).toString())
557 tx = self.wallet.transactions.get(tx_hash)
558 text = unicode( item.text(2) )
559 self.set_label(tx_hash, text)
561 item.setForeground(2, QBrush(QColor('black')))
563 text = self.wallet.get_default_label(tx_hash)
564 item.setText(2, text)
565 item.setForeground(2, QBrush(QColor('gray')))
569 def edit_label(self, is_recv):
570 l = self.receive_list if is_recv else self.contacts_list
571 item = l.currentItem()
572 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
573 l.editItem( item, 1 )
574 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
578 def address_label_clicked(self, item, column, l, column_addr, column_label):
579 if column == column_label and item.isSelected():
580 addr = unicode( item.text(column_addr) )
581 label = unicode( item.text(column_label) )
582 if label in self.wallet.aliases.keys():
584 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
585 l.editItem( item, column )
586 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
589 def address_label_changed(self, item, column, l, column_addr, column_label):
590 if column == column_label:
591 addr = unicode( item.text(column_addr) )
592 text = unicode( item.text(column_label) )
595 if text in self.wallet.aliases.keys():
596 print_error("Error: This is one of your aliases")
597 label = self.wallet.labels.get(addr,'')
598 item.setText(column_label, QString(label))
601 changed = self.set_label(addr, text)
603 self.update_history_tab()
604 self.update_completions()
606 self.current_item_changed(item)
608 self.run_hook('item_changed', (item, column))
611 def current_item_changed(self, a):
612 self.run_hook('current_item_changed', (a,))
616 def update_history_tab(self):
618 self.history_list.clear()
619 for item in self.wallet.get_tx_history():
620 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
623 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
629 icon = QIcon(":icons/unconfirmed.png")
631 icon = QIcon(":icons/clock%d.png"%conf)
633 icon = QIcon(":icons/confirmed.png")
636 icon = QIcon(":icons/unconfirmed.png")
638 if value is not None:
639 v_str = format_satoshis(value, True, self.wallet.num_zeros)
643 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
646 label, is_default_label = self.wallet.get_label(tx_hash)
648 label = _('Pruned transaction outputs')
649 is_default_label = False
651 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
652 item.setFont(2, QFont(MONOSPACE_FONT))
653 item.setFont(3, QFont(MONOSPACE_FONT))
654 item.setFont(4, QFont(MONOSPACE_FONT))
656 item.setForeground(3, QBrush(QColor("#BC1E1E")))
658 item.setData(0, Qt.UserRole, tx_hash)
659 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
661 item.setForeground(2, QBrush(QColor('grey')))
663 item.setIcon(0, icon)
664 self.history_list.insertTopLevelItem(0,item)
667 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
670 def create_send_tab(self):
675 grid.setColumnMinimumWidth(3,300)
676 grid.setColumnStretch(5,1)
678 self.payto_e = QLineEdit()
679 grid.addWidget(QLabel(_('Pay to')), 1, 0)
680 grid.addWidget(self.payto_e, 1, 1, 1, 3)
682 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)
684 completer = QCompleter()
685 completer.setCaseSensitivity(False)
686 self.payto_e.setCompleter(completer)
687 completer.setModel(self.completions)
689 self.message_e = QLineEdit()
690 grid.addWidget(QLabel(_('Description')), 2, 0)
691 grid.addWidget(self.message_e, 2, 1, 1, 3)
692 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)
694 self.amount_e = QLineEdit()
695 grid.addWidget(QLabel(_('Amount')), 3, 0)
696 grid.addWidget(self.amount_e, 3, 1, 1, 2)
697 grid.addWidget(HelpButton(
698 _('Amount to be sent.') + '\n\n' \
699 + _('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)
701 self.fee_e = QLineEdit()
702 grid.addWidget(QLabel(_('Fee')), 4, 0)
703 grid.addWidget(self.fee_e, 4, 1, 1, 2)
704 grid.addWidget(HelpButton(
705 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
706 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
707 + _('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)
710 b = EnterButton(_("Send"), self.do_send)
712 b = EnterButton(_("Create unsigned transaction"), self.do_send)
713 grid.addWidget(b, 6, 1)
715 b = EnterButton(_("Clear"),self.do_clear)
716 grid.addWidget(b, 6, 2)
718 self.payto_sig = QLabel('')
719 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
721 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
722 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
731 def entry_changed( is_fee ):
732 self.funds_error = False
733 amount = numbify(self.amount_e)
734 fee = numbify(self.fee_e)
735 if not is_fee: fee = None
738 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
740 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
743 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
744 text = self.status_text
747 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
748 self.funds_error = True
749 text = _( "Not enough funds" )
751 self.statusBar().showMessage(text)
752 self.amount_e.setPalette(palette)
753 self.fee_e.setPalette(palette)
755 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
756 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
758 self.run_hook('create_send_tab', (grid,))
762 def update_completions(self):
764 for addr,label in self.wallet.labels.items():
765 if addr in self.wallet.addressbook:
766 l.append( label + ' <' + addr + '>')
767 l = l + self.wallet.aliases.keys()
769 self.completions.setStringList(l)
773 return lambda s, *args: s.do_protect(func, args)
777 def do_send(self, password):
779 label = unicode( self.message_e.text() )
780 r = unicode( self.payto_e.text() )
784 m1 = re.match(ALIAS_REGEXP, r)
785 # label or alias, with address in brackets
786 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
789 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
793 to_address = m2.group(2)
797 if not is_valid(to_address):
798 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
802 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
804 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
807 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
809 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
813 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
814 except BaseException, e:
815 self.show_message(str(e))
818 self.run_hook('send_tx', (tx,))
821 self.set_label(tx.hash(), label)
824 h = self.wallet.send_tx(tx)
825 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
826 status, msg = self.wallet.receive_tx( h )
828 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
830 self.update_contacts_tab()
832 QMessageBox.warning(self, _('Error'), msg, _('OK'))
834 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
836 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
837 with open(fileName,'w') as f:
838 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
839 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
841 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
846 def set_url(self, url):
847 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
848 self.tabs.setCurrentIndex(1)
849 label = self.wallet.labels.get(payto)
850 m_addr = label + ' <'+ payto+'>' if label else payto
851 self.payto_e.setText(m_addr)
853 self.message_e.setText(message)
854 self.amount_e.setText(amount)
856 self.set_frozen(self.payto_e,True)
857 self.set_frozen(self.amount_e,True)
858 self.set_frozen(self.message_e,True)
859 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
861 self.payto_sig.setVisible(False)
864 self.payto_sig.setVisible(False)
865 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
867 self.set_frozen(e,False)
869 def set_frozen(self,entry,frozen):
871 entry.setReadOnly(True)
872 entry.setFrame(False)
874 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
875 entry.setPalette(palette)
877 entry.setReadOnly(False)
880 palette.setColor(entry.backgroundRole(), QColor('white'))
881 entry.setPalette(palette)
884 def toggle_freeze(self,addr):
886 if addr in self.wallet.frozen_addresses:
887 self.wallet.unfreeze(addr)
889 self.wallet.freeze(addr)
890 self.update_receive_tab()
892 def toggle_priority(self,addr):
894 if addr in self.wallet.prioritized_addresses:
895 self.wallet.unprioritize(addr)
897 self.wallet.prioritize(addr)
898 self.update_receive_tab()
901 def create_list_tab(self, headers):
902 "generic tab creation method"
903 l = MyTreeWidget(self)
904 l.setColumnCount( len(headers) )
905 l.setHeaderLabels( headers )
915 vbox.addWidget(buttons)
920 buttons.setLayout(hbox)
925 def create_receive_tab(self):
926 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
927 l.setContextMenuPolicy(Qt.CustomContextMenu)
928 l.customContextMenuRequested.connect(self.create_receive_menu)
929 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
930 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
931 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
932 self.receive_list = l
933 self.receive_buttons_hbox = hbox
938 def receive_tab_set_mode(self, i):
939 self.save_column_widths()
940 self.expert_mode = (i == 1)
941 self.config.set_key('classic_expert_mode', self.expert_mode, True)
943 self.update_receive_tab()
946 def save_column_widths(self):
947 if not self.expert_mode:
948 widths = [ self.receive_list.columnWidth(0) ]
951 for i in range(self.receive_list.columnCount() -1):
952 widths.append(self.receive_list.columnWidth(i))
953 self.column_widths["receive"][self.expert_mode] = widths
955 self.column_widths["history"] = []
956 for i in range(self.history_list.columnCount() - 1):
957 self.column_widths["history"].append(self.history_list.columnWidth(i))
959 self.column_widths["contacts"] = []
960 for i in range(self.contacts_list.columnCount() - 1):
961 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
964 def create_contacts_tab(self):
965 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
966 l.setContextMenuPolicy(Qt.CustomContextMenu)
967 l.customContextMenuRequested.connect(self.create_contact_menu)
968 for i,width in enumerate(self.column_widths['contacts']):
969 l.setColumnWidth(i, width)
971 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
972 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
973 self.contacts_list = l
974 self.contacts_buttons_hbox = hbox
975 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
980 def delete_imported_key(self, addr):
981 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
982 self.wallet.imported_keys.pop(addr)
983 self.update_receive_tab()
984 self.update_history_tab()
988 def create_receive_menu(self, position):
989 # fixme: this function apparently has a side effect.
990 # if it is not called the menu pops up several times
991 #self.receive_list.selectedIndexes()
993 item = self.receive_list.itemAt(position)
995 addr = unicode(item.text(0))
996 if not is_valid(addr):
997 item.setExpanded(not item.isExpanded())
1000 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1001 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1002 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1003 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1004 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1005 if addr in self.wallet.imported_keys:
1006 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1008 if self.expert_mode:
1009 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1010 menu.addAction(t, lambda: self.toggle_freeze(addr))
1011 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1012 menu.addAction(t, lambda: self.toggle_priority(addr))
1014 self.run_hook('receive_menu', (menu,))
1015 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1018 def payto(self, x, is_alias):
1025 label = self.wallet.labels.get(addr)
1026 m_addr = label + ' <' + addr + '>' if label else addr
1027 self.tabs.setCurrentIndex(1)
1028 self.payto_e.setText(m_addr)
1029 self.amount_e.setFocus()
1031 def delete_contact(self, x, is_alias):
1032 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1033 if not is_alias and x in self.wallet.addressbook:
1034 self.wallet.addressbook.remove(x)
1035 self.set_label(x, None)
1036 elif is_alias and x in self.wallet.aliases:
1037 self.wallet.aliases.pop(x)
1038 self.update_history_tab()
1039 self.update_contacts_tab()
1040 self.update_completions()
1042 def create_contact_menu(self, position):
1043 # fixme: this function apparently has a side effect.
1044 # if it is not called the menu pops up several times
1045 #self.contacts_list.selectedIndexes()
1047 item = self.contacts_list.itemAt(position)
1049 addr = unicode(item.text(0))
1050 label = unicode(item.text(1))
1051 is_alias = label in self.wallet.aliases.keys()
1052 x = label if is_alias else addr
1054 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1055 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1056 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1058 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1060 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1061 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1062 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1065 def update_receive_item(self, item):
1066 item.setFont(0, QFont(MONOSPACE_FONT))
1067 address = str(item.data(0,0).toString())
1068 label = self.wallet.labels.get(address,'')
1069 item.setData(1,0,label)
1071 self.run_hook('update_receive_item', (address, item))
1073 c, u = self.wallet.get_addr_balance(address)
1074 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1075 item.setData(2,0,balance)
1077 if self.expert_mode:
1078 if address in self.wallet.frozen_addresses:
1079 item.setBackgroundColor(0, QColor('lightblue'))
1080 elif address in self.wallet.prioritized_addresses:
1081 item.setBackgroundColor(0, QColor('lightgreen'))
1084 def update_receive_tab(self):
1085 l = self.receive_list
1088 l.setColumnHidden(2, not self.expert_mode)
1089 l.setColumnHidden(3, not self.expert_mode)
1090 if not self.expert_mode:
1091 width = self.column_widths['receive'][0][0]
1092 l.setColumnWidth(0, width)
1094 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1095 l.setColumnWidth(i, width)
1098 for k, account in self.wallet.accounts.items():
1099 name = account.get('name',str(k))
1100 c,u = self.wallet.get_account_balance(k)
1101 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1102 l.addTopLevelItem(account_item)
1103 account_item.setExpanded(True)
1106 for is_change in [0,1]:
1107 name = "Receiving" if not is_change else "Change"
1108 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1109 account_item.addChild(seq_item)
1110 if not is_change: seq_item.setExpanded(True)
1114 for address in account[is_change]:
1115 h = self.wallet.history.get(address,[])
1120 if gap > self.wallet.gap_limit:
1125 num_tx = '*' if h == ['*'] else "%d"%len(h)
1126 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1127 self.update_receive_item(item)
1129 item.setBackgroundColor(1, QColor('red'))
1130 seq_item.addChild(item)
1132 if self.wallet.imported_keys:
1133 c,u = self.wallet.get_imported_balance()
1134 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1135 l.addTopLevelItem(account_item)
1136 account_item.setExpanded(True)
1137 for address in self.wallet.imported_keys.keys():
1138 item = QTreeWidgetItem( [ address, '', '', ''] )
1139 self.update_receive_item(item)
1140 account_item.addChild(item)
1143 # we use column 1 because column 0 may be hidden
1144 l.setCurrentItem(l.topLevelItem(0),1)
1146 def show_contact_details(self, m):
1147 a = self.wallet.aliases.get(m)
1149 if a[0] in self.wallet.authorities.keys():
1150 s = self.wallet.authorities.get(a[0])
1153 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1154 QMessageBox.information(self, 'Alias', msg, 'OK')
1156 def update_contacts_tab(self):
1158 l = self.contacts_list
1162 for alias, v in self.wallet.aliases.items():
1164 alias_targets.append(target)
1165 item = QTreeWidgetItem( [ target, alias, '-'] )
1166 item.setBackgroundColor(0, QColor('lightgray'))
1167 l.addTopLevelItem(item)
1169 for address in self.wallet.addressbook:
1170 if address in alias_targets: continue
1171 label = self.wallet.labels.get(address,'')
1173 for tx in self.wallet.transactions.values():
1174 if address in map(lambda x: x[0], tx.outputs): n += 1
1176 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1177 item.setFont(0, QFont(MONOSPACE_FONT))
1178 l.addTopLevelItem(item)
1180 l.setCurrentItem(l.topLevelItem(0))
1183 def create_console_tab(self):
1184 from qt_console import Console
1185 self.console = console = Console()
1186 self.console.history = self.config.get("console-history",[])
1187 self.console.history_index = len(self.console.history)
1189 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1190 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1192 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1194 def mkfunc(f, method):
1195 return lambda *args: apply( f, (method, args, self.password_dialog ))
1197 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1198 methods[m] = mkfunc(c._run, m)
1200 console.updateNamespace(methods)
1204 def create_status_bar(self):
1205 self.status_text = ""
1207 sb.setFixedHeight(35)
1208 qtVersion = qVersion()
1210 update_notification = UpdateLabel(self.config)
1211 if(update_notification.new_version):
1212 sb.addPermanentWidget(update_notification)
1214 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1215 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1216 if self.wallet.seed:
1217 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1218 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1219 if self.wallet.seed:
1220 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1221 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1222 sb.addPermanentWidget( self.status_button )
1224 self.run_hook('create_status_bar', (sb,))
1226 self.setStatusBar(sb)
1230 self.config.set_key('gui', 'lite', True)
1233 self.lite.mini.show()
1235 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1236 self.lite.main(None)
1238 def new_contact_dialog(self):
1239 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1240 address = unicode(text)
1242 if is_valid(address):
1243 self.wallet.addressbook.append(address)
1245 self.update_contacts_tab()
1246 self.update_history_tab()
1247 self.update_completions()
1249 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1251 def show_master_public_key(self):
1252 dialog = QDialog(self)
1254 dialog.setWindowTitle(_("Master Public Key"))
1256 main_text = QTextEdit()
1257 main_text.setText(self.wallet.get_master_public_key())
1258 main_text.setReadOnly(True)
1259 main_text.setMaximumHeight(170)
1260 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1262 ok_button = QPushButton(_("OK"))
1263 ok_button.setDefault(True)
1264 ok_button.clicked.connect(dialog.accept)
1266 main_layout = QGridLayout()
1267 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1269 main_layout.addWidget(main_text, 1, 0)
1270 main_layout.addWidget(qrw, 1, 1 )
1272 vbox = QVBoxLayout()
1273 vbox.addLayout(main_layout)
1274 hbox = QHBoxLayout()
1276 hbox.addWidget(ok_button)
1277 vbox.addLayout(hbox)
1279 dialog.setLayout(vbox)
1284 def show_seed_dialog(self, password):
1285 if not self.wallet.seed:
1286 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1289 seed = self.wallet.decode_seed(password)
1291 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1293 self.show_seed(seed, self)
1297 def show_seed(self, seed, parent=None):
1298 dialog = QDialog(parent)
1300 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1302 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1304 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1306 seed_text = QTextEdit(brainwallet)
1307 seed_text.setReadOnly(True)
1308 seed_text.setMaximumHeight(130)
1310 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1311 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1312 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1313 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1314 label2 = QLabel(msg2)
1315 label2.setWordWrap(True)
1318 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1319 logo.setMaximumWidth(60)
1321 qrw = QRCodeWidget(seed)
1323 ok_button = QPushButton(_("OK"))
1324 ok_button.setDefault(True)
1325 ok_button.clicked.connect(dialog.accept)
1327 grid = QGridLayout()
1328 #main_layout.addWidget(logo, 0, 0)
1330 grid.addWidget(logo, 0, 0)
1331 grid.addWidget(label1, 0, 1)
1333 grid.addWidget(seed_text, 1, 0, 1, 2)
1335 grid.addWidget(qrw, 0, 2, 2, 1)
1337 vbox = QVBoxLayout()
1338 vbox.addLayout(grid)
1339 vbox.addWidget(label2)
1341 hbox = QHBoxLayout()
1343 hbox.addWidget(ok_button)
1344 vbox.addLayout(hbox)
1346 dialog.setLayout(vbox)
1349 def show_qrcode(self, data, title = "QR code"):
1353 d.setWindowTitle(title)
1354 d.setMinimumSize(270, 300)
1355 vbox = QVBoxLayout()
1356 qrw = QRCodeWidget(data)
1357 vbox.addWidget(qrw, 1)
1358 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1359 hbox = QHBoxLayout()
1363 filename = "qrcode.bmp"
1364 bmp.save_qrcode(qrw.qr, filename)
1365 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1367 b = QPushButton(_("Save"))
1369 b.clicked.connect(print_qr)
1371 b = QPushButton(_("Close"))
1373 b.clicked.connect(d.accept)
1376 vbox.addLayout(hbox)
1381 def do_protect(self, func, args):
1382 if self.wallet.use_encryption:
1383 password = self.password_dialog()
1389 if args != (False,):
1390 args = (self,) + args + (password,)
1392 args = (self,password)
1397 def show_private_key(self, address, password):
1398 if not address: return
1400 pk = self.wallet.get_private_key(address, password)
1401 except BaseException, e:
1402 self.show_message(str(e))
1404 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1408 def do_sign(self, address, message, signature, password):
1410 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1411 signature.setText(sig)
1412 except BaseException, e:
1413 self.show_message(str(e))
1415 def sign_message(self, address):
1416 if not address: return
1419 d.setWindowTitle(_('Sign Message'))
1420 d.setMinimumSize(410, 290)
1422 tab_widget = QTabWidget()
1424 layout = QGridLayout(tab)
1426 sign_address = QLineEdit()
1428 sign_address.setText(address)
1429 layout.addWidget(QLabel(_('Address')), 1, 0)
1430 layout.addWidget(sign_address, 1, 1)
1432 sign_message = QTextEdit()
1433 layout.addWidget(QLabel(_('Message')), 2, 0)
1434 layout.addWidget(sign_message, 2, 1)
1435 layout.setRowStretch(2,3)
1437 sign_signature = QTextEdit()
1438 layout.addWidget(QLabel(_('Signature')), 3, 0)
1439 layout.addWidget(sign_signature, 3, 1)
1440 layout.setRowStretch(3,1)
1443 hbox = QHBoxLayout()
1444 b = QPushButton(_("Sign"))
1446 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1447 b = QPushButton(_("Close"))
1448 b.clicked.connect(d.accept)
1450 layout.addLayout(hbox, 4, 1)
1451 tab_widget.addTab(tab, _("Sign"))
1455 layout = QGridLayout(tab)
1457 verify_address = QLineEdit()
1458 layout.addWidget(QLabel(_('Address')), 1, 0)
1459 layout.addWidget(verify_address, 1, 1)
1461 verify_message = QTextEdit()
1462 layout.addWidget(QLabel(_('Message')), 2, 0)
1463 layout.addWidget(verify_message, 2, 1)
1464 layout.setRowStretch(2,3)
1466 verify_signature = QTextEdit()
1467 layout.addWidget(QLabel(_('Signature')), 3, 0)
1468 layout.addWidget(verify_signature, 3, 1)
1469 layout.setRowStretch(3,1)
1473 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1474 self.show_message(_("Signature verified"))
1475 except BaseException, e:
1476 self.show_message(str(e))
1479 hbox = QHBoxLayout()
1480 b = QPushButton(_("Verify"))
1481 b.clicked.connect(do_verify)
1483 b = QPushButton(_("Close"))
1484 b.clicked.connect(d.accept)
1486 layout.addLayout(hbox, 4, 1)
1487 tab_widget.addTab(tab, _("Verify"))
1489 vbox = QVBoxLayout()
1490 vbox.addWidget(tab_widget)
1497 def question(self, msg):
1498 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1500 def show_message(self, msg):
1501 QMessageBox.information(self, _('Message'), msg, _('OK'))
1503 def password_dialog(self ):
1510 vbox = QVBoxLayout()
1511 msg = _('Please enter your password')
1512 vbox.addWidget(QLabel(msg))
1514 grid = QGridLayout()
1516 grid.addWidget(QLabel(_('Password')), 1, 0)
1517 grid.addWidget(pw, 1, 1)
1518 vbox.addLayout(grid)
1520 vbox.addLayout(ok_cancel_buttons(d))
1523 if not d.exec_(): return
1524 return unicode(pw.text())
1531 def change_password_dialog( wallet, parent=None ):
1534 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1542 new_pw = QLineEdit()
1543 new_pw.setEchoMode(2)
1544 conf_pw = QLineEdit()
1545 conf_pw.setEchoMode(2)
1547 vbox = QVBoxLayout()
1549 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1550 +_('To disable wallet encryption, enter an empty new password.')) \
1551 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1553 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1554 +_("Leave these fields empty if you want to disable encryption.")
1555 vbox.addWidget(QLabel(msg))
1557 grid = QGridLayout()
1560 if wallet.use_encryption:
1561 grid.addWidget(QLabel(_('Password')), 1, 0)
1562 grid.addWidget(pw, 1, 1)
1564 grid.addWidget(QLabel(_('New Password')), 2, 0)
1565 grid.addWidget(new_pw, 2, 1)
1567 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1568 grid.addWidget(conf_pw, 3, 1)
1569 vbox.addLayout(grid)
1571 vbox.addLayout(ok_cancel_buttons(d))
1574 if not d.exec_(): return
1576 password = unicode(pw.text()) if wallet.use_encryption else None
1577 new_password = unicode(new_pw.text())
1578 new_password2 = unicode(conf_pw.text())
1581 seed = wallet.decode_seed(password)
1583 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1586 if new_password != new_password2:
1587 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1588 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1590 wallet.update_password(seed, password, new_password)
1593 def seed_dialog(wallet, parent=None):
1597 vbox = QVBoxLayout()
1598 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1599 vbox.addWidget(QLabel(msg))
1601 grid = QGridLayout()
1604 seed_e = QLineEdit()
1605 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1606 grid.addWidget(seed_e, 1, 1)
1610 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1611 grid.addWidget(gap_e, 2, 1)
1612 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1613 vbox.addLayout(grid)
1615 vbox.addLayout(ok_cancel_buttons(d))
1618 if not d.exec_(): return
1621 gap = int(unicode(gap_e.text()))
1623 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1627 seed = str(seed_e.text())
1630 print_error("Warning: Not hex, trying decode")
1632 seed = mnemonic.mn_decode( seed.split(' ') )
1634 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1638 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1643 def generate_transaction_information_widget(self, tx):
1644 tabs = QTabWidget(self)
1647 grid_ui = QGridLayout(tab1)
1648 grid_ui.setColumnStretch(0,1)
1649 tabs.addTab(tab1, _('Outputs') )
1651 tree_widget = MyTreeWidget(self)
1652 tree_widget.setColumnCount(2)
1653 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1654 tree_widget.setColumnWidth(0, 300)
1655 tree_widget.setColumnWidth(1, 50)
1657 for address, value in tx.outputs:
1658 item = QTreeWidgetItem( [address, "%s" % ( format_satoshis(value))] )
1659 tree_widget.addTopLevelItem(item)
1661 tree_widget.setMaximumHeight(100)
1663 grid_ui.addWidget(tree_widget)
1666 grid_ui = QGridLayout(tab2)
1667 grid_ui.setColumnStretch(0,1)
1668 tabs.addTab(tab2, _('Inputs') )
1670 tree_widget = MyTreeWidget(self)
1671 tree_widget.setColumnCount(2)
1672 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1674 for input_line in tx.inputs:
1675 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1676 tree_widget.addTopLevelItem(item)
1678 tree_widget.setMaximumHeight(100)
1680 grid_ui.addWidget(tree_widget)
1684 def tx_dict_from_text(self, txt):
1686 tx_dict = json.loads(str(txt))
1687 assert "hex" in tx_dict.keys()
1688 assert "complete" in tx_dict.keys()
1689 if not tx_dict["complete"]:
1690 assert "input_info" in tx_dict.keys()
1692 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1697 def read_tx_from_file(self):
1698 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1702 with open(fileName, "r") as f:
1703 file_content = f.read()
1704 except (ValueError, IOError, os.error), reason:
1705 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1707 return self.tx_dict_from_text(file_content)
1711 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1713 self.wallet.signrawtransaction(tx, input_info, [], password)
1715 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1717 with open(fileName, "w+") as f:
1718 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1719 self.show_message(_("Transaction saved successfully"))
1722 except BaseException, e:
1723 self.show_message(str(e))
1726 def send_raw_transaction(self, raw_tx, dialog = ""):
1727 result, result_message = self.wallet.sendtx( raw_tx )
1729 self.show_message("Transaction successfully sent: %s" % (result_message))
1733 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1735 def do_process_from_text(self):
1736 dialog = QDialog(self)
1737 dialog.setMinimumWidth(500)
1738 dialog.setWindowTitle(_('Input raw transaction'))
1742 l.addWidget(QLabel(_("Transaction:")))
1746 ok_button = QPushButton(_("Load transaction"))
1747 ok_button.setDefault(True)
1748 ok_button.clicked.connect(dialog.accept)
1749 l.addWidget(ok_button)
1752 tx_dict = self.tx_dict_from_text(unicode(txt.toPlainText()))
1754 self.create_process_transaction_window(tx_dict)
1756 def do_process_from_file(self):
1757 tx_dict = self.read_tx_from_file()
1759 self.create_process_transaction_window(tx_dict)
1761 def create_process_transaction_window(self, tx_dict):
1762 tx = Transaction(tx_dict["hex"])
1764 dialog = QDialog(self)
1765 dialog.setMinimumWidth(500)
1766 dialog.setWindowTitle(_('Process raw transaction'))
1772 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1773 l.addWidget(QLabel(_("Actions")), 4,0)
1775 if tx_dict["complete"] == False:
1776 l.addWidget(QLabel(_("Unsigned")), 3,1)
1777 if self.wallet.seed :
1778 b = QPushButton("Sign transaction")
1779 input_info = json.loads(tx_dict["input_info"])
1780 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1781 l.addWidget(b, 4, 1)
1783 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1785 l.addWidget(QLabel(_("Signed")), 3,1)
1786 b = QPushButton("Broadcast transaction")
1787 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1790 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1791 cancelButton = QPushButton(_("Cancel"))
1792 cancelButton.clicked.connect(lambda: dialog.done(0))
1793 l.addWidget(cancelButton, 4,2)
1799 def do_export_privkeys(self, password):
1800 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.")))
1803 select_export = _('Select file to export your private keys to')
1804 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1806 with open(fileName, "w+") as csvfile:
1807 transaction = csv.writer(csvfile)
1808 transaction.writerow(["address", "private_key"])
1811 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1812 transaction.writerow(["%34s"%addr,pk])
1814 self.show_message(_("Private keys exported."))
1816 except (IOError, os.error), reason:
1817 export_error_label = _("Electrum was unable to produce a private key-export.")
1818 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1820 except BaseException, e:
1821 self.show_message(str(e))
1825 def do_import_labels(self):
1826 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1827 if not labelsFile: return
1829 f = open(labelsFile, 'r')
1832 for key, value in json.loads(data).items():
1833 self.wallet.labels[key] = value
1835 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
1836 except (IOError, os.error), reason:
1837 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1840 def do_export_labels(self):
1841 labels = self.wallet.labels
1843 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1845 with open(fileName, 'w+') as f:
1846 json.dump(labels, f)
1847 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1848 except (IOError, os.error), reason:
1849 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1852 def do_export_history(self):
1853 from gui_lite import csv_transaction
1854 csv_transaction(self.wallet)
1858 def do_import_privkey(self, password):
1859 if not self.wallet.imported_keys:
1860 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1861 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1862 + _('Are you sure you understand what you are doing?'), 3, 4)
1865 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1867 sec = str(text).strip()
1869 addr = self.wallet.import_key(sec, password)
1871 QMessageBox.critical(None, _("Unable to import key"), "error")
1873 QMessageBox.information(None, _("Key imported"), addr)
1874 self.update_receive_tab()
1875 self.update_history_tab()
1876 except BaseException as e:
1877 QMessageBox.critical(None, _("Unable to import key"), str(e))
1880 def settings_dialog(self):
1882 d.setWindowTitle(_('Electrum Settings'))
1884 vbox = QVBoxLayout()
1886 tabs = QTabWidget(self)
1887 self.settings_tab = tabs
1888 vbox.addWidget(tabs)
1891 grid_ui = QGridLayout(tab1)
1892 grid_ui.setColumnStretch(0,1)
1893 tabs.addTab(tab1, _('Display') )
1895 nz_label = QLabel(_('Display zeros'))
1896 grid_ui.addWidget(nz_label, 0, 0)
1898 nz_e.setText("%d"% self.wallet.num_zeros)
1899 grid_ui.addWidget(nz_e, 0, 1)
1900 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1901 grid_ui.addWidget(HelpButton(msg), 0, 2)
1902 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1903 if not self.config.is_modifiable('num_zeros'):
1904 for w in [nz_e, nz_label]: w.setEnabled(False)
1906 lang_label=QLabel(_('Language') + ':')
1907 grid_ui.addWidget(lang_label, 1, 0)
1908 lang_combo = QComboBox()
1909 from i18n import languages
1910 lang_combo.addItems(languages.values())
1912 index = languages.keys().index(self.config.get("language",''))
1915 lang_combo.setCurrentIndex(index)
1916 grid_ui.addWidget(lang_combo, 1, 1)
1917 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1918 if not self.config.is_modifiable('language'):
1919 for w in [lang_combo, lang_label]: w.setEnabled(False)
1921 currencies = self.exchanger.get_currencies()
1922 currencies.insert(0, "None")
1924 cur_label=QLabel(_('Currency') + ':')
1925 grid_ui.addWidget(cur_label , 2, 0)
1926 cur_combo = QComboBox()
1927 cur_combo.addItems(currencies)
1929 index = currencies.index(self.config.get('currency', "None"))
1932 cur_combo.setCurrentIndex(index)
1933 grid_ui.addWidget(cur_combo, 2, 1)
1934 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1936 view_label=QLabel(_('Receive Tab') + ':')
1937 grid_ui.addWidget(view_label , 3, 0)
1938 view_combo = QComboBox()
1939 view_combo.addItems([_('Simple'), _('Advanced')])
1940 view_combo.setCurrentIndex(self.expert_mode)
1941 grid_ui.addWidget(view_combo, 3, 1)
1942 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1943 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1944 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n'
1946 grid_ui.addWidget(HelpButton(hh), 3, 2)
1947 grid_ui.setRowStretch(4,1)
1951 grid_wallet = QGridLayout(tab2)
1952 grid_wallet.setColumnStretch(0,1)
1953 tabs.addTab(tab2, _('Wallet') )
1955 fee_label = QLabel(_('Transaction fee'))
1956 grid_wallet.addWidget(fee_label, 0, 0)
1958 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1959 grid_wallet.addWidget(fee_e, 0, 2)
1960 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1961 + _('Recommended value') + ': 0.001'
1962 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1963 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1964 if not self.config.is_modifiable('fee'):
1965 for w in [fee_e, fee_label]: w.setEnabled(False)
1967 usechange_label = QLabel(_('Use change addresses'))
1968 grid_wallet.addWidget(usechange_label, 1, 0)
1969 usechange_combo = QComboBox()
1970 usechange_combo.addItems([_('Yes'), _('No')])
1971 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1972 grid_wallet.addWidget(usechange_combo, 1, 2)
1973 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1974 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1976 gap_label = QLabel(_('Gap limit'))
1977 grid_wallet.addWidget(gap_label, 2, 0)
1979 gap_e.setText("%d"% self.wallet.gap_limit)
1980 grid_wallet.addWidget(gap_e, 2, 2)
1981 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1982 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1983 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1984 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1985 + _('Warning') + ': ' \
1986 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1987 + _('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'
1988 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1989 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1990 if not self.config.is_modifiable('gap_limit'):
1991 for w in [gap_e, gap_label]: w.setEnabled(False)
1993 grid_wallet.setRowStretch(3,1)
1998 grid_io = QGridLayout(tab3)
1999 grid_io.setColumnStretch(0,1)
2000 tabs.addTab(tab3, _('Import/Export') )
2002 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
2003 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2004 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2005 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2007 grid_io.addWidget(QLabel(_('History')), 2, 0)
2008 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2009 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2011 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2013 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2014 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2015 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2017 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2018 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2019 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2020 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2021 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2024 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
2025 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
2026 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
2027 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
2029 grid_io.setRowStretch(5,1)
2034 tab5 = QScrollArea()
2035 grid_plugins = QGridLayout(tab5)
2036 grid_plugins.setColumnStretch(0,1)
2037 tabs.addTab(tab5, _('Plugins') )
2038 def mk_toggle(cb, p):
2039 return lambda: cb.setChecked(p.toggle(self))
2040 for i, p in enumerate(self.plugins):
2042 name, description = p.get_info()
2043 cb = QCheckBox(name)
2044 cb.setDisabled(not p.is_available())
2045 cb.setChecked(p.is_enabled())
2046 cb.clicked.connect(mk_toggle(cb,p))
2047 grid_plugins.addWidget(cb, i, 0)
2048 grid_plugins.addWidget(HelpButton(description), i, 1)
2050 print_msg("Error: cannot display plugin", p)
2051 traceback.print_exc(file=sys.stdout)
2052 grid_plugins.setRowStretch(i+1,1)
2054 self.run_hook('create_settings_tab', (self,tabs,))
2056 vbox.addLayout(ok_cancel_buttons(d))
2060 if not d.exec_(): return
2062 fee = unicode(fee_e.text())
2064 fee = int( 100000000 * Decimal(fee) )
2066 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2069 if self.wallet.fee != fee:
2070 self.wallet.fee = fee
2073 nz = unicode(nz_e.text())
2078 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2081 if self.wallet.num_zeros != nz:
2082 self.wallet.num_zeros = nz
2083 self.config.set_key('num_zeros', nz, True)
2084 self.update_history_tab()
2085 self.update_receive_tab()
2087 usechange_result = usechange_combo.currentIndex() == 0
2088 if self.wallet.use_change != usechange_result:
2089 self.wallet.use_change = usechange_result
2090 self.config.set_key('use_change', self.wallet.use_change, True)
2093 n = int(gap_e.text())
2095 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2098 if self.wallet.gap_limit != n:
2099 r = self.wallet.change_gap_limit(n)
2101 self.update_receive_tab()
2102 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2104 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2106 need_restart = False
2108 lang_request = languages.keys()[lang_combo.currentIndex()]
2109 if lang_request != self.config.get('language'):
2110 self.config.set_key("language", lang_request, True)
2113 cur_request = str(currencies[cur_combo.currentIndex()])
2114 if cur_request != self.config.get('currency', "None"):
2115 self.config.set_key('currency', cur_request, True)
2116 self.update_wallet()
2118 self.run_hook('close_settings_dialog', (self,))
2121 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2123 self.receive_tab_set_mode(view_combo.currentIndex())
2127 def network_dialog(wallet, parent=None):
2128 interface = wallet.interface
2130 if interface.is_connected:
2131 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2133 status = _("Not connected")
2134 server = interface.server
2137 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2138 server = interface.server
2140 plist, servers_list = interface.get_servers_list()
2144 d.setWindowTitle(_('Server'))
2145 d.setMinimumSize(375, 20)
2147 vbox = QVBoxLayout()
2150 hbox = QHBoxLayout()
2152 l.setPixmap(QPixmap(":icons/network.png"))
2155 hbox.addWidget(QLabel(status))
2157 vbox.addLayout(hbox)
2161 grid = QGridLayout()
2163 vbox.addLayout(grid)
2166 server_protocol = QComboBox()
2167 server_host = QLineEdit()
2168 server_host.setFixedWidth(200)
2169 server_port = QLineEdit()
2170 server_port.setFixedWidth(60)
2172 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2173 protocol_letters = 'thsg'
2174 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2175 server_protocol.addItems(protocol_names)
2177 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2178 grid.addWidget(server_protocol, 0, 1)
2179 grid.addWidget(server_host, 0, 2)
2180 grid.addWidget(server_port, 0, 3)
2182 def change_protocol(p):
2183 protocol = protocol_letters[p]
2184 host = unicode(server_host.text())
2185 pp = plist.get(host,DEFAULT_PORTS)
2186 if protocol not in pp.keys():
2187 protocol = pp.keys()[0]
2189 server_host.setText( host )
2190 server_port.setText( port )
2192 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2194 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2195 servers_list_widget = QTreeWidget(parent)
2196 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2197 servers_list_widget.setMaximumHeight(150)
2198 servers_list_widget.setColumnWidth(0, 240)
2199 for _host in servers_list.keys():
2200 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2201 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2203 def change_server(host, protocol=None):
2204 pp = plist.get(host,DEFAULT_PORTS)
2206 port = pp.get(protocol)
2207 if not port: protocol = None
2210 if 't' in pp.keys():
2212 port = pp.get(protocol)
2214 protocol = pp.keys()[0]
2215 port = pp.get(protocol)
2217 server_host.setText( host )
2218 server_port.setText( port )
2219 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2221 if not plist: return
2222 for p in protocol_letters:
2223 i = protocol_letters.index(p)
2224 j = server_protocol.model().index(i,0)
2225 if p not in pp.keys():
2226 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2228 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2232 host, port, protocol = server.split(':')
2233 change_server(host,protocol)
2235 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2236 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2238 if not wallet.config.is_modifiable('server'):
2239 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2242 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2243 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2244 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2245 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2248 proxy_mode = QComboBox()
2249 proxy_host = QLineEdit()
2250 proxy_host.setFixedWidth(200)
2251 proxy_port = QLineEdit()
2252 proxy_port.setFixedWidth(60)
2253 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2255 def check_for_disable(index = False):
2256 if proxy_mode.currentText() != 'NONE':
2257 proxy_host.setEnabled(True)
2258 proxy_port.setEnabled(True)
2260 proxy_host.setEnabled(False)
2261 proxy_port.setEnabled(False)
2264 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2266 if not wallet.config.is_modifiable('proxy'):
2267 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2269 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2270 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2271 proxy_host.setText(proxy_config.get("host"))
2272 proxy_port.setText(proxy_config.get("port"))
2274 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2275 grid.addWidget(proxy_mode, 2, 1)
2276 grid.addWidget(proxy_host, 2, 2)
2277 grid.addWidget(proxy_port, 2, 3)
2280 vbox.addLayout(ok_cancel_buttons(d))
2283 if not d.exec_(): return
2285 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2286 if proxy_mode.currentText() != 'NONE':
2287 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2291 wallet.config.set_key("proxy", proxy, True)
2292 wallet.config.set_key("server", server, True)
2293 interface.set_server(server, proxy)
2294 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2297 def closeEvent(self, event):
2299 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2300 self.save_column_widths()
2301 self.config.set_key("column-widths", self.column_widths, True)
2302 self.config.set_key("console-history",self.console.history[-50:])
2308 def __init__(self, wallet, config, app=None):
2309 self.wallet = wallet
2310 self.config = config
2312 self.app = QApplication(sys.argv)
2315 def restore_or_create(self):
2316 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2317 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2318 if r==2: return None
2319 return 'restore' if r==1 else 'create'
2321 def seed_dialog(self):
2322 return ElectrumWindow.seed_dialog( self.wallet )
2324 def network_dialog(self):
2325 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2328 def show_seed(self):
2329 ElectrumWindow.show_seed(self.wallet.seed)
2332 def password_dialog(self):
2333 if self.wallet.seed:
2334 ElectrumWindow.change_password_dialog(self.wallet)
2337 def restore_wallet(self):
2338 wallet = self.wallet
2339 # wait until we are connected, because the user might have selected another server
2340 if not wallet.interface.is_connected:
2341 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2342 waiting_dialog(waiting)
2344 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2345 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2347 wallet.set_up_to_date(False)
2348 wallet.interface.poke('synchronizer')
2349 waiting_dialog(waiting)
2350 if wallet.is_found():
2351 print_error( "Recovery successful" )
2353 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2360 w = ElectrumWindow(self.wallet, self.config)
2361 if url: w.set_url(url)