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)
572 QMainWindow.close(self)
573 self.run_hook('close_main_window')
575 def connect_slots(self, sender):
576 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
577 self.previous_payto_e=''
579 def timer_actions(self):
580 if self.need_update.is_set():
582 self.need_update.clear()
583 self.run_hook('timer_actions')
585 def format_amount(self, x, is_diff=False, whitespaces=False):
586 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
588 def read_amount(self, x):
589 if x in['.', '']: return None
590 p = pow(10, self.decimal_point)
591 return int( p * Decimal(x) )
594 assert self.decimal_point in [5,8]
595 return "BTC" if self.decimal_point == 8 else "mBTC"
597 def update_status(self):
598 if self.wallet.interface and self.wallet.interface.is_connected:
599 if not self.wallet.up_to_date:
600 text = _("Synchronizing...")
601 icon = QIcon(":icons/status_waiting.png")
603 c, u = self.wallet.get_account_balance(self.current_account)
604 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
605 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
606 text += self.create_quote_text(Decimal(c+u)/100000000)
607 self.tray.setToolTip(text)
608 icon = QIcon(":icons/status_connected.png")
610 text = _("Not connected")
611 icon = QIcon(":icons/status_disconnected.png")
613 self.balance_label.setText(text)
614 self.status_button.setIcon( icon )
616 def update_wallet(self):
618 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
619 self.update_history_tab()
620 self.update_receive_tab()
621 self.update_contacts_tab()
622 self.update_completions()
625 def create_quote_text(self, btc_balance):
626 quote_currency = self.config.get("currency", "None")
627 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
628 if quote_balance is None:
631 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
634 def create_history_tab(self):
635 self.history_list = l = MyTreeWidget(self)
637 for i,width in enumerate(self.column_widths['history']):
638 l.setColumnWidth(i, width)
639 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
640 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
641 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
643 l.setContextMenuPolicy(Qt.CustomContextMenu)
644 l.customContextMenuRequested.connect(self.create_history_menu)
648 def create_history_menu(self, position):
649 self.history_list.selectedIndexes()
650 item = self.history_list.currentItem()
652 tx_hash = str(item.data(0, Qt.UserRole).toString())
653 if not tx_hash: return
655 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
656 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
657 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
658 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
661 def show_tx_details(self, tx):
662 dialog = QDialog(self)
664 dialog.setWindowTitle(_("Transaction Details"))
666 dialog.setLayout(vbox)
667 dialog.setMinimumSize(600,300)
670 if tx_hash in self.wallet.transactions.keys():
671 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
672 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
674 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
680 vbox.addWidget(QLabel("Transaction ID:"))
681 e = QLineEdit(tx_hash)
685 vbox.addWidget(QLabel("Date: %s"%time_str))
686 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
689 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
690 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
692 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
693 vbox.addWidget(QLabel("Transaction fee: unknown"))
695 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
697 vbox.addWidget( self.generate_transaction_information_widget(tx) )
699 ok_button = QPushButton(_("Close"))
700 ok_button.setDefault(True)
701 ok_button.clicked.connect(dialog.accept)
705 hbox.addWidget(ok_button)
709 def tx_label_clicked(self, item, column):
710 if column==2 and item.isSelected():
712 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
713 self.history_list.editItem( item, column )
714 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
717 def tx_label_changed(self, item, column):
721 tx_hash = str(item.data(0, Qt.UserRole).toString())
722 tx = self.wallet.transactions.get(tx_hash)
723 text = unicode( item.text(2) )
724 self.set_label(tx_hash, text)
726 item.setForeground(2, QBrush(QColor('black')))
728 text = self.wallet.get_default_label(tx_hash)
729 item.setText(2, text)
730 item.setForeground(2, QBrush(QColor('gray')))
734 def edit_label(self, is_recv):
735 l = self.receive_list if is_recv else self.contacts_list
736 item = l.currentItem()
737 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
738 l.editItem( item, 1 )
739 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
743 def address_label_clicked(self, item, column, l, column_addr, column_label):
744 if column == column_label and item.isSelected():
745 is_editable = item.data(0, 32).toBool()
748 addr = unicode( item.text(column_addr) )
749 label = unicode( item.text(column_label) )
750 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
751 l.editItem( item, column )
752 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
755 def address_label_changed(self, item, column, l, column_addr, column_label):
756 if column == column_label:
757 addr = unicode( item.text(column_addr) )
758 text = unicode( item.text(column_label) )
759 is_editable = item.data(0, 32).toBool()
763 changed = self.set_label(addr, text)
765 self.update_history_tab()
766 self.update_completions()
768 self.current_item_changed(item)
770 self.run_hook('item_changed', item, column)
773 def current_item_changed(self, a):
774 self.run_hook('current_item_changed', a)
778 def update_history_tab(self):
780 self.history_list.clear()
781 for item in self.wallet.get_tx_history(self.current_account):
782 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
785 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
790 time_str = 'unverified'
791 icon = QIcon(":icons/unconfirmed.png")
794 icon = QIcon(":icons/unconfirmed.png")
796 icon = QIcon(":icons/clock%d.png"%conf)
798 icon = QIcon(":icons/confirmed.png")
800 if value is not None:
801 v_str = self.format_amount(value, True, whitespaces=True)
805 balance_str = self.format_amount(balance, whitespaces=True)
808 label, is_default_label = self.wallet.get_label(tx_hash)
810 label = _('Pruned transaction outputs')
811 is_default_label = False
813 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
814 item.setFont(2, QFont(MONOSPACE_FONT))
815 item.setFont(3, QFont(MONOSPACE_FONT))
816 item.setFont(4, QFont(MONOSPACE_FONT))
818 item.setForeground(3, QBrush(QColor("#BC1E1E")))
820 item.setData(0, Qt.UserRole, tx_hash)
821 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
823 item.setForeground(2, QBrush(QColor('grey')))
825 item.setIcon(0, icon)
826 self.history_list.insertTopLevelItem(0,item)
829 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
832 def create_send_tab(self):
837 grid.setColumnMinimumWidth(3,300)
838 grid.setColumnStretch(5,1)
841 self.payto_e = QLineEdit()
842 grid.addWidget(QLabel(_('Pay to')), 1, 0)
843 grid.addWidget(self.payto_e, 1, 1, 1, 3)
845 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)
847 completer = QCompleter()
848 completer.setCaseSensitivity(False)
849 self.payto_e.setCompleter(completer)
850 completer.setModel(self.completions)
852 self.message_e = QLineEdit()
853 grid.addWidget(QLabel(_('Description')), 2, 0)
854 grid.addWidget(self.message_e, 2, 1, 1, 3)
855 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)
857 self.amount_e = AmountEdit(self.base_unit)
858 grid.addWidget(QLabel(_('Amount')), 3, 0)
859 grid.addWidget(self.amount_e, 3, 1, 1, 2)
860 grid.addWidget(HelpButton(
861 _('Amount to be sent.') + '\n\n' \
862 + _('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.') \
863 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
865 self.fee_e = AmountEdit(self.base_unit)
866 grid.addWidget(QLabel(_('Fee')), 4, 0)
867 grid.addWidget(self.fee_e, 4, 1, 1, 2)
868 grid.addWidget(HelpButton(
869 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
870 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
871 + _('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)
874 self.send_button = EnterButton(_("Send"), self.do_send)
875 grid.addWidget(self.send_button, 6, 1)
877 b = EnterButton(_("Clear"),self.do_clear)
878 grid.addWidget(b, 6, 2)
880 self.payto_sig = QLabel('')
881 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
883 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
884 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
893 def entry_changed( is_fee ):
894 self.funds_error = False
896 if self.amount_e.is_shortcut:
897 self.amount_e.is_shortcut = False
898 c, u = self.wallet.get_account_balance(self.current_account)
899 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
900 fee = self.wallet.estimated_fee(inputs)
902 self.amount_e.setText( self.format_amount(amount) )
903 self.fee_e.setText( self.format_amount( fee ) )
906 amount = self.read_amount(str(self.amount_e.text()))
907 fee = self.read_amount(str(self.fee_e.text()))
909 if not is_fee: fee = None
912 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
914 self.fee_e.setText( self.format_amount( fee ) )
917 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
921 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
922 self.funds_error = True
923 text = _( "Not enough funds" )
924 c, u = self.wallet.get_frozen_balance()
925 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
927 self.statusBar().showMessage(text)
928 self.amount_e.setPalette(palette)
929 self.fee_e.setPalette(palette)
931 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
932 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
934 self.run_hook('create_send_tab', grid)
938 def update_completions(self):
940 for addr,label in self.wallet.labels.items():
941 if addr in self.wallet.addressbook:
942 l.append( label + ' <' + addr + '>')
944 self.run_hook('update_completions', l)
945 self.completions.setStringList(l)
949 return lambda s, *args: s.do_protect(func, args)
954 label = unicode( self.message_e.text() )
955 r = unicode( self.payto_e.text() )
958 # label or alias, with address in brackets
959 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
960 to_address = m.group(2) if m else r
962 if not is_valid(to_address):
963 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
967 amount = self.read_amount(unicode( self.amount_e.text()))
969 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
972 fee = self.read_amount(unicode( self.fee_e.text()))
974 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
977 confirm_amount = self.config.get('confirm_amount', 100000000)
978 if amount >= confirm_amount:
979 if not self.question("send %s to %s?"%(self.format_amount(amount) + ' '+ self.base_unit(), to_address)):
982 self.send_tx(to_address, amount, fee, label)
986 def send_tx(self, to_address, amount, fee, label, password):
989 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
990 except BaseException, e:
991 traceback.print_exc(file=sys.stdout)
992 self.show_message(str(e))
995 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
996 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
999 self.run_hook('send_tx', tx)
1002 self.set_label(tx.hash(), label)
1005 h = self.wallet.send_tx(tx)
1006 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
1007 status, msg = self.wallet.receive_tx( h )
1009 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
1011 self.update_contacts_tab()
1013 QMessageBox.warning(self, _('Error'), msg, _('OK'))
1015 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
1017 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
1018 with open(fileName,'w') as f:
1019 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1020 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
1022 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
1024 # add recipient to addressbook
1025 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
1026 self.wallet.addressbook.append(to_address)
1031 def set_url(self, url):
1032 address, amount, label, message, signature, identity, url = util.parse_url(url)
1033 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
1035 if label and self.wallet.labels.get(address) != label:
1036 if self.question('Give label "%s" to address %s ?'%(label,address)):
1037 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
1038 self.wallet.addressbook.append(address)
1039 self.set_label(address, label)
1041 self.run_hook('set_url', url, self.show_message, self.question)
1043 self.tabs.setCurrentIndex(1)
1044 label = self.wallet.labels.get(address)
1045 m_addr = label + ' <'+ address +'>' if label else address
1046 self.payto_e.setText(m_addr)
1048 self.message_e.setText(message)
1049 self.amount_e.setText(amount)
1051 self.set_frozen(self.payto_e,True)
1052 self.set_frozen(self.amount_e,True)
1053 self.set_frozen(self.message_e,True)
1054 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
1056 self.payto_sig.setVisible(False)
1059 self.payto_sig.setVisible(False)
1060 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1062 self.set_frozen(e,False)
1063 self.update_status()
1065 def set_frozen(self,entry,frozen):
1067 entry.setReadOnly(True)
1068 entry.setFrame(False)
1069 palette = QPalette()
1070 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1071 entry.setPalette(palette)
1073 entry.setReadOnly(False)
1074 entry.setFrame(True)
1075 palette = QPalette()
1076 palette.setColor(entry.backgroundRole(), QColor('white'))
1077 entry.setPalette(palette)
1080 def toggle_freeze(self,addr):
1082 if addr in self.wallet.frozen_addresses:
1083 self.wallet.unfreeze(addr)
1085 self.wallet.freeze(addr)
1086 self.update_receive_tab()
1088 def toggle_priority(self,addr):
1090 if addr in self.wallet.prioritized_addresses:
1091 self.wallet.unprioritize(addr)
1093 self.wallet.prioritize(addr)
1094 self.update_receive_tab()
1097 def create_list_tab(self, headers):
1098 "generic tab creation method"
1099 l = MyTreeWidget(self)
1100 l.setColumnCount( len(headers) )
1101 l.setHeaderLabels( headers )
1104 vbox = QVBoxLayout()
1111 vbox.addWidget(buttons)
1113 hbox = QHBoxLayout()
1116 buttons.setLayout(hbox)
1121 def create_receive_tab(self):
1122 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1123 l.setContextMenuPolicy(Qt.CustomContextMenu)
1124 l.customContextMenuRequested.connect(self.create_receive_menu)
1125 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1126 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1127 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1128 self.receive_list = l
1129 self.receive_buttons_hbox = hbox
1134 def receive_tab_set_mode(self, i):
1135 self.save_column_widths()
1136 self.expert_mode = (i == 1)
1137 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1138 self.update_receive_tab()
1141 def save_column_widths(self):
1142 if not self.expert_mode:
1143 widths = [ self.receive_list.columnWidth(0) ]
1146 for i in range(self.receive_list.columnCount() -1):
1147 widths.append(self.receive_list.columnWidth(i))
1148 self.column_widths["receive"][self.expert_mode] = widths
1150 self.column_widths["history"] = []
1151 for i in range(self.history_list.columnCount() - 1):
1152 self.column_widths["history"].append(self.history_list.columnWidth(i))
1154 self.column_widths["contacts"] = []
1155 for i in range(self.contacts_list.columnCount() - 1):
1156 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1158 self.config.set_key("column_widths", self.column_widths, True)
1161 def create_contacts_tab(self):
1162 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1163 l.setContextMenuPolicy(Qt.CustomContextMenu)
1164 l.customContextMenuRequested.connect(self.create_contact_menu)
1165 for i,width in enumerate(self.column_widths['contacts']):
1166 l.setColumnWidth(i, width)
1168 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1169 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1170 self.contacts_list = l
1171 self.contacts_buttons_hbox = hbox
1176 def delete_imported_key(self, addr):
1177 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1178 self.wallet.delete_imported_key(addr)
1179 self.update_receive_tab()
1180 self.update_history_tab()
1183 def create_receive_menu(self, position):
1184 # fixme: this function apparently has a side effect.
1185 # if it is not called the menu pops up several times
1186 #self.receive_list.selectedIndexes()
1188 item = self.receive_list.itemAt(position)
1190 addr = unicode(item.text(0))
1191 if not is_valid(addr):
1192 item.setExpanded(not item.isExpanded())
1195 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1196 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1197 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1198 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1199 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1200 if addr in self.wallet.imported_keys:
1201 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1203 if self.expert_mode:
1204 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1205 menu.addAction(t, lambda: self.toggle_freeze(addr))
1206 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1207 menu.addAction(t, lambda: self.toggle_priority(addr))
1209 self.run_hook('receive_menu', menu)
1210 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1213 def payto(self, addr):
1215 label = self.wallet.labels.get(addr)
1216 m_addr = label + ' <' + addr + '>' if label else addr
1217 self.tabs.setCurrentIndex(1)
1218 self.payto_e.setText(m_addr)
1219 self.amount_e.setFocus()
1222 def delete_contact(self, x):
1223 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1224 self.wallet.delete_contact(x)
1225 self.set_label(x, None)
1226 self.update_history_tab()
1227 self.update_contacts_tab()
1228 self.update_completions()
1231 def create_contact_menu(self, position):
1232 item = self.contacts_list.itemAt(position)
1234 addr = unicode(item.text(0))
1235 label = unicode(item.text(1))
1236 is_editable = item.data(0,32).toBool()
1237 payto_addr = item.data(0,33).toString()
1239 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1240 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1241 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1243 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1244 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1246 self.run_hook('create_contact_menu', menu, item)
1247 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1250 def update_receive_item(self, item):
1251 item.setFont(0, QFont(MONOSPACE_FONT))
1252 address = str(item.data(0,0).toString())
1253 label = self.wallet.labels.get(address,'')
1254 item.setData(1,0,label)
1255 item.setData(0,32, True) # is editable
1257 self.run_hook('update_receive_item', address, item)
1259 c, u = self.wallet.get_addr_balance(address)
1260 balance = self.format_amount(c + u)
1261 item.setData(2,0,balance)
1263 if self.expert_mode:
1264 if address in self.wallet.frozen_addresses:
1265 item.setBackgroundColor(0, QColor('lightblue'))
1266 elif address in self.wallet.prioritized_addresses:
1267 item.setBackgroundColor(0, QColor('lightgreen'))
1270 def update_receive_tab(self):
1271 l = self.receive_list
1274 l.setColumnHidden(2, not self.expert_mode)
1275 l.setColumnHidden(3, not self.expert_mode)
1276 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1277 l.setColumnWidth(i, width)
1279 if self.current_account is None:
1280 account_items = self.wallet.accounts.items()
1281 elif self.current_account != -1:
1282 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1286 for k, account in account_items:
1287 name = self.wallet.get_account_name(k)
1288 c,u = self.wallet.get_account_balance(k)
1289 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1290 l.addTopLevelItem(account_item)
1291 account_item.setExpanded(True)
1293 for is_change in ([0,1] if self.expert_mode else [0]):
1294 if self.expert_mode:
1295 name = "Receiving" if not is_change else "Change"
1296 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1297 account_item.addChild(seq_item)
1298 if not is_change: seq_item.setExpanded(True)
1300 seq_item = account_item
1304 for address in account.get_addresses(is_change):
1305 h = self.wallet.history.get(address,[])
1309 if gap > self.wallet.gap_limit:
1314 num_tx = '*' if h == ['*'] else "%d"%len(h)
1315 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1316 self.update_receive_item(item)
1318 item.setBackgroundColor(1, QColor('red'))
1319 seq_item.addChild(item)
1322 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1323 c,u = self.wallet.get_imported_balance()
1324 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1325 l.addTopLevelItem(account_item)
1326 account_item.setExpanded(True)
1327 for address in self.wallet.imported_keys.keys():
1328 item = QTreeWidgetItem( [ address, '', '', ''] )
1329 self.update_receive_item(item)
1330 account_item.addChild(item)
1333 # we use column 1 because column 0 may be hidden
1334 l.setCurrentItem(l.topLevelItem(0),1)
1337 def update_contacts_tab(self):
1338 l = self.contacts_list
1341 for address in self.wallet.addressbook:
1342 label = self.wallet.labels.get(address,'')
1343 n = self.wallet.get_num_tx(address)
1344 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1345 item.setFont(0, QFont(MONOSPACE_FONT))
1346 # 32 = label can be edited (bool)
1347 item.setData(0,32, True)
1349 item.setData(0,33, address)
1350 l.addTopLevelItem(item)
1352 self.run_hook('update_contacts_tab', l)
1353 l.setCurrentItem(l.topLevelItem(0))
1357 def create_console_tab(self):
1358 from qt_console import Console
1359 self.console = console = Console()
1363 def update_console(self):
1364 console = self.console
1365 console.history = self.config.get("console-history",[])
1366 console.history_index = len(console.history)
1368 console.updateNamespace({'wallet' : self.wallet, 'network' : self.wallet.network, 'gui':self})
1369 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1371 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1373 def mkfunc(f, method):
1374 return lambda *args: apply( f, (method, args, self.password_dialog ))
1376 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1377 methods[m] = mkfunc(c._run, m)
1379 console.updateNamespace(methods)
1382 def change_account(self,s):
1383 if s == _("All accounts"):
1384 self.current_account = None
1386 accounts = self.wallet.get_account_names()
1387 for k, v in accounts.items():
1389 self.current_account = k
1390 self.update_history_tab()
1391 self.update_status()
1392 self.update_receive_tab()
1394 def create_status_bar(self):
1397 sb.setFixedHeight(35)
1398 qtVersion = qVersion()
1400 self.balance_label = QLabel("")
1401 sb.addWidget(self.balance_label)
1403 update_notification = UpdateLabel(self.config)
1404 if(update_notification.new_version):
1405 sb.addPermanentWidget(update_notification)
1407 self.account_selector = QComboBox()
1408 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1409 sb.addPermanentWidget(self.account_selector)
1411 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1412 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1414 self.lock_icon = QIcon()
1415 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1416 sb.addPermanentWidget( self.password_button )
1418 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1419 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1420 sb.addPermanentWidget( self.seed_button )
1421 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1422 sb.addPermanentWidget( self.status_button )
1424 self.run_hook('create_status_bar', (sb,))
1426 self.setStatusBar(sb)
1429 def update_lock_icon(self):
1430 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1431 self.password_button.setIcon( icon )
1434 def update_buttons_on_seed(self):
1435 if self.wallet.seed:
1436 self.seed_button.show()
1437 self.password_button.show()
1438 self.send_button.setText(_("Send"))
1440 self.password_button.hide()
1441 self.seed_button.hide()
1442 self.send_button.setText(_("Create unsigned transaction"))
1445 def change_password_dialog(self):
1446 from password_dialog import PasswordDialog
1447 d = PasswordDialog(self.wallet, self)
1449 self.update_lock_icon()
1454 self.config.set_key('gui', 'lite', True)
1457 self.lite.mini.show()
1459 self.lite = gui_lite.ElectrumGui(self.config, None, None, self)
1460 self.lite.main(None)
1463 def new_contact_dialog(self):
1464 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1465 address = unicode(text)
1467 if is_valid(address):
1468 self.wallet.add_contact(address)
1469 self.update_contacts_tab()
1470 self.update_history_tab()
1471 self.update_completions()
1473 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1476 def new_account_dialog(self):
1478 dialog = QDialog(self)
1480 dialog.setWindowTitle(_("New Account"))
1482 addr = self.wallet.new_account_address()
1483 vbox = QVBoxLayout()
1484 vbox.addWidget(QLabel(_("To create a new account, please send coins to the first address of that account:")))
1489 ok_button = QPushButton(_("OK"))
1490 ok_button.setDefault(True)
1491 ok_button.clicked.connect(dialog.accept)
1493 hbox = QHBoxLayout()
1495 hbox.addWidget(ok_button)
1496 vbox.addLayout(hbox)
1498 dialog.setLayout(vbox)
1503 def show_master_public_key(self):
1504 dialog = QDialog(self)
1506 dialog.setWindowTitle(_("Master Public Key"))
1508 main_text = QTextEdit()
1509 main_text.setText(self.wallet.get_master_public_key())
1510 main_text.setReadOnly(True)
1511 main_text.setMaximumHeight(170)
1512 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1514 ok_button = QPushButton(_("OK"))
1515 ok_button.setDefault(True)
1516 ok_button.clicked.connect(dialog.accept)
1518 main_layout = QGridLayout()
1519 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1521 main_layout.addWidget(main_text, 1, 0)
1522 main_layout.addWidget(qrw, 1, 1 )
1524 vbox = QVBoxLayout()
1525 vbox.addLayout(main_layout)
1526 hbox = QHBoxLayout()
1528 hbox.addWidget(ok_button)
1529 vbox.addLayout(hbox)
1531 dialog.setLayout(vbox)
1536 def show_seed_dialog(self, password):
1537 if not self.wallet.seed:
1538 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1541 seed = self.wallet.decode_seed(password)
1543 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1546 from seed_dialog import SeedDialog
1547 d = SeedDialog(self)
1548 d.show_seed(seed, self.wallet.imported_keys)
1552 def show_qrcode(self, data, title = "QR code"):
1556 d.setWindowTitle(title)
1557 d.setMinimumSize(270, 300)
1558 vbox = QVBoxLayout()
1559 qrw = QRCodeWidget(data)
1560 vbox.addWidget(qrw, 1)
1561 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1562 hbox = QHBoxLayout()
1566 filename = "qrcode.bmp"
1567 bmp.save_qrcode(qrw.qr, filename)
1568 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1570 b = QPushButton(_("Save"))
1572 b.clicked.connect(print_qr)
1574 b = QPushButton(_("Close"))
1576 b.clicked.connect(d.accept)
1579 vbox.addLayout(hbox)
1584 def do_protect(self, func, args):
1585 if self.wallet.use_encryption:
1586 password = self.password_dialog()
1592 if args != (False,):
1593 args = (self,) + args + (password,)
1595 args = (self,password)
1600 def show_private_key(self, address, password):
1601 if not address: return
1603 pk = self.wallet.get_private_key(address, password)
1604 except BaseException, e:
1605 self.show_message(str(e))
1607 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1611 def do_sign(self, address, message, signature, password):
1613 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1614 signature.setText(sig)
1615 except BaseException, e:
1616 self.show_message(str(e))
1618 def sign_message(self, address):
1619 if not address: return
1622 d.setWindowTitle(_('Sign Message'))
1623 d.setMinimumSize(410, 290)
1625 tab_widget = QTabWidget()
1627 layout = QGridLayout(tab)
1629 sign_address = QLineEdit()
1631 sign_address.setText(address)
1632 layout.addWidget(QLabel(_('Address')), 1, 0)
1633 layout.addWidget(sign_address, 1, 1)
1635 sign_message = QTextEdit()
1636 layout.addWidget(QLabel(_('Message')), 2, 0)
1637 layout.addWidget(sign_message, 2, 1)
1638 layout.setRowStretch(2,3)
1640 sign_signature = QTextEdit()
1641 layout.addWidget(QLabel(_('Signature')), 3, 0)
1642 layout.addWidget(sign_signature, 3, 1)
1643 layout.setRowStretch(3,1)
1646 hbox = QHBoxLayout()
1647 b = QPushButton(_("Sign"))
1649 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1650 b = QPushButton(_("Close"))
1651 b.clicked.connect(d.accept)
1653 layout.addLayout(hbox, 4, 1)
1654 tab_widget.addTab(tab, _("Sign"))
1658 layout = QGridLayout(tab)
1660 verify_address = QLineEdit()
1661 layout.addWidget(QLabel(_('Address')), 1, 0)
1662 layout.addWidget(verify_address, 1, 1)
1664 verify_message = QTextEdit()
1665 layout.addWidget(QLabel(_('Message')), 2, 0)
1666 layout.addWidget(verify_message, 2, 1)
1667 layout.setRowStretch(2,3)
1669 verify_signature = QTextEdit()
1670 layout.addWidget(QLabel(_('Signature')), 3, 0)
1671 layout.addWidget(verify_signature, 3, 1)
1672 layout.setRowStretch(3,1)
1675 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1676 self.show_message(_("Signature verified"))
1678 self.show_message(_("Error: wrong signature"))
1680 hbox = QHBoxLayout()
1681 b = QPushButton(_("Verify"))
1682 b.clicked.connect(do_verify)
1684 b = QPushButton(_("Close"))
1685 b.clicked.connect(d.accept)
1687 layout.addLayout(hbox, 4, 1)
1688 tab_widget.addTab(tab, _("Verify"))
1690 vbox = QVBoxLayout()
1691 vbox.addWidget(tab_widget)
1698 def question(self, msg):
1699 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1701 def show_message(self, msg):
1702 QMessageBox.information(self, _('Message'), msg, _('OK'))
1704 def password_dialog(self ):
1711 vbox = QVBoxLayout()
1712 msg = _('Please enter your password')
1713 vbox.addWidget(QLabel(msg))
1715 grid = QGridLayout()
1717 grid.addWidget(QLabel(_('Password')), 1, 0)
1718 grid.addWidget(pw, 1, 1)
1719 vbox.addLayout(grid)
1721 vbox.addLayout(ok_cancel_buttons(d))
1724 self.run_hook('password_dialog', pw, grid, 1)
1725 if not d.exec_(): return
1726 return unicode(pw.text())
1733 def generate_transaction_information_widget(self, tx):
1734 tabs = QTabWidget(self)
1737 grid_ui = QGridLayout(tab1)
1738 grid_ui.setColumnStretch(0,1)
1739 tabs.addTab(tab1, _('Outputs') )
1741 tree_widget = MyTreeWidget(self)
1742 tree_widget.setColumnCount(2)
1743 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1744 tree_widget.setColumnWidth(0, 300)
1745 tree_widget.setColumnWidth(1, 50)
1747 for address, value in tx.outputs:
1748 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1749 tree_widget.addTopLevelItem(item)
1751 tree_widget.setMaximumHeight(100)
1753 grid_ui.addWidget(tree_widget)
1756 grid_ui = QGridLayout(tab2)
1757 grid_ui.setColumnStretch(0,1)
1758 tabs.addTab(tab2, _('Inputs') )
1760 tree_widget = MyTreeWidget(self)
1761 tree_widget.setColumnCount(2)
1762 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1764 for input_line in tx.inputs:
1765 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1766 tree_widget.addTopLevelItem(item)
1768 tree_widget.setMaximumHeight(100)
1770 grid_ui.addWidget(tree_widget)
1774 def tx_dict_from_text(self, txt):
1776 tx_dict = json.loads(str(txt))
1777 assert "hex" in tx_dict.keys()
1778 assert "complete" in tx_dict.keys()
1779 if not tx_dict["complete"]:
1780 assert "input_info" in tx_dict.keys()
1782 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1787 def read_tx_from_file(self):
1788 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1792 with open(fileName, "r") as f:
1793 file_content = f.read()
1794 except (ValueError, IOError, os.error), reason:
1795 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1797 return self.tx_dict_from_text(file_content)
1801 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1803 self.wallet.signrawtransaction(tx, input_info, [], password)
1805 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1807 with open(fileName, "w+") as f:
1808 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1809 self.show_message(_("Transaction saved successfully"))
1812 except BaseException, e:
1813 self.show_message(str(e))
1816 def send_raw_transaction(self, raw_tx, dialog = ""):
1817 result, result_message = self.wallet.sendtx( raw_tx )
1819 self.show_message("Transaction successfully sent: %s" % (result_message))
1823 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1825 def do_process_from_text(self):
1826 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1829 tx_dict = self.tx_dict_from_text(text)
1831 self.create_process_transaction_window(tx_dict)
1833 def do_process_from_file(self):
1834 tx_dict = self.read_tx_from_file()
1836 self.create_process_transaction_window(tx_dict)
1838 def do_process_from_csvReader(self, csvReader):
1841 for row in csvReader:
1843 amount = float(row[1])
1844 amount = int(100000000*amount)
1845 outputs.append((address, amount))
1846 except (ValueError, IOError, os.error), reason:
1847 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1851 tx = self.wallet.make_unsigned_transaction(outputs, None, None, account=self.current_account)
1852 except BaseException, e:
1853 self.show_message(str(e))
1856 tx_dict = tx.as_dict()
1857 self.create_process_transaction_window(tx_dict)
1859 def do_process_from_csv_file(self):
1860 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1864 with open(fileName, "r") as f:
1865 csvReader = csv.reader(f)
1866 self.do_process_from_csvReader(csvReader)
1867 except (ValueError, IOError, os.error), reason:
1868 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1871 def do_process_from_csv_text(self):
1872 text = text_dialog(self, _('Input CSV'), _("CSV:"), _("Load CSV"))
1875 f = StringIO.StringIO(text)
1876 csvReader = csv.reader(f)
1877 self.do_process_from_csvReader(csvReader)
1879 def create_process_transaction_window(self, tx_dict):
1880 tx = Transaction(tx_dict["hex"])
1882 dialog = QDialog(self)
1883 dialog.setMinimumWidth(500)
1884 dialog.setWindowTitle(_('Process raw transaction'))
1890 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1891 l.addWidget(QLabel(_("Actions")), 4,0)
1893 if tx_dict["complete"] == False:
1894 l.addWidget(QLabel(_("Unsigned")), 3,1)
1895 if self.wallet.seed :
1896 b = QPushButton("Sign transaction")
1897 input_info = json.loads(tx_dict["input_info"])
1898 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1899 l.addWidget(b, 4, 1)
1901 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1903 l.addWidget(QLabel(_("Signed")), 3,1)
1904 b = QPushButton("Broadcast transaction")
1905 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1908 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1909 cancelButton = QPushButton(_("Cancel"))
1910 cancelButton.clicked.connect(lambda: dialog.done(0))
1911 l.addWidget(cancelButton, 4,2)
1917 def do_export_privkeys(self, password):
1918 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.")))
1921 select_export = _('Select file to export your private keys to')
1922 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1924 with open(fileName, "w+") as csvfile:
1925 transaction = csv.writer(csvfile)
1926 transaction.writerow(["address", "private_key"])
1929 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1930 transaction.writerow(["%34s"%addr,pk])
1932 self.show_message(_("Private keys exported."))
1934 except (IOError, os.error), reason:
1935 export_error_label = _("Electrum was unable to produce a private key-export.")
1936 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1938 except BaseException, e:
1939 self.show_message(str(e))
1943 def do_import_labels(self):
1944 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1945 if not labelsFile: return
1947 f = open(labelsFile, 'r')
1950 for key, value in json.loads(data).items():
1951 self.wallet.labels[key] = value
1953 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1954 except (IOError, os.error), reason:
1955 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1958 def do_export_labels(self):
1959 labels = self.wallet.labels
1961 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1963 with open(fileName, 'w+') as f:
1964 json.dump(labels, f)
1965 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1966 except (IOError, os.error), reason:
1967 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1970 def do_export_history(self):
1971 from gui_lite import csv_transaction
1972 csv_transaction(self.wallet)
1976 def do_import_privkey(self, password):
1977 if not self.wallet.imported_keys:
1978 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1979 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1980 + _('Are you sure you understand what you are doing?'), 3, 4)
1983 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1986 text = str(text).split()
1991 addr = self.wallet.import_key(key, password)
1992 except BaseException as e:
1998 addrlist.append(addr)
2000 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2002 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2003 self.update_receive_tab()
2004 self.update_history_tab()
2007 def settings_dialog(self):
2009 d.setWindowTitle(_('Electrum Settings'))
2011 vbox = QVBoxLayout()
2013 tabs = QTabWidget(self)
2014 self.settings_tab = tabs
2015 vbox.addWidget(tabs)
2018 grid_ui = QGridLayout(tab1)
2019 grid_ui.setColumnStretch(0,1)
2020 tabs.addTab(tab1, _('Display') )
2022 nz_label = QLabel(_('Display zeros'))
2023 grid_ui.addWidget(nz_label, 0, 0)
2024 nz_e = AmountEdit(None,True)
2025 nz_e.setText("%d"% self.num_zeros)
2026 grid_ui.addWidget(nz_e, 0, 1)
2027 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2028 grid_ui.addWidget(HelpButton(msg), 0, 2)
2029 if not self.config.is_modifiable('num_zeros'):
2030 for w in [nz_e, nz_label]: w.setEnabled(False)
2032 lang_label=QLabel(_('Language') + ':')
2033 grid_ui.addWidget(lang_label, 1, 0)
2034 lang_combo = QComboBox()
2035 from i18n import languages
2036 lang_combo.addItems(languages.values())
2038 index = languages.keys().index(self.config.get("language",''))
2041 lang_combo.setCurrentIndex(index)
2042 grid_ui.addWidget(lang_combo, 1, 1)
2043 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2044 if not self.config.is_modifiable('language'):
2045 for w in [lang_combo, lang_label]: w.setEnabled(False)
2047 currencies = self.exchanger.get_currencies()
2048 currencies.insert(0, "None")
2050 cur_label=QLabel(_('Currency') + ':')
2051 grid_ui.addWidget(cur_label , 2, 0)
2052 cur_combo = QComboBox()
2053 cur_combo.addItems(currencies)
2055 index = currencies.index(self.config.get('currency', "None"))
2058 cur_combo.setCurrentIndex(index)
2059 grid_ui.addWidget(cur_combo, 2, 1)
2060 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
2062 expert_cb = QCheckBox(_('Expert mode'))
2063 expert_cb.setChecked(self.expert_mode)
2064 grid_ui.addWidget(expert_cb, 3, 0)
2065 hh = _('In expert mode, your client will:') + '\n' \
2066 + _(' - Show change addresses in the Receive tab') + '\n' \
2067 + _(' - Display the balance of each address') + '\n' \
2068 + _(' - Add freeze/prioritize actions to addresses.')
2069 grid_ui.addWidget(HelpButton(hh), 3, 2)
2070 grid_ui.setRowStretch(4,1)
2074 grid_wallet = QGridLayout(tab2)
2075 grid_wallet.setColumnStretch(0,1)
2076 tabs.addTab(tab2, _('Wallet') )
2078 fee_label = QLabel(_('Transaction fee'))
2079 grid_wallet.addWidget(fee_label, 0, 0)
2080 fee_e = AmountEdit(self.base_unit)
2081 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2082 grid_wallet.addWidget(fee_e, 0, 2)
2083 msg = _('Fee per kilobyte of transaction.') + ' ' \
2084 + _('Recommended value') + ': ' + self.format_amount(50000)
2085 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2086 if not self.config.is_modifiable('fee_per_kb'):
2087 for w in [fee_e, fee_label]: w.setEnabled(False)
2089 usechange_cb = QCheckBox(_('Use change addresses'))
2090 usechange_cb.setChecked(self.wallet.use_change)
2091 grid_wallet.addWidget(usechange_cb, 1, 0)
2092 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2093 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2095 gap_label = QLabel(_('Gap limit'))
2096 grid_wallet.addWidget(gap_label, 2, 0)
2097 gap_e = AmountEdit(None,True)
2098 gap_e.setText("%d"% self.wallet.gap_limit)
2099 grid_wallet.addWidget(gap_e, 2, 2)
2100 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2101 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2102 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2103 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2104 + _('Warning') + ': ' \
2105 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2106 + _('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'
2107 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2108 if not self.config.is_modifiable('gap_limit'):
2109 for w in [gap_e, gap_label]: w.setEnabled(False)
2111 units = ['BTC', 'mBTC']
2112 unit_label = QLabel(_('Base unit'))
2113 grid_wallet.addWidget(unit_label, 3, 0)
2114 unit_combo = QComboBox()
2115 unit_combo.addItems(units)
2116 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2117 grid_wallet.addWidget(unit_combo, 3, 2)
2118 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2119 + '\n1BTC=1000mBTC.\n' \
2120 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2121 grid_wallet.setRowStretch(4,1)
2125 tab5 = QScrollArea()
2126 tab5.setEnabled(True)
2127 tab5.setWidgetResizable(True)
2129 grid_plugins = QGridLayout()
2130 grid_plugins.setColumnStretch(0,1)
2133 w.setLayout(grid_plugins)
2136 w.setMinimumHeight(len(self.plugins)*35)
2138 tabs.addTab(tab5, _('Plugins') )
2139 def mk_toggle(cb, p):
2140 return lambda: cb.setChecked(p.toggle())
2141 for i, p in enumerate(self.plugins):
2143 cb = QCheckBox(p.fullname())
2144 cb.setDisabled(not p.is_available())
2145 cb.setChecked(p.is_enabled())
2146 cb.clicked.connect(mk_toggle(cb,p))
2147 grid_plugins.addWidget(cb, i, 0)
2148 if p.requires_settings():
2149 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2150 grid_plugins.addWidget(HelpButton(p.description()), i, 2)
2152 print_msg("Error: cannot display plugin", p)
2153 traceback.print_exc(file=sys.stdout)
2154 grid_plugins.setRowStretch(i+1,1)
2156 self.run_hook('create_settings_tab', tabs)
2158 vbox.addLayout(ok_cancel_buttons(d))
2162 if not d.exec_(): return
2164 fee = unicode(fee_e.text())
2166 fee = self.read_amount(fee)
2168 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2171 self.wallet.set_fee(fee)
2173 nz = unicode(nz_e.text())
2178 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2181 if self.num_zeros != nz:
2183 self.config.set_key('num_zeros', nz, True)
2184 self.update_history_tab()
2185 self.update_receive_tab()
2187 usechange_result = usechange_cb.isChecked()
2188 if self.wallet.use_change != usechange_result:
2189 self.wallet.use_change = usechange_result
2190 self.config.set_key('use_change', self.wallet.use_change, True)
2192 unit_result = units[unit_combo.currentIndex()]
2193 if self.base_unit() != unit_result:
2194 self.decimal_point = 8 if unit_result == 'BTC' else 5
2195 self.config.set_key('decimal_point', self.decimal_point, True)
2196 self.update_history_tab()
2197 self.update_status()
2200 n = int(gap_e.text())
2202 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2205 if self.wallet.gap_limit != n:
2206 r = self.wallet.change_gap_limit(n)
2208 self.update_receive_tab()
2209 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2211 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2213 need_restart = False
2215 lang_request = languages.keys()[lang_combo.currentIndex()]
2216 if lang_request != self.config.get('language'):
2217 self.config.set_key("language", lang_request, True)
2220 cur_request = str(currencies[cur_combo.currentIndex()])
2221 if cur_request != self.config.get('currency', "None"):
2222 self.config.set_key('currency', cur_request, True)
2223 self.update_wallet()
2225 self.run_hook('close_settings_dialog')
2228 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2230 self.receive_tab_set_mode(expert_cb.isChecked())
2232 def run_network_dialog(self):
2233 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2235 def closeEvent(self, event):
2237 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2238 self.save_column_widths()
2239 self.config.set_key("console-history", self.console.history[-50:], True)
2242 class OpenFileEventFilter(QObject):
2243 def __init__(self, windows):
2244 self.windows = windows
2245 super(OpenFileEventFilter, self).__init__()
2247 def eventFilter(self, obj, event):
2248 if event.type() == QtCore.QEvent.FileOpen:
2249 if len(self.windows) >= 1:
2250 self.windows[0].set_url(event.url().toString())
2259 def __init__(self, config, network, app=None):
2260 self.network = network
2261 #self.interface = interface
2262 self.config = config
2263 #self.blockchain = network.blockchain
2265 self.efilter = OpenFileEventFilter(self.windows)
2267 self.app = QApplication(sys.argv)
2268 self.app.installEventFilter(self.efilter)
2271 def main(self, url):
2273 storage = WalletStorage(self.config)
2274 if not storage.file_exists:
2275 import installwizard
2276 wizard = installwizard.InstallWizard(self.config, self.interface, self.blockchain, storage)
2277 wallet = wizard.run()
2281 wallet = Wallet(storage)
2283 wallet.start_threads(self.network)
2287 w = ElectrumWindow(self.config)
2288 w.load_wallet(wallet)
2290 self.windows.append(w)
2291 if url: w.set_url(url)
2299 wallet.stop_threads()