3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re, threading
20 from i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
29 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
31 from PyQt4.QtGui import *
32 from PyQt4.QtCore import *
33 import PyQt4.QtCore as QtCore
35 from electrum.bitcoin import MIN_RELAY_TX_FEE
40 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'")
42 from electrum.wallet import format_satoshis
43 from electrum.bitcoin import Transaction, is_valid
44 from electrum import mnemonic
45 from electrum import util, bitcoin, commands, Interface, Wallet, WalletVerifier, WalletSynchronizer
47 import bmp, pyqrnative
50 from amountedit import AmountEdit
51 from network_dialog import NetworkDialog
52 from qrcodewidget import QRCodeWidget
54 from decimal import Decimal
62 if platform.system() == 'Windows':
63 MONOSPACE_FONT = 'Lucida Console'
64 elif platform.system() == 'Darwin':
65 MONOSPACE_FONT = 'Monaco'
67 MONOSPACE_FONT = 'monospace'
69 from electrum import ELECTRUM_VERSION
74 class UpdateLabel(QLabel):
75 def __init__(self, config, parent=None):
76 QLabel.__init__(self, parent)
77 self.new_version = False
80 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
81 con.request("GET", "/version")
82 res = con.getresponse()
83 except socket.error as msg:
84 print_error("Could not retrieve version information")
88 self.latest_version = res.read()
89 self.latest_version = self.latest_version.replace("\n","")
90 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
92 self.current_version = ELECTRUM_VERSION
93 if(self.compare_versions(self.latest_version, self.current_version) == 1):
94 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
95 if(self.compare_versions(self.latest_version, latest_seen) == 1):
96 self.new_version = True
97 self.setText(_("New version available") + ": " + self.latest_version)
100 def compare_versions(self, version1, version2):
102 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
103 return cmp(normalize(version1), normalize(version2))
105 def ignore_this_version(self):
107 self.config.set_key("last_seen_version", self.latest_version, True)
108 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
111 def ignore_all_version(self):
113 self.config.set_key("last_seen_version", "9.9.9", True)
114 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
117 def open_website(self):
118 webbrowser.open("http://electrum.org/download.html")
121 def mouseReleaseEvent(self, event):
122 dialog = QDialog(self)
123 dialog.setWindowTitle(_('Electrum update'))
126 main_layout = QGridLayout()
127 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
129 ignore_version = QPushButton(_("Ignore this version"))
130 ignore_version.clicked.connect(self.ignore_this_version)
132 ignore_all_versions = QPushButton(_("Ignore all versions"))
133 ignore_all_versions.clicked.connect(self.ignore_all_version)
135 open_website = QPushButton(_("Goto download page"))
136 open_website.clicked.connect(self.open_website)
138 main_layout.addWidget(ignore_version, 1, 0)
139 main_layout.addWidget(ignore_all_versions, 1, 1)
140 main_layout.addWidget(open_website, 1, 2)
142 dialog.setLayout(main_layout)
146 if not dialog.exec_(): return
151 class MyTreeWidget(QTreeWidget):
152 def __init__(self, parent):
153 QTreeWidget.__init__(self, parent)
156 for i in range(0,self.viewport().height()/5):
157 if self.itemAt(QPoint(0,i*5)) == item:
161 for j in range(0,30):
162 if self.itemAt(QPoint(0,i*5 + j)) != item:
164 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
166 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
171 class StatusBarButton(QPushButton):
172 def __init__(self, icon, tooltip, func):
173 QPushButton.__init__(self, icon, '')
174 self.setToolTip(tooltip)
176 self.setMaximumWidth(25)
177 self.clicked.connect(func)
180 def keyPressEvent(self, e):
181 if e.key() == QtCore.Qt.Key_Return:
193 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive":[[370], [370,200,130]] }
195 class ElectrumWindow(QMainWindow):
196 def changeEvent(self, event):
197 flags = self.windowFlags();
198 if event and event.type() == QtCore.QEvent.WindowStateChange:
199 if self.windowState() & QtCore.Qt.WindowMinimized:
200 self.build_menu(True)
201 # The only way to toggle the icon in the window managers taskbar is to use the Qt.Tooltip flag
202 # The problem is that it somehow creates an (in)visible window that will stay active and prevent
203 # Electrum from closing.
204 # As for now I have no clue how to implement a proper 'hide to tray' functionality.
205 # self.setWindowFlags(flags & ~Qt.ToolTip)
206 elif event.oldState() & QtCore.Qt.WindowMinimized:
207 self.build_menu(False)
208 #self.setWindowFlags(flags | Qt.ToolTip)
210 def build_menu(self, is_hidden = False):
212 if self.isMinimized():
213 m.addAction(_("Show"), self.showNormal)
215 m.addAction(_("Hide"), self.showMinimized)
218 m.addAction(_("Exit Electrum"), self.close)
219 self.tray.setContextMenu(m)
221 def tray_activated(self, reason):
222 if reason == QSystemTrayIcon.DoubleClick:
226 def __init__(self, config):
227 QMainWindow.__init__(self)
228 self._close_electrum = False
231 self.current_account = self.config.get("current_account", None)
233 self.icon = QIcon(os.getcwd() + '/icons/electrum.png')
234 self.tray = QSystemTrayIcon(self.icon, self)
235 self.tray.setToolTip('Electrum')
236 self.tray.activated.connect(self.tray_activated)
242 self.create_status_bar()
244 self.need_update = threading.Event()
246 self.expert_mode = config.get('classic_expert_mode', False)
247 self.decimal_point = config.get('decimal_point', 8)
249 set_language(config.get('language'))
251 self.funds_error = False
252 self.completions = QStringListModel()
254 self.tabs = tabs = QTabWidget(self)
255 self.column_widths = self.config.get("column_widths", default_column_widths )
256 tabs.addTab(self.create_history_tab(), _('History') )
257 tabs.addTab(self.create_send_tab(), _('Send') )
258 tabs.addTab(self.create_receive_tab(), _('Receive') )
259 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
260 tabs.addTab(self.create_console_tab(), _('Console') )
261 tabs.setMinimumSize(600, 400)
262 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
263 self.setCentralWidget(tabs)
265 g = self.config.get("winpos-qt",[100, 100, 840, 400])
266 self.setGeometry(g[0], g[1], g[2], g[3])
270 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
271 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
272 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
273 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
274 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
276 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
277 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
278 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
279 self.history_list.setFocus(True)
281 self.exchanger = exchange_rate.Exchanger(self)
282 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
284 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
285 if platform.system() == 'Windows':
286 n = 3 if self.wallet.seed else 2
287 tabs.setCurrentIndex (n)
288 tabs.setCurrentIndex (0)
291 # plugins that need to change the GUI do it here
292 self.run_hook('init')
295 def load_wallet(self, wallet):
299 self.wallet.interface.register_callback('updated', lambda: self.need_update.set())
300 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
301 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
302 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
303 self.wallet.interface.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
304 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
305 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
306 self.setWindowTitle( title )
308 # set initial message
309 self.console.showMessage(self.wallet.interface.banner)
310 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
311 self.notify_transactions()
314 accounts = self.wallet.get_accounts()
315 if len(accounts) > 1:
316 self.account_selector.addItems([_("All accounts")] + accounts.values())
317 self.account_selector.setCurrentIndex(0)
319 self.update_lock_icon()
320 self.update_buttons_on_seed()
323 def select_wallet_file(self):
324 wallet_folder = self.wallet.config.path
325 re.sub("(\/\w*.dat)$", "", wallet_folder)
326 file_name = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat") )
330 def open_wallet(self):
331 from electrum import SimpleConfig, Wallet, WalletSynchronizer
333 filename = self.select_wallet_file()
337 config = SimpleConfig({'wallet_path': filename})
338 if not config.wallet_file_exists:
339 self.show_message("file not found "+ filename)
342 interface = self.wallet.interface
343 verifier = self.wallet.verifier
344 self.wallet.synchronizer.stop()
349 wallet = Wallet(config)
350 wallet.interface = interface
351 wallet.verifier = verifier
352 synchronizer = WalletSynchronizer(wallet, config)
355 self.load_wallet(wallet)
358 def new_wallet(self):
359 from electrum import SimpleConfig, Wallet, WalletSynchronizer
362 wallet_folder = self.wallet.config.path
363 re.sub("(\/\w*.dat)$", "", wallet_folder)
364 filename = self.getSaveFileName("Select your wallet file", wallet_folder, "*.dat")
366 config = SimpleConfig({'wallet_path': filename})
367 assert not config.wallet_file_exists
369 wizard = installwizard.InstallWizard(config, self.wallet.interface)
370 wallet = wizard.run()
372 self.load_wallet(wallet)
376 def init_menubar(self):
379 electrum_menu = menubar.addMenu(_("&File"))
380 open_wallet_action = electrum_menu.addAction(_("Open wallet"))
381 open_wallet_action.triggered.connect(self.open_wallet)
383 new_wallet_action = electrum_menu.addAction(_("New wallet"))
384 new_wallet_action.triggered.connect(self.new_wallet)
386 preferences_name = _("Preferences")
387 if sys.platform == 'darwin':
388 preferences_name = _("Electrum preferences") # Settings / Preferences are all reserved keywords in OSX using this as work around
390 preferences_menu = electrum_menu.addAction(preferences_name)
391 preferences_menu.triggered.connect(self.settings_dialog)
392 electrum_menu.addSeparator()
394 raw_transaction_menu = electrum_menu.addMenu(_("&Load raw transaction"))
396 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
397 raw_transaction_file.triggered.connect(self.do_process_from_file)
399 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
400 raw_transaction_text.triggered.connect(self.do_process_from_text)
402 electrum_menu.addSeparator()
403 quit_item = electrum_menu.addAction(_("&Close"))
404 quit_item.triggered.connect(self.close)
406 wallet_menu = menubar.addMenu(_("&Wallet"))
407 wallet_backup = wallet_menu.addAction(_("&Create backup"))
408 wallet_backup.triggered.connect(lambda: backup_wallet(self.config.path))
410 show_menu = wallet_menu.addMenu(_("Show"))
412 #if self.wallet.seed:
413 show_seed = show_menu.addAction(_("&Seed"))
414 show_seed.triggered.connect(self.show_seed_dialog)
416 show_mpk = show_menu.addAction(_("&Master Public Key"))
417 show_mpk.triggered.connect(self.show_master_public_key)
419 wallet_menu.addSeparator()
420 new_contact = wallet_menu.addAction(_("&New contact"))
421 new_contact.triggered.connect(self.new_contact_dialog)
423 new_account = wallet_menu.addAction(_("&New account"))
424 new_account.triggered.connect(self.new_account_dialog)
426 import_menu = menubar.addMenu(_("&Import"))
427 in_labels = import_menu.addAction(_("&Labels"))
428 in_labels.triggered.connect(self.do_import_labels)
430 in_private_keys = import_menu.addAction(_("&Private keys"))
431 in_private_keys.triggered.connect(self.do_import_privkey)
433 export_menu = menubar.addMenu(_("&Export"))
434 ex_private_keys = export_menu.addAction(_("&Private keys"))
435 ex_private_keys.triggered.connect(self.do_export_privkeys)
437 ex_history = export_menu.addAction(_("&History"))
438 ex_history.triggered.connect(self.do_export_history)
440 ex_labels = export_menu.addAction(_("&Labels"))
441 ex_labels.triggered.connect(self.do_export_labels)
443 help_menu = menubar.addMenu(_("&Help"))
444 doc_open = help_menu.addAction(_("&Documentation"))
445 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
446 web_open = help_menu.addAction(_("&Official website"))
447 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
449 self.setMenuBar(menubar)
453 def notify_transactions(self):
454 print_error("Notifying GUI")
455 if len(self.wallet.interface.pending_transactions_for_notifications) > 0:
456 # Combine the transactions if there are more then three
457 tx_amount = len(self.wallet.interface.pending_transactions_for_notifications)
460 for tx in self.wallet.interface.pending_transactions_for_notifications:
461 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
465 self.notify("%s new transactions received. Total amount received in the new transactions %s %s" \
466 % (tx_amount, self.format_amount(total_amount), self.base_unit()))
468 self.wallet.interface.pending_transactions_for_notifications = []
470 for tx in self.wallet.interface.pending_transactions_for_notifications:
472 self.wallet.interface.pending_transactions_for_notifications.remove(tx)
473 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
475 self.notify("New transaction received. %s %s" % (self.format_amount(v), self.base_unit()))
477 def notify(self, message):
478 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
481 def init_plugins(self):
482 import imp, pkgutil, __builtin__
483 if __builtin__.use_local_modules:
484 fp, pathname, description = imp.find_module('plugins')
485 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
486 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
487 imp.load_module('electrum_plugins', fp, pathname, description)
488 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
490 import electrum_plugins
491 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
492 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
495 for name, p in zip(plugin_names, plugins):
497 self.plugins.append( p.Plugin(self, name) )
499 print_msg("Error:cannot initialize plugin",p)
500 traceback.print_exc(file=sys.stdout)
503 def run_hook(self, name, *args):
504 for p in self.plugins:
505 if not p.is_enabled():
514 print_error("Plugin error")
515 traceback.print_exc(file=sys.stdout)
520 def set_label(self, name, text = None):
522 old_text = self.wallet.labels.get(name)
525 self.wallet.labels[name] = text
526 self.wallet.config.set_key('labels', self.wallet.labels)
530 self.wallet.labels.pop(name)
532 self.run_hook('set_label', name, text, changed)
536 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
537 def getOpenFileName(self, title, filter = None):
538 directory = self.config.get('io_dir', os.path.expanduser('~'))
539 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
540 if fileName and directory != os.path.dirname(fileName):
541 self.config.set_key('io_dir', os.path.dirname(fileName), True)
544 def getSaveFileName(self, title, filename, filter = None):
545 directory = self.config.get('io_dir', os.path.expanduser('~'))
546 path = os.path.join( directory, filename )
547 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
548 if fileName and directory != os.path.dirname(fileName):
549 self.config.set_key('io_dir', os.path.dirname(fileName), True)
555 QMainWindow.close(self)
556 self.run_hook('close_main_window')
558 def connect_slots(self, sender):
559 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
560 self.previous_payto_e=''
562 def timer_actions(self):
563 if self.need_update.is_set():
565 self.need_update.clear()
566 self.run_hook('timer_actions')
568 def format_amount(self, x, is_diff=False, whitespaces=False):
569 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point, whitespaces)
571 def read_amount(self, x):
572 if x in['.', '']: return None
573 p = pow(10, self.decimal_point)
574 return int( p * Decimal(x) )
577 assert self.decimal_point in [5,8]
578 return "BTC" if self.decimal_point == 8 else "mBTC"
580 def update_status(self):
581 if self.wallet.interface and self.wallet.interface.is_connected:
582 if not self.wallet.up_to_date:
583 text = _("Synchronizing...")
584 icon = QIcon(":icons/status_waiting.png")
586 c, u = self.wallet.get_account_balance(self.current_account)
587 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
588 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
589 text += self.create_quote_text(Decimal(c+u)/100000000)
590 self.tray.setToolTip(text)
591 icon = QIcon(":icons/status_connected.png")
593 text = _("Not connected")
594 icon = QIcon(":icons/status_disconnected.png")
596 self.balance_label.setText(text)
597 self.status_button.setIcon( icon )
599 def update_wallet(self):
601 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
602 self.update_history_tab()
603 self.update_receive_tab()
604 self.update_contacts_tab()
605 self.update_completions()
608 def create_quote_text(self, btc_balance):
609 quote_currency = self.config.get("currency", "None")
610 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
611 if quote_balance is None:
614 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
617 def create_history_tab(self):
618 self.history_list = l = MyTreeWidget(self)
620 for i,width in enumerate(self.column_widths['history']):
621 l.setColumnWidth(i, width)
622 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
623 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
624 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
626 l.setContextMenuPolicy(Qt.CustomContextMenu)
627 l.customContextMenuRequested.connect(self.create_history_menu)
631 def create_history_menu(self, position):
632 self.history_list.selectedIndexes()
633 item = self.history_list.currentItem()
635 tx_hash = str(item.data(0, Qt.UserRole).toString())
636 if not tx_hash: return
638 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
639 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
640 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
641 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
644 def show_tx_details(self, tx):
645 dialog = QDialog(self)
647 dialog.setWindowTitle(_("Transaction Details"))
649 dialog.setLayout(vbox)
650 dialog.setMinimumSize(600,300)
653 if tx_hash in self.wallet.transactions.keys():
654 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
655 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
657 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
663 vbox.addWidget(QLabel("Transaction ID:"))
664 e = QLineEdit(tx_hash)
668 vbox.addWidget(QLabel("Date: %s"%time_str))
669 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
672 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
673 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
675 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
676 vbox.addWidget(QLabel("Transaction fee: unknown"))
678 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
680 vbox.addWidget( self.generate_transaction_information_widget(tx) )
682 ok_button = QPushButton(_("Close"))
683 ok_button.setDefault(True)
684 ok_button.clicked.connect(dialog.accept)
688 hbox.addWidget(ok_button)
692 def tx_label_clicked(self, item, column):
693 if column==2 and item.isSelected():
695 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
696 self.history_list.editItem( item, column )
697 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
700 def tx_label_changed(self, item, column):
704 tx_hash = str(item.data(0, Qt.UserRole).toString())
705 tx = self.wallet.transactions.get(tx_hash)
706 text = unicode( item.text(2) )
707 self.set_label(tx_hash, text)
709 item.setForeground(2, QBrush(QColor('black')))
711 text = self.wallet.get_default_label(tx_hash)
712 item.setText(2, text)
713 item.setForeground(2, QBrush(QColor('gray')))
717 def edit_label(self, is_recv):
718 l = self.receive_list if is_recv else self.contacts_list
719 item = l.currentItem()
720 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
721 l.editItem( item, 1 )
722 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
726 def address_label_clicked(self, item, column, l, column_addr, column_label):
727 if column == column_label and item.isSelected():
728 is_editable = item.data(0, 32).toBool()
731 addr = unicode( item.text(column_addr) )
732 label = unicode( item.text(column_label) )
733 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
734 l.editItem( item, column )
735 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
738 def address_label_changed(self, item, column, l, column_addr, column_label):
739 if column == column_label:
740 addr = unicode( item.text(column_addr) )
741 text = unicode( item.text(column_label) )
742 is_editable = item.data(0, 32).toBool()
746 changed = self.set_label(addr, text)
748 self.update_history_tab()
749 self.update_completions()
751 self.current_item_changed(item)
753 self.run_hook('item_changed', item, column)
756 def current_item_changed(self, a):
757 self.run_hook('current_item_changed', a)
761 def update_history_tab(self):
763 self.history_list.clear()
764 for item in self.wallet.get_tx_history(self.current_account):
765 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
768 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
773 time_str = 'unverified'
774 icon = QIcon(":icons/unconfirmed.png")
777 icon = QIcon(":icons/unconfirmed.png")
779 icon = QIcon(":icons/clock%d.png"%conf)
781 icon = QIcon(":icons/confirmed.png")
783 if value is not None:
784 v_str = self.format_amount(value, True, whitespaces=True)
788 balance_str = self.format_amount(balance, whitespaces=True)
791 label, is_default_label = self.wallet.get_label(tx_hash)
793 label = _('Pruned transaction outputs')
794 is_default_label = False
796 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
797 item.setFont(2, QFont(MONOSPACE_FONT))
798 item.setFont(3, QFont(MONOSPACE_FONT))
799 item.setFont(4, QFont(MONOSPACE_FONT))
801 item.setForeground(3, QBrush(QColor("#BC1E1E")))
803 item.setData(0, Qt.UserRole, tx_hash)
804 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
806 item.setForeground(2, QBrush(QColor('grey')))
808 item.setIcon(0, icon)
809 self.history_list.insertTopLevelItem(0,item)
812 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
815 def create_send_tab(self):
820 grid.setColumnMinimumWidth(3,300)
821 grid.setColumnStretch(5,1)
824 self.payto_e = QLineEdit()
825 grid.addWidget(QLabel(_('Pay to')), 1, 0)
826 grid.addWidget(self.payto_e, 1, 1, 1, 3)
828 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)
830 completer = QCompleter()
831 completer.setCaseSensitivity(False)
832 self.payto_e.setCompleter(completer)
833 completer.setModel(self.completions)
835 self.message_e = QLineEdit()
836 grid.addWidget(QLabel(_('Description')), 2, 0)
837 grid.addWidget(self.message_e, 2, 1, 1, 3)
838 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)
840 self.amount_e = AmountEdit(self.base_unit)
841 grid.addWidget(QLabel(_('Amount')), 3, 0)
842 grid.addWidget(self.amount_e, 3, 1, 1, 2)
843 grid.addWidget(HelpButton(
844 _('Amount to be sent.') + '\n\n' \
845 + _('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.') \
846 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
848 self.fee_e = AmountEdit(self.base_unit)
849 grid.addWidget(QLabel(_('Fee')), 4, 0)
850 grid.addWidget(self.fee_e, 4, 1, 1, 2)
851 grid.addWidget(HelpButton(
852 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
853 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
854 + _('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)
857 self.send_button = EnterButton(_("Send"), self.do_send)
858 grid.addWidget(self.send_button, 6, 1)
860 b = EnterButton(_("Clear"),self.do_clear)
861 grid.addWidget(b, 6, 2)
863 self.payto_sig = QLabel('')
864 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
866 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
867 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
876 def entry_changed( is_fee ):
877 self.funds_error = False
879 if self.amount_e.is_shortcut:
880 self.amount_e.is_shortcut = False
881 c, u = self.wallet.get_account_balance(self.current_account)
882 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
883 fee = self.wallet.estimated_fee(inputs)
885 self.amount_e.setText( self.format_amount(amount) )
886 self.fee_e.setText( self.format_amount( fee ) )
889 amount = self.read_amount(str(self.amount_e.text()))
890 fee = self.read_amount(str(self.fee_e.text()))
892 if not is_fee: fee = None
895 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
897 self.fee_e.setText( self.format_amount( fee ) )
900 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
904 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
905 self.funds_error = True
906 text = _( "Not enough funds" )
907 c, u = self.wallet.get_frozen_balance()
908 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
910 self.statusBar().showMessage(text)
911 self.amount_e.setPalette(palette)
912 self.fee_e.setPalette(palette)
914 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
915 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
917 self.run_hook('create_send_tab', grid)
921 def update_completions(self):
923 for addr,label in self.wallet.labels.items():
924 if addr in self.wallet.addressbook:
925 l.append( label + ' <' + addr + '>')
927 self.run_hook('update_completions', l)
928 self.completions.setStringList(l)
932 return lambda s, *args: s.do_protect(func, args)
937 label = unicode( self.message_e.text() )
938 r = unicode( self.payto_e.text() )
941 # label or alias, with address in brackets
942 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
943 to_address = m.group(2) if m else r
945 if not is_valid(to_address):
946 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
950 amount = self.read_amount(unicode( self.amount_e.text()))
952 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
955 fee = self.read_amount(unicode( self.fee_e.text()))
957 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
960 confirm_amount = self.config.get('confirm_amount', 100000000)
961 if amount >= confirm_amount:
962 if not self.question("send %s to %s?"%(self.format_amount(amount) + ' '+ self.base_unit(), to_address)):
965 self.send_tx(to_address, amount, fee, label)
969 def send_tx(self, to_address, amount, fee, label, password):
972 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
973 except BaseException, e:
974 traceback.print_exc(file=sys.stdout)
975 self.show_message(str(e))
978 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
979 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
982 self.run_hook('send_tx', tx)
985 self.set_label(tx.hash(), label)
988 h = self.wallet.send_tx(tx)
989 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
990 status, msg = self.wallet.receive_tx( h )
992 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
994 self.update_contacts_tab()
996 QMessageBox.warning(self, _('Error'), msg, _('OK'))
998 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
1000 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
1001 with open(fileName,'w') as f:
1002 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1003 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
1005 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
1010 def set_url(self, url):
1011 address, amount, label, message, signature, identity, url = util.parse_url(url)
1012 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
1014 if label and self.wallet.labels.get(address) != label:
1015 if self.question('Give label "%s" to address %s ?'%(label,address)):
1016 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
1017 self.wallet.addressbook.append(address)
1018 self.set_label(address, label)
1020 self.run_hook('set_url', url, self.show_message, self.question)
1022 self.tabs.setCurrentIndex(1)
1023 label = self.wallet.labels.get(address)
1024 m_addr = label + ' <'+ address +'>' if label else address
1025 self.payto_e.setText(m_addr)
1027 self.message_e.setText(message)
1028 self.amount_e.setText(amount)
1030 self.set_frozen(self.payto_e,True)
1031 self.set_frozen(self.amount_e,True)
1032 self.set_frozen(self.message_e,True)
1033 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
1035 self.payto_sig.setVisible(False)
1038 self.payto_sig.setVisible(False)
1039 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1041 self.set_frozen(e,False)
1042 self.update_status()
1044 def set_frozen(self,entry,frozen):
1046 entry.setReadOnly(True)
1047 entry.setFrame(False)
1048 palette = QPalette()
1049 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1050 entry.setPalette(palette)
1052 entry.setReadOnly(False)
1053 entry.setFrame(True)
1054 palette = QPalette()
1055 palette.setColor(entry.backgroundRole(), QColor('white'))
1056 entry.setPalette(palette)
1059 def toggle_freeze(self,addr):
1061 if addr in self.wallet.frozen_addresses:
1062 self.wallet.unfreeze(addr)
1064 self.wallet.freeze(addr)
1065 self.update_receive_tab()
1067 def toggle_priority(self,addr):
1069 if addr in self.wallet.prioritized_addresses:
1070 self.wallet.unprioritize(addr)
1072 self.wallet.prioritize(addr)
1073 self.update_receive_tab()
1076 def create_list_tab(self, headers):
1077 "generic tab creation method"
1078 l = MyTreeWidget(self)
1079 l.setColumnCount( len(headers) )
1080 l.setHeaderLabels( headers )
1083 vbox = QVBoxLayout()
1090 vbox.addWidget(buttons)
1092 hbox = QHBoxLayout()
1095 buttons.setLayout(hbox)
1100 def create_receive_tab(self):
1101 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1102 l.setContextMenuPolicy(Qt.CustomContextMenu)
1103 l.customContextMenuRequested.connect(self.create_receive_menu)
1104 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1105 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1106 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1107 self.receive_list = l
1108 self.receive_buttons_hbox = hbox
1113 def receive_tab_set_mode(self, i):
1114 self.save_column_widths()
1115 self.expert_mode = (i == 1)
1116 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1117 self.update_receive_tab()
1120 def save_column_widths(self):
1121 if not self.expert_mode:
1122 widths = [ self.receive_list.columnWidth(0) ]
1125 for i in range(self.receive_list.columnCount() -1):
1126 widths.append(self.receive_list.columnWidth(i))
1127 self.column_widths["receive"][self.expert_mode] = widths
1129 self.column_widths["history"] = []
1130 for i in range(self.history_list.columnCount() - 1):
1131 self.column_widths["history"].append(self.history_list.columnWidth(i))
1133 self.column_widths["contacts"] = []
1134 for i in range(self.contacts_list.columnCount() - 1):
1135 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1137 self.config.set_key("column_widths", self.column_widths, True)
1140 def create_contacts_tab(self):
1141 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1142 l.setContextMenuPolicy(Qt.CustomContextMenu)
1143 l.customContextMenuRequested.connect(self.create_contact_menu)
1144 for i,width in enumerate(self.column_widths['contacts']):
1145 l.setColumnWidth(i, width)
1147 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1148 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1149 self.contacts_list = l
1150 self.contacts_buttons_hbox = hbox
1155 def delete_imported_key(self, addr):
1156 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1157 self.wallet.delete_imported_key(addr)
1158 self.update_receive_tab()
1159 self.update_history_tab()
1162 def create_receive_menu(self, position):
1163 # fixme: this function apparently has a side effect.
1164 # if it is not called the menu pops up several times
1165 #self.receive_list.selectedIndexes()
1167 item = self.receive_list.itemAt(position)
1169 addr = unicode(item.text(0))
1170 if not is_valid(addr):
1171 item.setExpanded(not item.isExpanded())
1174 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1175 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1176 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1177 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1178 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1179 if addr in self.wallet.imported_keys:
1180 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1182 if self.expert_mode:
1183 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1184 menu.addAction(t, lambda: self.toggle_freeze(addr))
1185 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1186 menu.addAction(t, lambda: self.toggle_priority(addr))
1188 self.run_hook('receive_menu', menu)
1189 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1192 def payto(self, addr):
1194 label = self.wallet.labels.get(addr)
1195 m_addr = label + ' <' + addr + '>' if label else addr
1196 self.tabs.setCurrentIndex(1)
1197 self.payto_e.setText(m_addr)
1198 self.amount_e.setFocus()
1201 def delete_contact(self, x):
1202 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1203 self.wallet.delete_contact(x)
1204 self.set_label(x, None)
1205 self.update_history_tab()
1206 self.update_contacts_tab()
1207 self.update_completions()
1210 def create_contact_menu(self, position):
1211 item = self.contacts_list.itemAt(position)
1213 addr = unicode(item.text(0))
1214 label = unicode(item.text(1))
1215 is_editable = item.data(0,32).toBool()
1216 payto_addr = item.data(0,33).toString()
1218 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1219 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1220 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1222 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1223 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1225 self.run_hook('create_contact_menu', menu, item)
1226 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1229 def update_receive_item(self, item):
1230 item.setFont(0, QFont(MONOSPACE_FONT))
1231 address = str(item.data(0,0).toString())
1232 label = self.wallet.labels.get(address,'')
1233 item.setData(1,0,label)
1234 item.setData(0,32, True) # is editable
1236 self.run_hook('update_receive_item', address, item)
1238 c, u = self.wallet.get_addr_balance(address)
1239 balance = self.format_amount(c + u)
1240 item.setData(2,0,balance)
1242 if self.expert_mode:
1243 if address in self.wallet.frozen_addresses:
1244 item.setBackgroundColor(0, QColor('lightblue'))
1245 elif address in self.wallet.prioritized_addresses:
1246 item.setBackgroundColor(0, QColor('lightgreen'))
1249 def update_receive_tab(self):
1250 l = self.receive_list
1253 l.setColumnHidden(2, not self.expert_mode)
1254 l.setColumnHidden(3, not self.expert_mode)
1255 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1256 l.setColumnWidth(i, width)
1258 if self.current_account is None:
1259 account_items = self.wallet.accounts.items()
1260 elif self.current_account != -1:
1261 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1265 for k, account in account_items:
1266 name = self.wallet.labels.get(k, 'unnamed account')
1267 c,u = self.wallet.get_account_balance(k)
1268 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1269 l.addTopLevelItem(account_item)
1270 account_item.setExpanded(True)
1272 for is_change in ([0,1] if self.expert_mode else [0]):
1273 if self.expert_mode:
1274 name = "Receiving" if not is_change else "Change"
1275 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1276 account_item.addChild(seq_item)
1277 if not is_change: seq_item.setExpanded(True)
1279 seq_item = account_item
1283 for address in account.get_addresses(is_change):
1284 h = self.wallet.history.get(address,[])
1288 if gap > self.wallet.gap_limit:
1293 num_tx = '*' if h == ['*'] else "%d"%len(h)
1294 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1295 self.update_receive_item(item)
1297 item.setBackgroundColor(1, QColor('red'))
1298 seq_item.addChild(item)
1301 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1302 c,u = self.wallet.get_imported_balance()
1303 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1304 l.addTopLevelItem(account_item)
1305 account_item.setExpanded(True)
1306 for address in self.wallet.imported_keys.keys():
1307 item = QTreeWidgetItem( [ address, '', '', ''] )
1308 self.update_receive_item(item)
1309 account_item.addChild(item)
1312 # we use column 1 because column 0 may be hidden
1313 l.setCurrentItem(l.topLevelItem(0),1)
1316 def update_contacts_tab(self):
1317 l = self.contacts_list
1320 for address in self.wallet.addressbook:
1321 label = self.wallet.labels.get(address,'')
1322 n = self.wallet.get_num_tx(address)
1323 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1324 item.setFont(0, QFont(MONOSPACE_FONT))
1325 # 32 = label can be edited (bool)
1326 item.setData(0,32, True)
1328 item.setData(0,33, address)
1329 l.addTopLevelItem(item)
1331 self.run_hook('update_contacts_tab', l)
1332 l.setCurrentItem(l.topLevelItem(0))
1336 def create_console_tab(self):
1337 from qt_console import Console
1338 self.console = console = Console()
1342 self.console.history = self.config.get("console-history",[])
1343 self.console.history_index = len(self.console.history)
1345 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1346 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1348 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1350 def mkfunc(f, method):
1351 return lambda *args: apply( f, (method, args, self.password_dialog ))
1353 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1354 methods[m] = mkfunc(c._run, m)
1356 console.updateNamespace(methods)
1359 def change_account(self,s):
1360 if s == _("All accounts"):
1361 self.current_account = None
1363 accounts = self.wallet.get_accounts()
1364 for k, v in accounts.items():
1366 self.current_account = k
1367 self.update_history_tab()
1368 self.update_status()
1369 self.update_receive_tab()
1371 def create_status_bar(self):
1374 sb.setFixedHeight(35)
1375 qtVersion = qVersion()
1377 self.balance_label = QLabel("")
1378 sb.addWidget(self.balance_label)
1380 update_notification = UpdateLabel(self.config)
1381 if(update_notification.new_version):
1382 sb.addPermanentWidget(update_notification)
1384 self.account_selector = QComboBox()
1385 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1386 sb.addPermanentWidget(self.account_selector)
1388 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1389 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1391 self.lock_icon = QIcon()
1392 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1393 sb.addPermanentWidget( self.password_button )
1395 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1396 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1397 sb.addPermanentWidget( self.seed_button )
1398 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1399 sb.addPermanentWidget( self.status_button )
1401 self.run_hook('create_status_bar', (sb,))
1403 self.setStatusBar(sb)
1406 def update_lock_icon(self):
1407 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1408 self.password_button.setIcon( icon )
1411 def update_buttons_on_seed(self):
1412 if self.wallet.seed:
1413 self.seed_button.show()
1414 self.password_button.show()
1415 self.send_button.setText(_("Send"))
1417 self.password_button.hide()
1418 self.seed_button.hide()
1419 self.send_button.setText(_("Create unsigned transaction"))
1422 def change_password_dialog(self):
1423 from password_dialog import PasswordDialog
1424 d = PasswordDialog(self.wallet, self)
1426 self.update_lock_icon()
1431 self.config.set_key('gui', 'lite', True)
1434 self.lite.mini.show()
1436 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1437 self.lite.main(None)
1440 def new_contact_dialog(self):
1441 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1442 address = unicode(text)
1444 if is_valid(address):
1445 self.wallet.add_contact(address)
1446 self.update_contacts_tab()
1447 self.update_history_tab()
1448 self.update_completions()
1450 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1453 def new_account_dialog(self):
1455 dialog = QDialog(self)
1457 dialog.setWindowTitle(_("New Account"))
1459 addr = self.wallet.new_account_address()
1460 vbox = QVBoxLayout()
1461 vbox.addWidget(QLabel("To add another account, please send bitcoins to the following address:"))
1466 ok_button = QPushButton(_("OK"))
1467 ok_button.setDefault(True)
1468 ok_button.clicked.connect(dialog.accept)
1470 hbox = QHBoxLayout()
1472 hbox.addWidget(ok_button)
1473 vbox.addLayout(hbox)
1475 dialog.setLayout(vbox)
1480 def show_master_public_key(self):
1481 dialog = QDialog(self)
1483 dialog.setWindowTitle(_("Master Public Key"))
1485 main_text = QTextEdit()
1486 main_text.setText(self.wallet.get_master_public_key())
1487 main_text.setReadOnly(True)
1488 main_text.setMaximumHeight(170)
1489 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1491 ok_button = QPushButton(_("OK"))
1492 ok_button.setDefault(True)
1493 ok_button.clicked.connect(dialog.accept)
1495 main_layout = QGridLayout()
1496 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1498 main_layout.addWidget(main_text, 1, 0)
1499 main_layout.addWidget(qrw, 1, 1 )
1501 vbox = QVBoxLayout()
1502 vbox.addLayout(main_layout)
1503 hbox = QHBoxLayout()
1505 hbox.addWidget(ok_button)
1506 vbox.addLayout(hbox)
1508 dialog.setLayout(vbox)
1513 def show_seed_dialog(self, password):
1514 if not self.wallet.seed:
1515 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1518 seed = self.wallet.decode_seed(password)
1520 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1523 from seed_dialog import SeedDialog
1524 d = SeedDialog(self)
1525 d.show_seed(seed, self.wallet.imported_keys)
1529 def show_qrcode(self, data, title = "QR code"):
1533 d.setWindowTitle(title)
1534 d.setMinimumSize(270, 300)
1535 vbox = QVBoxLayout()
1536 qrw = QRCodeWidget(data)
1537 vbox.addWidget(qrw, 1)
1538 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1539 hbox = QHBoxLayout()
1543 filename = "qrcode.bmp"
1544 bmp.save_qrcode(qrw.qr, filename)
1545 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1547 b = QPushButton(_("Save"))
1549 b.clicked.connect(print_qr)
1551 b = QPushButton(_("Close"))
1553 b.clicked.connect(d.accept)
1556 vbox.addLayout(hbox)
1561 def do_protect(self, func, args):
1562 if self.wallet.use_encryption:
1563 password = self.password_dialog()
1569 if args != (False,):
1570 args = (self,) + args + (password,)
1572 args = (self,password)
1577 def show_private_key(self, address, password):
1578 if not address: return
1580 pk = self.wallet.get_private_key(address, password)
1581 except BaseException, e:
1582 self.show_message(str(e))
1584 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1588 def do_sign(self, address, message, signature, password):
1590 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1591 signature.setText(sig)
1592 except BaseException, e:
1593 self.show_message(str(e))
1595 def sign_message(self, address):
1596 if not address: return
1599 d.setWindowTitle(_('Sign Message'))
1600 d.setMinimumSize(410, 290)
1602 tab_widget = QTabWidget()
1604 layout = QGridLayout(tab)
1606 sign_address = QLineEdit()
1608 sign_address.setText(address)
1609 layout.addWidget(QLabel(_('Address')), 1, 0)
1610 layout.addWidget(sign_address, 1, 1)
1612 sign_message = QTextEdit()
1613 layout.addWidget(QLabel(_('Message')), 2, 0)
1614 layout.addWidget(sign_message, 2, 1)
1615 layout.setRowStretch(2,3)
1617 sign_signature = QTextEdit()
1618 layout.addWidget(QLabel(_('Signature')), 3, 0)
1619 layout.addWidget(sign_signature, 3, 1)
1620 layout.setRowStretch(3,1)
1623 hbox = QHBoxLayout()
1624 b = QPushButton(_("Sign"))
1626 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1627 b = QPushButton(_("Close"))
1628 b.clicked.connect(d.accept)
1630 layout.addLayout(hbox, 4, 1)
1631 tab_widget.addTab(tab, _("Sign"))
1635 layout = QGridLayout(tab)
1637 verify_address = QLineEdit()
1638 layout.addWidget(QLabel(_('Address')), 1, 0)
1639 layout.addWidget(verify_address, 1, 1)
1641 verify_message = QTextEdit()
1642 layout.addWidget(QLabel(_('Message')), 2, 0)
1643 layout.addWidget(verify_message, 2, 1)
1644 layout.setRowStretch(2,3)
1646 verify_signature = QTextEdit()
1647 layout.addWidget(QLabel(_('Signature')), 3, 0)
1648 layout.addWidget(verify_signature, 3, 1)
1649 layout.setRowStretch(3,1)
1652 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1653 self.show_message(_("Signature verified"))
1655 self.show_message(_("Error: wrong signature"))
1657 hbox = QHBoxLayout()
1658 b = QPushButton(_("Verify"))
1659 b.clicked.connect(do_verify)
1661 b = QPushButton(_("Close"))
1662 b.clicked.connect(d.accept)
1664 layout.addLayout(hbox, 4, 1)
1665 tab_widget.addTab(tab, _("Verify"))
1667 vbox = QVBoxLayout()
1668 vbox.addWidget(tab_widget)
1675 def question(self, msg):
1676 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1678 def show_message(self, msg):
1679 QMessageBox.information(self, _('Message'), msg, _('OK'))
1681 def password_dialog(self ):
1688 vbox = QVBoxLayout()
1689 msg = _('Please enter your password')
1690 vbox.addWidget(QLabel(msg))
1692 grid = QGridLayout()
1694 grid.addWidget(QLabel(_('Password')), 1, 0)
1695 grid.addWidget(pw, 1, 1)
1696 vbox.addLayout(grid)
1698 vbox.addLayout(ok_cancel_buttons(d))
1701 self.run_hook('password_dialog', pw, grid, 1)
1702 if not d.exec_(): return
1703 return unicode(pw.text())
1710 def generate_transaction_information_widget(self, tx):
1711 tabs = QTabWidget(self)
1714 grid_ui = QGridLayout(tab1)
1715 grid_ui.setColumnStretch(0,1)
1716 tabs.addTab(tab1, _('Outputs') )
1718 tree_widget = MyTreeWidget(self)
1719 tree_widget.setColumnCount(2)
1720 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1721 tree_widget.setColumnWidth(0, 300)
1722 tree_widget.setColumnWidth(1, 50)
1724 for address, value in tx.outputs:
1725 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1726 tree_widget.addTopLevelItem(item)
1728 tree_widget.setMaximumHeight(100)
1730 grid_ui.addWidget(tree_widget)
1733 grid_ui = QGridLayout(tab2)
1734 grid_ui.setColumnStretch(0,1)
1735 tabs.addTab(tab2, _('Inputs') )
1737 tree_widget = MyTreeWidget(self)
1738 tree_widget.setColumnCount(2)
1739 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1741 for input_line in tx.inputs:
1742 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1743 tree_widget.addTopLevelItem(item)
1745 tree_widget.setMaximumHeight(100)
1747 grid_ui.addWidget(tree_widget)
1751 def tx_dict_from_text(self, txt):
1753 tx_dict = json.loads(str(txt))
1754 assert "hex" in tx_dict.keys()
1755 assert "complete" in tx_dict.keys()
1756 if not tx_dict["complete"]:
1757 assert "input_info" in tx_dict.keys()
1759 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1764 def read_tx_from_file(self):
1765 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1769 with open(fileName, "r") as f:
1770 file_content = f.read()
1771 except (ValueError, IOError, os.error), reason:
1772 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1774 return self.tx_dict_from_text(file_content)
1778 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1780 self.wallet.signrawtransaction(tx, input_info, [], password)
1782 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1784 with open(fileName, "w+") as f:
1785 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1786 self.show_message(_("Transaction saved successfully"))
1789 except BaseException, e:
1790 self.show_message(str(e))
1793 def send_raw_transaction(self, raw_tx, dialog = ""):
1794 result, result_message = self.wallet.sendtx( raw_tx )
1796 self.show_message("Transaction successfully sent: %s" % (result_message))
1800 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1802 def do_process_from_text(self):
1803 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1806 tx_dict = self.tx_dict_from_text(text)
1808 self.create_process_transaction_window(tx_dict)
1810 def do_process_from_file(self):
1811 tx_dict = self.read_tx_from_file()
1813 self.create_process_transaction_window(tx_dict)
1815 def create_process_transaction_window(self, tx_dict):
1816 tx = Transaction(tx_dict["hex"])
1818 dialog = QDialog(self)
1819 dialog.setMinimumWidth(500)
1820 dialog.setWindowTitle(_('Process raw transaction'))
1826 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1827 l.addWidget(QLabel(_("Actions")), 4,0)
1829 if tx_dict["complete"] == False:
1830 l.addWidget(QLabel(_("Unsigned")), 3,1)
1831 if self.wallet.seed :
1832 b = QPushButton("Sign transaction")
1833 input_info = json.loads(tx_dict["input_info"])
1834 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1835 l.addWidget(b, 4, 1)
1837 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1839 l.addWidget(QLabel(_("Signed")), 3,1)
1840 b = QPushButton("Broadcast transaction")
1841 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1844 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1845 cancelButton = QPushButton(_("Cancel"))
1846 cancelButton.clicked.connect(lambda: dialog.done(0))
1847 l.addWidget(cancelButton, 4,2)
1853 def do_export_privkeys(self, password):
1854 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.")))
1857 select_export = _('Select file to export your private keys to')
1858 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1860 with open(fileName, "w+") as csvfile:
1861 transaction = csv.writer(csvfile)
1862 transaction.writerow(["address", "private_key"])
1865 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1866 transaction.writerow(["%34s"%addr,pk])
1868 self.show_message(_("Private keys exported."))
1870 except (IOError, os.error), reason:
1871 export_error_label = _("Electrum was unable to produce a private key-export.")
1872 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1874 except BaseException, e:
1875 self.show_message(str(e))
1879 def do_import_labels(self):
1880 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1881 if not labelsFile: return
1883 f = open(labelsFile, 'r')
1886 for key, value in json.loads(data).items():
1887 self.wallet.labels[key] = value
1889 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1890 except (IOError, os.error), reason:
1891 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1894 def do_export_labels(self):
1895 labels = self.wallet.labels
1897 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1899 with open(fileName, 'w+') as f:
1900 json.dump(labels, f)
1901 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1902 except (IOError, os.error), reason:
1903 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1906 def do_export_history(self):
1907 from gui_lite import csv_transaction
1908 csv_transaction(self.wallet)
1912 def do_import_privkey(self, password):
1913 if not self.wallet.imported_keys:
1914 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1915 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1916 + _('Are you sure you understand what you are doing?'), 3, 4)
1919 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1922 text = str(text).split()
1927 addr = self.wallet.import_key(key, password)
1928 except BaseException as e:
1934 addrlist.append(addr)
1936 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1938 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1939 self.update_receive_tab()
1940 self.update_history_tab()
1943 def settings_dialog(self):
1945 d.setWindowTitle(_('Electrum Settings'))
1947 vbox = QVBoxLayout()
1949 tabs = QTabWidget(self)
1950 self.settings_tab = tabs
1951 vbox.addWidget(tabs)
1954 grid_ui = QGridLayout(tab1)
1955 grid_ui.setColumnStretch(0,1)
1956 tabs.addTab(tab1, _('Display') )
1958 nz_label = QLabel(_('Display zeros'))
1959 grid_ui.addWidget(nz_label, 0, 0)
1960 nz_e = AmountEdit(None,True)
1961 nz_e.setText("%d"% self.wallet.num_zeros)
1962 grid_ui.addWidget(nz_e, 0, 1)
1963 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1964 grid_ui.addWidget(HelpButton(msg), 0, 2)
1965 if not self.config.is_modifiable('num_zeros'):
1966 for w in [nz_e, nz_label]: w.setEnabled(False)
1968 lang_label=QLabel(_('Language') + ':')
1969 grid_ui.addWidget(lang_label, 1, 0)
1970 lang_combo = QComboBox()
1971 from i18n import languages
1972 lang_combo.addItems(languages.values())
1974 index = languages.keys().index(self.config.get("language",''))
1977 lang_combo.setCurrentIndex(index)
1978 grid_ui.addWidget(lang_combo, 1, 1)
1979 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1980 if not self.config.is_modifiable('language'):
1981 for w in [lang_combo, lang_label]: w.setEnabled(False)
1983 currencies = self.exchanger.get_currencies()
1984 currencies.insert(0, "None")
1986 cur_label=QLabel(_('Currency') + ':')
1987 grid_ui.addWidget(cur_label , 2, 0)
1988 cur_combo = QComboBox()
1989 cur_combo.addItems(currencies)
1991 index = currencies.index(self.config.get('currency', "None"))
1994 cur_combo.setCurrentIndex(index)
1995 grid_ui.addWidget(cur_combo, 2, 1)
1996 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1998 expert_cb = QCheckBox(_('Expert mode'))
1999 expert_cb.setChecked(self.expert_mode)
2000 grid_ui.addWidget(expert_cb, 3, 0)
2001 hh = _('In expert mode, your client will:') + '\n' \
2002 + _(' - Show change addresses in the Receive tab') + '\n' \
2003 + _(' - Display the balance of each address') + '\n' \
2004 + _(' - Add freeze/prioritize actions to addresses.')
2005 grid_ui.addWidget(HelpButton(hh), 3, 2)
2006 grid_ui.setRowStretch(4,1)
2010 grid_wallet = QGridLayout(tab2)
2011 grid_wallet.setColumnStretch(0,1)
2012 tabs.addTab(tab2, _('Wallet') )
2014 fee_label = QLabel(_('Transaction fee'))
2015 grid_wallet.addWidget(fee_label, 0, 0)
2016 fee_e = AmountEdit(self.base_unit)
2017 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2018 grid_wallet.addWidget(fee_e, 0, 2)
2019 msg = _('Fee per kilobyte of transaction.') + ' ' \
2020 + _('Recommended value') + ': ' + self.format_amount(50000)
2021 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2022 if not self.config.is_modifiable('fee_per_kb'):
2023 for w in [fee_e, fee_label]: w.setEnabled(False)
2025 usechange_cb = QCheckBox(_('Use change addresses'))
2026 usechange_cb.setChecked(self.wallet.use_change)
2027 grid_wallet.addWidget(usechange_cb, 1, 0)
2028 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2029 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2031 gap_label = QLabel(_('Gap limit'))
2032 grid_wallet.addWidget(gap_label, 2, 0)
2033 gap_e = AmountEdit(None,True)
2034 gap_e.setText("%d"% self.wallet.gap_limit)
2035 grid_wallet.addWidget(gap_e, 2, 2)
2036 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2037 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2038 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2039 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2040 + _('Warning') + ': ' \
2041 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2042 + _('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'
2043 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2044 if not self.config.is_modifiable('gap_limit'):
2045 for w in [gap_e, gap_label]: w.setEnabled(False)
2047 units = ['BTC', 'mBTC']
2048 unit_label = QLabel(_('Base unit'))
2049 grid_wallet.addWidget(unit_label, 3, 0)
2050 unit_combo = QComboBox()
2051 unit_combo.addItems(units)
2052 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2053 grid_wallet.addWidget(unit_combo, 3, 2)
2054 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2055 + '\n1BTC=1000mBTC.\n' \
2056 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2057 grid_wallet.setRowStretch(4,1)
2061 tab5 = QScrollArea()
2062 tab5.setEnabled(True)
2063 tab5.setWidgetResizable(True)
2065 grid_plugins = QGridLayout()
2066 grid_plugins.setColumnStretch(0,1)
2069 w.setLayout(grid_plugins)
2072 w.setMinimumHeight(len(self.plugins)*35)
2074 tabs.addTab(tab5, _('Plugins') )
2075 def mk_toggle(cb, p):
2076 return lambda: cb.setChecked(p.toggle())
2077 for i, p in enumerate(self.plugins):
2079 cb = QCheckBox(p.fullname())
2080 cb.setDisabled(not p.is_available())
2081 cb.setChecked(p.is_enabled())
2082 cb.clicked.connect(mk_toggle(cb,p))
2083 grid_plugins.addWidget(cb, i, 0)
2084 if p.requires_settings():
2085 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2086 grid_plugins.addWidget(HelpButton(p.description()), i, 2)
2088 print_msg("Error: cannot display plugin", p)
2089 traceback.print_exc(file=sys.stdout)
2090 grid_plugins.setRowStretch(i+1,1)
2092 self.run_hook('create_settings_tab', tabs)
2094 vbox.addLayout(ok_cancel_buttons(d))
2098 if not d.exec_(): return
2100 fee = unicode(fee_e.text())
2102 fee = self.read_amount(fee)
2104 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2107 self.wallet.set_fee(fee)
2109 nz = unicode(nz_e.text())
2114 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2117 if self.wallet.num_zeros != nz:
2118 self.wallet.num_zeros = nz
2119 self.config.set_key('num_zeros', nz, True)
2120 self.update_history_tab()
2121 self.update_receive_tab()
2123 usechange_result = usechange_cb.isChecked()
2124 if self.wallet.use_change != usechange_result:
2125 self.wallet.use_change = usechange_result
2126 self.config.set_key('use_change', self.wallet.use_change, True)
2128 unit_result = units[unit_combo.currentIndex()]
2129 if self.base_unit() != unit_result:
2130 self.decimal_point = 8 if unit_result == 'BTC' else 5
2131 self.config.set_key('decimal_point', self.decimal_point, True)
2132 self.update_history_tab()
2133 self.update_status()
2136 n = int(gap_e.text())
2138 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2141 if self.wallet.gap_limit != n:
2142 r = self.wallet.change_gap_limit(n)
2144 self.update_receive_tab()
2145 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2147 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2149 need_restart = False
2151 lang_request = languages.keys()[lang_combo.currentIndex()]
2152 if lang_request != self.config.get('language'):
2153 self.config.set_key("language", lang_request, True)
2156 cur_request = str(currencies[cur_combo.currentIndex()])
2157 if cur_request != self.config.get('currency', "None"):
2158 self.config.set_key('currency', cur_request, True)
2159 self.update_wallet()
2161 self.run_hook('close_settings_dialog')
2164 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2166 self.receive_tab_set_mode(expert_cb.isChecked())
2168 def run_network_dialog(self):
2169 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2171 def closeEvent(self, event):
2173 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2174 self.save_column_widths()
2175 self.config.set_key("console-history",self.console.history[-50:])
2178 class OpenFileEventFilter(QObject):
2179 def __init__(self, windows):
2180 self.windows = windows
2181 super(OpenFileEventFilter, self).__init__()
2183 def eventFilter(self, obj, event):
2184 if event.type() == QtCore.QEvent.FileOpen:
2185 if len(self.windows) >= 1:
2186 self.windows[0].set_url(event.url().toString())
2195 def __init__(self, config, interface, app=None):
2196 self.interface = interface
2197 self.config = config
2199 self.efilter = OpenFileEventFilter(self.windows)
2201 self.app = QApplication(sys.argv)
2202 self.app.installEventFilter(self.efilter)
2205 def main(self, url):
2207 found = self.config.wallet_file_exists
2209 import installwizard
2210 wizard = installwizard.InstallWizard(self.config, self.interface)
2211 wallet = wizard.run()
2215 wallet = Wallet(self.config)
2217 wallet.interface = self.interface
2219 verifier = WalletVerifier(self.interface, self.config)
2221 wallet.set_verifier(verifier)
2222 synchronizer = WalletSynchronizer(wallet, self.config)
2223 synchronizer.start()
2227 w = ElectrumWindow(self.config)
2228 w.load_wallet(wallet)
2230 self.windows.append(w)
2231 if url: w.set_url(url)