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, threading
20 from i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
29 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
31 from PyQt4.QtGui import *
32 from PyQt4.QtCore import *
33 import PyQt4.QtCore as QtCore
35 from electrum.bitcoin import MIN_RELAY_TX_FEE
40 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'")
42 from electrum.wallet import format_satoshis
43 from electrum.bitcoin import Transaction, is_valid
44 from electrum import mnemonic
45 from electrum import util, bitcoin, commands
47 import bmp, pyqrnative
50 from amountedit import AmountEdit
51 from network_dialog import NetworkDialog
52 from qrcodewidget import QRCodeWidget
54 from decimal import Decimal
62 if platform.system() == 'Windows':
63 MONOSPACE_FONT = 'Lucida Console'
64 elif platform.system() == 'Darwin':
65 MONOSPACE_FONT = 'Monaco'
67 MONOSPACE_FONT = 'monospace'
69 from electrum import ELECTRUM_VERSION
74 class UpdateLabel(QLabel):
75 def __init__(self, config, parent=None):
76 QLabel.__init__(self, parent)
77 self.new_version = False
80 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
81 con.request("GET", "/version")
82 res = con.getresponse()
83 except socket.error as msg:
84 print_error("Could not retrieve version information")
88 self.latest_version = res.read()
89 self.latest_version = self.latest_version.replace("\n","")
90 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
92 self.current_version = ELECTRUM_VERSION
93 if(self.compare_versions(self.latest_version, self.current_version) == 1):
94 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
95 if(self.compare_versions(self.latest_version, latest_seen) == 1):
96 self.new_version = True
97 self.setText(_("New version available") + ": " + self.latest_version)
100 def compare_versions(self, version1, version2):
102 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
103 return cmp(normalize(version1), normalize(version2))
105 def ignore_this_version(self):
107 self.config.set_key("last_seen_version", self.latest_version, True)
108 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
111 def ignore_all_version(self):
113 self.config.set_key("last_seen_version", "9.9.9", True)
114 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
117 def open_website(self):
118 webbrowser.open("http://electrum.org/download.html")
121 def mouseReleaseEvent(self, event):
122 dialog = QDialog(self)
123 dialog.setWindowTitle(_('Electrum update'))
126 main_layout = QGridLayout()
127 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
129 ignore_version = QPushButton(_("Ignore this version"))
130 ignore_version.clicked.connect(self.ignore_this_version)
132 ignore_all_versions = QPushButton(_("Ignore all versions"))
133 ignore_all_versions.clicked.connect(self.ignore_all_version)
135 open_website = QPushButton(_("Goto download page"))
136 open_website.clicked.connect(self.open_website)
138 main_layout.addWidget(ignore_version, 1, 0)
139 main_layout.addWidget(ignore_all_versions, 1, 1)
140 main_layout.addWidget(open_website, 1, 2)
142 dialog.setLayout(main_layout)
146 if not dialog.exec_(): return
150 class Timer(QtCore.QThread):
153 self.emit(QtCore.SIGNAL('timersignal'))
156 class HelpButton(QPushButton):
157 def __init__(self, text):
158 QPushButton.__init__(self, '?')
159 self.setFocusPolicy(Qt.NoFocus)
160 self.setFixedWidth(20)
161 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
164 class EnterButton(QPushButton):
165 def __init__(self, text, func):
166 QPushButton.__init__(self, text)
168 self.clicked.connect(func)
170 def keyPressEvent(self, e):
171 if e.key() == QtCore.Qt.Key_Return:
174 class MyTreeWidget(QTreeWidget):
175 def __init__(self, parent):
176 QTreeWidget.__init__(self, parent)
179 for i in range(0,self.viewport().height()/5):
180 if self.itemAt(QPoint(0,i*5)) == item:
184 for j in range(0,30):
185 if self.itemAt(QPoint(0,i*5 + j)) != item:
187 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
189 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
194 class StatusBarButton(QPushButton):
195 def __init__(self, icon, tooltip, func):
196 QPushButton.__init__(self, icon, '')
197 self.setToolTip(tooltip)
199 self.setMaximumWidth(25)
200 self.clicked.connect(func)
203 def keyPressEvent(self, e):
204 if e.key() == QtCore.Qt.Key_Return:
211 def waiting_dialog(f):
217 w.setWindowTitle('Electrum')
227 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
236 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive":[[370], [370,200,130]] }
238 class ElectrumWindow(QMainWindow):
239 def changeEvent(self, event):
240 flags = self.windowFlags();
241 if event and event.type() == QtCore.QEvent.WindowStateChange:
242 if self.windowState() & QtCore.Qt.WindowMinimized:
243 self.build_menu(True)
244 # The only way to toggle the icon in the window managers taskbar is to use the Qt.Tooltip flag
245 # The problem is that it somehow creates an (in)visible window that will stay active and prevent
246 # Electrum from closing.
247 # As for now I have no clue how to implement a proper 'hide to tray' functionality.
248 # self.setWindowFlags(flags & ~Qt.ToolTip)
249 elif event.oldState() & QtCore.Qt.WindowMinimized:
250 self.build_menu(False)
251 #self.setWindowFlags(flags | Qt.ToolTip)
253 def build_menu(self, is_hidden = False):
255 if self.isMinimized():
256 m.addAction(_("Show"), self.showNormal)
258 m.addAction(_("Hide"), self.showMinimized)
261 m.addAction(_("Exit Electrum"), self.close)
262 self.tray.setContextMenu(m)
264 def tray_activated(self, reason):
265 if reason == QSystemTrayIcon.DoubleClick:
268 def __init__(self, wallet, config):
269 QMainWindow.__init__(self)
270 self._close_electrum = False
274 self.current_account = self.config.get("current_account", None)
276 self.icon = QIcon(':/icons/electrum.png')
277 self.tray = QSystemTrayIcon(self.icon, self)
278 self.tray.setToolTip('Electrum')
279 self.tray.activated.connect(self.tray_activated)
285 self.create_status_bar()
287 self.need_update = threading.Event()
288 self.wallet.interface.register_callback('updated', lambda: self.need_update.set())
289 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
290 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
291 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
292 self.wallet.interface.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
294 self.expert_mode = config.get('classic_expert_mode', False)
295 self.decimal_point = config.get('decimal_point', 8)
297 set_language(config.get('language'))
299 self.funds_error = False
300 self.completions = QStringListModel()
302 self.tabs = tabs = QTabWidget(self)
303 self.column_widths = self.config.get("column_widths", default_column_widths )
304 tabs.addTab(self.create_history_tab(), _('History') )
305 tabs.addTab(self.create_send_tab(), _('Send') )
306 tabs.addTab(self.create_receive_tab(), _('Receive') )
307 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
308 tabs.addTab(self.create_console_tab(), _('Console') )
309 tabs.setMinimumSize(600, 400)
310 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
311 self.setCentralWidget(tabs)
313 g = self.config.get("winpos-qt",[100, 100, 840, 400])
314 self.setGeometry(g[0], g[1], g[2], g[3])
315 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
316 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
317 self.setWindowTitle( title )
321 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
322 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
323 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
324 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
325 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
327 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
328 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
329 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
330 self.history_list.setFocus(True)
332 self.exchanger = exchange_rate.Exchanger(self)
333 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
335 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
336 if platform.system() == 'Windows':
337 n = 3 if self.wallet.seed else 2
338 tabs.setCurrentIndex (n)
339 tabs.setCurrentIndex (0)
341 # set initial message
342 self.console.showMessage(self.wallet.interface.banner)
344 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
345 self.notify_transactions()
347 # plugins that need to change the GUI do it here
348 self.run_hook('init_gui')
350 def select_wallet_file(self):
351 wallet_folder = self.wallet.config.path
352 re.sub("(\/\w*.dat)$", "", wallet_folder)
353 file_name = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat")
357 self.load_wallet(file_name)
360 def init_menubar(self):
363 electrum_menu = menubar.addMenu(_("&File"))
364 open_wallet_action = electrum_menu.addAction(_("Open wallet"))
365 open_wallet_action.triggered.connect(self.select_wallet_file)
367 preferences_name = _("Preferences")
368 if sys.platform == 'darwin':
369 preferences_name = _("Electrum preferences") # Settings / Preferences are all reserved keywords in OSX using this as work around
371 preferences_menu = electrum_menu.addAction(preferences_name)
372 preferences_menu.triggered.connect(self.settings_dialog)
373 electrum_menu.addSeparator()
375 raw_transaction_menu = electrum_menu.addMenu(_("&Load raw transaction"))
377 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
378 raw_transaction_file.triggered.connect(self.do_process_from_file)
380 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
381 raw_transaction_text.triggered.connect(self.do_process_from_text)
383 electrum_menu.addSeparator()
384 quit_item = electrum_menu.addAction(_("&Close"))
385 quit_item.triggered.connect(self.close)
387 wallet_menu = menubar.addMenu(_("&Wallet"))
388 wallet_backup = wallet_menu.addAction(_("&Create backup"))
389 wallet_backup.triggered.connect(lambda: backup_wallet(self.config.path))
391 show_menu = wallet_menu.addMenu(_("Show"))
394 show_seed = show_menu.addAction(_("&Seed"))
395 show_seed.triggered.connect(self.show_seed_dialog)
397 show_mpk = show_menu.addAction(_("&Master Public Key"))
398 show_mpk.triggered.connect(self.show_master_public_key)
400 wallet_menu.addSeparator()
401 new_contact = wallet_menu.addAction(_("&New contact"))
402 new_contact.triggered.connect(self.new_contact_dialog)
404 import_menu = menubar.addMenu(_("&Import"))
405 in_labels = import_menu.addAction(_("&Labels"))
406 in_labels.triggered.connect(self.do_import_labels)
408 in_private_keys = import_menu.addAction(_("&Private keys"))
409 in_private_keys.triggered.connect(self.do_import_privkey)
411 export_menu = menubar.addMenu(_("&Export"))
412 ex_private_keys = export_menu.addAction(_("&Private keys"))
413 ex_private_keys.triggered.connect(self.do_export_privkeys)
415 ex_history = export_menu.addAction(_("&History"))
416 ex_history.triggered.connect(self.do_export_history)
418 ex_labels = export_menu.addAction(_("&Labels"))
419 ex_labels.triggered.connect(self.do_export_labels)
421 help_menu = menubar.addMenu(_("&Help"))
422 doc_open = help_menu.addAction(_("&Documentation"))
423 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
424 web_open = help_menu.addAction(_("&Official website"))
425 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
427 self.setMenuBar(menubar)
429 def load_wallet(self, filename):
432 config = electrum.SimpleConfig({'wallet_path': filename})
433 if not config.wallet_file_exists:
434 self.show_message("file not found "+ filename)
437 #self.wallet.verifier.stop()
438 interface = self.wallet.interface
439 verifier = self.wallet.verifier
440 self.wallet.synchronizer.stop()
443 self.wallet = electrum.Wallet(self.config)
444 self.wallet.interface = interface
445 self.wallet.verifier = verifier
447 synchronizer = electrum.WalletSynchronizer(self.wallet, self.config)
452 def notify_transactions(self):
453 print_error("Notifying GUI")
454 if len(self.wallet.interface.pending_transactions_for_notifications) > 0:
455 # Combine the transactions if there are more then three
456 tx_amount = len(self.wallet.interface.pending_transactions_for_notifications)
459 for tx in self.wallet.interface.pending_transactions_for_notifications:
460 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
464 self.notify("%s new transactions received. Total amount received in the new transactions %s %s" \
465 % (tx_amount, self.format_amount(total_amount), self.base_unit()))
467 self.wallet.interface.pending_transactions_for_notifications = []
469 for tx in self.wallet.interface.pending_transactions_for_notifications:
471 self.wallet.interface.pending_transactions_for_notifications.remove(tx)
472 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
474 self.notify("New transaction received. %s %s" % (self.format_amount(v), self.base_unit()))
476 def notify(self, message):
477 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
480 def init_plugins(self):
481 import imp, pkgutil, __builtin__
482 if __builtin__.use_local_modules:
483 fp, pathname, description = imp.find_module('plugins')
484 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
485 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
486 imp.load_module('electrum_plugins', fp, pathname, description)
487 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
489 import electrum_plugins
490 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
491 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
496 self.plugins.append( p.Plugin(self) )
498 print_msg("Error:cannot initialize plugin",p)
499 traceback.print_exc(file=sys.stdout)
502 def run_hook(self, name, *args):
503 for p in self.plugins:
504 if not p.is_enabled():
513 print_error("Plugin error")
514 traceback.print_exc(file=sys.stdout)
519 def set_label(self, name, text = None):
521 old_text = self.wallet.labels.get(name)
524 self.wallet.labels[name] = text
525 self.wallet.config.set_key('labels', self.wallet.labels)
529 self.wallet.labels.pop(name)
531 self.run_hook('set_label', name, text, changed)
535 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
536 def getOpenFileName(self, title, filter = None):
537 directory = self.config.get('io_dir', os.path.expanduser('~'))
538 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
539 if fileName and directory != os.path.dirname(fileName):
540 self.config.set_key('io_dir', os.path.dirname(fileName), True)
543 def getSaveFileName(self, title, filename, filter = None):
544 directory = self.config.get('io_dir', os.path.expanduser('~'))
545 path = os.path.join( directory, filename )
546 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
547 if fileName and directory != os.path.dirname(fileName):
548 self.config.set_key('io_dir', os.path.dirname(fileName), True)
554 QMainWindow.close(self)
555 self.run_hook('close_main_window')
557 def connect_slots(self, sender):
558 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
559 self.previous_payto_e=''
561 def timer_actions(self):
562 if self.need_update.is_set():
564 self.need_update.clear()
565 self.run_hook('timer_actions')
567 def format_amount(self, x, is_diff=False, whitespaces=False):
568 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point, whitespaces)
570 def read_amount(self, x):
571 if x in['.', '']: return None
572 p = pow(10, self.decimal_point)
573 return int( p * Decimal(x) )
576 assert self.decimal_point in [5,8]
577 return "BTC" if self.decimal_point == 8 else "mBTC"
579 def update_status(self):
580 if self.wallet.interface and self.wallet.interface.is_connected:
581 if not self.wallet.up_to_date:
582 text = _("Synchronizing...")
583 icon = QIcon(":icons/status_waiting.png")
585 c, u = self.wallet.get_account_balance(self.current_account)
586 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
587 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
588 text += self.create_quote_text(Decimal(c+u)/100000000)
589 self.tray.setToolTip(text)
590 icon = QIcon(":icons/status_connected.png")
592 text = _("Not connected")
593 icon = QIcon(":icons/status_disconnected.png")
595 self.balance_label.setText(text)
596 self.status_button.setIcon( icon )
598 def update_wallet(self):
600 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
601 self.update_history_tab()
602 self.update_receive_tab()
603 self.update_contacts_tab()
604 self.update_completions()
607 def create_quote_text(self, btc_balance):
608 quote_currency = self.config.get("currency", "None")
609 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
610 if quote_balance is None:
613 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
616 def create_history_tab(self):
617 self.history_list = l = MyTreeWidget(self)
619 for i,width in enumerate(self.column_widths['history']):
620 l.setColumnWidth(i, width)
621 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
622 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
623 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
625 l.setContextMenuPolicy(Qt.CustomContextMenu)
626 l.customContextMenuRequested.connect(self.create_history_menu)
630 def create_history_menu(self, position):
631 self.history_list.selectedIndexes()
632 item = self.history_list.currentItem()
634 tx_hash = str(item.data(0, Qt.UserRole).toString())
635 if not tx_hash: return
637 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
638 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
639 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
640 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
643 def show_tx_details(self, tx):
644 dialog = QDialog(self)
646 dialog.setWindowTitle(_("Transaction Details"))
648 dialog.setLayout(vbox)
649 dialog.setMinimumSize(600,300)
652 if tx_hash in self.wallet.transactions.keys():
653 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
654 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
656 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
662 vbox.addWidget(QLabel("Transaction ID:"))
663 e = QLineEdit(tx_hash)
667 vbox.addWidget(QLabel("Date: %s"%time_str))
668 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
671 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
672 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
674 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
675 vbox.addWidget(QLabel("Transaction fee: unknown"))
677 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
679 vbox.addWidget( self.generate_transaction_information_widget(tx) )
681 ok_button = QPushButton(_("Close"))
682 ok_button.setDefault(True)
683 ok_button.clicked.connect(dialog.accept)
687 hbox.addWidget(ok_button)
691 def tx_label_clicked(self, item, column):
692 if column==2 and item.isSelected():
694 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
695 self.history_list.editItem( item, column )
696 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
699 def tx_label_changed(self, item, column):
703 tx_hash = str(item.data(0, Qt.UserRole).toString())
704 tx = self.wallet.transactions.get(tx_hash)
705 text = unicode( item.text(2) )
706 self.set_label(tx_hash, text)
708 item.setForeground(2, QBrush(QColor('black')))
710 text = self.wallet.get_default_label(tx_hash)
711 item.setText(2, text)
712 item.setForeground(2, QBrush(QColor('gray')))
716 def edit_label(self, is_recv):
717 l = self.receive_list if is_recv else self.contacts_list
718 item = l.currentItem()
719 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
720 l.editItem( item, 1 )
721 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
725 def address_label_clicked(self, item, column, l, column_addr, column_label):
726 if column == column_label and item.isSelected():
727 is_editable = item.data(0, 32).toBool()
730 addr = unicode( item.text(column_addr) )
731 label = unicode( item.text(column_label) )
732 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
733 l.editItem( item, column )
734 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
737 def address_label_changed(self, item, column, l, column_addr, column_label):
738 if column == column_label:
739 addr = unicode( item.text(column_addr) )
740 text = unicode( item.text(column_label) )
741 is_editable = item.data(0, 32).toBool()
745 changed = self.set_label(addr, text)
747 self.update_history_tab()
748 self.update_completions()
750 self.current_item_changed(item)
752 self.run_hook('item_changed', item, column)
755 def current_item_changed(self, a):
756 self.run_hook('current_item_changed', a)
760 def update_history_tab(self):
762 self.history_list.clear()
763 for item in self.wallet.get_tx_history(self.current_account):
764 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
767 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
772 time_str = 'unverified'
773 icon = QIcon(":icons/unconfirmed.png")
776 icon = QIcon(":icons/unconfirmed.png")
778 icon = QIcon(":icons/clock%d.png"%conf)
780 icon = QIcon(":icons/confirmed.png")
782 if value is not None:
783 v_str = self.format_amount(value, True, whitespaces=True)
787 balance_str = self.format_amount(balance, whitespaces=True)
790 label, is_default_label = self.wallet.get_label(tx_hash)
792 label = _('Pruned transaction outputs')
793 is_default_label = False
795 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
796 item.setFont(2, QFont(MONOSPACE_FONT))
797 item.setFont(3, QFont(MONOSPACE_FONT))
798 item.setFont(4, QFont(MONOSPACE_FONT))
800 item.setForeground(3, QBrush(QColor("#BC1E1E")))
802 item.setData(0, Qt.UserRole, tx_hash)
803 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
805 item.setForeground(2, QBrush(QColor('grey')))
807 item.setIcon(0, icon)
808 self.history_list.insertTopLevelItem(0,item)
811 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
814 def create_send_tab(self):
819 grid.setColumnMinimumWidth(3,300)
820 grid.setColumnStretch(5,1)
823 self.payto_e = QLineEdit()
824 grid.addWidget(QLabel(_('Pay to')), 1, 0)
825 grid.addWidget(self.payto_e, 1, 1, 1, 3)
827 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)
829 completer = QCompleter()
830 completer.setCaseSensitivity(False)
831 self.payto_e.setCompleter(completer)
832 completer.setModel(self.completions)
834 self.message_e = QLineEdit()
835 grid.addWidget(QLabel(_('Description')), 2, 0)
836 grid.addWidget(self.message_e, 2, 1, 1, 3)
837 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)
839 self.amount_e = AmountEdit(self.base_unit)
840 grid.addWidget(QLabel(_('Amount')), 3, 0)
841 grid.addWidget(self.amount_e, 3, 1, 1, 2)
842 grid.addWidget(HelpButton(
843 _('Amount to be sent.') + '\n\n' \
844 + _('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.') \
845 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
847 self.fee_e = AmountEdit(self.base_unit)
848 grid.addWidget(QLabel(_('Fee')), 4, 0)
849 grid.addWidget(self.fee_e, 4, 1, 1, 2)
850 grid.addWidget(HelpButton(
851 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
852 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
853 + _('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)
856 b = EnterButton(_("Send"), self.do_send)
858 b = EnterButton(_("Create unsigned transaction"), self.do_send)
859 grid.addWidget(b, 6, 1)
861 b = EnterButton(_("Clear"),self.do_clear)
862 grid.addWidget(b, 6, 2)
864 self.payto_sig = QLabel('')
865 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
867 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
868 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
877 def entry_changed( is_fee ):
878 self.funds_error = False
880 if self.amount_e.is_shortcut:
881 self.amount_e.is_shortcut = False
882 c, u = self.wallet.get_account_balance(self.current_account)
883 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
884 fee = self.wallet.estimated_fee(inputs)
886 self.amount_e.setText( self.format_amount(amount) )
887 self.fee_e.setText( self.format_amount( fee ) )
890 amount = self.read_amount(str(self.amount_e.text()))
891 fee = self.read_amount(str(self.fee_e.text()))
893 if not is_fee: fee = None
896 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
898 self.fee_e.setText( self.format_amount( fee ) )
901 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
905 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
906 self.funds_error = True
907 text = _( "Not enough funds" )
908 c, u = self.wallet.get_frozen_balance()
909 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
911 self.statusBar().showMessage(text)
912 self.amount_e.setPalette(palette)
913 self.fee_e.setPalette(palette)
915 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
916 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
918 self.run_hook('create_send_tab', grid)
922 def update_completions(self):
924 for addr,label in self.wallet.labels.items():
925 if addr in self.wallet.addressbook:
926 l.append( label + ' <' + addr + '>')
928 self.run_hook('update_completions', l)
929 self.completions.setStringList(l)
933 return lambda s, *args: s.do_protect(func, args)
938 label = unicode( self.message_e.text() )
939 r = unicode( self.payto_e.text() )
942 # label or alias, with address in brackets
943 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
944 to_address = m.group(2) if m else r
946 if not is_valid(to_address):
947 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
951 amount = self.read_amount(unicode( self.amount_e.text()))
953 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
956 fee = self.read_amount(unicode( self.fee_e.text()))
958 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
961 confirm_amount = self.config.get('confirm_amount', 100000000)
962 if amount >= confirm_amount:
963 if not self.question("send %s to %s?"%(self.format_amount(amount) + ' '+ self.base_unit(), to_address)):
966 self.send_tx(to_address, amount, fee, label)
970 def send_tx(self, to_address, amount, fee, label, password):
973 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
974 except BaseException, e:
975 self.show_message(str(e))
978 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
979 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
982 self.run_hook('send_tx', tx)
985 self.set_label(tx.hash(), label)
988 h = self.wallet.send_tx(tx)
989 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
990 status, msg = self.wallet.receive_tx( h )
992 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
994 self.update_contacts_tab()
996 QMessageBox.warning(self, _('Error'), msg, _('OK'))
998 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
1000 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
1001 with open(fileName,'w') as f:
1002 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1003 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
1005 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
1010 def set_url(self, url):
1011 address, amount, label, message, signature, identity, url = util.parse_url(url)
1012 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
1014 if label and self.wallet.labels.get(address) != label:
1015 if self.question('Give label "%s" to address %s ?'%(label,address)):
1016 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
1017 self.wallet.addressbook.append(address)
1018 self.set_label(address, label)
1020 self.run_hook('set_url', url, self.show_message, self.question)
1022 self.tabs.setCurrentIndex(1)
1023 label = self.wallet.labels.get(address)
1024 m_addr = label + ' <'+ address +'>' if label else address
1025 self.payto_e.setText(m_addr)
1027 self.message_e.setText(message)
1028 self.amount_e.setText(amount)
1030 self.set_frozen(self.payto_e,True)
1031 self.set_frozen(self.amount_e,True)
1032 self.set_frozen(self.message_e,True)
1033 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
1035 self.payto_sig.setVisible(False)
1038 self.payto_sig.setVisible(False)
1039 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1041 self.set_frozen(e,False)
1042 self.update_status()
1044 def set_frozen(self,entry,frozen):
1046 entry.setReadOnly(True)
1047 entry.setFrame(False)
1048 palette = QPalette()
1049 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1050 entry.setPalette(palette)
1052 entry.setReadOnly(False)
1053 entry.setFrame(True)
1054 palette = QPalette()
1055 palette.setColor(entry.backgroundRole(), QColor('white'))
1056 entry.setPalette(palette)
1059 def toggle_freeze(self,addr):
1061 if addr in self.wallet.frozen_addresses:
1062 self.wallet.unfreeze(addr)
1064 self.wallet.freeze(addr)
1065 self.update_receive_tab()
1067 def toggle_priority(self,addr):
1069 if addr in self.wallet.prioritized_addresses:
1070 self.wallet.unprioritize(addr)
1072 self.wallet.prioritize(addr)
1073 self.update_receive_tab()
1076 def create_list_tab(self, headers):
1077 "generic tab creation method"
1078 l = MyTreeWidget(self)
1079 l.setColumnCount( len(headers) )
1080 l.setHeaderLabels( headers )
1083 vbox = QVBoxLayout()
1090 vbox.addWidget(buttons)
1092 hbox = QHBoxLayout()
1095 buttons.setLayout(hbox)
1100 def create_receive_tab(self):
1101 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1102 l.setContextMenuPolicy(Qt.CustomContextMenu)
1103 l.customContextMenuRequested.connect(self.create_receive_menu)
1104 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1105 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1106 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1107 self.receive_list = l
1108 self.receive_buttons_hbox = hbox
1113 def receive_tab_set_mode(self, i):
1114 self.save_column_widths()
1115 self.expert_mode = (i == 1)
1116 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1117 self.update_receive_tab()
1120 def save_column_widths(self):
1121 if not self.expert_mode:
1122 widths = [ self.receive_list.columnWidth(0) ]
1125 for i in range(self.receive_list.columnCount() -1):
1126 widths.append(self.receive_list.columnWidth(i))
1127 self.column_widths["receive"][self.expert_mode] = widths
1129 self.column_widths["history"] = []
1130 for i in range(self.history_list.columnCount() - 1):
1131 self.column_widths["history"].append(self.history_list.columnWidth(i))
1133 self.column_widths["contacts"] = []
1134 for i in range(self.contacts_list.columnCount() - 1):
1135 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1137 self.config.set_key("column_widths", self.column_widths, True)
1140 def create_contacts_tab(self):
1141 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1142 l.setContextMenuPolicy(Qt.CustomContextMenu)
1143 l.customContextMenuRequested.connect(self.create_contact_menu)
1144 for i,width in enumerate(self.column_widths['contacts']):
1145 l.setColumnWidth(i, width)
1147 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1148 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1149 self.contacts_list = l
1150 self.contacts_buttons_hbox = hbox
1155 def delete_imported_key(self, addr):
1156 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1157 self.wallet.delete_imported_key(addr)
1158 self.update_receive_tab()
1159 self.update_history_tab()
1162 def create_receive_menu(self, position):
1163 # fixme: this function apparently has a side effect.
1164 # if it is not called the menu pops up several times
1165 #self.receive_list.selectedIndexes()
1167 item = self.receive_list.itemAt(position)
1169 addr = unicode(item.text(0))
1170 if not is_valid(addr):
1171 item.setExpanded(not item.isExpanded())
1174 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1175 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1176 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1177 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1178 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1179 if addr in self.wallet.imported_keys:
1180 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1182 if self.expert_mode:
1183 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1184 menu.addAction(t, lambda: self.toggle_freeze(addr))
1185 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1186 menu.addAction(t, lambda: self.toggle_priority(addr))
1188 self.run_hook('receive_menu', menu)
1189 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1192 def payto(self, addr):
1194 label = self.wallet.labels.get(addr)
1195 m_addr = label + ' <' + addr + '>' if label else addr
1196 self.tabs.setCurrentIndex(1)
1197 self.payto_e.setText(m_addr)
1198 self.amount_e.setFocus()
1201 def delete_contact(self, x):
1202 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1203 self.wallet.delete_contact(x)
1204 self.set_label(x, None)
1205 self.update_history_tab()
1206 self.update_contacts_tab()
1207 self.update_completions()
1210 def create_contact_menu(self, position):
1211 item = self.contacts_list.itemAt(position)
1213 addr = unicode(item.text(0))
1214 label = unicode(item.text(1))
1215 is_editable = item.data(0,32).toBool()
1216 payto_addr = item.data(0,33).toString()
1218 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1219 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1220 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1222 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1223 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1225 self.run_hook('create_contact_menu', menu, item)
1226 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1229 def update_receive_item(self, item):
1230 item.setFont(0, QFont(MONOSPACE_FONT))
1231 address = str(item.data(0,0).toString())
1232 label = self.wallet.labels.get(address,'')
1233 item.setData(1,0,label)
1234 item.setData(0,32, True) # is editable
1236 self.run_hook('update_receive_item', address, item)
1238 c, u = self.wallet.get_addr_balance(address)
1239 balance = self.format_amount(c + u)
1240 item.setData(2,0,balance)
1242 if self.expert_mode:
1243 if address in self.wallet.frozen_addresses:
1244 item.setBackgroundColor(0, QColor('lightblue'))
1245 elif address in self.wallet.prioritized_addresses:
1246 item.setBackgroundColor(0, QColor('lightgreen'))
1249 def update_receive_tab(self):
1250 l = self.receive_list
1253 l.setColumnHidden(2, not self.expert_mode)
1254 l.setColumnHidden(3, not self.expert_mode)
1255 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1256 l.setColumnWidth(i, width)
1258 if self.current_account is None:
1259 account_items = self.wallet.accounts.items()
1260 elif self.current_account != -1:
1261 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1265 for k, account in account_items:
1266 name = account.get('name',str(k))
1267 c,u = self.wallet.get_account_balance(k)
1268 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1269 l.addTopLevelItem(account_item)
1270 account_item.setExpanded(True)
1272 for is_change in ([0,1] if self.expert_mode else [0]):
1273 if self.expert_mode:
1274 name = "Receiving" if not is_change else "Change"
1275 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1276 account_item.addChild(seq_item)
1277 if not is_change: seq_item.setExpanded(True)
1279 seq_item = account_item
1283 for address in account[is_change]:
1284 h = self.wallet.history.get(address,[])
1288 if gap > self.wallet.gap_limit:
1293 num_tx = '*' if h == ['*'] else "%d"%len(h)
1294 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1295 self.update_receive_item(item)
1297 item.setBackgroundColor(1, QColor('red'))
1298 seq_item.addChild(item)
1301 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1302 c,u = self.wallet.get_imported_balance()
1303 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1304 l.addTopLevelItem(account_item)
1305 account_item.setExpanded(True)
1306 for address in self.wallet.imported_keys.keys():
1307 item = QTreeWidgetItem( [ address, '', '', ''] )
1308 self.update_receive_item(item)
1309 account_item.addChild(item)
1312 # we use column 1 because column 0 may be hidden
1313 l.setCurrentItem(l.topLevelItem(0),1)
1316 def update_contacts_tab(self):
1317 l = self.contacts_list
1320 for address in self.wallet.addressbook:
1321 label = self.wallet.labels.get(address,'')
1322 n = self.wallet.get_num_tx(address)
1323 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1324 item.setFont(0, QFont(MONOSPACE_FONT))
1325 # 32 = label can be edited (bool)
1326 item.setData(0,32, True)
1328 item.setData(0,33, address)
1329 l.addTopLevelItem(item)
1331 self.run_hook('update_contacts_tab', l)
1332 l.setCurrentItem(l.topLevelItem(0))
1336 def create_console_tab(self):
1337 from qt_console import Console
1338 self.console = console = Console()
1339 self.console.history = self.config.get("console-history",[])
1340 self.console.history_index = len(self.console.history)
1342 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1343 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1345 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1347 def mkfunc(f, method):
1348 return lambda *args: apply( f, (method, args, self.password_dialog ))
1350 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1351 methods[m] = mkfunc(c._run, m)
1353 console.updateNamespace(methods)
1356 def change_account(self,s):
1357 if s == _("All accounts"):
1358 self.current_account = None
1360 accounts = self.wallet.get_accounts()
1361 for k, v in accounts.items():
1363 self.current_account = k
1364 self.update_history_tab()
1365 self.update_status()
1366 self.update_receive_tab()
1368 def create_status_bar(self):
1371 sb.setFixedHeight(35)
1372 qtVersion = qVersion()
1374 self.balance_label = QLabel("")
1375 sb.addWidget(self.balance_label)
1377 update_notification = UpdateLabel(self.config)
1378 if(update_notification.new_version):
1379 sb.addPermanentWidget(update_notification)
1381 accounts = self.wallet.get_accounts()
1382 if len(accounts) > 1:
1383 from_combo = QComboBox()
1384 from_combo.addItems([_("All accounts")] + accounts.values())
1385 from_combo.setCurrentIndex(0)
1386 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1387 sb.addPermanentWidget(from_combo)
1389 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1390 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1391 if self.wallet.seed:
1392 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1393 self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1394 sb.addPermanentWidget( self.password_button )
1395 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1396 if self.wallet.seed:
1397 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1398 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1399 sb.addPermanentWidget( self.status_button )
1401 self.run_hook('create_status_bar', (sb,))
1403 self.setStatusBar(sb)
1407 self.config.set_key('gui', 'lite', True)
1410 self.lite.mini.show()
1412 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1413 self.lite.main(None)
1415 def new_contact_dialog(self):
1416 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1417 address = unicode(text)
1419 if is_valid(address):
1420 self.wallet.add_contact(address)
1421 self.update_contacts_tab()
1422 self.update_history_tab()
1423 self.update_completions()
1425 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1427 def show_master_public_key(self):
1428 dialog = QDialog(self)
1430 dialog.setWindowTitle(_("Master Public Key"))
1432 main_text = QTextEdit()
1433 main_text.setText(self.wallet.get_master_public_key())
1434 main_text.setReadOnly(True)
1435 main_text.setMaximumHeight(170)
1436 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1438 ok_button = QPushButton(_("OK"))
1439 ok_button.setDefault(True)
1440 ok_button.clicked.connect(dialog.accept)
1442 main_layout = QGridLayout()
1443 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1445 main_layout.addWidget(main_text, 1, 0)
1446 main_layout.addWidget(qrw, 1, 1 )
1448 vbox = QVBoxLayout()
1449 vbox.addLayout(main_layout)
1450 hbox = QHBoxLayout()
1452 hbox.addWidget(ok_button)
1453 vbox.addLayout(hbox)
1455 dialog.setLayout(vbox)
1460 def show_seed_dialog(self, password):
1461 if not self.wallet.seed:
1462 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1465 seed = self.wallet.decode_seed(password)
1467 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1469 self.show_seed(seed, self.wallet.imported_keys, self)
1473 def show_seed(self, seed, imported_keys, parent=None):
1474 dialog = QDialog(parent)
1476 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1478 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1480 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1482 seed_text = QTextEdit(brainwallet)
1483 seed_text.setReadOnly(True)
1484 seed_text.setMaximumHeight(130)
1486 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1487 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1488 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1489 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1491 msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1492 label2 = QLabel(msg2)
1493 label2.setWordWrap(True)
1496 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1497 logo.setMaximumWidth(60)
1499 qrw = QRCodeWidget(seed)
1501 ok_button = QPushButton(_("OK"))
1502 ok_button.setDefault(True)
1503 ok_button.clicked.connect(dialog.accept)
1505 grid = QGridLayout()
1506 #main_layout.addWidget(logo, 0, 0)
1508 grid.addWidget(logo, 0, 0)
1509 grid.addWidget(label1, 0, 1)
1511 grid.addWidget(seed_text, 1, 0, 1, 2)
1513 grid.addWidget(qrw, 0, 2, 2, 1)
1515 vbox = QVBoxLayout()
1516 vbox.addLayout(grid)
1517 vbox.addWidget(label2)
1519 hbox = QHBoxLayout()
1521 hbox.addWidget(ok_button)
1522 vbox.addLayout(hbox)
1524 dialog.setLayout(vbox)
1527 def show_qrcode(self, data, title = "QR code"):
1531 d.setWindowTitle(title)
1532 d.setMinimumSize(270, 300)
1533 vbox = QVBoxLayout()
1534 qrw = QRCodeWidget(data)
1535 vbox.addWidget(qrw, 1)
1536 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1537 hbox = QHBoxLayout()
1541 filename = "qrcode.bmp"
1542 bmp.save_qrcode(qrw.qr, filename)
1543 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1545 b = QPushButton(_("Save"))
1547 b.clicked.connect(print_qr)
1549 b = QPushButton(_("Close"))
1551 b.clicked.connect(d.accept)
1554 vbox.addLayout(hbox)
1559 def do_protect(self, func, args):
1560 if self.wallet.use_encryption:
1561 password = self.password_dialog()
1567 if args != (False,):
1568 args = (self,) + args + (password,)
1570 args = (self,password)
1575 def show_private_key(self, address, password):
1576 if not address: return
1578 pk = self.wallet.get_private_key(address, password)
1579 except BaseException, e:
1580 self.show_message(str(e))
1582 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1586 def do_sign(self, address, message, signature, password):
1588 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1589 signature.setText(sig)
1590 except BaseException, e:
1591 self.show_message(str(e))
1593 def sign_message(self, address):
1594 if not address: return
1597 d.setWindowTitle(_('Sign Message'))
1598 d.setMinimumSize(410, 290)
1600 tab_widget = QTabWidget()
1602 layout = QGridLayout(tab)
1604 sign_address = QLineEdit()
1606 sign_address.setText(address)
1607 layout.addWidget(QLabel(_('Address')), 1, 0)
1608 layout.addWidget(sign_address, 1, 1)
1610 sign_message = QTextEdit()
1611 layout.addWidget(QLabel(_('Message')), 2, 0)
1612 layout.addWidget(sign_message, 2, 1)
1613 layout.setRowStretch(2,3)
1615 sign_signature = QTextEdit()
1616 layout.addWidget(QLabel(_('Signature')), 3, 0)
1617 layout.addWidget(sign_signature, 3, 1)
1618 layout.setRowStretch(3,1)
1621 hbox = QHBoxLayout()
1622 b = QPushButton(_("Sign"))
1624 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1625 b = QPushButton(_("Close"))
1626 b.clicked.connect(d.accept)
1628 layout.addLayout(hbox, 4, 1)
1629 tab_widget.addTab(tab, _("Sign"))
1633 layout = QGridLayout(tab)
1635 verify_address = QLineEdit()
1636 layout.addWidget(QLabel(_('Address')), 1, 0)
1637 layout.addWidget(verify_address, 1, 1)
1639 verify_message = QTextEdit()
1640 layout.addWidget(QLabel(_('Message')), 2, 0)
1641 layout.addWidget(verify_message, 2, 1)
1642 layout.setRowStretch(2,3)
1644 verify_signature = QTextEdit()
1645 layout.addWidget(QLabel(_('Signature')), 3, 0)
1646 layout.addWidget(verify_signature, 3, 1)
1647 layout.setRowStretch(3,1)
1650 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1651 self.show_message(_("Signature verified"))
1653 self.show_message(_("Error: wrong signature"))
1655 hbox = QHBoxLayout()
1656 b = QPushButton(_("Verify"))
1657 b.clicked.connect(do_verify)
1659 b = QPushButton(_("Close"))
1660 b.clicked.connect(d.accept)
1662 layout.addLayout(hbox, 4, 1)
1663 tab_widget.addTab(tab, _("Verify"))
1665 vbox = QVBoxLayout()
1666 vbox.addWidget(tab_widget)
1673 def question(self, msg):
1674 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1676 def show_message(self, msg):
1677 QMessageBox.information(self, _('Message'), msg, _('OK'))
1679 def password_dialog(self ):
1686 vbox = QVBoxLayout()
1687 msg = _('Please enter your password')
1688 vbox.addWidget(QLabel(msg))
1690 grid = QGridLayout()
1692 grid.addWidget(QLabel(_('Password')), 1, 0)
1693 grid.addWidget(pw, 1, 1)
1694 vbox.addLayout(grid)
1696 vbox.addLayout(ok_cancel_buttons(d))
1699 self.run_hook('password_dialog', pw, grid, 1)
1700 if not d.exec_(): return
1701 return unicode(pw.text())
1708 def change_password_dialog( wallet, parent=None ):
1711 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1719 new_pw = QLineEdit()
1720 new_pw.setEchoMode(2)
1721 conf_pw = QLineEdit()
1722 conf_pw.setEchoMode(2)
1724 vbox = QVBoxLayout()
1726 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1727 +_('To disable wallet encryption, enter an empty new password.')) \
1728 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1730 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1731 +_("Leave these fields empty if you want to disable encryption.")
1732 vbox.addWidget(QLabel(msg))
1734 grid = QGridLayout()
1737 if wallet.use_encryption:
1738 grid.addWidget(QLabel(_('Password')), 1, 0)
1739 grid.addWidget(pw, 1, 1)
1741 grid.addWidget(QLabel(_('New Password')), 2, 0)
1742 grid.addWidget(new_pw, 2, 1)
1744 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1745 grid.addWidget(conf_pw, 3, 1)
1746 vbox.addLayout(grid)
1748 vbox.addLayout(ok_cancel_buttons(d))
1751 if not d.exec_(): return
1753 password = unicode(pw.text()) if wallet.use_encryption else None
1754 new_password = unicode(new_pw.text())
1755 new_password2 = unicode(conf_pw.text())
1758 seed = wallet.decode_seed(password)
1760 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1763 if new_password != new_password2:
1764 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1765 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1768 wallet.update_password(seed, password, new_password)
1770 QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
1773 QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK'))
1776 icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1777 parent.password_button.setIcon( icon )
1781 def generate_transaction_information_widget(self, tx):
1782 tabs = QTabWidget(self)
1785 grid_ui = QGridLayout(tab1)
1786 grid_ui.setColumnStretch(0,1)
1787 tabs.addTab(tab1, _('Outputs') )
1789 tree_widget = MyTreeWidget(self)
1790 tree_widget.setColumnCount(2)
1791 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1792 tree_widget.setColumnWidth(0, 300)
1793 tree_widget.setColumnWidth(1, 50)
1795 for address, value in tx.outputs:
1796 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1797 tree_widget.addTopLevelItem(item)
1799 tree_widget.setMaximumHeight(100)
1801 grid_ui.addWidget(tree_widget)
1804 grid_ui = QGridLayout(tab2)
1805 grid_ui.setColumnStretch(0,1)
1806 tabs.addTab(tab2, _('Inputs') )
1808 tree_widget = MyTreeWidget(self)
1809 tree_widget.setColumnCount(2)
1810 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1812 for input_line in tx.inputs:
1813 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1814 tree_widget.addTopLevelItem(item)
1816 tree_widget.setMaximumHeight(100)
1818 grid_ui.addWidget(tree_widget)
1822 def tx_dict_from_text(self, txt):
1824 tx_dict = json.loads(str(txt))
1825 assert "hex" in tx_dict.keys()
1826 assert "complete" in tx_dict.keys()
1827 if not tx_dict["complete"]:
1828 assert "input_info" in tx_dict.keys()
1830 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1835 def read_tx_from_file(self):
1836 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1840 with open(fileName, "r") as f:
1841 file_content = f.read()
1842 except (ValueError, IOError, os.error), reason:
1843 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1845 return self.tx_dict_from_text(file_content)
1849 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1851 self.wallet.signrawtransaction(tx, input_info, [], password)
1853 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1855 with open(fileName, "w+") as f:
1856 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1857 self.show_message(_("Transaction saved successfully"))
1860 except BaseException, e:
1861 self.show_message(str(e))
1864 def send_raw_transaction(self, raw_tx, dialog = ""):
1865 result, result_message = self.wallet.sendtx( raw_tx )
1867 self.show_message("Transaction successfully sent: %s" % (result_message))
1871 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1873 def do_process_from_text(self):
1874 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1877 tx_dict = self.tx_dict_from_text(text)
1879 self.create_process_transaction_window(tx_dict)
1881 def do_process_from_file(self):
1882 tx_dict = self.read_tx_from_file()
1884 self.create_process_transaction_window(tx_dict)
1886 def create_process_transaction_window(self, tx_dict):
1887 tx = Transaction(tx_dict["hex"])
1889 dialog = QDialog(self)
1890 dialog.setMinimumWidth(500)
1891 dialog.setWindowTitle(_('Process raw transaction'))
1897 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1898 l.addWidget(QLabel(_("Actions")), 4,0)
1900 if tx_dict["complete"] == False:
1901 l.addWidget(QLabel(_("Unsigned")), 3,1)
1902 if self.wallet.seed :
1903 b = QPushButton("Sign transaction")
1904 input_info = json.loads(tx_dict["input_info"])
1905 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1906 l.addWidget(b, 4, 1)
1908 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1910 l.addWidget(QLabel(_("Signed")), 3,1)
1911 b = QPushButton("Broadcast transaction")
1912 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1915 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1916 cancelButton = QPushButton(_("Cancel"))
1917 cancelButton.clicked.connect(lambda: dialog.done(0))
1918 l.addWidget(cancelButton, 4,2)
1924 def do_export_privkeys(self, password):
1925 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.")))
1928 select_export = _('Select file to export your private keys to')
1929 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1931 with open(fileName, "w+") as csvfile:
1932 transaction = csv.writer(csvfile)
1933 transaction.writerow(["address", "private_key"])
1936 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1937 transaction.writerow(["%34s"%addr,pk])
1939 self.show_message(_("Private keys exported."))
1941 except (IOError, os.error), reason:
1942 export_error_label = _("Electrum was unable to produce a private key-export.")
1943 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1945 except BaseException, e:
1946 self.show_message(str(e))
1950 def do_import_labels(self):
1951 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1952 if not labelsFile: return
1954 f = open(labelsFile, 'r')
1957 for key, value in json.loads(data).items():
1958 self.wallet.labels[key] = value
1960 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1961 except (IOError, os.error), reason:
1962 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1965 def do_export_labels(self):
1966 labels = self.wallet.labels
1968 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1970 with open(fileName, 'w+') as f:
1971 json.dump(labels, f)
1972 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1973 except (IOError, os.error), reason:
1974 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1977 def do_export_history(self):
1978 from gui_lite import csv_transaction
1979 csv_transaction(self.wallet)
1983 def do_import_privkey(self, password):
1984 if not self.wallet.imported_keys:
1985 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1986 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1987 + _('Are you sure you understand what you are doing?'), 3, 4)
1990 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1993 text = str(text).split()
1998 addr = self.wallet.import_key(key, password)
1999 except BaseException as e:
2005 addrlist.append(addr)
2007 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2009 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2010 self.update_receive_tab()
2011 self.update_history_tab()
2014 def settings_dialog(self):
2016 d.setWindowTitle(_('Electrum Settings'))
2018 vbox = QVBoxLayout()
2020 tabs = QTabWidget(self)
2021 self.settings_tab = tabs
2022 vbox.addWidget(tabs)
2025 grid_ui = QGridLayout(tab1)
2026 grid_ui.setColumnStretch(0,1)
2027 tabs.addTab(tab1, _('Display') )
2029 nz_label = QLabel(_('Display zeros'))
2030 grid_ui.addWidget(nz_label, 0, 0)
2031 nz_e = AmountEdit(None,True)
2032 nz_e.setText("%d"% self.wallet.num_zeros)
2033 grid_ui.addWidget(nz_e, 0, 1)
2034 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2035 grid_ui.addWidget(HelpButton(msg), 0, 2)
2036 if not self.config.is_modifiable('num_zeros'):
2037 for w in [nz_e, nz_label]: w.setEnabled(False)
2039 lang_label=QLabel(_('Language') + ':')
2040 grid_ui.addWidget(lang_label, 1, 0)
2041 lang_combo = QComboBox()
2042 from i18n import languages
2043 lang_combo.addItems(languages.values())
2045 index = languages.keys().index(self.config.get("language",''))
2048 lang_combo.setCurrentIndex(index)
2049 grid_ui.addWidget(lang_combo, 1, 1)
2050 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2051 if not self.config.is_modifiable('language'):
2052 for w in [lang_combo, lang_label]: w.setEnabled(False)
2054 currencies = self.exchanger.get_currencies()
2055 currencies.insert(0, "None")
2057 cur_label=QLabel(_('Currency') + ':')
2058 grid_ui.addWidget(cur_label , 2, 0)
2059 cur_combo = QComboBox()
2060 cur_combo.addItems(currencies)
2062 index = currencies.index(self.config.get('currency', "None"))
2065 cur_combo.setCurrentIndex(index)
2066 grid_ui.addWidget(cur_combo, 2, 1)
2067 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
2069 expert_cb = QCheckBox(_('Expert mode'))
2070 expert_cb.setChecked(self.expert_mode)
2071 grid_ui.addWidget(expert_cb, 3, 0)
2072 hh = _('In expert mode, your client will:') + '\n' \
2073 + _(' - Show change addresses in the Receive tab') + '\n' \
2074 + _(' - Display the balance of each address') + '\n' \
2075 + _(' - Add freeze/prioritize actions to addresses.')
2076 grid_ui.addWidget(HelpButton(hh), 3, 2)
2077 grid_ui.setRowStretch(4,1)
2081 grid_wallet = QGridLayout(tab2)
2082 grid_wallet.setColumnStretch(0,1)
2083 tabs.addTab(tab2, _('Wallet') )
2085 fee_label = QLabel(_('Transaction fee'))
2086 grid_wallet.addWidget(fee_label, 0, 0)
2087 fee_e = AmountEdit(self.base_unit)
2088 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2089 grid_wallet.addWidget(fee_e, 0, 2)
2090 msg = _('Fee per kilobyte of transaction.') + ' ' \
2091 + _('Recommended value') + ': ' + self.format_amount(50000)
2092 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2093 if not self.config.is_modifiable('fee_per_kb'):
2094 for w in [fee_e, fee_label]: w.setEnabled(False)
2096 usechange_cb = QCheckBox(_('Use change addresses'))
2097 usechange_cb.setChecked(self.wallet.use_change)
2098 grid_wallet.addWidget(usechange_cb, 1, 0)
2099 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2100 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2102 gap_label = QLabel(_('Gap limit'))
2103 grid_wallet.addWidget(gap_label, 2, 0)
2104 gap_e = AmountEdit(None,True)
2105 gap_e.setText("%d"% self.wallet.gap_limit)
2106 grid_wallet.addWidget(gap_e, 2, 2)
2107 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2108 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2109 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2110 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2111 + _('Warning') + ': ' \
2112 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2113 + _('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'
2114 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2115 if not self.config.is_modifiable('gap_limit'):
2116 for w in [gap_e, gap_label]: w.setEnabled(False)
2118 units = ['BTC', 'mBTC']
2119 unit_label = QLabel(_('Base unit'))
2120 grid_wallet.addWidget(unit_label, 3, 0)
2121 unit_combo = QComboBox()
2122 unit_combo.addItems(units)
2123 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2124 grid_wallet.addWidget(unit_combo, 3, 2)
2125 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2126 + '\n1BTC=1000mBTC.\n' \
2127 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2128 grid_wallet.setRowStretch(4,1)
2132 tab5 = QScrollArea()
2133 tab5.setEnabled(True)
2134 tab5.setWidgetResizable(True)
2136 grid_plugins = QGridLayout()
2137 grid_plugins.setColumnStretch(0,1)
2140 w.setLayout(grid_plugins)
2143 w.setMinimumHeight(len(self.plugins)*35)
2145 tabs.addTab(tab5, _('Plugins') )
2146 def mk_toggle(cb, p):
2147 return lambda: cb.setChecked(p.toggle())
2148 for i, p in enumerate(self.plugins):
2150 name, description = p.get_info()
2151 cb = QCheckBox(name)
2152 cb.setDisabled(not p.is_available())
2153 cb.setChecked(p.is_enabled())
2154 cb.clicked.connect(mk_toggle(cb,p))
2155 grid_plugins.addWidget(cb, i, 0)
2156 if p.requires_settings():
2157 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2158 grid_plugins.addWidget(HelpButton(description), i, 2)
2160 print_msg("Error: cannot display plugin", p)
2161 traceback.print_exc(file=sys.stdout)
2162 grid_plugins.setRowStretch(i+1,1)
2164 self.run_hook('create_settings_tab', tabs)
2166 vbox.addLayout(ok_cancel_buttons(d))
2170 if not d.exec_(): return
2172 fee = unicode(fee_e.text())
2174 fee = self.read_amount(fee)
2176 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2179 self.wallet.set_fee(fee)
2181 nz = unicode(nz_e.text())
2186 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2189 if self.wallet.num_zeros != nz:
2190 self.wallet.num_zeros = nz
2191 self.config.set_key('num_zeros', nz, True)
2192 self.update_history_tab()
2193 self.update_receive_tab()
2195 usechange_result = usechange_cb.isChecked()
2196 if self.wallet.use_change != usechange_result:
2197 self.wallet.use_change = usechange_result
2198 self.config.set_key('use_change', self.wallet.use_change, True)
2200 unit_result = units[unit_combo.currentIndex()]
2201 if self.base_unit() != unit_result:
2202 self.decimal_point = 8 if unit_result == 'BTC' else 5
2203 self.config.set_key('decimal_point', self.decimal_point, True)
2204 self.update_history_tab()
2205 self.update_status()
2208 n = int(gap_e.text())
2210 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2213 if self.wallet.gap_limit != n:
2214 r = self.wallet.change_gap_limit(n)
2216 self.update_receive_tab()
2217 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2219 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2221 need_restart = False
2223 lang_request = languages.keys()[lang_combo.currentIndex()]
2224 if lang_request != self.config.get('language'):
2225 self.config.set_key("language", lang_request, True)
2228 cur_request = str(currencies[cur_combo.currentIndex()])
2229 if cur_request != self.config.get('currency', "None"):
2230 self.config.set_key('currency', cur_request, True)
2231 self.update_wallet()
2233 self.run_hook('close_settings_dialog')
2236 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2238 self.receive_tab_set_mode(expert_cb.isChecked())
2240 def run_network_dialog(self):
2241 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2243 def closeEvent(self, event):
2245 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2246 self.save_column_widths()
2247 self.config.set_key("console-history",self.console.history[-50:])
2250 class OpenFileEventFilter(QObject):
2251 def __init__(self, windows):
2252 self.windows = windows
2253 super(OpenFileEventFilter, self).__init__()
2255 def eventFilter(self, obj, event):
2256 if event.type() == QtCore.QEvent.FileOpen:
2257 if len(self.windows) >= 1:
2258 self.windows[0].set_url(event.url().toString())
2264 def __init__(self, wallet, config, app=None):
2265 self.wallet = wallet
2266 self.config = config
2268 self.efilter = OpenFileEventFilter(self.windows)
2270 self.app = QApplication(sys.argv)
2271 self.app.installEventFilter(self.efilter)
2273 def restore_or_create(self):
2274 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2275 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2276 if r==2: return None
2277 return 'restore' if r==1 else 'create'
2280 def verify_seed(self):
2281 r = self.seed_dialog(False)
2282 if r != self.wallet.seed:
2283 QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2290 def seed_dialog(self, is_restore=True):
2294 vbox = QVBoxLayout()
2296 msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2298 msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2300 msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2303 label.setWordWrap(True)
2304 vbox.addWidget(label)
2306 seed_e = QTextEdit()
2307 seed_e.setMaximumHeight(100)
2308 vbox.addWidget(seed_e)
2311 grid = QGridLayout()
2313 gap_e = AmountEdit(None, True)
2315 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2316 grid.addWidget(gap_e, 2, 1)
2317 grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2318 vbox.addLayout(grid)
2320 vbox.addLayout(ok_cancel_buttons(d))
2323 if not d.exec_(): return
2326 seed = str(seed_e.toPlainText())
2330 seed = mnemonic.mn_decode( seed.split() )
2332 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2336 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2343 gap = int(unicode(gap_e.text()))
2345 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2350 def network_dialog(self):
2351 return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
2354 def show_seed(self):
2355 ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2357 def password_dialog(self):
2358 if self.wallet.seed:
2359 ElectrumWindow.change_password_dialog(self.wallet)
2362 def restore_wallet(self):
2363 wallet = self.wallet
2364 # wait until we are connected, because the user might have selected another server
2365 if not wallet.interface.is_connected:
2366 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2367 waiting_dialog(waiting)
2369 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2370 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2372 wallet.set_up_to_date(False)
2373 wallet.interface.poke('synchronizer')
2374 waiting_dialog(waiting)
2375 if wallet.is_found():
2376 print_error( "Recovery successful" )
2378 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2385 w = ElectrumWindow(self.wallet, self.config)
2386 self.windows.append(w)
2387 if url: w.set_url(url)