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
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)
183 def keyPressEvent(self, e):
184 if e.key() == QtCore.Qt.Key_Return:
196 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive":[[370], [370,200,130]] }
198 class ElectrumWindow(QMainWindow):
199 def changeEvent(self, event):
200 flags = self.windowFlags();
201 if event and event.type() == QtCore.QEvent.WindowStateChange:
202 if self.windowState() & QtCore.Qt.WindowMinimized:
203 self.build_menu(True)
204 # The only way to toggle the icon in the window managers taskbar is to use the Qt.Tooltip flag
205 # The problem is that it somehow creates an (in)visible window that will stay active and prevent
206 # Electrum from closing.
207 # As for now I have no clue how to implement a proper 'hide to tray' functionality.
208 # self.setWindowFlags(flags & ~Qt.ToolTip)
209 elif event.oldState() & QtCore.Qt.WindowMinimized:
210 self.build_menu(False)
211 #self.setWindowFlags(flags | Qt.ToolTip)
213 def build_menu(self, is_hidden = False):
215 if self.isMinimized():
216 m.addAction(_("Show"), self.showNormal)
218 m.addAction(_("Hide"), self.showMinimized)
221 m.addAction(_("Exit Electrum"), self.close)
222 self.tray.setContextMenu(m)
224 def tray_activated(self, reason):
225 if reason == QSystemTrayIcon.DoubleClick:
229 def __init__(self, config):
230 QMainWindow.__init__(self)
235 self._close_electrum = False
237 self.current_account = self.config.get("current_account", None)
239 self.icon = QIcon(os.getcwd() + '/icons/electrum.png')
240 self.tray = QSystemTrayIcon(self.icon, self)
241 self.tray.setToolTip('Electrum')
242 self.tray.activated.connect(self.tray_activated)
246 self.create_status_bar()
248 self.need_update = threading.Event()
250 self.expert_mode = config.get('classic_expert_mode', False)
251 self.decimal_point = config.get('decimal_point', 8)
252 self.num_zeros = int(config.get('num_zeros',0))
254 set_language(config.get('language'))
256 self.funds_error = False
257 self.completions = QStringListModel()
259 self.tabs = tabs = QTabWidget(self)
260 self.column_widths = self.config.get("column_widths", default_column_widths )
261 tabs.addTab(self.create_history_tab(), _('History') )
262 tabs.addTab(self.create_send_tab(), _('Send') )
263 tabs.addTab(self.create_receive_tab(), _('Receive') )
264 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
265 tabs.addTab(self.create_console_tab(), _('Console') )
266 tabs.setMinimumSize(600, 400)
267 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
268 self.setCentralWidget(tabs)
270 g = self.config.get("winpos-qt",[100, 100, 840, 400])
271 self.setGeometry(g[0], g[1], g[2], g[3])
275 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
276 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
277 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
278 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
279 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
281 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
282 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
283 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
284 self.history_list.setFocus(True)
286 self.exchanger = exchange_rate.Exchanger(self)
287 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
289 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
290 if platform.system() == 'Windows':
291 n = 3 if self.wallet.seed else 2
292 tabs.setCurrentIndex (n)
293 tabs.setCurrentIndex (0)
295 # plugins that need to change the GUI do it here
296 self.run_hook('init')
301 def load_wallet(self, wallet):
305 self.wallet.interface.register_callback('updated', lambda: self.need_update.set())
306 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
307 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
308 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
309 self.wallet.interface.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
310 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
311 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
312 self.setWindowTitle( title )
314 # set initial message
315 self.console.showMessage(self.wallet.interface.banner)
316 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
317 self.notify_transactions()
320 accounts = self.wallet.get_account_names()
321 self.account_selector.clear()
322 if len(accounts) > 1:
323 self.account_selector.addItems([_("All accounts")] + accounts.values())
324 self.account_selector.setCurrentIndex(0)
325 self.account_selector.show()
327 self.account_selector.hide()
329 self.new_account.setEnabled(self.wallet.seed_version>4)
331 self.update_lock_icon()
332 self.update_buttons_on_seed()
333 self.update_console()
335 self.run_hook('load_wallet')
338 def select_wallet_file(self):
339 wallet_folder = self.wallet.storage.path
340 re.sub("(\/\w*.dat)$", "", wallet_folder)
341 file_name = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat") )
345 def open_wallet(self):
347 filename = self.select_wallet_file()
351 storage = WalletStorage({'wallet_path': filename})
352 if not storage.file_exists:
353 self.show_message("file not found "+ filename)
356 interface = self.wallet.interface
357 blockchain = self.wallet.verifier.blockchain
358 self.wallet.stop_threads()
361 wallet = Wallet(storage)
362 wallet.start_threads(interface, blockchain)
364 self.load_wallet(wallet)
367 def new_wallet(self):
370 wallet_folder = self.wallet.storage.path
371 re.sub("(\/\w*.dat)$", "", wallet_folder)
372 filename = self.getSaveFileName("Select your wallet file", wallet_folder, "*.dat")
374 storage = WalletStorage({'wallet_path': filename})
375 assert not storage.file_exists
377 wizard = installwizard.InstallWizard(self.config, self.wallet.interface, self.wallet.verifier.blockchain, storage)
378 wallet = wizard.run()
380 self.load_wallet(wallet)
384 def init_menubar(self):
387 file_menu = menubar.addMenu(_("&File"))
388 open_wallet_action = file_menu.addAction(_("&Open"))
389 open_wallet_action.triggered.connect(self.open_wallet)
391 new_wallet_action = file_menu.addAction(_("&Create/Restore"))
392 new_wallet_action.triggered.connect(self.new_wallet)
394 wallet_backup = file_menu.addAction(_("&Copy"))
395 wallet_backup.triggered.connect(lambda: backup_wallet(self.wallet.storage.path))
397 quit_item = file_menu.addAction(_("&Close"))
398 quit_item.triggered.connect(self.close)
400 wallet_menu = menubar.addMenu(_("&Wallet"))
402 # Settings / Preferences are all reserved keywords in OSX using this as work around
403 preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
404 preferences_menu = wallet_menu.addAction(preferences_name)
405 preferences_menu.triggered.connect(self.settings_dialog)
407 wallet_menu.addSeparator()
409 raw_transaction_menu = wallet_menu.addMenu(_("&Load raw transaction"))
411 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
412 raw_transaction_file.triggered.connect(self.do_process_from_file)
414 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
415 raw_transaction_text.triggered.connect(self.do_process_from_text)
417 csv_transaction_menu = wallet_menu.addMenu(_("&Load CSV transaction"))
419 csv_transaction_file = csv_transaction_menu.addAction(_("&From file"))
420 csv_transaction_file.triggered.connect(self.do_process_from_csv_file)
422 csv_transaction_text = csv_transaction_menu.addAction(_("&From text"))
423 csv_transaction_text.triggered.connect(self.do_process_from_csv_text)
425 wallet_menu.addSeparator()
427 show_menu = wallet_menu.addMenu(_("Show"))
429 #if self.wallet.seed:
430 show_seed = show_menu.addAction(_("&Seed"))
431 show_seed.triggered.connect(self.show_seed_dialog)
433 show_mpk = show_menu.addAction(_("&Master Public Key"))
434 show_mpk.triggered.connect(self.show_master_public_key)
436 wallet_menu.addSeparator()
437 new_contact = wallet_menu.addAction(_("&New contact"))
438 new_contact.triggered.connect(self.new_contact_dialog)
440 self.new_account = wallet_menu.addAction(_("&New account"))
441 self.new_account.triggered.connect(self.new_account_dialog)
443 import_menu = menubar.addMenu(_("&Import"))
444 in_labels = import_menu.addAction(_("&Labels"))
445 in_labels.triggered.connect(self.do_import_labels)
447 in_private_keys = import_menu.addAction(_("&Private keys"))
448 in_private_keys.triggered.connect(self.do_import_privkey)
450 export_menu = menubar.addMenu(_("&Export"))
451 ex_private_keys = export_menu.addAction(_("&Private keys"))
452 ex_private_keys.triggered.connect(self.do_export_privkeys)
454 ex_history = export_menu.addAction(_("&History"))
455 ex_history.triggered.connect(self.do_export_history)
457 ex_labels = export_menu.addAction(_("&Labels"))
458 ex_labels.triggered.connect(self.do_export_labels)
460 help_menu = menubar.addMenu(_("&Help"))
461 doc_open = help_menu.addAction(_("&Documentation"))
462 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
463 web_open = help_menu.addAction(_("&Official website"))
464 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
466 self.setMenuBar(menubar)
470 def notify_transactions(self):
471 print_error("Notifying GUI")
472 if len(self.wallet.interface.pending_transactions_for_notifications) > 0:
473 # Combine the transactions if there are more then three
474 tx_amount = len(self.wallet.interface.pending_transactions_for_notifications)
477 for tx in self.wallet.interface.pending_transactions_for_notifications:
478 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
482 self.notify("%s new transactions received. Total amount received in the new transactions %s %s" \
483 % (tx_amount, self.format_amount(total_amount), self.base_unit()))
485 self.wallet.interface.pending_transactions_for_notifications = []
487 for tx in self.wallet.interface.pending_transactions_for_notifications:
489 self.wallet.interface.pending_transactions_for_notifications.remove(tx)
490 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
492 self.notify("New transaction received. %s %s" % (self.format_amount(v), self.base_unit()))
494 def notify(self, message):
495 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
498 def init_plugins(self):
499 import imp, pkgutil, __builtin__
500 if __builtin__.use_local_modules:
501 fp, pathname, description = imp.find_module('plugins')
502 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
503 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
504 imp.load_module('electrum_plugins', fp, pathname, description)
505 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
507 import electrum_plugins
508 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
509 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
512 for name, p in zip(plugin_names, plugins):
514 self.plugins.append( p.Plugin(self, name) )
516 print_msg("Error:cannot initialize plugin",p)
517 traceback.print_exc(file=sys.stdout)
520 def run_hook(self, name, *args):
521 for p in self.plugins:
522 if not p.is_enabled():
531 print_error("Plugin error")
532 traceback.print_exc(file=sys.stdout)
538 def set_label(self, name, text = None):
540 old_text = self.wallet.labels.get(name)
543 self.wallet.labels[name] = text
544 self.wallet.storage.put('labels', self.wallet.labels)
548 self.wallet.labels.pop(name)
550 self.run_hook('set_label', name, text, changed)
554 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
555 def getOpenFileName(self, title, filter = None):
556 directory = self.config.get('io_dir', os.path.expanduser('~'))
557 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
558 if fileName and directory != os.path.dirname(fileName):
559 self.config.set_key('io_dir', os.path.dirname(fileName), True)
562 def getSaveFileName(self, title, filename, filter = None):
563 directory = self.config.get('io_dir', os.path.expanduser('~'))
564 path = os.path.join( directory, filename )
565 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
566 if fileName and directory != os.path.dirname(fileName):
567 self.config.set_key('io_dir', os.path.dirname(fileName), True)
573 QMainWindow.close(self)
574 self.run_hook('close_main_window')
576 def connect_slots(self, sender):
577 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
578 self.previous_payto_e=''
580 def timer_actions(self):
581 if self.need_update.is_set():
583 self.need_update.clear()
584 self.run_hook('timer_actions')
586 def format_amount(self, x, is_diff=False, whitespaces=False):
587 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
589 def read_amount(self, x):
590 if x in['.', '']: return None
591 p = pow(10, self.decimal_point)
592 return int( p * Decimal(x) )
595 assert self.decimal_point in [5,8]
596 return "BTC" if self.decimal_point == 8 else "mBTC"
598 def update_status(self):
599 if self.wallet.interface and self.wallet.interface.is_connected:
600 if not self.wallet.up_to_date:
601 text = _("Synchronizing...")
602 icon = QIcon(":icons/status_waiting.png")
604 c, u = self.wallet.get_account_balance(self.current_account)
605 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
606 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
607 text += self.create_quote_text(Decimal(c+u)/100000000)
608 self.tray.setToolTip(text)
609 icon = QIcon(":icons/status_connected.png")
611 text = _("Not connected")
612 icon = QIcon(":icons/status_disconnected.png")
614 self.balance_label.setText(text)
615 self.status_button.setIcon( icon )
617 def update_wallet(self):
619 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
620 self.update_history_tab()
621 self.update_receive_tab()
622 self.update_contacts_tab()
623 self.update_completions()
626 def create_quote_text(self, btc_balance):
627 quote_currency = self.config.get("currency", "None")
628 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
629 if quote_balance is None:
632 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
635 def create_history_tab(self):
636 self.history_list = l = MyTreeWidget(self)
638 for i,width in enumerate(self.column_widths['history']):
639 l.setColumnWidth(i, width)
640 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
641 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
642 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
644 l.setContextMenuPolicy(Qt.CustomContextMenu)
645 l.customContextMenuRequested.connect(self.create_history_menu)
649 def create_history_menu(self, position):
650 self.history_list.selectedIndexes()
651 item = self.history_list.currentItem()
653 tx_hash = str(item.data(0, Qt.UserRole).toString())
654 if not tx_hash: return
656 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
657 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
658 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
659 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
662 def show_tx_details(self, tx):
663 dialog = QDialog(self)
665 dialog.setWindowTitle(_("Transaction Details"))
667 dialog.setLayout(vbox)
668 dialog.setMinimumSize(600,300)
671 if tx_hash in self.wallet.transactions.keys():
672 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
673 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
675 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
681 vbox.addWidget(QLabel("Transaction ID:"))
682 e = QLineEdit(tx_hash)
686 vbox.addWidget(QLabel("Date: %s"%time_str))
687 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
690 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
691 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
693 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
694 vbox.addWidget(QLabel("Transaction fee: unknown"))
696 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
698 vbox.addWidget( self.generate_transaction_information_widget(tx) )
700 ok_button = QPushButton(_("Close"))
701 ok_button.setDefault(True)
702 ok_button.clicked.connect(dialog.accept)
706 hbox.addWidget(ok_button)
710 def tx_label_clicked(self, item, column):
711 if column==2 and item.isSelected():
713 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
714 self.history_list.editItem( item, column )
715 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
718 def tx_label_changed(self, item, column):
722 tx_hash = str(item.data(0, Qt.UserRole).toString())
723 tx = self.wallet.transactions.get(tx_hash)
724 text = unicode( item.text(2) )
725 self.set_label(tx_hash, text)
727 item.setForeground(2, QBrush(QColor('black')))
729 text = self.wallet.get_default_label(tx_hash)
730 item.setText(2, text)
731 item.setForeground(2, QBrush(QColor('gray')))
735 def edit_label(self, is_recv):
736 l = self.receive_list if is_recv else self.contacts_list
737 item = l.currentItem()
738 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
739 l.editItem( item, 1 )
740 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
744 def address_label_clicked(self, item, column, l, column_addr, column_label):
745 if column == column_label and item.isSelected():
746 is_editable = item.data(0, 32).toBool()
749 addr = unicode( item.text(column_addr) )
750 label = unicode( item.text(column_label) )
751 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
752 l.editItem( item, column )
753 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
756 def address_label_changed(self, item, column, l, column_addr, column_label):
757 if column == column_label:
758 addr = unicode( item.text(column_addr) )
759 text = unicode( item.text(column_label) )
760 is_editable = item.data(0, 32).toBool()
764 changed = self.set_label(addr, text)
766 self.update_history_tab()
767 self.update_completions()
769 self.current_item_changed(item)
771 self.run_hook('item_changed', item, column)
774 def current_item_changed(self, a):
775 self.run_hook('current_item_changed', a)
779 def update_history_tab(self):
781 self.history_list.clear()
782 for item in self.wallet.get_tx_history(self.current_account):
783 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
786 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
791 time_str = 'unverified'
792 icon = QIcon(":icons/unconfirmed.png")
795 icon = QIcon(":icons/unconfirmed.png")
797 icon = QIcon(":icons/clock%d.png"%conf)
799 icon = QIcon(":icons/confirmed.png")
801 if value is not None:
802 v_str = self.format_amount(value, True, whitespaces=True)
806 balance_str = self.format_amount(balance, whitespaces=True)
809 label, is_default_label = self.wallet.get_label(tx_hash)
811 label = _('Pruned transaction outputs')
812 is_default_label = False
814 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
815 item.setFont(2, QFont(MONOSPACE_FONT))
816 item.setFont(3, QFont(MONOSPACE_FONT))
817 item.setFont(4, QFont(MONOSPACE_FONT))
819 item.setForeground(3, QBrush(QColor("#BC1E1E")))
821 item.setData(0, Qt.UserRole, tx_hash)
822 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
824 item.setForeground(2, QBrush(QColor('grey')))
826 item.setIcon(0, icon)
827 self.history_list.insertTopLevelItem(0,item)
830 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
833 def create_send_tab(self):
838 grid.setColumnMinimumWidth(3,300)
839 grid.setColumnStretch(5,1)
842 self.payto_e = QLineEdit()
843 grid.addWidget(QLabel(_('Pay to')), 1, 0)
844 grid.addWidget(self.payto_e, 1, 1, 1, 3)
846 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)
848 completer = QCompleter()
849 completer.setCaseSensitivity(False)
850 self.payto_e.setCompleter(completer)
851 completer.setModel(self.completions)
853 self.message_e = QLineEdit()
854 grid.addWidget(QLabel(_('Description')), 2, 0)
855 grid.addWidget(self.message_e, 2, 1, 1, 3)
856 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)
858 self.amount_e = AmountEdit(self.base_unit)
859 grid.addWidget(QLabel(_('Amount')), 3, 0)
860 grid.addWidget(self.amount_e, 3, 1, 1, 2)
861 grid.addWidget(HelpButton(
862 _('Amount to be sent.') + '\n\n' \
863 + _('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.') \
864 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
866 self.fee_e = AmountEdit(self.base_unit)
867 grid.addWidget(QLabel(_('Fee')), 4, 0)
868 grid.addWidget(self.fee_e, 4, 1, 1, 2)
869 grid.addWidget(HelpButton(
870 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
871 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
872 + _('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)
875 self.send_button = EnterButton(_("Send"), self.do_send)
876 grid.addWidget(self.send_button, 6, 1)
878 b = EnterButton(_("Clear"),self.do_clear)
879 grid.addWidget(b, 6, 2)
881 self.payto_sig = QLabel('')
882 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
884 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
885 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
894 def entry_changed( is_fee ):
895 self.funds_error = False
897 if self.amount_e.is_shortcut:
898 self.amount_e.is_shortcut = False
899 c, u = self.wallet.get_account_balance(self.current_account)
900 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
901 fee = self.wallet.estimated_fee(inputs)
903 self.amount_e.setText( self.format_amount(amount) )
904 self.fee_e.setText( self.format_amount( fee ) )
907 amount = self.read_amount(str(self.amount_e.text()))
908 fee = self.read_amount(str(self.fee_e.text()))
910 if not is_fee: fee = None
913 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
915 self.fee_e.setText( self.format_amount( fee ) )
918 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
922 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
923 self.funds_error = True
924 text = _( "Not enough funds" )
925 c, u = self.wallet.get_frozen_balance()
926 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
928 self.statusBar().showMessage(text)
929 self.amount_e.setPalette(palette)
930 self.fee_e.setPalette(palette)
932 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
933 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
935 self.run_hook('create_send_tab', grid)
939 def update_completions(self):
941 for addr,label in self.wallet.labels.items():
942 if addr in self.wallet.addressbook:
943 l.append( label + ' <' + addr + '>')
945 self.run_hook('update_completions', l)
946 self.completions.setStringList(l)
950 return lambda s, *args: s.do_protect(func, args)
955 label = unicode( self.message_e.text() )
956 r = unicode( self.payto_e.text() )
959 # label or alias, with address in brackets
960 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
961 to_address = m.group(2) if m else r
963 if not is_valid(to_address):
964 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
968 amount = self.read_amount(unicode( self.amount_e.text()))
970 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
973 fee = self.read_amount(unicode( self.fee_e.text()))
975 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
978 confirm_amount = self.config.get('confirm_amount', 100000000)
979 if amount >= confirm_amount:
980 if not self.question("send %s to %s?"%(self.format_amount(amount) + ' '+ self.base_unit(), to_address)):
983 self.send_tx(to_address, amount, fee, label)
987 def send_tx(self, to_address, amount, fee, label, password):
990 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
991 except BaseException, e:
992 traceback.print_exc(file=sys.stdout)
993 self.show_message(str(e))
996 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
997 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
1000 self.run_hook('send_tx', tx)
1003 self.set_label(tx.hash(), label)
1006 h = self.wallet.send_tx(tx)
1007 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
1008 status, msg = self.wallet.receive_tx( h )
1010 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
1012 self.update_contacts_tab()
1014 QMessageBox.warning(self, _('Error'), msg, _('OK'))
1016 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
1018 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
1019 with open(fileName,'w') as f:
1020 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1021 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
1023 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
1025 # add recipient to addressbook
1026 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
1027 self.wallet.addressbook.append(to_address)
1032 def set_url(self, url):
1033 address, amount, label, message, signature, identity, url = util.parse_url(url)
1034 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
1036 if label and self.wallet.labels.get(address) != label:
1037 if self.question('Give label "%s" to address %s ?'%(label,address)):
1038 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
1039 self.wallet.addressbook.append(address)
1040 self.set_label(address, label)
1042 self.run_hook('set_url', url, self.show_message, self.question)
1044 self.tabs.setCurrentIndex(1)
1045 label = self.wallet.labels.get(address)
1046 m_addr = label + ' <'+ address +'>' if label else address
1047 self.payto_e.setText(m_addr)
1049 self.message_e.setText(message)
1050 self.amount_e.setText(amount)
1052 self.set_frozen(self.payto_e,True)
1053 self.set_frozen(self.amount_e,True)
1054 self.set_frozen(self.message_e,True)
1055 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
1057 self.payto_sig.setVisible(False)
1060 self.payto_sig.setVisible(False)
1061 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1063 self.set_frozen(e,False)
1064 self.update_status()
1066 def set_frozen(self,entry,frozen):
1068 entry.setReadOnly(True)
1069 entry.setFrame(False)
1070 palette = QPalette()
1071 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1072 entry.setPalette(palette)
1074 entry.setReadOnly(False)
1075 entry.setFrame(True)
1076 palette = QPalette()
1077 palette.setColor(entry.backgroundRole(), QColor('white'))
1078 entry.setPalette(palette)
1081 def toggle_freeze(self,addr):
1083 if addr in self.wallet.frozen_addresses:
1084 self.wallet.unfreeze(addr)
1086 self.wallet.freeze(addr)
1087 self.update_receive_tab()
1089 def toggle_priority(self,addr):
1091 if addr in self.wallet.prioritized_addresses:
1092 self.wallet.unprioritize(addr)
1094 self.wallet.prioritize(addr)
1095 self.update_receive_tab()
1098 def create_list_tab(self, headers):
1099 "generic tab creation method"
1100 l = MyTreeWidget(self)
1101 l.setColumnCount( len(headers) )
1102 l.setHeaderLabels( headers )
1105 vbox = QVBoxLayout()
1112 vbox.addWidget(buttons)
1114 hbox = QHBoxLayout()
1117 buttons.setLayout(hbox)
1122 def create_receive_tab(self):
1123 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1124 l.setContextMenuPolicy(Qt.CustomContextMenu)
1125 l.customContextMenuRequested.connect(self.create_receive_menu)
1126 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1127 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1128 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1129 self.receive_list = l
1130 self.receive_buttons_hbox = hbox
1135 def receive_tab_set_mode(self, i):
1136 self.save_column_widths()
1137 self.expert_mode = (i == 1)
1138 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1139 self.update_receive_tab()
1142 def save_column_widths(self):
1143 if not self.expert_mode:
1144 widths = [ self.receive_list.columnWidth(0) ]
1147 for i in range(self.receive_list.columnCount() -1):
1148 widths.append(self.receive_list.columnWidth(i))
1149 self.column_widths["receive"][self.expert_mode] = widths
1151 self.column_widths["history"] = []
1152 for i in range(self.history_list.columnCount() - 1):
1153 self.column_widths["history"].append(self.history_list.columnWidth(i))
1155 self.column_widths["contacts"] = []
1156 for i in range(self.contacts_list.columnCount() - 1):
1157 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1159 self.config.set_key("column_widths", self.column_widths, True)
1162 def create_contacts_tab(self):
1163 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1164 l.setContextMenuPolicy(Qt.CustomContextMenu)
1165 l.customContextMenuRequested.connect(self.create_contact_menu)
1166 for i,width in enumerate(self.column_widths['contacts']):
1167 l.setColumnWidth(i, width)
1169 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1170 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1171 self.contacts_list = l
1172 self.contacts_buttons_hbox = hbox
1177 def delete_imported_key(self, addr):
1178 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1179 self.wallet.delete_imported_key(addr)
1180 self.update_receive_tab()
1181 self.update_history_tab()
1184 def create_receive_menu(self, position):
1185 # fixme: this function apparently has a side effect.
1186 # if it is not called the menu pops up several times
1187 #self.receive_list.selectedIndexes()
1189 item = self.receive_list.itemAt(position)
1191 addr = unicode(item.text(0))
1192 if not is_valid(addr):
1193 item.setExpanded(not item.isExpanded())
1196 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1197 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1198 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1199 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1200 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1201 if addr in self.wallet.imported_keys:
1202 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1204 if self.expert_mode:
1205 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1206 menu.addAction(t, lambda: self.toggle_freeze(addr))
1207 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1208 menu.addAction(t, lambda: self.toggle_priority(addr))
1210 self.run_hook('receive_menu', menu)
1211 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1214 def payto(self, addr):
1216 label = self.wallet.labels.get(addr)
1217 m_addr = label + ' <' + addr + '>' if label else addr
1218 self.tabs.setCurrentIndex(1)
1219 self.payto_e.setText(m_addr)
1220 self.amount_e.setFocus()
1223 def delete_contact(self, x):
1224 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1225 self.wallet.delete_contact(x)
1226 self.set_label(x, None)
1227 self.update_history_tab()
1228 self.update_contacts_tab()
1229 self.update_completions()
1232 def create_contact_menu(self, position):
1233 item = self.contacts_list.itemAt(position)
1235 addr = unicode(item.text(0))
1236 label = unicode(item.text(1))
1237 is_editable = item.data(0,32).toBool()
1238 payto_addr = item.data(0,33).toString()
1240 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1241 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1242 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1244 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1245 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1247 self.run_hook('create_contact_menu', menu, item)
1248 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1251 def update_receive_item(self, item):
1252 item.setFont(0, QFont(MONOSPACE_FONT))
1253 address = str(item.data(0,0).toString())
1254 label = self.wallet.labels.get(address,'')
1255 item.setData(1,0,label)
1256 item.setData(0,32, True) # is editable
1258 self.run_hook('update_receive_item', address, item)
1260 c, u = self.wallet.get_addr_balance(address)
1261 balance = self.format_amount(c + u)
1262 item.setData(2,0,balance)
1264 if self.expert_mode:
1265 if address in self.wallet.frozen_addresses:
1266 item.setBackgroundColor(0, QColor('lightblue'))
1267 elif address in self.wallet.prioritized_addresses:
1268 item.setBackgroundColor(0, QColor('lightgreen'))
1271 def update_receive_tab(self):
1272 l = self.receive_list
1275 l.setColumnHidden(2, not self.expert_mode)
1276 l.setColumnHidden(3, not self.expert_mode)
1277 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1278 l.setColumnWidth(i, width)
1280 if self.current_account is None:
1281 account_items = self.wallet.accounts.items()
1282 elif self.current_account != -1:
1283 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1287 for k, account in account_items:
1288 name = self.wallet.get_account_name(k)
1289 c,u = self.wallet.get_account_balance(k)
1290 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1291 l.addTopLevelItem(account_item)
1292 account_item.setExpanded(True)
1294 for is_change in ([0,1] if self.expert_mode else [0]):
1295 if self.expert_mode:
1296 name = "Receiving" if not is_change else "Change"
1297 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1298 account_item.addChild(seq_item)
1299 if not is_change: seq_item.setExpanded(True)
1301 seq_item = account_item
1305 for address in account.get_addresses(is_change):
1306 h = self.wallet.history.get(address,[])
1310 if gap > self.wallet.gap_limit:
1315 num_tx = '*' if h == ['*'] else "%d"%len(h)
1316 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1317 self.update_receive_item(item)
1319 item.setBackgroundColor(1, QColor('red'))
1320 seq_item.addChild(item)
1323 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1324 c,u = self.wallet.get_imported_balance()
1325 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1326 l.addTopLevelItem(account_item)
1327 account_item.setExpanded(True)
1328 for address in self.wallet.imported_keys.keys():
1329 item = QTreeWidgetItem( [ address, '', '', ''] )
1330 self.update_receive_item(item)
1331 account_item.addChild(item)
1334 # we use column 1 because column 0 may be hidden
1335 l.setCurrentItem(l.topLevelItem(0),1)
1338 def update_contacts_tab(self):
1339 l = self.contacts_list
1342 for address in self.wallet.addressbook:
1343 label = self.wallet.labels.get(address,'')
1344 n = self.wallet.get_num_tx(address)
1345 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1346 item.setFont(0, QFont(MONOSPACE_FONT))
1347 # 32 = label can be edited (bool)
1348 item.setData(0,32, True)
1350 item.setData(0,33, address)
1351 l.addTopLevelItem(item)
1353 self.run_hook('update_contacts_tab', l)
1354 l.setCurrentItem(l.topLevelItem(0))
1358 def create_console_tab(self):
1359 from qt_console import Console
1360 self.console = console = Console()
1364 def update_console(self):
1365 console = self.console
1366 console.history = self.config.get("console-history",[])
1367 console.history_index = len(console.history)
1369 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1370 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1372 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1374 def mkfunc(f, method):
1375 return lambda *args: apply( f, (method, args, self.password_dialog ))
1377 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1378 methods[m] = mkfunc(c._run, m)
1380 console.updateNamespace(methods)
1383 def change_account(self,s):
1384 if s == _("All accounts"):
1385 self.current_account = None
1387 accounts = self.wallet.get_account_names()
1388 for k, v in accounts.items():
1390 self.current_account = k
1391 self.update_history_tab()
1392 self.update_status()
1393 self.update_receive_tab()
1395 def create_status_bar(self):
1398 sb.setFixedHeight(35)
1399 qtVersion = qVersion()
1401 self.balance_label = QLabel("")
1402 sb.addWidget(self.balance_label)
1404 update_notification = UpdateLabel(self.config)
1405 if(update_notification.new_version):
1406 sb.addPermanentWidget(update_notification)
1408 self.account_selector = QComboBox()
1409 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1410 sb.addPermanentWidget(self.account_selector)
1412 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1413 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1415 self.lock_icon = QIcon()
1416 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1417 sb.addPermanentWidget( self.password_button )
1419 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1420 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1421 sb.addPermanentWidget( self.seed_button )
1422 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1423 sb.addPermanentWidget( self.status_button )
1425 self.run_hook('create_status_bar', (sb,))
1427 self.setStatusBar(sb)
1430 def update_lock_icon(self):
1431 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1432 self.password_button.setIcon( icon )
1435 def update_buttons_on_seed(self):
1436 if self.wallet.seed:
1437 self.seed_button.show()
1438 self.password_button.show()
1439 self.send_button.setText(_("Send"))
1441 self.password_button.hide()
1442 self.seed_button.hide()
1443 self.send_button.setText(_("Create unsigned transaction"))
1446 def change_password_dialog(self):
1447 from password_dialog import PasswordDialog
1448 d = PasswordDialog(self.wallet, self)
1450 self.update_lock_icon()
1455 self.config.set_key('gui', 'lite', True)
1458 self.lite.mini.show()
1460 self.lite = gui_lite.ElectrumGui(self.config, None, None, self)
1461 self.lite.main(None)
1464 def new_contact_dialog(self):
1465 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1466 address = unicode(text)
1468 if is_valid(address):
1469 self.wallet.add_contact(address)
1470 self.update_contacts_tab()
1471 self.update_history_tab()
1472 self.update_completions()
1474 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1477 def new_account_dialog(self):
1479 dialog = QDialog(self)
1481 dialog.setWindowTitle(_("New Account"))
1483 addr = self.wallet.new_account_address()
1484 vbox = QVBoxLayout()
1485 vbox.addWidget(QLabel(_("To create a new account, please send coins to the first address of that account:")))
1490 ok_button = QPushButton(_("OK"))
1491 ok_button.setDefault(True)
1492 ok_button.clicked.connect(dialog.accept)
1494 hbox = QHBoxLayout()
1496 hbox.addWidget(ok_button)
1497 vbox.addLayout(hbox)
1499 dialog.setLayout(vbox)
1504 def show_master_public_key(self):
1505 dialog = QDialog(self)
1507 dialog.setWindowTitle(_("Master Public Key"))
1509 main_text = QTextEdit()
1510 main_text.setText(self.wallet.get_master_public_key())
1511 main_text.setReadOnly(True)
1512 main_text.setMaximumHeight(170)
1513 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1515 ok_button = QPushButton(_("OK"))
1516 ok_button.setDefault(True)
1517 ok_button.clicked.connect(dialog.accept)
1519 main_layout = QGridLayout()
1520 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1522 main_layout.addWidget(main_text, 1, 0)
1523 main_layout.addWidget(qrw, 1, 1 )
1525 vbox = QVBoxLayout()
1526 vbox.addLayout(main_layout)
1527 hbox = QHBoxLayout()
1529 hbox.addWidget(ok_button)
1530 vbox.addLayout(hbox)
1532 dialog.setLayout(vbox)
1537 def show_seed_dialog(self, password):
1538 if not self.wallet.seed:
1539 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1542 seed = self.wallet.decode_seed(password)
1544 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1547 from seed_dialog import SeedDialog
1548 d = SeedDialog(self)
1549 d.show_seed(seed, self.wallet.imported_keys)
1553 def show_qrcode(self, data, title = "QR code"):
1557 d.setWindowTitle(title)
1558 d.setMinimumSize(270, 300)
1559 vbox = QVBoxLayout()
1560 qrw = QRCodeWidget(data)
1561 vbox.addWidget(qrw, 1)
1562 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1563 hbox = QHBoxLayout()
1567 filename = "qrcode.bmp"
1568 bmp.save_qrcode(qrw.qr, filename)
1569 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1571 b = QPushButton(_("Save"))
1573 b.clicked.connect(print_qr)
1575 b = QPushButton(_("Close"))
1577 b.clicked.connect(d.accept)
1580 vbox.addLayout(hbox)
1585 def do_protect(self, func, args):
1586 if self.wallet.use_encryption:
1587 password = self.password_dialog()
1593 if args != (False,):
1594 args = (self,) + args + (password,)
1596 args = (self,password)
1601 def show_private_key(self, address, password):
1602 if not address: return
1604 pk = self.wallet.get_private_key(address, password)
1605 except BaseException, e:
1606 self.show_message(str(e))
1608 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1612 def do_sign(self, address, message, signature, password):
1614 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1615 signature.setText(sig)
1616 except BaseException, e:
1617 self.show_message(str(e))
1619 def sign_message(self, address):
1620 if not address: return
1623 d.setWindowTitle(_('Sign Message'))
1624 d.setMinimumSize(410, 290)
1626 tab_widget = QTabWidget()
1628 layout = QGridLayout(tab)
1630 sign_address = QLineEdit()
1632 sign_address.setText(address)
1633 layout.addWidget(QLabel(_('Address')), 1, 0)
1634 layout.addWidget(sign_address, 1, 1)
1636 sign_message = QTextEdit()
1637 layout.addWidget(QLabel(_('Message')), 2, 0)
1638 layout.addWidget(sign_message, 2, 1)
1639 layout.setRowStretch(2,3)
1641 sign_signature = QTextEdit()
1642 layout.addWidget(QLabel(_('Signature')), 3, 0)
1643 layout.addWidget(sign_signature, 3, 1)
1644 layout.setRowStretch(3,1)
1647 hbox = QHBoxLayout()
1648 b = QPushButton(_("Sign"))
1650 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1651 b = QPushButton(_("Close"))
1652 b.clicked.connect(d.accept)
1654 layout.addLayout(hbox, 4, 1)
1655 tab_widget.addTab(tab, _("Sign"))
1659 layout = QGridLayout(tab)
1661 verify_address = QLineEdit()
1662 layout.addWidget(QLabel(_('Address')), 1, 0)
1663 layout.addWidget(verify_address, 1, 1)
1665 verify_message = QTextEdit()
1666 layout.addWidget(QLabel(_('Message')), 2, 0)
1667 layout.addWidget(verify_message, 2, 1)
1668 layout.setRowStretch(2,3)
1670 verify_signature = QTextEdit()
1671 layout.addWidget(QLabel(_('Signature')), 3, 0)
1672 layout.addWidget(verify_signature, 3, 1)
1673 layout.setRowStretch(3,1)
1676 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1677 self.show_message(_("Signature verified"))
1679 self.show_message(_("Error: wrong signature"))
1681 hbox = QHBoxLayout()
1682 b = QPushButton(_("Verify"))
1683 b.clicked.connect(do_verify)
1685 b = QPushButton(_("Close"))
1686 b.clicked.connect(d.accept)
1688 layout.addLayout(hbox, 4, 1)
1689 tab_widget.addTab(tab, _("Verify"))
1691 vbox = QVBoxLayout()
1692 vbox.addWidget(tab_widget)
1699 def question(self, msg):
1700 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1702 def show_message(self, msg):
1703 QMessageBox.information(self, _('Message'), msg, _('OK'))
1705 def password_dialog(self ):
1712 vbox = QVBoxLayout()
1713 msg = _('Please enter your password')
1714 vbox.addWidget(QLabel(msg))
1716 grid = QGridLayout()
1718 grid.addWidget(QLabel(_('Password')), 1, 0)
1719 grid.addWidget(pw, 1, 1)
1720 vbox.addLayout(grid)
1722 vbox.addLayout(ok_cancel_buttons(d))
1725 self.run_hook('password_dialog', pw, grid, 1)
1726 if not d.exec_(): return
1727 return unicode(pw.text())
1734 def generate_transaction_information_widget(self, tx):
1735 tabs = QTabWidget(self)
1738 grid_ui = QGridLayout(tab1)
1739 grid_ui.setColumnStretch(0,1)
1740 tabs.addTab(tab1, _('Outputs') )
1742 tree_widget = MyTreeWidget(self)
1743 tree_widget.setColumnCount(2)
1744 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1745 tree_widget.setColumnWidth(0, 300)
1746 tree_widget.setColumnWidth(1, 50)
1748 for address, value in tx.outputs:
1749 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1750 tree_widget.addTopLevelItem(item)
1752 tree_widget.setMaximumHeight(100)
1754 grid_ui.addWidget(tree_widget)
1757 grid_ui = QGridLayout(tab2)
1758 grid_ui.setColumnStretch(0,1)
1759 tabs.addTab(tab2, _('Inputs') )
1761 tree_widget = MyTreeWidget(self)
1762 tree_widget.setColumnCount(2)
1763 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1765 for input_line in tx.inputs:
1766 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1767 tree_widget.addTopLevelItem(item)
1769 tree_widget.setMaximumHeight(100)
1771 grid_ui.addWidget(tree_widget)
1775 def tx_dict_from_text(self, txt):
1777 tx_dict = json.loads(str(txt))
1778 assert "hex" in tx_dict.keys()
1779 assert "complete" in tx_dict.keys()
1780 if not tx_dict["complete"]:
1781 assert "input_info" in tx_dict.keys()
1783 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1788 def read_tx_from_file(self):
1789 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1793 with open(fileName, "r") as f:
1794 file_content = f.read()
1795 except (ValueError, IOError, os.error), reason:
1796 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1798 return self.tx_dict_from_text(file_content)
1802 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1804 self.wallet.signrawtransaction(tx, input_info, [], password)
1806 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1808 with open(fileName, "w+") as f:
1809 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1810 self.show_message(_("Transaction saved successfully"))
1813 except BaseException, e:
1814 self.show_message(str(e))
1817 def send_raw_transaction(self, raw_tx, dialog = ""):
1818 result, result_message = self.wallet.sendtx( raw_tx )
1820 self.show_message("Transaction successfully sent: %s" % (result_message))
1824 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1826 def do_process_from_text(self):
1827 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1830 tx_dict = self.tx_dict_from_text(text)
1832 self.create_process_transaction_window(tx_dict)
1834 def do_process_from_file(self):
1835 tx_dict = self.read_tx_from_file()
1837 self.create_process_transaction_window(tx_dict)
1839 def do_process_from_csvReader(self, csvReader):
1842 for row in csvReader:
1844 amount = float(row[1])
1845 amount = int(100000000*amount)
1846 outputs.append((address, amount))
1847 except (ValueError, IOError, os.error), reason:
1848 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1852 tx = self.wallet.make_unsigned_transaction(outputs, None, None, account=self.current_account)
1853 except BaseException, e:
1854 self.show_message(str(e))
1857 tx_dict = tx.as_dict()
1858 self.create_process_transaction_window(tx_dict)
1860 def do_process_from_csv_file(self):
1861 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1865 with open(fileName, "r") as f:
1866 csvReader = csv.reader(f)
1867 self.do_process_from_csvReader(csvReader)
1868 except (ValueError, IOError, os.error), reason:
1869 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1872 def do_process_from_csv_text(self):
1873 text = text_dialog(self, _('Input CSV'), _("CSV:"), _("Load CSV"))
1876 f = StringIO.StringIO(text)
1877 csvReader = csv.reader(f)
1878 self.do_process_from_csvReader(csvReader)
1880 def create_process_transaction_window(self, tx_dict):
1881 tx = Transaction(tx_dict["hex"])
1883 dialog = QDialog(self)
1884 dialog.setMinimumWidth(500)
1885 dialog.setWindowTitle(_('Process raw transaction'))
1891 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1892 l.addWidget(QLabel(_("Actions")), 4,0)
1894 if tx_dict["complete"] == False:
1895 l.addWidget(QLabel(_("Unsigned")), 3,1)
1896 if self.wallet.seed :
1897 b = QPushButton("Sign transaction")
1898 input_info = json.loads(tx_dict["input_info"])
1899 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1900 l.addWidget(b, 4, 1)
1902 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1904 l.addWidget(QLabel(_("Signed")), 3,1)
1905 b = QPushButton("Broadcast transaction")
1906 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1909 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1910 cancelButton = QPushButton(_("Cancel"))
1911 cancelButton.clicked.connect(lambda: dialog.done(0))
1912 l.addWidget(cancelButton, 4,2)
1918 def do_export_privkeys(self, password):
1919 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.")))
1922 select_export = _('Select file to export your private keys to')
1923 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1925 with open(fileName, "w+") as csvfile:
1926 transaction = csv.writer(csvfile)
1927 transaction.writerow(["address", "private_key"])
1930 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1931 transaction.writerow(["%34s"%addr,pk])
1933 self.show_message(_("Private keys exported."))
1935 except (IOError, os.error), reason:
1936 export_error_label = _("Electrum was unable to produce a private key-export.")
1937 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1939 except BaseException, e:
1940 self.show_message(str(e))
1944 def do_import_labels(self):
1945 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1946 if not labelsFile: return
1948 f = open(labelsFile, 'r')
1951 for key, value in json.loads(data).items():
1952 self.wallet.labels[key] = value
1954 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1955 except (IOError, os.error), reason:
1956 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1959 def do_export_labels(self):
1960 labels = self.wallet.labels
1962 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1964 with open(fileName, 'w+') as f:
1965 json.dump(labels, f)
1966 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1967 except (IOError, os.error), reason:
1968 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1971 def do_export_history(self):
1972 from gui_lite import csv_transaction
1973 csv_transaction(self.wallet)
1977 def do_import_privkey(self, password):
1978 if not self.wallet.imported_keys:
1979 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1980 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1981 + _('Are you sure you understand what you are doing?'), 3, 4)
1984 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1987 text = str(text).split()
1992 addr = self.wallet.import_key(key, password)
1993 except BaseException as e:
1999 addrlist.append(addr)
2001 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2003 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2004 self.update_receive_tab()
2005 self.update_history_tab()
2008 def settings_dialog(self):
2010 d.setWindowTitle(_('Electrum Settings'))
2012 vbox = QVBoxLayout()
2014 tabs = QTabWidget(self)
2015 self.settings_tab = tabs
2016 vbox.addWidget(tabs)
2019 grid_ui = QGridLayout(tab1)
2020 grid_ui.setColumnStretch(0,1)
2021 tabs.addTab(tab1, _('Display') )
2023 nz_label = QLabel(_('Display zeros'))
2024 grid_ui.addWidget(nz_label, 0, 0)
2025 nz_e = AmountEdit(None,True)
2026 nz_e.setText("%d"% self.num_zeros)
2027 grid_ui.addWidget(nz_e, 0, 1)
2028 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2029 grid_ui.addWidget(HelpButton(msg), 0, 2)
2030 if not self.config.is_modifiable('num_zeros'):
2031 for w in [nz_e, nz_label]: w.setEnabled(False)
2033 lang_label=QLabel(_('Language') + ':')
2034 grid_ui.addWidget(lang_label, 1, 0)
2035 lang_combo = QComboBox()
2036 from i18n import languages
2037 lang_combo.addItems(languages.values())
2039 index = languages.keys().index(self.config.get("language",''))
2042 lang_combo.setCurrentIndex(index)
2043 grid_ui.addWidget(lang_combo, 1, 1)
2044 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2045 if not self.config.is_modifiable('language'):
2046 for w in [lang_combo, lang_label]: w.setEnabled(False)
2048 currencies = self.exchanger.get_currencies()
2049 currencies.insert(0, "None")
2051 cur_label=QLabel(_('Currency') + ':')
2052 grid_ui.addWidget(cur_label , 2, 0)
2053 cur_combo = QComboBox()
2054 cur_combo.addItems(currencies)
2056 index = currencies.index(self.config.get('currency', "None"))
2059 cur_combo.setCurrentIndex(index)
2060 grid_ui.addWidget(cur_combo, 2, 1)
2061 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
2063 expert_cb = QCheckBox(_('Expert mode'))
2064 expert_cb.setChecked(self.expert_mode)
2065 grid_ui.addWidget(expert_cb, 3, 0)
2066 hh = _('In expert mode, your client will:') + '\n' \
2067 + _(' - Show change addresses in the Receive tab') + '\n' \
2068 + _(' - Display the balance of each address') + '\n' \
2069 + _(' - Add freeze/prioritize actions to addresses.')
2070 grid_ui.addWidget(HelpButton(hh), 3, 2)
2071 grid_ui.setRowStretch(4,1)
2075 grid_wallet = QGridLayout(tab2)
2076 grid_wallet.setColumnStretch(0,1)
2077 tabs.addTab(tab2, _('Wallet') )
2079 fee_label = QLabel(_('Transaction fee'))
2080 grid_wallet.addWidget(fee_label, 0, 0)
2081 fee_e = AmountEdit(self.base_unit)
2082 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2083 grid_wallet.addWidget(fee_e, 0, 2)
2084 msg = _('Fee per kilobyte of transaction.') + ' ' \
2085 + _('Recommended value') + ': ' + self.format_amount(50000)
2086 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2087 if not self.config.is_modifiable('fee_per_kb'):
2088 for w in [fee_e, fee_label]: w.setEnabled(False)
2090 usechange_cb = QCheckBox(_('Use change addresses'))
2091 usechange_cb.setChecked(self.wallet.use_change)
2092 grid_wallet.addWidget(usechange_cb, 1, 0)
2093 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2094 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2096 gap_label = QLabel(_('Gap limit'))
2097 grid_wallet.addWidget(gap_label, 2, 0)
2098 gap_e = AmountEdit(None,True)
2099 gap_e.setText("%d"% self.wallet.gap_limit)
2100 grid_wallet.addWidget(gap_e, 2, 2)
2101 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2102 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2103 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2104 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2105 + _('Warning') + ': ' \
2106 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2107 + _('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'
2108 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2109 if not self.config.is_modifiable('gap_limit'):
2110 for w in [gap_e, gap_label]: w.setEnabled(False)
2112 units = ['BTC', 'mBTC']
2113 unit_label = QLabel(_('Base unit'))
2114 grid_wallet.addWidget(unit_label, 3, 0)
2115 unit_combo = QComboBox()
2116 unit_combo.addItems(units)
2117 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2118 grid_wallet.addWidget(unit_combo, 3, 2)
2119 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2120 + '\n1BTC=1000mBTC.\n' \
2121 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2122 grid_wallet.setRowStretch(4,1)
2126 tab5 = QScrollArea()
2127 tab5.setEnabled(True)
2128 tab5.setWidgetResizable(True)
2130 grid_plugins = QGridLayout()
2131 grid_plugins.setColumnStretch(0,1)
2134 w.setLayout(grid_plugins)
2137 w.setMinimumHeight(len(self.plugins)*35)
2139 tabs.addTab(tab5, _('Plugins') )
2140 def mk_toggle(cb, p):
2141 return lambda: cb.setChecked(p.toggle())
2142 for i, p in enumerate(self.plugins):
2144 cb = QCheckBox(p.fullname())
2145 cb.setDisabled(not p.is_available())
2146 cb.setChecked(p.is_enabled())
2147 cb.clicked.connect(mk_toggle(cb,p))
2148 grid_plugins.addWidget(cb, i, 0)
2149 if p.requires_settings():
2150 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2151 grid_plugins.addWidget(HelpButton(p.description()), i, 2)
2153 print_msg("Error: cannot display plugin", p)
2154 traceback.print_exc(file=sys.stdout)
2155 grid_plugins.setRowStretch(i+1,1)
2157 self.run_hook('create_settings_tab', tabs)
2159 vbox.addLayout(ok_cancel_buttons(d))
2163 if not d.exec_(): return
2165 fee = unicode(fee_e.text())
2167 fee = self.read_amount(fee)
2169 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2172 self.wallet.set_fee(fee)
2174 nz = unicode(nz_e.text())
2179 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2182 if self.num_zeros != nz:
2184 self.config.set_key('num_zeros', nz, True)
2185 self.update_history_tab()
2186 self.update_receive_tab()
2188 usechange_result = usechange_cb.isChecked()
2189 if self.wallet.use_change != usechange_result:
2190 self.wallet.use_change = usechange_result
2191 self.config.set_key('use_change', self.wallet.use_change, True)
2193 unit_result = units[unit_combo.currentIndex()]
2194 if self.base_unit() != unit_result:
2195 self.decimal_point = 8 if unit_result == 'BTC' else 5
2196 self.config.set_key('decimal_point', self.decimal_point, True)
2197 self.update_history_tab()
2198 self.update_status()
2201 n = int(gap_e.text())
2203 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2206 if self.wallet.gap_limit != n:
2207 r = self.wallet.change_gap_limit(n)
2209 self.update_receive_tab()
2210 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2212 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2214 need_restart = False
2216 lang_request = languages.keys()[lang_combo.currentIndex()]
2217 if lang_request != self.config.get('language'):
2218 self.config.set_key("language", lang_request, True)
2221 cur_request = str(currencies[cur_combo.currentIndex()])
2222 if cur_request != self.config.get('currency', "None"):
2223 self.config.set_key('currency', cur_request, True)
2224 self.update_wallet()
2226 self.run_hook('close_settings_dialog')
2229 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2231 self.receive_tab_set_mode(expert_cb.isChecked())
2233 def run_network_dialog(self):
2234 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2236 def closeEvent(self, event):
2238 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2239 self.save_column_widths()
2240 self.config.set_key("console-history", self.console.history[-50:], True)
2243 class OpenFileEventFilter(QObject):
2244 def __init__(self, windows):
2245 self.windows = windows
2246 super(OpenFileEventFilter, self).__init__()
2248 def eventFilter(self, obj, event):
2249 if event.type() == QtCore.QEvent.FileOpen:
2250 if len(self.windows) >= 1:
2251 self.windows[0].set_url(event.url().toString())
2260 def __init__(self, config, interface, blockchain, app=None):
2261 self.interface = interface
2262 self.config = config
2263 self.blockchain = 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.interface, self.blockchain)
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()