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 file_menu = menubar.addMenu(_("&File"))
380 open_wallet_action = file_menu.addAction(_("&Open"))
381 open_wallet_action.triggered.connect(self.open_wallet)
383 new_wallet_action = file_menu.addAction(_("&Create/Restore"))
384 new_wallet_action.triggered.connect(self.new_wallet)
386 wallet_backup = file_menu.addAction(_("&Copy"))
387 wallet_backup.triggered.connect(lambda: backup_wallet(self.config.path))
389 quit_item = file_menu.addAction(_("&Close"))
390 quit_item.triggered.connect(self.close)
392 wallet_menu = menubar.addMenu(_("&Wallet"))
394 # Settings / Preferences are all reserved keywords in OSX using this as work around
395 preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
396 preferences_menu = wallet_menu.addAction(preferences_name)
397 preferences_menu.triggered.connect(self.settings_dialog)
399 wallet_menu.addSeparator()
401 raw_transaction_menu = wallet_menu.addMenu(_("&Load raw transaction"))
403 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
404 raw_transaction_file.triggered.connect(self.do_process_from_file)
406 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
407 raw_transaction_text.triggered.connect(self.do_process_from_text)
409 wallet_menu.addSeparator()
411 show_menu = wallet_menu.addMenu(_("Show"))
413 #if self.wallet.seed:
414 show_seed = show_menu.addAction(_("&Seed"))
415 show_seed.triggered.connect(self.show_seed_dialog)
417 show_mpk = show_menu.addAction(_("&Master Public Key"))
418 show_mpk.triggered.connect(self.show_master_public_key)
420 wallet_menu.addSeparator()
421 new_contact = wallet_menu.addAction(_("&New contact"))
422 new_contact.triggered.connect(self.new_contact_dialog)
424 new_account = wallet_menu.addAction(_("&New account"))
425 new_account.triggered.connect(self.new_account_dialog)
427 import_menu = menubar.addMenu(_("&Import"))
428 in_labels = import_menu.addAction(_("&Labels"))
429 in_labels.triggered.connect(self.do_import_labels)
431 in_private_keys = import_menu.addAction(_("&Private keys"))
432 in_private_keys.triggered.connect(self.do_import_privkey)
434 export_menu = menubar.addMenu(_("&Export"))
435 ex_private_keys = export_menu.addAction(_("&Private keys"))
436 ex_private_keys.triggered.connect(self.do_export_privkeys)
438 ex_history = export_menu.addAction(_("&History"))
439 ex_history.triggered.connect(self.do_export_history)
441 ex_labels = export_menu.addAction(_("&Labels"))
442 ex_labels.triggered.connect(self.do_export_labels)
444 help_menu = menubar.addMenu(_("&Help"))
445 doc_open = help_menu.addAction(_("&Documentation"))
446 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
447 web_open = help_menu.addAction(_("&Official website"))
448 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
450 self.setMenuBar(menubar)
454 def notify_transactions(self):
455 print_error("Notifying GUI")
456 if len(self.wallet.interface.pending_transactions_for_notifications) > 0:
457 # Combine the transactions if there are more then three
458 tx_amount = len(self.wallet.interface.pending_transactions_for_notifications)
461 for tx in self.wallet.interface.pending_transactions_for_notifications:
462 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
466 self.notify("%s new transactions received. Total amount received in the new transactions %s %s" \
467 % (tx_amount, self.format_amount(total_amount), self.base_unit()))
469 self.wallet.interface.pending_transactions_for_notifications = []
471 for tx in self.wallet.interface.pending_transactions_for_notifications:
473 self.wallet.interface.pending_transactions_for_notifications.remove(tx)
474 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
476 self.notify("New transaction received. %s %s" % (self.format_amount(v), self.base_unit()))
478 def notify(self, message):
479 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
482 def init_plugins(self):
483 import imp, pkgutil, __builtin__
484 if __builtin__.use_local_modules:
485 fp, pathname, description = imp.find_module('plugins')
486 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
487 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
488 imp.load_module('electrum_plugins', fp, pathname, description)
489 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
491 import electrum_plugins
492 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
493 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
496 for name, p in zip(plugin_names, plugins):
498 self.plugins.append( p.Plugin(self, name) )
500 print_msg("Error:cannot initialize plugin",p)
501 traceback.print_exc(file=sys.stdout)
504 def run_hook(self, name, *args):
505 for p in self.plugins:
506 if not p.is_enabled():
515 print_error("Plugin error")
516 traceback.print_exc(file=sys.stdout)
521 def set_label(self, name, text = None):
523 old_text = self.wallet.labels.get(name)
526 self.wallet.labels[name] = text
527 self.wallet.config.set_key('labels', self.wallet.labels)
531 self.wallet.labels.pop(name)
533 self.run_hook('set_label', name, text, changed)
537 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
538 def getOpenFileName(self, title, filter = None):
539 directory = self.config.get('io_dir', os.path.expanduser('~'))
540 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
541 if fileName and directory != os.path.dirname(fileName):
542 self.config.set_key('io_dir', os.path.dirname(fileName), True)
545 def getSaveFileName(self, title, filename, filter = None):
546 directory = self.config.get('io_dir', os.path.expanduser('~'))
547 path = os.path.join( directory, filename )
548 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
549 if fileName and directory != os.path.dirname(fileName):
550 self.config.set_key('io_dir', os.path.dirname(fileName), True)
556 QMainWindow.close(self)
557 self.run_hook('close_main_window')
559 def connect_slots(self, sender):
560 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
561 self.previous_payto_e=''
563 def timer_actions(self):
564 if self.need_update.is_set():
566 self.need_update.clear()
567 self.run_hook('timer_actions')
569 def format_amount(self, x, is_diff=False, whitespaces=False):
570 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point, whitespaces)
572 def read_amount(self, x):
573 if x in['.', '']: return None
574 p = pow(10, self.decimal_point)
575 return int( p * Decimal(x) )
578 assert self.decimal_point in [5,8]
579 return "BTC" if self.decimal_point == 8 else "mBTC"
581 def update_status(self):
582 if self.wallet.interface and self.wallet.interface.is_connected:
583 if not self.wallet.up_to_date:
584 text = _("Synchronizing...")
585 icon = QIcon(":icons/status_waiting.png")
587 c, u = self.wallet.get_account_balance(self.current_account)
588 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
589 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
590 text += self.create_quote_text(Decimal(c+u)/100000000)
591 self.tray.setToolTip(text)
592 icon = QIcon(":icons/status_connected.png")
594 text = _("Not connected")
595 icon = QIcon(":icons/status_disconnected.png")
597 self.balance_label.setText(text)
598 self.status_button.setIcon( icon )
600 def update_wallet(self):
602 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
603 self.update_history_tab()
604 self.update_receive_tab()
605 self.update_contacts_tab()
606 self.update_completions()
609 def create_quote_text(self, btc_balance):
610 quote_currency = self.config.get("currency", "None")
611 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
612 if quote_balance is None:
615 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
618 def create_history_tab(self):
619 self.history_list = l = MyTreeWidget(self)
621 for i,width in enumerate(self.column_widths['history']):
622 l.setColumnWidth(i, width)
623 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
624 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
625 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
627 l.setContextMenuPolicy(Qt.CustomContextMenu)
628 l.customContextMenuRequested.connect(self.create_history_menu)
632 def create_history_menu(self, position):
633 self.history_list.selectedIndexes()
634 item = self.history_list.currentItem()
636 tx_hash = str(item.data(0, Qt.UserRole).toString())
637 if not tx_hash: return
639 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
640 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
641 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
642 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
645 def show_tx_details(self, tx):
646 dialog = QDialog(self)
648 dialog.setWindowTitle(_("Transaction Details"))
650 dialog.setLayout(vbox)
651 dialog.setMinimumSize(600,300)
654 if tx_hash in self.wallet.transactions.keys():
655 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
656 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
658 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
664 vbox.addWidget(QLabel("Transaction ID:"))
665 e = QLineEdit(tx_hash)
669 vbox.addWidget(QLabel("Date: %s"%time_str))
670 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
673 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
674 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
676 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
677 vbox.addWidget(QLabel("Transaction fee: unknown"))
679 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
681 vbox.addWidget( self.generate_transaction_information_widget(tx) )
683 ok_button = QPushButton(_("Close"))
684 ok_button.setDefault(True)
685 ok_button.clicked.connect(dialog.accept)
689 hbox.addWidget(ok_button)
693 def tx_label_clicked(self, item, column):
694 if column==2 and item.isSelected():
696 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
697 self.history_list.editItem( item, column )
698 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
701 def tx_label_changed(self, item, column):
705 tx_hash = str(item.data(0, Qt.UserRole).toString())
706 tx = self.wallet.transactions.get(tx_hash)
707 text = unicode( item.text(2) )
708 self.set_label(tx_hash, text)
710 item.setForeground(2, QBrush(QColor('black')))
712 text = self.wallet.get_default_label(tx_hash)
713 item.setText(2, text)
714 item.setForeground(2, QBrush(QColor('gray')))
718 def edit_label(self, is_recv):
719 l = self.receive_list if is_recv else self.contacts_list
720 item = l.currentItem()
721 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
722 l.editItem( item, 1 )
723 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
727 def address_label_clicked(self, item, column, l, column_addr, column_label):
728 if column == column_label and item.isSelected():
729 is_editable = item.data(0, 32).toBool()
732 addr = unicode( item.text(column_addr) )
733 label = unicode( item.text(column_label) )
734 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
735 l.editItem( item, column )
736 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
739 def address_label_changed(self, item, column, l, column_addr, column_label):
740 if column == column_label:
741 addr = unicode( item.text(column_addr) )
742 text = unicode( item.text(column_label) )
743 is_editable = item.data(0, 32).toBool()
747 changed = self.set_label(addr, text)
749 self.update_history_tab()
750 self.update_completions()
752 self.current_item_changed(item)
754 self.run_hook('item_changed', item, column)
757 def current_item_changed(self, a):
758 self.run_hook('current_item_changed', a)
762 def update_history_tab(self):
764 self.history_list.clear()
765 for item in self.wallet.get_tx_history(self.current_account):
766 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
769 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
774 time_str = 'unverified'
775 icon = QIcon(":icons/unconfirmed.png")
778 icon = QIcon(":icons/unconfirmed.png")
780 icon = QIcon(":icons/clock%d.png"%conf)
782 icon = QIcon(":icons/confirmed.png")
784 if value is not None:
785 v_str = self.format_amount(value, True, whitespaces=True)
789 balance_str = self.format_amount(balance, whitespaces=True)
792 label, is_default_label = self.wallet.get_label(tx_hash)
794 label = _('Pruned transaction outputs')
795 is_default_label = False
797 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
798 item.setFont(2, QFont(MONOSPACE_FONT))
799 item.setFont(3, QFont(MONOSPACE_FONT))
800 item.setFont(4, QFont(MONOSPACE_FONT))
802 item.setForeground(3, QBrush(QColor("#BC1E1E")))
804 item.setData(0, Qt.UserRole, tx_hash)
805 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
807 item.setForeground(2, QBrush(QColor('grey')))
809 item.setIcon(0, icon)
810 self.history_list.insertTopLevelItem(0,item)
813 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
816 def create_send_tab(self):
821 grid.setColumnMinimumWidth(3,300)
822 grid.setColumnStretch(5,1)
825 self.payto_e = QLineEdit()
826 grid.addWidget(QLabel(_('Pay to')), 1, 0)
827 grid.addWidget(self.payto_e, 1, 1, 1, 3)
829 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)
831 completer = QCompleter()
832 completer.setCaseSensitivity(False)
833 self.payto_e.setCompleter(completer)
834 completer.setModel(self.completions)
836 self.message_e = QLineEdit()
837 grid.addWidget(QLabel(_('Description')), 2, 0)
838 grid.addWidget(self.message_e, 2, 1, 1, 3)
839 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)
841 self.amount_e = AmountEdit(self.base_unit)
842 grid.addWidget(QLabel(_('Amount')), 3, 0)
843 grid.addWidget(self.amount_e, 3, 1, 1, 2)
844 grid.addWidget(HelpButton(
845 _('Amount to be sent.') + '\n\n' \
846 + _('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.') \
847 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
849 self.fee_e = AmountEdit(self.base_unit)
850 grid.addWidget(QLabel(_('Fee')), 4, 0)
851 grid.addWidget(self.fee_e, 4, 1, 1, 2)
852 grid.addWidget(HelpButton(
853 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
854 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
855 + _('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)
858 self.send_button = EnterButton(_("Send"), self.do_send)
859 grid.addWidget(self.send_button, 6, 1)
861 b = EnterButton(_("Clear"),self.do_clear)
862 grid.addWidget(b, 6, 2)
864 self.payto_sig = QLabel('')
865 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
867 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
868 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
877 def entry_changed( is_fee ):
878 self.funds_error = False
880 if self.amount_e.is_shortcut:
881 self.amount_e.is_shortcut = False
882 c, u = self.wallet.get_account_balance(self.current_account)
883 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
884 fee = self.wallet.estimated_fee(inputs)
886 self.amount_e.setText( self.format_amount(amount) )
887 self.fee_e.setText( self.format_amount( fee ) )
890 amount = self.read_amount(str(self.amount_e.text()))
891 fee = self.read_amount(str(self.fee_e.text()))
893 if not is_fee: fee = None
896 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
898 self.fee_e.setText( self.format_amount( fee ) )
901 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
905 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
906 self.funds_error = True
907 text = _( "Not enough funds" )
908 c, u = self.wallet.get_frozen_balance()
909 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
911 self.statusBar().showMessage(text)
912 self.amount_e.setPalette(palette)
913 self.fee_e.setPalette(palette)
915 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
916 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
918 self.run_hook('create_send_tab', grid)
922 def update_completions(self):
924 for addr,label in self.wallet.labels.items():
925 if addr in self.wallet.addressbook:
926 l.append( label + ' <' + addr + '>')
928 self.run_hook('update_completions', l)
929 self.completions.setStringList(l)
933 return lambda s, *args: s.do_protect(func, args)
938 label = unicode( self.message_e.text() )
939 r = unicode( self.payto_e.text() )
942 # label or alias, with address in brackets
943 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
944 to_address = m.group(2) if m else r
946 if not is_valid(to_address):
947 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
951 amount = self.read_amount(unicode( self.amount_e.text()))
953 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
956 fee = self.read_amount(unicode( self.fee_e.text()))
958 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
961 confirm_amount = self.config.get('confirm_amount', 100000000)
962 if amount >= confirm_amount:
963 if not self.question("send %s to %s?"%(self.format_amount(amount) + ' '+ self.base_unit(), to_address)):
966 self.send_tx(to_address, amount, fee, label)
970 def send_tx(self, to_address, amount, fee, label, password):
973 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
974 except BaseException, e:
975 traceback.print_exc(file=sys.stdout)
976 self.show_message(str(e))
979 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
980 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
983 self.run_hook('send_tx', tx)
986 self.set_label(tx.hash(), label)
989 h = self.wallet.send_tx(tx)
990 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
991 status, msg = self.wallet.receive_tx( h )
993 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
995 self.update_contacts_tab()
997 QMessageBox.warning(self, _('Error'), msg, _('OK'))
999 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
1001 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
1002 with open(fileName,'w') as f:
1003 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1004 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
1006 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
1011 def set_url(self, url):
1012 address, amount, label, message, signature, identity, url = util.parse_url(url)
1013 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
1015 if label and self.wallet.labels.get(address) != label:
1016 if self.question('Give label "%s" to address %s ?'%(label,address)):
1017 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
1018 self.wallet.addressbook.append(address)
1019 self.set_label(address, label)
1021 self.run_hook('set_url', url, self.show_message, self.question)
1023 self.tabs.setCurrentIndex(1)
1024 label = self.wallet.labels.get(address)
1025 m_addr = label + ' <'+ address +'>' if label else address
1026 self.payto_e.setText(m_addr)
1028 self.message_e.setText(message)
1029 self.amount_e.setText(amount)
1031 self.set_frozen(self.payto_e,True)
1032 self.set_frozen(self.amount_e,True)
1033 self.set_frozen(self.message_e,True)
1034 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
1036 self.payto_sig.setVisible(False)
1039 self.payto_sig.setVisible(False)
1040 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1042 self.set_frozen(e,False)
1043 self.update_status()
1045 def set_frozen(self,entry,frozen):
1047 entry.setReadOnly(True)
1048 entry.setFrame(False)
1049 palette = QPalette()
1050 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1051 entry.setPalette(palette)
1053 entry.setReadOnly(False)
1054 entry.setFrame(True)
1055 palette = QPalette()
1056 palette.setColor(entry.backgroundRole(), QColor('white'))
1057 entry.setPalette(palette)
1060 def toggle_freeze(self,addr):
1062 if addr in self.wallet.frozen_addresses:
1063 self.wallet.unfreeze(addr)
1065 self.wallet.freeze(addr)
1066 self.update_receive_tab()
1068 def toggle_priority(self,addr):
1070 if addr in self.wallet.prioritized_addresses:
1071 self.wallet.unprioritize(addr)
1073 self.wallet.prioritize(addr)
1074 self.update_receive_tab()
1077 def create_list_tab(self, headers):
1078 "generic tab creation method"
1079 l = MyTreeWidget(self)
1080 l.setColumnCount( len(headers) )
1081 l.setHeaderLabels( headers )
1084 vbox = QVBoxLayout()
1091 vbox.addWidget(buttons)
1093 hbox = QHBoxLayout()
1096 buttons.setLayout(hbox)
1101 def create_receive_tab(self):
1102 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1103 l.setContextMenuPolicy(Qt.CustomContextMenu)
1104 l.customContextMenuRequested.connect(self.create_receive_menu)
1105 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1106 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1107 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1108 self.receive_list = l
1109 self.receive_buttons_hbox = hbox
1114 def receive_tab_set_mode(self, i):
1115 self.save_column_widths()
1116 self.expert_mode = (i == 1)
1117 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1118 self.update_receive_tab()
1121 def save_column_widths(self):
1122 if not self.expert_mode:
1123 widths = [ self.receive_list.columnWidth(0) ]
1126 for i in range(self.receive_list.columnCount() -1):
1127 widths.append(self.receive_list.columnWidth(i))
1128 self.column_widths["receive"][self.expert_mode] = widths
1130 self.column_widths["history"] = []
1131 for i in range(self.history_list.columnCount() - 1):
1132 self.column_widths["history"].append(self.history_list.columnWidth(i))
1134 self.column_widths["contacts"] = []
1135 for i in range(self.contacts_list.columnCount() - 1):
1136 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1138 self.config.set_key("column_widths", self.column_widths, True)
1141 def create_contacts_tab(self):
1142 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1143 l.setContextMenuPolicy(Qt.CustomContextMenu)
1144 l.customContextMenuRequested.connect(self.create_contact_menu)
1145 for i,width in enumerate(self.column_widths['contacts']):
1146 l.setColumnWidth(i, width)
1148 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1149 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1150 self.contacts_list = l
1151 self.contacts_buttons_hbox = hbox
1156 def delete_imported_key(self, addr):
1157 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1158 self.wallet.delete_imported_key(addr)
1159 self.update_receive_tab()
1160 self.update_history_tab()
1163 def create_receive_menu(self, position):
1164 # fixme: this function apparently has a side effect.
1165 # if it is not called the menu pops up several times
1166 #self.receive_list.selectedIndexes()
1168 item = self.receive_list.itemAt(position)
1170 addr = unicode(item.text(0))
1171 if not is_valid(addr):
1172 item.setExpanded(not item.isExpanded())
1175 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1176 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1177 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1178 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1179 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1180 if addr in self.wallet.imported_keys:
1181 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1183 if self.expert_mode:
1184 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1185 menu.addAction(t, lambda: self.toggle_freeze(addr))
1186 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1187 menu.addAction(t, lambda: self.toggle_priority(addr))
1189 self.run_hook('receive_menu', menu)
1190 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1193 def payto(self, addr):
1195 label = self.wallet.labels.get(addr)
1196 m_addr = label + ' <' + addr + '>' if label else addr
1197 self.tabs.setCurrentIndex(1)
1198 self.payto_e.setText(m_addr)
1199 self.amount_e.setFocus()
1202 def delete_contact(self, x):
1203 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1204 self.wallet.delete_contact(x)
1205 self.set_label(x, None)
1206 self.update_history_tab()
1207 self.update_contacts_tab()
1208 self.update_completions()
1211 def create_contact_menu(self, position):
1212 item = self.contacts_list.itemAt(position)
1214 addr = unicode(item.text(0))
1215 label = unicode(item.text(1))
1216 is_editable = item.data(0,32).toBool()
1217 payto_addr = item.data(0,33).toString()
1219 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1220 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1221 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1223 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1224 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1226 self.run_hook('create_contact_menu', menu, item)
1227 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1230 def update_receive_item(self, item):
1231 item.setFont(0, QFont(MONOSPACE_FONT))
1232 address = str(item.data(0,0).toString())
1233 label = self.wallet.labels.get(address,'')
1234 item.setData(1,0,label)
1235 item.setData(0,32, True) # is editable
1237 self.run_hook('update_receive_item', address, item)
1239 c, u = self.wallet.get_addr_balance(address)
1240 balance = self.format_amount(c + u)
1241 item.setData(2,0,balance)
1243 if self.expert_mode:
1244 if address in self.wallet.frozen_addresses:
1245 item.setBackgroundColor(0, QColor('lightblue'))
1246 elif address in self.wallet.prioritized_addresses:
1247 item.setBackgroundColor(0, QColor('lightgreen'))
1250 def update_receive_tab(self):
1251 l = self.receive_list
1254 l.setColumnHidden(2, not self.expert_mode)
1255 l.setColumnHidden(3, not self.expert_mode)
1256 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1257 l.setColumnWidth(i, width)
1259 if self.current_account is None:
1260 account_items = self.wallet.accounts.items()
1261 elif self.current_account != -1:
1262 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1266 for k, account in account_items:
1267 name = self.wallet.labels.get(k, 'unnamed account')
1268 c,u = self.wallet.get_account_balance(k)
1269 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1270 l.addTopLevelItem(account_item)
1271 account_item.setExpanded(True)
1273 for is_change in ([0,1] if self.expert_mode else [0]):
1274 if self.expert_mode:
1275 name = "Receiving" if not is_change else "Change"
1276 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1277 account_item.addChild(seq_item)
1278 if not is_change: seq_item.setExpanded(True)
1280 seq_item = account_item
1284 for address in account.get_addresses(is_change):
1285 h = self.wallet.history.get(address,[])
1289 if gap > self.wallet.gap_limit:
1294 num_tx = '*' if h == ['*'] else "%d"%len(h)
1295 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1296 self.update_receive_item(item)
1298 item.setBackgroundColor(1, QColor('red'))
1299 seq_item.addChild(item)
1302 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1303 c,u = self.wallet.get_imported_balance()
1304 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1305 l.addTopLevelItem(account_item)
1306 account_item.setExpanded(True)
1307 for address in self.wallet.imported_keys.keys():
1308 item = QTreeWidgetItem( [ address, '', '', ''] )
1309 self.update_receive_item(item)
1310 account_item.addChild(item)
1313 # we use column 1 because column 0 may be hidden
1314 l.setCurrentItem(l.topLevelItem(0),1)
1317 def update_contacts_tab(self):
1318 l = self.contacts_list
1321 for address in self.wallet.addressbook:
1322 label = self.wallet.labels.get(address,'')
1323 n = self.wallet.get_num_tx(address)
1324 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1325 item.setFont(0, QFont(MONOSPACE_FONT))
1326 # 32 = label can be edited (bool)
1327 item.setData(0,32, True)
1329 item.setData(0,33, address)
1330 l.addTopLevelItem(item)
1332 self.run_hook('update_contacts_tab', l)
1333 l.setCurrentItem(l.topLevelItem(0))
1337 def create_console_tab(self):
1338 from qt_console import Console
1339 self.console = console = Console()
1343 self.console.history = self.config.get("console-history",[])
1344 self.console.history_index = len(self.console.history)
1346 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1347 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1349 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1351 def mkfunc(f, method):
1352 return lambda *args: apply( f, (method, args, self.password_dialog ))
1354 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1355 methods[m] = mkfunc(c._run, m)
1357 console.updateNamespace(methods)
1360 def change_account(self,s):
1361 if s == _("All accounts"):
1362 self.current_account = None
1364 accounts = self.wallet.get_accounts()
1365 for k, v in accounts.items():
1367 self.current_account = k
1368 self.update_history_tab()
1369 self.update_status()
1370 self.update_receive_tab()
1372 def create_status_bar(self):
1375 sb.setFixedHeight(35)
1376 qtVersion = qVersion()
1378 self.balance_label = QLabel("")
1379 sb.addWidget(self.balance_label)
1381 update_notification = UpdateLabel(self.config)
1382 if(update_notification.new_version):
1383 sb.addPermanentWidget(update_notification)
1385 self.account_selector = QComboBox()
1386 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1387 sb.addPermanentWidget(self.account_selector)
1389 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1390 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1392 self.lock_icon = QIcon()
1393 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1394 sb.addPermanentWidget( self.password_button )
1396 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1397 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1398 sb.addPermanentWidget( self.seed_button )
1399 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1400 sb.addPermanentWidget( self.status_button )
1402 self.run_hook('create_status_bar', (sb,))
1404 self.setStatusBar(sb)
1407 def update_lock_icon(self):
1408 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1409 self.password_button.setIcon( icon )
1412 def update_buttons_on_seed(self):
1413 if self.wallet.seed:
1414 self.seed_button.show()
1415 self.password_button.show()
1416 self.send_button.setText(_("Send"))
1418 self.password_button.hide()
1419 self.seed_button.hide()
1420 self.send_button.setText(_("Create unsigned transaction"))
1423 def change_password_dialog(self):
1424 from password_dialog import PasswordDialog
1425 d = PasswordDialog(self.wallet, self)
1427 self.update_lock_icon()
1432 self.config.set_key('gui', 'lite', True)
1435 self.lite.mini.show()
1437 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1438 self.lite.main(None)
1441 def new_contact_dialog(self):
1442 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1443 address = unicode(text)
1445 if is_valid(address):
1446 self.wallet.add_contact(address)
1447 self.update_contacts_tab()
1448 self.update_history_tab()
1449 self.update_completions()
1451 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1454 def new_account_dialog(self):
1456 dialog = QDialog(self)
1458 dialog.setWindowTitle(_("New Account"))
1460 addr = self.wallet.new_account_address()
1461 vbox = QVBoxLayout()
1462 vbox.addWidget(QLabel(_("To create a new account, please send coins to the first address of that account:")))
1467 ok_button = QPushButton(_("OK"))
1468 ok_button.setDefault(True)
1469 ok_button.clicked.connect(dialog.accept)
1471 hbox = QHBoxLayout()
1473 hbox.addWidget(ok_button)
1474 vbox.addLayout(hbox)
1476 dialog.setLayout(vbox)
1481 def show_master_public_key(self):
1482 dialog = QDialog(self)
1484 dialog.setWindowTitle(_("Master Public Key"))
1486 main_text = QTextEdit()
1487 main_text.setText(self.wallet.get_master_public_key())
1488 main_text.setReadOnly(True)
1489 main_text.setMaximumHeight(170)
1490 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1492 ok_button = QPushButton(_("OK"))
1493 ok_button.setDefault(True)
1494 ok_button.clicked.connect(dialog.accept)
1496 main_layout = QGridLayout()
1497 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1499 main_layout.addWidget(main_text, 1, 0)
1500 main_layout.addWidget(qrw, 1, 1 )
1502 vbox = QVBoxLayout()
1503 vbox.addLayout(main_layout)
1504 hbox = QHBoxLayout()
1506 hbox.addWidget(ok_button)
1507 vbox.addLayout(hbox)
1509 dialog.setLayout(vbox)
1514 def show_seed_dialog(self, password):
1515 if not self.wallet.seed:
1516 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1519 seed = self.wallet.decode_seed(password)
1521 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1524 from seed_dialog import SeedDialog
1525 d = SeedDialog(self)
1526 d.show_seed(seed, self.wallet.imported_keys)
1530 def show_qrcode(self, data, title = "QR code"):
1534 d.setWindowTitle(title)
1535 d.setMinimumSize(270, 300)
1536 vbox = QVBoxLayout()
1537 qrw = QRCodeWidget(data)
1538 vbox.addWidget(qrw, 1)
1539 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1540 hbox = QHBoxLayout()
1544 filename = "qrcode.bmp"
1545 bmp.save_qrcode(qrw.qr, filename)
1546 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1548 b = QPushButton(_("Save"))
1550 b.clicked.connect(print_qr)
1552 b = QPushButton(_("Close"))
1554 b.clicked.connect(d.accept)
1557 vbox.addLayout(hbox)
1562 def do_protect(self, func, args):
1563 if self.wallet.use_encryption:
1564 password = self.password_dialog()
1570 if args != (False,):
1571 args = (self,) + args + (password,)
1573 args = (self,password)
1578 def show_private_key(self, address, password):
1579 if not address: return
1581 pk = self.wallet.get_private_key(address, password)
1582 except BaseException, e:
1583 self.show_message(str(e))
1585 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1589 def do_sign(self, address, message, signature, password):
1591 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1592 signature.setText(sig)
1593 except BaseException, e:
1594 self.show_message(str(e))
1596 def sign_message(self, address):
1597 if not address: return
1600 d.setWindowTitle(_('Sign Message'))
1601 d.setMinimumSize(410, 290)
1603 tab_widget = QTabWidget()
1605 layout = QGridLayout(tab)
1607 sign_address = QLineEdit()
1609 sign_address.setText(address)
1610 layout.addWidget(QLabel(_('Address')), 1, 0)
1611 layout.addWidget(sign_address, 1, 1)
1613 sign_message = QTextEdit()
1614 layout.addWidget(QLabel(_('Message')), 2, 0)
1615 layout.addWidget(sign_message, 2, 1)
1616 layout.setRowStretch(2,3)
1618 sign_signature = QTextEdit()
1619 layout.addWidget(QLabel(_('Signature')), 3, 0)
1620 layout.addWidget(sign_signature, 3, 1)
1621 layout.setRowStretch(3,1)
1624 hbox = QHBoxLayout()
1625 b = QPushButton(_("Sign"))
1627 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1628 b = QPushButton(_("Close"))
1629 b.clicked.connect(d.accept)
1631 layout.addLayout(hbox, 4, 1)
1632 tab_widget.addTab(tab, _("Sign"))
1636 layout = QGridLayout(tab)
1638 verify_address = QLineEdit()
1639 layout.addWidget(QLabel(_('Address')), 1, 0)
1640 layout.addWidget(verify_address, 1, 1)
1642 verify_message = QTextEdit()
1643 layout.addWidget(QLabel(_('Message')), 2, 0)
1644 layout.addWidget(verify_message, 2, 1)
1645 layout.setRowStretch(2,3)
1647 verify_signature = QTextEdit()
1648 layout.addWidget(QLabel(_('Signature')), 3, 0)
1649 layout.addWidget(verify_signature, 3, 1)
1650 layout.setRowStretch(3,1)
1653 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1654 self.show_message(_("Signature verified"))
1656 self.show_message(_("Error: wrong signature"))
1658 hbox = QHBoxLayout()
1659 b = QPushButton(_("Verify"))
1660 b.clicked.connect(do_verify)
1662 b = QPushButton(_("Close"))
1663 b.clicked.connect(d.accept)
1665 layout.addLayout(hbox, 4, 1)
1666 tab_widget.addTab(tab, _("Verify"))
1668 vbox = QVBoxLayout()
1669 vbox.addWidget(tab_widget)
1676 def question(self, msg):
1677 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1679 def show_message(self, msg):
1680 QMessageBox.information(self, _('Message'), msg, _('OK'))
1682 def password_dialog(self ):
1689 vbox = QVBoxLayout()
1690 msg = _('Please enter your password')
1691 vbox.addWidget(QLabel(msg))
1693 grid = QGridLayout()
1695 grid.addWidget(QLabel(_('Password')), 1, 0)
1696 grid.addWidget(pw, 1, 1)
1697 vbox.addLayout(grid)
1699 vbox.addLayout(ok_cancel_buttons(d))
1702 self.run_hook('password_dialog', pw, grid, 1)
1703 if not d.exec_(): return
1704 return unicode(pw.text())
1711 def generate_transaction_information_widget(self, tx):
1712 tabs = QTabWidget(self)
1715 grid_ui = QGridLayout(tab1)
1716 grid_ui.setColumnStretch(0,1)
1717 tabs.addTab(tab1, _('Outputs') )
1719 tree_widget = MyTreeWidget(self)
1720 tree_widget.setColumnCount(2)
1721 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1722 tree_widget.setColumnWidth(0, 300)
1723 tree_widget.setColumnWidth(1, 50)
1725 for address, value in tx.outputs:
1726 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1727 tree_widget.addTopLevelItem(item)
1729 tree_widget.setMaximumHeight(100)
1731 grid_ui.addWidget(tree_widget)
1734 grid_ui = QGridLayout(tab2)
1735 grid_ui.setColumnStretch(0,1)
1736 tabs.addTab(tab2, _('Inputs') )
1738 tree_widget = MyTreeWidget(self)
1739 tree_widget.setColumnCount(2)
1740 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1742 for input_line in tx.inputs:
1743 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1744 tree_widget.addTopLevelItem(item)
1746 tree_widget.setMaximumHeight(100)
1748 grid_ui.addWidget(tree_widget)
1752 def tx_dict_from_text(self, txt):
1754 tx_dict = json.loads(str(txt))
1755 assert "hex" in tx_dict.keys()
1756 assert "complete" in tx_dict.keys()
1757 if not tx_dict["complete"]:
1758 assert "input_info" in tx_dict.keys()
1760 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1765 def read_tx_from_file(self):
1766 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1770 with open(fileName, "r") as f:
1771 file_content = f.read()
1772 except (ValueError, IOError, os.error), reason:
1773 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1775 return self.tx_dict_from_text(file_content)
1779 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1781 self.wallet.signrawtransaction(tx, input_info, [], password)
1783 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1785 with open(fileName, "w+") as f:
1786 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1787 self.show_message(_("Transaction saved successfully"))
1790 except BaseException, e:
1791 self.show_message(str(e))
1794 def send_raw_transaction(self, raw_tx, dialog = ""):
1795 result, result_message = self.wallet.sendtx( raw_tx )
1797 self.show_message("Transaction successfully sent: %s" % (result_message))
1801 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1803 def do_process_from_text(self):
1804 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1807 tx_dict = self.tx_dict_from_text(text)
1809 self.create_process_transaction_window(tx_dict)
1811 def do_process_from_file(self):
1812 tx_dict = self.read_tx_from_file()
1814 self.create_process_transaction_window(tx_dict)
1816 def create_process_transaction_window(self, tx_dict):
1817 tx = Transaction(tx_dict["hex"])
1819 dialog = QDialog(self)
1820 dialog.setMinimumWidth(500)
1821 dialog.setWindowTitle(_('Process raw transaction'))
1827 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1828 l.addWidget(QLabel(_("Actions")), 4,0)
1830 if tx_dict["complete"] == False:
1831 l.addWidget(QLabel(_("Unsigned")), 3,1)
1832 if self.wallet.seed :
1833 b = QPushButton("Sign transaction")
1834 input_info = json.loads(tx_dict["input_info"])
1835 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1836 l.addWidget(b, 4, 1)
1838 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1840 l.addWidget(QLabel(_("Signed")), 3,1)
1841 b = QPushButton("Broadcast transaction")
1842 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1845 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1846 cancelButton = QPushButton(_("Cancel"))
1847 cancelButton.clicked.connect(lambda: dialog.done(0))
1848 l.addWidget(cancelButton, 4,2)
1854 def do_export_privkeys(self, password):
1855 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.")))
1858 select_export = _('Select file to export your private keys to')
1859 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1861 with open(fileName, "w+") as csvfile:
1862 transaction = csv.writer(csvfile)
1863 transaction.writerow(["address", "private_key"])
1866 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1867 transaction.writerow(["%34s"%addr,pk])
1869 self.show_message(_("Private keys exported."))
1871 except (IOError, os.error), reason:
1872 export_error_label = _("Electrum was unable to produce a private key-export.")
1873 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1875 except BaseException, e:
1876 self.show_message(str(e))
1880 def do_import_labels(self):
1881 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1882 if not labelsFile: return
1884 f = open(labelsFile, 'r')
1887 for key, value in json.loads(data).items():
1888 self.wallet.labels[key] = value
1890 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1891 except (IOError, os.error), reason:
1892 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1895 def do_export_labels(self):
1896 labels = self.wallet.labels
1898 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1900 with open(fileName, 'w+') as f:
1901 json.dump(labels, f)
1902 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1903 except (IOError, os.error), reason:
1904 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1907 def do_export_history(self):
1908 from gui_lite import csv_transaction
1909 csv_transaction(self.wallet)
1913 def do_import_privkey(self, password):
1914 if not self.wallet.imported_keys:
1915 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1916 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1917 + _('Are you sure you understand what you are doing?'), 3, 4)
1920 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1923 text = str(text).split()
1928 addr = self.wallet.import_key(key, password)
1929 except BaseException as e:
1935 addrlist.append(addr)
1937 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1939 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1940 self.update_receive_tab()
1941 self.update_history_tab()
1944 def settings_dialog(self):
1946 d.setWindowTitle(_('Electrum Settings'))
1948 vbox = QVBoxLayout()
1950 tabs = QTabWidget(self)
1951 self.settings_tab = tabs
1952 vbox.addWidget(tabs)
1955 grid_ui = QGridLayout(tab1)
1956 grid_ui.setColumnStretch(0,1)
1957 tabs.addTab(tab1, _('Display') )
1959 nz_label = QLabel(_('Display zeros'))
1960 grid_ui.addWidget(nz_label, 0, 0)
1961 nz_e = AmountEdit(None,True)
1962 nz_e.setText("%d"% self.wallet.num_zeros)
1963 grid_ui.addWidget(nz_e, 0, 1)
1964 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1965 grid_ui.addWidget(HelpButton(msg), 0, 2)
1966 if not self.config.is_modifiable('num_zeros'):
1967 for w in [nz_e, nz_label]: w.setEnabled(False)
1969 lang_label=QLabel(_('Language') + ':')
1970 grid_ui.addWidget(lang_label, 1, 0)
1971 lang_combo = QComboBox()
1972 from i18n import languages
1973 lang_combo.addItems(languages.values())
1975 index = languages.keys().index(self.config.get("language",''))
1978 lang_combo.setCurrentIndex(index)
1979 grid_ui.addWidget(lang_combo, 1, 1)
1980 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1981 if not self.config.is_modifiable('language'):
1982 for w in [lang_combo, lang_label]: w.setEnabled(False)
1984 currencies = self.exchanger.get_currencies()
1985 currencies.insert(0, "None")
1987 cur_label=QLabel(_('Currency') + ':')
1988 grid_ui.addWidget(cur_label , 2, 0)
1989 cur_combo = QComboBox()
1990 cur_combo.addItems(currencies)
1992 index = currencies.index(self.config.get('currency', "None"))
1995 cur_combo.setCurrentIndex(index)
1996 grid_ui.addWidget(cur_combo, 2, 1)
1997 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1999 expert_cb = QCheckBox(_('Expert mode'))
2000 expert_cb.setChecked(self.expert_mode)
2001 grid_ui.addWidget(expert_cb, 3, 0)
2002 hh = _('In expert mode, your client will:') + '\n' \
2003 + _(' - Show change addresses in the Receive tab') + '\n' \
2004 + _(' - Display the balance of each address') + '\n' \
2005 + _(' - Add freeze/prioritize actions to addresses.')
2006 grid_ui.addWidget(HelpButton(hh), 3, 2)
2007 grid_ui.setRowStretch(4,1)
2011 grid_wallet = QGridLayout(tab2)
2012 grid_wallet.setColumnStretch(0,1)
2013 tabs.addTab(tab2, _('Wallet') )
2015 fee_label = QLabel(_('Transaction fee'))
2016 grid_wallet.addWidget(fee_label, 0, 0)
2017 fee_e = AmountEdit(self.base_unit)
2018 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2019 grid_wallet.addWidget(fee_e, 0, 2)
2020 msg = _('Fee per kilobyte of transaction.') + ' ' \
2021 + _('Recommended value') + ': ' + self.format_amount(50000)
2022 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2023 if not self.config.is_modifiable('fee_per_kb'):
2024 for w in [fee_e, fee_label]: w.setEnabled(False)
2026 usechange_cb = QCheckBox(_('Use change addresses'))
2027 usechange_cb.setChecked(self.wallet.use_change)
2028 grid_wallet.addWidget(usechange_cb, 1, 0)
2029 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2030 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2032 gap_label = QLabel(_('Gap limit'))
2033 grid_wallet.addWidget(gap_label, 2, 0)
2034 gap_e = AmountEdit(None,True)
2035 gap_e.setText("%d"% self.wallet.gap_limit)
2036 grid_wallet.addWidget(gap_e, 2, 2)
2037 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2038 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2039 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2040 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2041 + _('Warning') + ': ' \
2042 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2043 + _('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'
2044 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2045 if not self.config.is_modifiable('gap_limit'):
2046 for w in [gap_e, gap_label]: w.setEnabled(False)
2048 units = ['BTC', 'mBTC']
2049 unit_label = QLabel(_('Base unit'))
2050 grid_wallet.addWidget(unit_label, 3, 0)
2051 unit_combo = QComboBox()
2052 unit_combo.addItems(units)
2053 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2054 grid_wallet.addWidget(unit_combo, 3, 2)
2055 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2056 + '\n1BTC=1000mBTC.\n' \
2057 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2058 grid_wallet.setRowStretch(4,1)
2062 tab5 = QScrollArea()
2063 tab5.setEnabled(True)
2064 tab5.setWidgetResizable(True)
2066 grid_plugins = QGridLayout()
2067 grid_plugins.setColumnStretch(0,1)
2070 w.setLayout(grid_plugins)
2073 w.setMinimumHeight(len(self.plugins)*35)
2075 tabs.addTab(tab5, _('Plugins') )
2076 def mk_toggle(cb, p):
2077 return lambda: cb.setChecked(p.toggle())
2078 for i, p in enumerate(self.plugins):
2080 cb = QCheckBox(p.fullname())
2081 cb.setDisabled(not p.is_available())
2082 cb.setChecked(p.is_enabled())
2083 cb.clicked.connect(mk_toggle(cb,p))
2084 grid_plugins.addWidget(cb, i, 0)
2085 if p.requires_settings():
2086 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2087 grid_plugins.addWidget(HelpButton(p.description()), i, 2)
2089 print_msg("Error: cannot display plugin", p)
2090 traceback.print_exc(file=sys.stdout)
2091 grid_plugins.setRowStretch(i+1,1)
2093 self.run_hook('create_settings_tab', tabs)
2095 vbox.addLayout(ok_cancel_buttons(d))
2099 if not d.exec_(): return
2101 fee = unicode(fee_e.text())
2103 fee = self.read_amount(fee)
2105 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2108 self.wallet.set_fee(fee)
2110 nz = unicode(nz_e.text())
2115 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2118 if self.wallet.num_zeros != nz:
2119 self.wallet.num_zeros = nz
2120 self.config.set_key('num_zeros', nz, True)
2121 self.update_history_tab()
2122 self.update_receive_tab()
2124 usechange_result = usechange_cb.isChecked()
2125 if self.wallet.use_change != usechange_result:
2126 self.wallet.use_change = usechange_result
2127 self.config.set_key('use_change', self.wallet.use_change, True)
2129 unit_result = units[unit_combo.currentIndex()]
2130 if self.base_unit() != unit_result:
2131 self.decimal_point = 8 if unit_result == 'BTC' else 5
2132 self.config.set_key('decimal_point', self.decimal_point, True)
2133 self.update_history_tab()
2134 self.update_status()
2137 n = int(gap_e.text())
2139 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2142 if self.wallet.gap_limit != n:
2143 r = self.wallet.change_gap_limit(n)
2145 self.update_receive_tab()
2146 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2148 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2150 need_restart = False
2152 lang_request = languages.keys()[lang_combo.currentIndex()]
2153 if lang_request != self.config.get('language'):
2154 self.config.set_key("language", lang_request, True)
2157 cur_request = str(currencies[cur_combo.currentIndex()])
2158 if cur_request != self.config.get('currency', "None"):
2159 self.config.set_key('currency', cur_request, True)
2160 self.update_wallet()
2162 self.run_hook('close_settings_dialog')
2165 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2167 self.receive_tab_set_mode(expert_cb.isChecked())
2169 def run_network_dialog(self):
2170 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2172 def closeEvent(self, event):
2174 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2175 self.save_column_widths()
2176 self.config.set_key("console-history",self.console.history[-50:])
2179 class OpenFileEventFilter(QObject):
2180 def __init__(self, windows):
2181 self.windows = windows
2182 super(OpenFileEventFilter, self).__init__()
2184 def eventFilter(self, obj, event):
2185 if event.type() == QtCore.QEvent.FileOpen:
2186 if len(self.windows) >= 1:
2187 self.windows[0].set_url(event.url().toString())
2196 def __init__(self, config, interface, app=None):
2197 self.interface = interface
2198 self.config = config
2200 self.efilter = OpenFileEventFilter(self.windows)
2202 self.app = QApplication(sys.argv)
2203 self.app.installEventFilter(self.efilter)
2206 def main(self, url):
2208 found = self.config.wallet_file_exists
2210 import installwizard
2211 wizard = installwizard.InstallWizard(self.config, self.interface)
2212 wallet = wizard.run()
2216 wallet = Wallet(self.config)
2218 wallet.interface = self.interface
2220 verifier = WalletVerifier(self.interface, self.config)
2222 wallet.set_verifier(verifier)
2223 synchronizer = WalletSynchronizer(wallet, self.config)
2224 synchronizer.start()
2228 w = ElectrumWindow(self.config)
2229 w.load_wallet(wallet)
2231 self.windows.append(w)
2232 if url: w.set_url(url)