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
30 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
32 from PyQt4.QtGui import *
33 from PyQt4.QtCore import *
34 import PyQt4.QtCore as QtCore
36 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
41 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'")
43 from electrum.wallet import format_satoshis
44 from electrum import Transaction
45 from electrum import mnemonic
46 from electrum import util, bitcoin, commands, Interface, Wallet
47 from electrum import SimpleConfig, Wallet, WalletStorage
50 import bmp, pyqrnative
53 from amountedit import AmountEdit
54 from network_dialog import NetworkDialog
55 from qrcodewidget import QRCodeWidget
57 from decimal import Decimal
65 if platform.system() == 'Windows':
66 MONOSPACE_FONT = 'Lucida Console'
67 elif platform.system() == 'Darwin':
68 MONOSPACE_FONT = 'Monaco'
70 MONOSPACE_FONT = 'monospace'
72 from electrum import ELECTRUM_VERSION
77 class UpdateLabel(QLabel):
78 def __init__(self, config, parent=None):
79 QLabel.__init__(self, parent)
80 self.new_version = False
83 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
84 con.request("GET", "/version")
85 res = con.getresponse()
86 except socket.error as msg:
87 print_error("Could not retrieve version information")
91 self.latest_version = res.read()
92 self.latest_version = self.latest_version.replace("\n","")
93 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
95 self.current_version = ELECTRUM_VERSION
96 if(self.compare_versions(self.latest_version, self.current_version) == 1):
97 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
98 if(self.compare_versions(self.latest_version, latest_seen) == 1):
99 self.new_version = True
100 self.setText(_("New version available") + ": " + self.latest_version)
103 def compare_versions(self, version1, version2):
105 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
106 return cmp(normalize(version1), normalize(version2))
108 def ignore_this_version(self):
110 self.config.set_key("last_seen_version", self.latest_version, True)
111 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
114 def ignore_all_version(self):
116 self.config.set_key("last_seen_version", "9.9.9", True)
117 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
120 def open_website(self):
121 webbrowser.open("http://electrum.org/download.html")
124 def mouseReleaseEvent(self, event):
125 dialog = QDialog(self)
126 dialog.setWindowTitle(_('Electrum update'))
129 main_layout = QGridLayout()
130 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
132 ignore_version = QPushButton(_("Ignore this version"))
133 ignore_version.clicked.connect(self.ignore_this_version)
135 ignore_all_versions = QPushButton(_("Ignore all versions"))
136 ignore_all_versions.clicked.connect(self.ignore_all_version)
138 open_website = QPushButton(_("Goto download page"))
139 open_website.clicked.connect(self.open_website)
141 main_layout.addWidget(ignore_version, 1, 0)
142 main_layout.addWidget(ignore_all_versions, 1, 1)
143 main_layout.addWidget(open_website, 1, 2)
145 dialog.setLayout(main_layout)
149 if not dialog.exec_(): return
154 class MyTreeWidget(QTreeWidget):
155 def __init__(self, parent):
156 QTreeWidget.__init__(self, parent)
159 for i in range(0,self.viewport().height()/5):
160 if self.itemAt(QPoint(0,i*5)) == item:
164 for j in range(0,30):
165 if self.itemAt(QPoint(0,i*5 + j)) != item:
167 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
169 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
174 class StatusBarButton(QPushButton):
175 def __init__(self, icon, tooltip, func):
176 QPushButton.__init__(self, icon, '')
177 self.setToolTip(tooltip)
179 self.setMaximumWidth(25)
180 self.clicked.connect(func)
182 self.setIconSize(QSize(25,25))
184 def keyPressEvent(self, e):
185 if e.key() == QtCore.Qt.Key_Return:
197 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive":[[370], [370,200,130]] }
199 class ElectrumWindow(QMainWindow):
200 def changeEvent(self, event):
201 flags = self.windowFlags();
202 if event and event.type() == QtCore.QEvent.WindowStateChange:
203 if self.windowState() & QtCore.Qt.WindowMinimized:
204 self.build_menu(True)
205 # The only way to toggle the icon in the window managers taskbar is to use the Qt.Tooltip flag
206 # The problem is that it somehow creates an (in)visible window that will stay active and prevent
207 # Electrum from closing.
208 # As for now I have no clue how to implement a proper 'hide to tray' functionality.
209 # self.setWindowFlags(flags & ~Qt.ToolTip)
210 elif event.oldState() & QtCore.Qt.WindowMinimized:
211 self.build_menu(False)
212 #self.setWindowFlags(flags | Qt.ToolTip)
214 def build_menu(self, is_hidden = False):
216 if self.isMinimized():
217 m.addAction(_("Show"), self.showNormal)
219 m.addAction(_("Hide"), self.showMinimized)
222 m.addAction(_("Exit Electrum"), self.close)
223 self.tray.setContextMenu(m)
225 def tray_activated(self, reason):
226 if reason == QSystemTrayIcon.DoubleClick:
230 def __init__(self, config):
231 QMainWindow.__init__(self)
236 self._close_electrum = False
238 self.current_account = self.config.get("current_account", None)
240 self.icon = QIcon(os.getcwd() + '/icons/electrum.png')
241 self.tray = QSystemTrayIcon(self.icon, self)
242 self.tray.setToolTip('Electrum')
243 self.tray.activated.connect(self.tray_activated)
247 self.create_status_bar()
249 self.need_update = threading.Event()
251 self.expert_mode = config.get('classic_expert_mode', False)
252 self.decimal_point = config.get('decimal_point', 8)
253 self.num_zeros = int(config.get('num_zeros',0))
255 set_language(config.get('language'))
257 self.funds_error = False
258 self.completions = QStringListModel()
260 self.tabs = tabs = QTabWidget(self)
261 self.column_widths = self.config.get("column_widths", default_column_widths )
262 tabs.addTab(self.create_history_tab(), _('History') )
263 tabs.addTab(self.create_send_tab(), _('Send') )
264 tabs.addTab(self.create_receive_tab(), _('Receive') )
265 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
266 tabs.addTab(self.create_console_tab(), _('Console') )
267 tabs.setMinimumSize(600, 400)
268 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
269 self.setCentralWidget(tabs)
271 g = self.config.get("winpos-qt",[100, 100, 840, 400])
272 self.setGeometry(g[0], g[1], g[2], g[3])
276 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
277 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
278 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
279 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
280 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
282 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
283 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
284 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
285 self.history_list.setFocus(True)
287 self.exchanger = exchange_rate.Exchanger(self)
288 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
290 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
291 if platform.system() == 'Windows':
292 n = 3 if self.wallet.seed else 2
293 tabs.setCurrentIndex (n)
294 tabs.setCurrentIndex (0)
296 # plugins that need to change the GUI do it here
297 self.run_hook('init')
302 def load_wallet(self, wallet):
306 self.wallet.interface.register_callback('updated', lambda: self.need_update.set())
307 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
308 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
309 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
310 self.wallet.interface.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
311 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
312 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
313 self.setWindowTitle( title )
315 # set initial message
316 self.console.showMessage(self.wallet.interface.banner)
317 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
318 self.notify_transactions()
321 accounts = self.wallet.get_account_names()
322 self.account_selector.clear()
323 if len(accounts) > 1:
324 self.account_selector.addItems([_("All accounts")] + accounts.values())
325 self.account_selector.setCurrentIndex(0)
326 self.account_selector.show()
328 self.account_selector.hide()
330 self.new_account.setEnabled(self.wallet.seed_version>4)
332 self.update_lock_icon()
333 self.update_buttons_on_seed()
334 self.update_console()
336 self.run_hook('load_wallet')
339 def select_wallet_file(self):
340 wallet_folder = self.wallet.storage.path
341 re.sub("(\/\w*.dat)$", "", wallet_folder)
342 file_name = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat") )
346 def open_wallet(self):
348 filename = self.select_wallet_file()
352 storage = WalletStorage({'wallet_path': filename})
353 if not storage.file_exists:
354 self.show_message("file not found "+ filename)
357 interface = self.wallet.interface
358 blockchain = self.wallet.verifier.blockchain
359 self.wallet.stop_threads()
362 wallet = Wallet(storage)
363 wallet.start_threads(interface, blockchain)
365 self.load_wallet(wallet)
368 def new_wallet(self):
371 wallet_folder = self.wallet.storage.path
372 re.sub("(\/\w*.dat)$", "", wallet_folder)
373 filename = self.getSaveFileName("Select your wallet file", wallet_folder, "*.dat")
375 storage = WalletStorage({'wallet_path': filename})
376 assert not storage.file_exists
378 wizard = installwizard.InstallWizard(self.config, self.wallet.interface, self.wallet.verifier.blockchain, storage)
379 wallet = wizard.run()
381 self.load_wallet(wallet)
385 def init_menubar(self):
388 file_menu = menubar.addMenu(_("&File"))
389 open_wallet_action = file_menu.addAction(_("&Open"))
390 open_wallet_action.triggered.connect(self.open_wallet)
392 new_wallet_action = file_menu.addAction(_("&Create/Restore"))
393 new_wallet_action.triggered.connect(self.new_wallet)
395 wallet_backup = file_menu.addAction(_("&Copy"))
396 wallet_backup.triggered.connect(lambda: backup_wallet(self.wallet.storage.path))
398 quit_item = file_menu.addAction(_("&Close"))
399 quit_item.triggered.connect(self.close)
401 wallet_menu = menubar.addMenu(_("&Wallet"))
403 # Settings / Preferences are all reserved keywords in OSX using this as work around
404 preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
405 preferences_menu = wallet_menu.addAction(preferences_name)
406 preferences_menu.triggered.connect(self.settings_dialog)
408 wallet_menu.addSeparator()
410 raw_transaction_menu = wallet_menu.addMenu(_("&Load raw transaction"))
412 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
413 raw_transaction_file.triggered.connect(self.do_process_from_file)
415 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
416 raw_transaction_text.triggered.connect(self.do_process_from_text)
418 csv_transaction_menu = wallet_menu.addMenu(_("&Load CSV transaction"))
420 csv_transaction_file = csv_transaction_menu.addAction(_("&From file"))
421 csv_transaction_file.triggered.connect(self.do_process_from_csv_file)
423 csv_transaction_text = csv_transaction_menu.addAction(_("&From text"))
424 csv_transaction_text.triggered.connect(self.do_process_from_csv_text)
426 wallet_menu.addSeparator()
428 show_menu = wallet_menu.addMenu(_("Show"))
430 #if self.wallet.seed:
431 show_seed = show_menu.addAction(_("&Seed"))
432 show_seed.triggered.connect(self.show_seed_dialog)
434 show_mpk = show_menu.addAction(_("&Master Public Key"))
435 show_mpk.triggered.connect(self.show_master_public_key)
437 wallet_menu.addSeparator()
438 new_contact = wallet_menu.addAction(_("&New contact"))
439 new_contact.triggered.connect(self.new_contact_dialog)
441 self.new_account = wallet_menu.addAction(_("&New account"))
442 self.new_account.triggered.connect(self.new_account_dialog)
444 import_menu = menubar.addMenu(_("&Import"))
445 in_labels = import_menu.addAction(_("&Labels"))
446 in_labels.triggered.connect(self.do_import_labels)
448 in_private_keys = import_menu.addAction(_("&Private keys"))
449 in_private_keys.triggered.connect(self.do_import_privkey)
451 export_menu = menubar.addMenu(_("&Export"))
452 ex_private_keys = export_menu.addAction(_("&Private keys"))
453 ex_private_keys.triggered.connect(self.do_export_privkeys)
455 ex_history = export_menu.addAction(_("&History"))
456 ex_history.triggered.connect(self.do_export_history)
458 ex_labels = export_menu.addAction(_("&Labels"))
459 ex_labels.triggered.connect(self.do_export_labels)
461 help_menu = menubar.addMenu(_("&Help"))
462 doc_open = help_menu.addAction(_("&Documentation"))
463 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
464 web_open = help_menu.addAction(_("&Official website"))
465 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
467 self.setMenuBar(menubar)
471 def notify_transactions(self):
472 print_error("Notifying GUI")
473 if len(self.wallet.interface.pending_transactions_for_notifications) > 0:
474 # Combine the transactions if there are more then three
475 tx_amount = len(self.wallet.interface.pending_transactions_for_notifications)
478 for tx in self.wallet.interface.pending_transactions_for_notifications:
479 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
483 self.notify("%s new transactions received. Total amount received in the new transactions %s %s" \
484 % (tx_amount, self.format_amount(total_amount), self.base_unit()))
486 self.wallet.interface.pending_transactions_for_notifications = []
488 for tx in self.wallet.interface.pending_transactions_for_notifications:
490 self.wallet.interface.pending_transactions_for_notifications.remove(tx)
491 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
493 self.notify("New transaction received. %s %s" % (self.format_amount(v), self.base_unit()))
495 def notify(self, message):
496 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
499 def init_plugins(self):
500 import imp, pkgutil, __builtin__
501 if __builtin__.use_local_modules:
502 fp, pathname, description = imp.find_module('plugins')
503 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
504 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
505 imp.load_module('electrum_plugins', fp, pathname, description)
506 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
508 import electrum_plugins
509 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
510 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
513 for name, p in zip(plugin_names, plugins):
515 self.plugins.append( p.Plugin(self, name) )
517 print_msg("Error:cannot initialize plugin",p)
518 traceback.print_exc(file=sys.stdout)
521 def run_hook(self, name, *args):
522 for p in self.plugins:
523 if not p.is_enabled():
532 print_error("Plugin error")
533 traceback.print_exc(file=sys.stdout)
539 def set_label(self, name, text = None):
541 old_text = self.wallet.labels.get(name)
544 self.wallet.labels[name] = text
545 self.wallet.storage.put('labels', self.wallet.labels)
549 self.wallet.labels.pop(name)
551 self.run_hook('set_label', name, text, changed)
555 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
556 def getOpenFileName(self, title, filter = None):
557 directory = self.config.get('io_dir', os.path.expanduser('~'))
558 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
559 if fileName and directory != os.path.dirname(fileName):
560 self.config.set_key('io_dir', os.path.dirname(fileName), True)
563 def getSaveFileName(self, title, filename, filter = None):
564 directory = self.config.get('io_dir', os.path.expanduser('~'))
565 path = os.path.join( directory, filename )
566 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
567 if fileName and directory != os.path.dirname(fileName):
568 self.config.set_key('io_dir', os.path.dirname(fileName), True)
574 QMainWindow.close(self)
575 self.run_hook('close_main_window')
577 def connect_slots(self, sender):
578 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
579 self.previous_payto_e=''
581 def timer_actions(self):
582 if self.need_update.is_set():
584 self.need_update.clear()
585 self.run_hook('timer_actions')
587 def format_amount(self, x, is_diff=False, whitespaces=False):
588 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
590 def read_amount(self, x):
591 if x in['.', '']: return None
592 p = pow(10, self.decimal_point)
593 return int( p * Decimal(x) )
596 assert self.decimal_point in [5,8]
597 return "BTC" if self.decimal_point == 8 else "mBTC"
599 def update_status(self):
600 if self.wallet.interface and self.wallet.interface.is_connected:
601 if not self.wallet.up_to_date:
602 text = _("Synchronizing...")
603 icon = QIcon(":icons/status_waiting.png")
605 c, u = self.wallet.get_account_balance(self.current_account)
606 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
607 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
608 text += self.create_quote_text(Decimal(c+u)/100000000)
609 self.tray.setToolTip(text)
610 icon = QIcon(":icons/status_connected.png")
612 text = _("Not connected")
613 icon = QIcon(":icons/status_disconnected.png")
615 self.balance_label.setText(text)
616 self.status_button.setIcon( icon )
618 def update_wallet(self):
620 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
621 self.update_history_tab()
622 self.update_receive_tab()
623 self.update_contacts_tab()
624 self.update_completions()
627 def create_quote_text(self, btc_balance):
628 quote_currency = self.config.get("currency", "None")
629 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
630 if quote_balance is None:
633 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
636 def create_history_tab(self):
637 self.history_list = l = MyTreeWidget(self)
639 for i,width in enumerate(self.column_widths['history']):
640 l.setColumnWidth(i, width)
641 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
642 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
643 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
645 l.setContextMenuPolicy(Qt.CustomContextMenu)
646 l.customContextMenuRequested.connect(self.create_history_menu)
650 def create_history_menu(self, position):
651 self.history_list.selectedIndexes()
652 item = self.history_list.currentItem()
654 tx_hash = str(item.data(0, Qt.UserRole).toString())
655 if not tx_hash: return
657 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
658 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
659 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
660 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
663 def show_tx_details(self, tx):
664 dialog = QDialog(self)
666 dialog.setWindowTitle(_("Transaction Details"))
668 dialog.setLayout(vbox)
669 dialog.setMinimumSize(600,300)
672 if tx_hash in self.wallet.transactions.keys():
673 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
674 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
676 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
682 vbox.addWidget(QLabel("Transaction ID:"))
683 e = QLineEdit(tx_hash)
687 vbox.addWidget(QLabel("Date: %s"%time_str))
688 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
691 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
692 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
694 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
695 vbox.addWidget(QLabel("Transaction fee: unknown"))
697 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
699 vbox.addWidget( self.generate_transaction_information_widget(tx) )
701 ok_button = QPushButton(_("Close"))
702 ok_button.setDefault(True)
703 ok_button.clicked.connect(dialog.accept)
707 hbox.addWidget(ok_button)
711 def tx_label_clicked(self, item, column):
712 if column==2 and item.isSelected():
714 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
715 self.history_list.editItem( item, column )
716 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
719 def tx_label_changed(self, item, column):
723 tx_hash = str(item.data(0, Qt.UserRole).toString())
724 tx = self.wallet.transactions.get(tx_hash)
725 text = unicode( item.text(2) )
726 self.set_label(tx_hash, text)
728 item.setForeground(2, QBrush(QColor('black')))
730 text = self.wallet.get_default_label(tx_hash)
731 item.setText(2, text)
732 item.setForeground(2, QBrush(QColor('gray')))
736 def edit_label(self, is_recv):
737 l = self.receive_list if is_recv else self.contacts_list
738 item = l.currentItem()
739 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
740 l.editItem( item, 1 )
741 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
745 def address_label_clicked(self, item, column, l, column_addr, column_label):
746 if column == column_label and item.isSelected():
747 is_editable = item.data(0, 32).toBool()
750 addr = unicode( item.text(column_addr) )
751 label = unicode( item.text(column_label) )
752 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
753 l.editItem( item, column )
754 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
757 def address_label_changed(self, item, column, l, column_addr, column_label):
758 if column == column_label:
759 addr = unicode( item.text(column_addr) )
760 text = unicode( item.text(column_label) )
761 is_editable = item.data(0, 32).toBool()
765 changed = self.set_label(addr, text)
767 self.update_history_tab()
768 self.update_completions()
770 self.current_item_changed(item)
772 self.run_hook('item_changed', item, column)
775 def current_item_changed(self, a):
776 self.run_hook('current_item_changed', a)
780 def update_history_tab(self):
782 self.history_list.clear()
783 for item in self.wallet.get_tx_history(self.current_account):
784 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
787 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
792 time_str = 'unverified'
793 icon = QIcon(":icons/unconfirmed.png")
796 icon = QIcon(":icons/unconfirmed.png")
798 icon = QIcon(":icons/clock%d.png"%conf)
800 icon = QIcon(":icons/confirmed.png")
802 if value is not None:
803 v_str = self.format_amount(value, True, whitespaces=True)
807 balance_str = self.format_amount(balance, whitespaces=True)
810 label, is_default_label = self.wallet.get_label(tx_hash)
812 label = _('Pruned transaction outputs')
813 is_default_label = False
815 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
816 item.setFont(2, QFont(MONOSPACE_FONT))
817 item.setFont(3, QFont(MONOSPACE_FONT))
818 item.setFont(4, QFont(MONOSPACE_FONT))
820 item.setForeground(3, QBrush(QColor("#BC1E1E")))
822 item.setData(0, Qt.UserRole, tx_hash)
823 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
825 item.setForeground(2, QBrush(QColor('grey')))
827 item.setIcon(0, icon)
828 self.history_list.insertTopLevelItem(0,item)
831 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
834 def create_send_tab(self):
839 grid.setColumnMinimumWidth(3,300)
840 grid.setColumnStretch(5,1)
843 self.payto_e = QLineEdit()
844 grid.addWidget(QLabel(_('Pay to')), 1, 0)
845 grid.addWidget(self.payto_e, 1, 1, 1, 3)
847 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)
849 completer = QCompleter()
850 completer.setCaseSensitivity(False)
851 self.payto_e.setCompleter(completer)
852 completer.setModel(self.completions)
854 self.message_e = QLineEdit()
855 grid.addWidget(QLabel(_('Description')), 2, 0)
856 grid.addWidget(self.message_e, 2, 1, 1, 3)
857 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)
859 self.amount_e = AmountEdit(self.base_unit)
860 grid.addWidget(QLabel(_('Amount')), 3, 0)
861 grid.addWidget(self.amount_e, 3, 1, 1, 2)
862 grid.addWidget(HelpButton(
863 _('Amount to be sent.') + '\n\n' \
864 + _('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.') \
865 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
867 self.fee_e = AmountEdit(self.base_unit)
868 grid.addWidget(QLabel(_('Fee')), 4, 0)
869 grid.addWidget(self.fee_e, 4, 1, 1, 2)
870 grid.addWidget(HelpButton(
871 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
872 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
873 + _('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)
876 self.send_button = EnterButton(_("Send"), self.do_send)
877 grid.addWidget(self.send_button, 6, 1)
879 b = EnterButton(_("Clear"),self.do_clear)
880 grid.addWidget(b, 6, 2)
882 self.payto_sig = QLabel('')
883 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
885 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
886 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
895 def entry_changed( is_fee ):
896 self.funds_error = False
898 if self.amount_e.is_shortcut:
899 self.amount_e.is_shortcut = False
900 c, u = self.wallet.get_account_balance(self.current_account)
901 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
902 fee = self.wallet.estimated_fee(inputs)
904 self.amount_e.setText( self.format_amount(amount) )
905 self.fee_e.setText( self.format_amount( fee ) )
908 amount = self.read_amount(str(self.amount_e.text()))
909 fee = self.read_amount(str(self.fee_e.text()))
911 if not is_fee: fee = None
914 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
916 self.fee_e.setText( self.format_amount( fee ) )
919 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
923 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
924 self.funds_error = True
925 text = _( "Not enough funds" )
926 c, u = self.wallet.get_frozen_balance()
927 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
929 self.statusBar().showMessage(text)
930 self.amount_e.setPalette(palette)
931 self.fee_e.setPalette(palette)
933 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
934 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
936 self.run_hook('create_send_tab', grid)
940 def update_completions(self):
942 for addr,label in self.wallet.labels.items():
943 if addr in self.wallet.addressbook:
944 l.append( label + ' <' + addr + '>')
946 self.run_hook('update_completions', l)
947 self.completions.setStringList(l)
951 return lambda s, *args: s.do_protect(func, args)
956 label = unicode( self.message_e.text() )
957 r = unicode( self.payto_e.text() )
960 # label or alias, with address in brackets
961 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
962 to_address = m.group(2) if m else r
964 if not is_valid(to_address):
965 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
969 amount = self.read_amount(unicode( self.amount_e.text()))
971 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
974 fee = self.read_amount(unicode( self.fee_e.text()))
976 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
979 confirm_amount = self.config.get('confirm_amount', 100000000)
980 if amount >= confirm_amount:
981 if not self.question("send %s to %s?"%(self.format_amount(amount) + ' '+ self.base_unit(), to_address)):
984 self.send_tx(to_address, amount, fee, label)
988 def send_tx(self, to_address, amount, fee, label, password):
991 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
992 except BaseException, e:
993 traceback.print_exc(file=sys.stdout)
994 self.show_message(str(e))
997 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
998 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
1001 self.run_hook('send_tx', tx)
1004 self.set_label(tx.hash(), label)
1007 h = self.wallet.send_tx(tx)
1008 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
1009 status, msg = self.wallet.receive_tx( h )
1011 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
1013 self.update_contacts_tab()
1015 QMessageBox.warning(self, _('Error'), msg, _('OK'))
1017 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
1019 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
1020 with open(fileName,'w') as f:
1021 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1022 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
1024 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
1026 # add recipient to addressbook
1027 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
1028 self.wallet.addressbook.append(to_address)
1033 def set_url(self, url):
1034 address, amount, label, message, signature, identity, url = util.parse_url(url)
1035 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
1037 if label and self.wallet.labels.get(address) != label:
1038 if self.question('Give label "%s" to address %s ?'%(label,address)):
1039 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
1040 self.wallet.addressbook.append(address)
1041 self.set_label(address, label)
1043 self.run_hook('set_url', url, self.show_message, self.question)
1045 self.tabs.setCurrentIndex(1)
1046 label = self.wallet.labels.get(address)
1047 m_addr = label + ' <'+ address +'>' if label else address
1048 self.payto_e.setText(m_addr)
1050 self.message_e.setText(message)
1051 self.amount_e.setText(amount)
1053 self.set_frozen(self.payto_e,True)
1054 self.set_frozen(self.amount_e,True)
1055 self.set_frozen(self.message_e,True)
1056 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
1058 self.payto_sig.setVisible(False)
1061 self.payto_sig.setVisible(False)
1062 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1064 self.set_frozen(e,False)
1065 self.update_status()
1067 def set_frozen(self,entry,frozen):
1069 entry.setReadOnly(True)
1070 entry.setFrame(False)
1071 palette = QPalette()
1072 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1073 entry.setPalette(palette)
1075 entry.setReadOnly(False)
1076 entry.setFrame(True)
1077 palette = QPalette()
1078 palette.setColor(entry.backgroundRole(), QColor('white'))
1079 entry.setPalette(palette)
1082 def toggle_freeze(self,addr):
1084 if addr in self.wallet.frozen_addresses:
1085 self.wallet.unfreeze(addr)
1087 self.wallet.freeze(addr)
1088 self.update_receive_tab()
1090 def toggle_priority(self,addr):
1092 if addr in self.wallet.prioritized_addresses:
1093 self.wallet.unprioritize(addr)
1095 self.wallet.prioritize(addr)
1096 self.update_receive_tab()
1099 def create_list_tab(self, headers):
1100 "generic tab creation method"
1101 l = MyTreeWidget(self)
1102 l.setColumnCount( len(headers) )
1103 l.setHeaderLabels( headers )
1106 vbox = QVBoxLayout()
1113 vbox.addWidget(buttons)
1115 hbox = QHBoxLayout()
1118 buttons.setLayout(hbox)
1123 def create_receive_tab(self):
1124 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1125 l.setContextMenuPolicy(Qt.CustomContextMenu)
1126 l.customContextMenuRequested.connect(self.create_receive_menu)
1127 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1128 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1129 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1130 self.receive_list = l
1131 self.receive_buttons_hbox = hbox
1136 def receive_tab_set_mode(self, i):
1137 self.save_column_widths()
1138 self.expert_mode = (i == 1)
1139 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1140 self.update_receive_tab()
1143 def save_column_widths(self):
1144 if not self.expert_mode:
1145 widths = [ self.receive_list.columnWidth(0) ]
1148 for i in range(self.receive_list.columnCount() -1):
1149 widths.append(self.receive_list.columnWidth(i))
1150 self.column_widths["receive"][self.expert_mode] = widths
1152 self.column_widths["history"] = []
1153 for i in range(self.history_list.columnCount() - 1):
1154 self.column_widths["history"].append(self.history_list.columnWidth(i))
1156 self.column_widths["contacts"] = []
1157 for i in range(self.contacts_list.columnCount() - 1):
1158 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1160 self.config.set_key("column_widths", self.column_widths, True)
1163 def create_contacts_tab(self):
1164 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1165 l.setContextMenuPolicy(Qt.CustomContextMenu)
1166 l.customContextMenuRequested.connect(self.create_contact_menu)
1167 for i,width in enumerate(self.column_widths['contacts']):
1168 l.setColumnWidth(i, width)
1170 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1171 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1172 self.contacts_list = l
1173 self.contacts_buttons_hbox = hbox
1178 def delete_imported_key(self, addr):
1179 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1180 self.wallet.delete_imported_key(addr)
1181 self.update_receive_tab()
1182 self.update_history_tab()
1185 def create_receive_menu(self, position):
1186 # fixme: this function apparently has a side effect.
1187 # if it is not called the menu pops up several times
1188 #self.receive_list.selectedIndexes()
1190 item = self.receive_list.itemAt(position)
1192 addr = unicode(item.text(0))
1193 if not is_valid(addr):
1194 item.setExpanded(not item.isExpanded())
1197 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1198 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1199 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1200 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1201 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1202 if addr in self.wallet.imported_keys:
1203 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1205 if self.expert_mode:
1206 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1207 menu.addAction(t, lambda: self.toggle_freeze(addr))
1208 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1209 menu.addAction(t, lambda: self.toggle_priority(addr))
1211 self.run_hook('receive_menu', menu)
1212 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1215 def payto(self, addr):
1217 label = self.wallet.labels.get(addr)
1218 m_addr = label + ' <' + addr + '>' if label else addr
1219 self.tabs.setCurrentIndex(1)
1220 self.payto_e.setText(m_addr)
1221 self.amount_e.setFocus()
1224 def delete_contact(self, x):
1225 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1226 self.wallet.delete_contact(x)
1227 self.set_label(x, None)
1228 self.update_history_tab()
1229 self.update_contacts_tab()
1230 self.update_completions()
1233 def create_contact_menu(self, position):
1234 item = self.contacts_list.itemAt(position)
1236 addr = unicode(item.text(0))
1237 label = unicode(item.text(1))
1238 is_editable = item.data(0,32).toBool()
1239 payto_addr = item.data(0,33).toString()
1241 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1242 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1243 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1245 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1246 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1248 self.run_hook('create_contact_menu', menu, item)
1249 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1252 def update_receive_item(self, item):
1253 item.setFont(0, QFont(MONOSPACE_FONT))
1254 address = str(item.data(0,0).toString())
1255 label = self.wallet.labels.get(address,'')
1256 item.setData(1,0,label)
1257 item.setData(0,32, True) # is editable
1259 self.run_hook('update_receive_item', address, item)
1261 c, u = self.wallet.get_addr_balance(address)
1262 balance = self.format_amount(c + u)
1263 item.setData(2,0,balance)
1265 if self.expert_mode:
1266 if address in self.wallet.frozen_addresses:
1267 item.setBackgroundColor(0, QColor('lightblue'))
1268 elif address in self.wallet.prioritized_addresses:
1269 item.setBackgroundColor(0, QColor('lightgreen'))
1272 def update_receive_tab(self):
1273 l = self.receive_list
1276 l.setColumnHidden(2, not self.expert_mode)
1277 l.setColumnHidden(3, not self.expert_mode)
1278 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1279 l.setColumnWidth(i, width)
1281 if self.current_account is None:
1282 account_items = self.wallet.accounts.items()
1283 elif self.current_account != -1:
1284 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1288 for k, account in account_items:
1289 name = self.wallet.get_account_name(k)
1290 c,u = self.wallet.get_account_balance(k)
1291 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1292 l.addTopLevelItem(account_item)
1293 account_item.setExpanded(True)
1295 for is_change in ([0,1] if self.expert_mode else [0]):
1296 if self.expert_mode:
1297 name = "Receiving" if not is_change else "Change"
1298 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1299 account_item.addChild(seq_item)
1300 if not is_change: seq_item.setExpanded(True)
1302 seq_item = account_item
1306 for address in account.get_addresses(is_change):
1307 h = self.wallet.history.get(address,[])
1311 if gap > self.wallet.gap_limit:
1316 num_tx = '*' if h == ['*'] else "%d"%len(h)
1317 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1318 self.update_receive_item(item)
1320 item.setBackgroundColor(1, QColor('red'))
1321 seq_item.addChild(item)
1324 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1325 c,u = self.wallet.get_imported_balance()
1326 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1327 l.addTopLevelItem(account_item)
1328 account_item.setExpanded(True)
1329 for address in self.wallet.imported_keys.keys():
1330 item = QTreeWidgetItem( [ address, '', '', ''] )
1331 self.update_receive_item(item)
1332 account_item.addChild(item)
1335 # we use column 1 because column 0 may be hidden
1336 l.setCurrentItem(l.topLevelItem(0),1)
1339 def update_contacts_tab(self):
1340 l = self.contacts_list
1343 for address in self.wallet.addressbook:
1344 label = self.wallet.labels.get(address,'')
1345 n = self.wallet.get_num_tx(address)
1346 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1347 item.setFont(0, QFont(MONOSPACE_FONT))
1348 # 32 = label can be edited (bool)
1349 item.setData(0,32, True)
1351 item.setData(0,33, address)
1352 l.addTopLevelItem(item)
1354 self.run_hook('update_contacts_tab', l)
1355 l.setCurrentItem(l.topLevelItem(0))
1359 def create_console_tab(self):
1360 from qt_console import Console
1361 self.console = console = Console()
1365 def update_console(self):
1366 console = self.console
1367 console.history = self.config.get("console-history",[])
1368 console.history_index = len(console.history)
1370 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1371 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1373 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1375 def mkfunc(f, method):
1376 return lambda *args: apply( f, (method, args, self.password_dialog ))
1378 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1379 methods[m] = mkfunc(c._run, m)
1381 console.updateNamespace(methods)
1384 def change_account(self,s):
1385 if s == _("All accounts"):
1386 self.current_account = None
1388 accounts = self.wallet.get_account_names()
1389 for k, v in accounts.items():
1391 self.current_account = k
1392 self.update_history_tab()
1393 self.update_status()
1394 self.update_receive_tab()
1396 def create_status_bar(self):
1399 sb.setFixedHeight(35)
1400 qtVersion = qVersion()
1402 self.balance_label = QLabel("")
1403 sb.addWidget(self.balance_label)
1405 update_notification = UpdateLabel(self.config)
1406 if(update_notification.new_version):
1407 sb.addPermanentWidget(update_notification)
1409 self.account_selector = QComboBox()
1410 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1411 sb.addPermanentWidget(self.account_selector)
1413 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1414 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1416 self.lock_icon = QIcon()
1417 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1418 sb.addPermanentWidget( self.password_button )
1420 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1421 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1422 sb.addPermanentWidget( self.seed_button )
1423 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1424 sb.addPermanentWidget( self.status_button )
1426 self.run_hook('create_status_bar', (sb,))
1428 self.setStatusBar(sb)
1431 def update_lock_icon(self):
1432 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1433 self.password_button.setIcon( icon )
1436 def update_buttons_on_seed(self):
1437 if self.wallet.seed:
1438 self.seed_button.show()
1439 self.password_button.show()
1440 self.send_button.setText(_("Send"))
1442 self.password_button.hide()
1443 self.seed_button.hide()
1444 self.send_button.setText(_("Create unsigned transaction"))
1447 def change_password_dialog(self):
1448 from password_dialog import PasswordDialog
1449 d = PasswordDialog(self.wallet, self)
1451 self.update_lock_icon()
1456 self.config.set_key('gui', 'lite', True)
1459 self.lite.mini.show()
1461 self.lite = gui_lite.ElectrumGui(self.config, None, None, self)
1462 self.lite.main(None)
1465 def new_contact_dialog(self):
1466 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1467 address = unicode(text)
1469 if is_valid(address):
1470 self.wallet.add_contact(address)
1471 self.update_contacts_tab()
1472 self.update_history_tab()
1473 self.update_completions()
1475 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1478 def new_account_dialog(self):
1480 dialog = QDialog(self)
1482 dialog.setWindowTitle(_("New Account"))
1484 addr = self.wallet.new_account_address()
1485 vbox = QVBoxLayout()
1486 vbox.addWidget(QLabel(_("To create a new account, please send coins to the first address of that account:")))
1491 ok_button = QPushButton(_("OK"))
1492 ok_button.setDefault(True)
1493 ok_button.clicked.connect(dialog.accept)
1495 hbox = QHBoxLayout()
1497 hbox.addWidget(ok_button)
1498 vbox.addLayout(hbox)
1500 dialog.setLayout(vbox)
1505 def show_master_public_key(self):
1506 dialog = QDialog(self)
1508 dialog.setWindowTitle(_("Master Public Key"))
1510 main_text = QTextEdit()
1511 main_text.setText(self.wallet.get_master_public_key())
1512 main_text.setReadOnly(True)
1513 main_text.setMaximumHeight(170)
1514 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1516 ok_button = QPushButton(_("OK"))
1517 ok_button.setDefault(True)
1518 ok_button.clicked.connect(dialog.accept)
1520 main_layout = QGridLayout()
1521 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1523 main_layout.addWidget(main_text, 1, 0)
1524 main_layout.addWidget(qrw, 1, 1 )
1526 vbox = QVBoxLayout()
1527 vbox.addLayout(main_layout)
1528 hbox = QHBoxLayout()
1530 hbox.addWidget(ok_button)
1531 vbox.addLayout(hbox)
1533 dialog.setLayout(vbox)
1538 def show_seed_dialog(self, password):
1539 if not self.wallet.seed:
1540 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1543 seed = self.wallet.decode_seed(password)
1545 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1548 from seed_dialog import SeedDialog
1549 d = SeedDialog(self)
1550 d.show_seed(seed, self.wallet.imported_keys)
1554 def show_qrcode(self, data, title = "QR code"):
1558 d.setWindowTitle(title)
1559 d.setMinimumSize(270, 300)
1560 vbox = QVBoxLayout()
1561 qrw = QRCodeWidget(data)
1562 vbox.addWidget(qrw, 1)
1563 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1564 hbox = QHBoxLayout()
1568 filename = "qrcode.bmp"
1569 bmp.save_qrcode(qrw.qr, filename)
1570 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1572 b = QPushButton(_("Save"))
1574 b.clicked.connect(print_qr)
1576 b = QPushButton(_("Close"))
1578 b.clicked.connect(d.accept)
1581 vbox.addLayout(hbox)
1586 def do_protect(self, func, args):
1587 if self.wallet.use_encryption:
1588 password = self.password_dialog()
1594 if args != (False,):
1595 args = (self,) + args + (password,)
1597 args = (self,password)
1602 def show_private_key(self, address, password):
1603 if not address: return
1605 pk = self.wallet.get_private_key(address, password)
1606 except BaseException, e:
1607 self.show_message(str(e))
1609 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1613 def do_sign(self, address, message, signature, password):
1615 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1616 signature.setText(sig)
1617 except BaseException, e:
1618 self.show_message(str(e))
1620 def sign_message(self, address):
1621 if not address: return
1624 d.setWindowTitle(_('Sign Message'))
1625 d.setMinimumSize(410, 290)
1627 tab_widget = QTabWidget()
1629 layout = QGridLayout(tab)
1631 sign_address = QLineEdit()
1633 sign_address.setText(address)
1634 layout.addWidget(QLabel(_('Address')), 1, 0)
1635 layout.addWidget(sign_address, 1, 1)
1637 sign_message = QTextEdit()
1638 layout.addWidget(QLabel(_('Message')), 2, 0)
1639 layout.addWidget(sign_message, 2, 1)
1640 layout.setRowStretch(2,3)
1642 sign_signature = QTextEdit()
1643 layout.addWidget(QLabel(_('Signature')), 3, 0)
1644 layout.addWidget(sign_signature, 3, 1)
1645 layout.setRowStretch(3,1)
1648 hbox = QHBoxLayout()
1649 b = QPushButton(_("Sign"))
1651 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1652 b = QPushButton(_("Close"))
1653 b.clicked.connect(d.accept)
1655 layout.addLayout(hbox, 4, 1)
1656 tab_widget.addTab(tab, _("Sign"))
1660 layout = QGridLayout(tab)
1662 verify_address = QLineEdit()
1663 layout.addWidget(QLabel(_('Address')), 1, 0)
1664 layout.addWidget(verify_address, 1, 1)
1666 verify_message = QTextEdit()
1667 layout.addWidget(QLabel(_('Message')), 2, 0)
1668 layout.addWidget(verify_message, 2, 1)
1669 layout.setRowStretch(2,3)
1671 verify_signature = QTextEdit()
1672 layout.addWidget(QLabel(_('Signature')), 3, 0)
1673 layout.addWidget(verify_signature, 3, 1)
1674 layout.setRowStretch(3,1)
1677 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1678 self.show_message(_("Signature verified"))
1680 self.show_message(_("Error: wrong signature"))
1682 hbox = QHBoxLayout()
1683 b = QPushButton(_("Verify"))
1684 b.clicked.connect(do_verify)
1686 b = QPushButton(_("Close"))
1687 b.clicked.connect(d.accept)
1689 layout.addLayout(hbox, 4, 1)
1690 tab_widget.addTab(tab, _("Verify"))
1692 vbox = QVBoxLayout()
1693 vbox.addWidget(tab_widget)
1700 def question(self, msg):
1701 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1703 def show_message(self, msg):
1704 QMessageBox.information(self, _('Message'), msg, _('OK'))
1706 def password_dialog(self ):
1713 vbox = QVBoxLayout()
1714 msg = _('Please enter your password')
1715 vbox.addWidget(QLabel(msg))
1717 grid = QGridLayout()
1719 grid.addWidget(QLabel(_('Password')), 1, 0)
1720 grid.addWidget(pw, 1, 1)
1721 vbox.addLayout(grid)
1723 vbox.addLayout(ok_cancel_buttons(d))
1726 self.run_hook('password_dialog', pw, grid, 1)
1727 if not d.exec_(): return
1728 return unicode(pw.text())
1735 def generate_transaction_information_widget(self, tx):
1736 tabs = QTabWidget(self)
1739 grid_ui = QGridLayout(tab1)
1740 grid_ui.setColumnStretch(0,1)
1741 tabs.addTab(tab1, _('Outputs') )
1743 tree_widget = MyTreeWidget(self)
1744 tree_widget.setColumnCount(2)
1745 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1746 tree_widget.setColumnWidth(0, 300)
1747 tree_widget.setColumnWidth(1, 50)
1749 for address, value in tx.outputs:
1750 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1751 tree_widget.addTopLevelItem(item)
1753 tree_widget.setMaximumHeight(100)
1755 grid_ui.addWidget(tree_widget)
1758 grid_ui = QGridLayout(tab2)
1759 grid_ui.setColumnStretch(0,1)
1760 tabs.addTab(tab2, _('Inputs') )
1762 tree_widget = MyTreeWidget(self)
1763 tree_widget.setColumnCount(2)
1764 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1766 for input_line in tx.inputs:
1767 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1768 tree_widget.addTopLevelItem(item)
1770 tree_widget.setMaximumHeight(100)
1772 grid_ui.addWidget(tree_widget)
1776 def tx_dict_from_text(self, txt):
1778 tx_dict = json.loads(str(txt))
1779 assert "hex" in tx_dict.keys()
1780 assert "complete" in tx_dict.keys()
1781 if not tx_dict["complete"]:
1782 assert "input_info" in tx_dict.keys()
1784 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1789 def read_tx_from_file(self):
1790 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1794 with open(fileName, "r") as f:
1795 file_content = f.read()
1796 except (ValueError, IOError, os.error), reason:
1797 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1799 return self.tx_dict_from_text(file_content)
1803 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1805 self.wallet.signrawtransaction(tx, input_info, [], password)
1807 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1809 with open(fileName, "w+") as f:
1810 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1811 self.show_message(_("Transaction saved successfully"))
1814 except BaseException, e:
1815 self.show_message(str(e))
1818 def send_raw_transaction(self, raw_tx, dialog = ""):
1819 result, result_message = self.wallet.sendtx( raw_tx )
1821 self.show_message("Transaction successfully sent: %s" % (result_message))
1825 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1827 def do_process_from_text(self):
1828 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1831 tx_dict = self.tx_dict_from_text(text)
1833 self.create_process_transaction_window(tx_dict)
1835 def do_process_from_file(self):
1836 tx_dict = self.read_tx_from_file()
1838 self.create_process_transaction_window(tx_dict)
1840 def do_process_from_csvReader(self, csvReader):
1843 for row in csvReader:
1845 amount = float(row[1])
1846 amount = int(100000000*amount)
1847 outputs.append((address, amount))
1848 except (ValueError, IOError, os.error), reason:
1849 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1853 tx = self.wallet.make_unsigned_transaction(outputs, None, None, account=self.current_account)
1854 except BaseException, e:
1855 self.show_message(str(e))
1858 tx_dict = tx.as_dict()
1859 self.create_process_transaction_window(tx_dict)
1861 def do_process_from_csv_file(self):
1862 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1866 with open(fileName, "r") as f:
1867 csvReader = csv.reader(f)
1868 self.do_process_from_csvReader(csvReader)
1869 except (ValueError, IOError, os.error), reason:
1870 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1873 def do_process_from_csv_text(self):
1874 text = text_dialog(self, _('Input CSV'), _("CSV:"), _("Load CSV"))
1877 f = StringIO.StringIO(text)
1878 csvReader = csv.reader(f)
1879 self.do_process_from_csvReader(csvReader)
1881 def create_process_transaction_window(self, tx_dict):
1882 tx = Transaction(tx_dict["hex"])
1884 dialog = QDialog(self)
1885 dialog.setMinimumWidth(500)
1886 dialog.setWindowTitle(_('Process raw transaction'))
1892 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1893 l.addWidget(QLabel(_("Actions")), 4,0)
1895 if tx_dict["complete"] == False:
1896 l.addWidget(QLabel(_("Unsigned")), 3,1)
1897 if self.wallet.seed :
1898 b = QPushButton("Sign transaction")
1899 input_info = json.loads(tx_dict["input_info"])
1900 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1901 l.addWidget(b, 4, 1)
1903 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1905 l.addWidget(QLabel(_("Signed")), 3,1)
1906 b = QPushButton("Broadcast transaction")
1907 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1910 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1911 cancelButton = QPushButton(_("Cancel"))
1912 cancelButton.clicked.connect(lambda: dialog.done(0))
1913 l.addWidget(cancelButton, 4,2)
1919 def do_export_privkeys(self, password):
1920 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.")))
1923 select_export = _('Select file to export your private keys to')
1924 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1926 with open(fileName, "w+") as csvfile:
1927 transaction = csv.writer(csvfile)
1928 transaction.writerow(["address", "private_key"])
1931 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1932 transaction.writerow(["%34s"%addr,pk])
1934 self.show_message(_("Private keys exported."))
1936 except (IOError, os.error), reason:
1937 export_error_label = _("Electrum was unable to produce a private key-export.")
1938 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1940 except BaseException, e:
1941 self.show_message(str(e))
1945 def do_import_labels(self):
1946 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1947 if not labelsFile: return
1949 f = open(labelsFile, 'r')
1952 for key, value in json.loads(data).items():
1953 self.wallet.labels[key] = value
1955 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1956 except (IOError, os.error), reason:
1957 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1960 def do_export_labels(self):
1961 labels = self.wallet.labels
1963 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1965 with open(fileName, 'w+') as f:
1966 json.dump(labels, f)
1967 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1968 except (IOError, os.error), reason:
1969 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1972 def do_export_history(self):
1973 from gui_lite import csv_transaction
1974 csv_transaction(self.wallet)
1978 def do_import_privkey(self, password):
1979 if not self.wallet.imported_keys:
1980 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1981 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1982 + _('Are you sure you understand what you are doing?'), 3, 4)
1985 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1988 text = str(text).split()
1993 addr = self.wallet.import_key(key, password)
1994 except BaseException as e:
2000 addrlist.append(addr)
2002 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2004 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2005 self.update_receive_tab()
2006 self.update_history_tab()
2009 def settings_dialog(self):
2011 d.setWindowTitle(_('Electrum Settings'))
2013 vbox = QVBoxLayout()
2015 tabs = QTabWidget(self)
2016 self.settings_tab = tabs
2017 vbox.addWidget(tabs)
2020 grid_ui = QGridLayout(tab1)
2021 grid_ui.setColumnStretch(0,1)
2022 tabs.addTab(tab1, _('Display') )
2024 nz_label = QLabel(_('Display zeros'))
2025 grid_ui.addWidget(nz_label, 0, 0)
2026 nz_e = AmountEdit(None,True)
2027 nz_e.setText("%d"% self.num_zeros)
2028 grid_ui.addWidget(nz_e, 0, 1)
2029 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2030 grid_ui.addWidget(HelpButton(msg), 0, 2)
2031 if not self.config.is_modifiable('num_zeros'):
2032 for w in [nz_e, nz_label]: w.setEnabled(False)
2034 lang_label=QLabel(_('Language') + ':')
2035 grid_ui.addWidget(lang_label, 1, 0)
2036 lang_combo = QComboBox()
2037 from i18n import languages
2038 lang_combo.addItems(languages.values())
2040 index = languages.keys().index(self.config.get("language",''))
2043 lang_combo.setCurrentIndex(index)
2044 grid_ui.addWidget(lang_combo, 1, 1)
2045 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2046 if not self.config.is_modifiable('language'):
2047 for w in [lang_combo, lang_label]: w.setEnabled(False)
2049 currencies = self.exchanger.get_currencies()
2050 currencies.insert(0, "None")
2052 cur_label=QLabel(_('Currency') + ':')
2053 grid_ui.addWidget(cur_label , 2, 0)
2054 cur_combo = QComboBox()
2055 cur_combo.addItems(currencies)
2057 index = currencies.index(self.config.get('currency', "None"))
2060 cur_combo.setCurrentIndex(index)
2061 grid_ui.addWidget(cur_combo, 2, 1)
2062 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
2064 expert_cb = QCheckBox(_('Expert mode'))
2065 expert_cb.setChecked(self.expert_mode)
2066 grid_ui.addWidget(expert_cb, 3, 0)
2067 hh = _('In expert mode, your client will:') + '\n' \
2068 + _(' - Show change addresses in the Receive tab') + '\n' \
2069 + _(' - Display the balance of each address') + '\n' \
2070 + _(' - Add freeze/prioritize actions to addresses.')
2071 grid_ui.addWidget(HelpButton(hh), 3, 2)
2072 grid_ui.setRowStretch(4,1)
2076 grid_wallet = QGridLayout(tab2)
2077 grid_wallet.setColumnStretch(0,1)
2078 tabs.addTab(tab2, _('Wallet') )
2080 fee_label = QLabel(_('Transaction fee'))
2081 grid_wallet.addWidget(fee_label, 0, 0)
2082 fee_e = AmountEdit(self.base_unit)
2083 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2084 grid_wallet.addWidget(fee_e, 0, 2)
2085 msg = _('Fee per kilobyte of transaction.') + ' ' \
2086 + _('Recommended value') + ': ' + self.format_amount(50000)
2087 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2088 if not self.config.is_modifiable('fee_per_kb'):
2089 for w in [fee_e, fee_label]: w.setEnabled(False)
2091 usechange_cb = QCheckBox(_('Use change addresses'))
2092 usechange_cb.setChecked(self.wallet.use_change)
2093 grid_wallet.addWidget(usechange_cb, 1, 0)
2094 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2095 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2097 gap_label = QLabel(_('Gap limit'))
2098 grid_wallet.addWidget(gap_label, 2, 0)
2099 gap_e = AmountEdit(None,True)
2100 gap_e.setText("%d"% self.wallet.gap_limit)
2101 grid_wallet.addWidget(gap_e, 2, 2)
2102 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2103 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2104 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2105 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2106 + _('Warning') + ': ' \
2107 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2108 + _('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'
2109 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2110 if not self.config.is_modifiable('gap_limit'):
2111 for w in [gap_e, gap_label]: w.setEnabled(False)
2113 units = ['BTC', 'mBTC']
2114 unit_label = QLabel(_('Base unit'))
2115 grid_wallet.addWidget(unit_label, 3, 0)
2116 unit_combo = QComboBox()
2117 unit_combo.addItems(units)
2118 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2119 grid_wallet.addWidget(unit_combo, 3, 2)
2120 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2121 + '\n1BTC=1000mBTC.\n' \
2122 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2123 grid_wallet.setRowStretch(4,1)
2127 tab5 = QScrollArea()
2128 tab5.setEnabled(True)
2129 tab5.setWidgetResizable(True)
2131 grid_plugins = QGridLayout()
2132 grid_plugins.setColumnStretch(0,1)
2135 w.setLayout(grid_plugins)
2138 w.setMinimumHeight(len(self.plugins)*35)
2140 tabs.addTab(tab5, _('Plugins') )
2141 def mk_toggle(cb, p):
2142 return lambda: cb.setChecked(p.toggle())
2143 for i, p in enumerate(self.plugins):
2145 cb = QCheckBox(p.fullname())
2146 cb.setDisabled(not p.is_available())
2147 cb.setChecked(p.is_enabled())
2148 cb.clicked.connect(mk_toggle(cb,p))
2149 grid_plugins.addWidget(cb, i, 0)
2150 if p.requires_settings():
2151 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2152 grid_plugins.addWidget(HelpButton(p.description()), i, 2)
2154 print_msg("Error: cannot display plugin", p)
2155 traceback.print_exc(file=sys.stdout)
2156 grid_plugins.setRowStretch(i+1,1)
2158 self.run_hook('create_settings_tab', tabs)
2160 vbox.addLayout(ok_cancel_buttons(d))
2164 if not d.exec_(): return
2166 fee = unicode(fee_e.text())
2168 fee = self.read_amount(fee)
2170 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2173 self.wallet.set_fee(fee)
2175 nz = unicode(nz_e.text())
2180 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2183 if self.num_zeros != nz:
2185 self.config.set_key('num_zeros', nz, True)
2186 self.update_history_tab()
2187 self.update_receive_tab()
2189 usechange_result = usechange_cb.isChecked()
2190 if self.wallet.use_change != usechange_result:
2191 self.wallet.use_change = usechange_result
2192 self.config.set_key('use_change', self.wallet.use_change, True)
2194 unit_result = units[unit_combo.currentIndex()]
2195 if self.base_unit() != unit_result:
2196 self.decimal_point = 8 if unit_result == 'BTC' else 5
2197 self.config.set_key('decimal_point', self.decimal_point, True)
2198 self.update_history_tab()
2199 self.update_status()
2202 n = int(gap_e.text())
2204 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2207 if self.wallet.gap_limit != n:
2208 r = self.wallet.change_gap_limit(n)
2210 self.update_receive_tab()
2211 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2213 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2215 need_restart = False
2217 lang_request = languages.keys()[lang_combo.currentIndex()]
2218 if lang_request != self.config.get('language'):
2219 self.config.set_key("language", lang_request, True)
2222 cur_request = str(currencies[cur_combo.currentIndex()])
2223 if cur_request != self.config.get('currency', "None"):
2224 self.config.set_key('currency', cur_request, True)
2225 self.update_wallet()
2227 self.run_hook('close_settings_dialog')
2230 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2232 self.receive_tab_set_mode(expert_cb.isChecked())
2234 def run_network_dialog(self):
2235 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2237 def closeEvent(self, event):
2239 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2240 self.save_column_widths()
2241 self.config.set_key("console-history", self.console.history[-50:], True)
2244 class OpenFileEventFilter(QObject):
2245 def __init__(self, windows):
2246 self.windows = windows
2247 super(OpenFileEventFilter, self).__init__()
2249 def eventFilter(self, obj, event):
2250 if event.type() == QtCore.QEvent.FileOpen:
2251 if len(self.windows) >= 1:
2252 self.windows[0].set_url(event.url().toString())
2261 def __init__(self, config, interface, blockchain, app=None):
2262 self.interface = interface
2263 self.config = config
2264 self.blockchain = blockchain
2266 self.efilter = OpenFileEventFilter(self.windows)
2268 self.app = QApplication(sys.argv)
2269 self.app.installEventFilter(self.efilter)
2272 def main(self, url):
2274 storage = WalletStorage(self.config)
2275 if not storage.file_exists:
2276 import installwizard
2277 wizard = installwizard.InstallWizard(self.config, self.interface, self.blockchain, storage)
2278 wallet = wizard.run()
2282 wallet = Wallet(storage)
2284 wallet.start_threads(self.interface, self.blockchain)
2288 w = ElectrumWindow(self.config)
2289 w.load_wallet(wallet)
2291 self.windows.append(w)
2292 if url: w.set_url(url)
2300 wallet.stop_threads()