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 self.account_selector.clear()
316 if len(accounts) > 1:
317 self.account_selector.addItems([_("All accounts")] + accounts.values())
318 self.account_selector.setCurrentIndex(0)
319 self.account_selector.show()
321 self.account_selector.hide()
323 self.update_lock_icon()
324 self.update_buttons_on_seed()
325 self.update_console()
328 def select_wallet_file(self):
329 wallet_folder = self.wallet.config.path
330 re.sub("(\/\w*.dat)$", "", wallet_folder)
331 file_name = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat") )
335 def open_wallet(self):
336 from electrum import SimpleConfig, Wallet, WalletSynchronizer
338 filename = self.select_wallet_file()
342 config = SimpleConfig({'wallet_path': filename})
343 if not config.wallet_file_exists:
344 self.show_message("file not found "+ filename)
347 interface = self.wallet.interface
348 verifier = self.wallet.verifier
349 self.wallet.synchronizer.stop()
354 wallet = Wallet(config)
355 wallet.interface = interface
356 wallet.verifier = verifier
357 synchronizer = WalletSynchronizer(wallet, config)
360 self.load_wallet(wallet)
363 def new_wallet(self):
364 from electrum import SimpleConfig, Wallet, WalletSynchronizer
367 wallet_folder = self.wallet.config.path
368 re.sub("(\/\w*.dat)$", "", wallet_folder)
369 filename = self.getSaveFileName("Select your wallet file", wallet_folder, "*.dat")
371 config = SimpleConfig({'wallet_path': filename})
372 assert not config.wallet_file_exists
374 wizard = installwizard.InstallWizard(config, self.wallet.interface)
375 wallet = wizard.run()
377 self.load_wallet(wallet)
381 def init_menubar(self):
384 file_menu = menubar.addMenu(_("&File"))
385 open_wallet_action = file_menu.addAction(_("&Open"))
386 open_wallet_action.triggered.connect(self.open_wallet)
388 new_wallet_action = file_menu.addAction(_("&Create/Restore"))
389 new_wallet_action.triggered.connect(self.new_wallet)
391 wallet_backup = file_menu.addAction(_("&Copy"))
392 wallet_backup.triggered.connect(lambda: backup_wallet(self.config.path))
394 quit_item = file_menu.addAction(_("&Close"))
395 quit_item.triggered.connect(self.close)
397 wallet_menu = menubar.addMenu(_("&Wallet"))
399 # Settings / Preferences are all reserved keywords in OSX using this as work around
400 preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
401 preferences_menu = wallet_menu.addAction(preferences_name)
402 preferences_menu.triggered.connect(self.settings_dialog)
404 wallet_menu.addSeparator()
406 raw_transaction_menu = wallet_menu.addMenu(_("&Load raw transaction"))
408 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
409 raw_transaction_file.triggered.connect(self.do_process_from_file)
411 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
412 raw_transaction_text.triggered.connect(self.do_process_from_text)
414 wallet_menu.addSeparator()
416 show_menu = wallet_menu.addMenu(_("Show"))
418 #if self.wallet.seed:
419 show_seed = show_menu.addAction(_("&Seed"))
420 show_seed.triggered.connect(self.show_seed_dialog)
422 show_mpk = show_menu.addAction(_("&Master Public Key"))
423 show_mpk.triggered.connect(self.show_master_public_key)
425 wallet_menu.addSeparator()
426 new_contact = wallet_menu.addAction(_("&New contact"))
427 new_contact.triggered.connect(self.new_contact_dialog)
429 new_account = wallet_menu.addAction(_("&New account"))
430 new_account.triggered.connect(self.new_account_dialog)
432 import_menu = menubar.addMenu(_("&Import"))
433 in_labels = import_menu.addAction(_("&Labels"))
434 in_labels.triggered.connect(self.do_import_labels)
436 in_private_keys = import_menu.addAction(_("&Private keys"))
437 in_private_keys.triggered.connect(self.do_import_privkey)
439 export_menu = menubar.addMenu(_("&Export"))
440 ex_private_keys = export_menu.addAction(_("&Private keys"))
441 ex_private_keys.triggered.connect(self.do_export_privkeys)
443 ex_history = export_menu.addAction(_("&History"))
444 ex_history.triggered.connect(self.do_export_history)
446 ex_labels = export_menu.addAction(_("&Labels"))
447 ex_labels.triggered.connect(self.do_export_labels)
449 help_menu = menubar.addMenu(_("&Help"))
450 doc_open = help_menu.addAction(_("&Documentation"))
451 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
452 web_open = help_menu.addAction(_("&Official website"))
453 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
455 self.setMenuBar(menubar)
459 def notify_transactions(self):
460 print_error("Notifying GUI")
461 if len(self.wallet.interface.pending_transactions_for_notifications) > 0:
462 # Combine the transactions if there are more then three
463 tx_amount = len(self.wallet.interface.pending_transactions_for_notifications)
466 for tx in self.wallet.interface.pending_transactions_for_notifications:
467 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
471 self.notify("%s new transactions received. Total amount received in the new transactions %s %s" \
472 % (tx_amount, self.format_amount(total_amount), self.base_unit()))
474 self.wallet.interface.pending_transactions_for_notifications = []
476 for tx in self.wallet.interface.pending_transactions_for_notifications:
478 self.wallet.interface.pending_transactions_for_notifications.remove(tx)
479 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
481 self.notify("New transaction received. %s %s" % (self.format_amount(v), self.base_unit()))
483 def notify(self, message):
484 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
487 def init_plugins(self):
488 import imp, pkgutil, __builtin__
489 if __builtin__.use_local_modules:
490 fp, pathname, description = imp.find_module('plugins')
491 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
492 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
493 imp.load_module('electrum_plugins', fp, pathname, description)
494 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
496 import electrum_plugins
497 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
498 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
501 for name, p in zip(plugin_names, plugins):
503 self.plugins.append( p.Plugin(self, name) )
505 print_msg("Error:cannot initialize plugin",p)
506 traceback.print_exc(file=sys.stdout)
509 def run_hook(self, name, *args):
510 for p in self.plugins:
511 if not p.is_enabled():
520 print_error("Plugin error")
521 traceback.print_exc(file=sys.stdout)
526 def set_label(self, name, text = None):
528 old_text = self.wallet.labels.get(name)
531 self.wallet.labels[name] = text
532 self.wallet.config.set_key('labels', self.wallet.labels)
536 self.wallet.labels.pop(name)
538 self.run_hook('set_label', name, text, changed)
542 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
543 def getOpenFileName(self, title, filter = None):
544 directory = self.config.get('io_dir', os.path.expanduser('~'))
545 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
546 if fileName and directory != os.path.dirname(fileName):
547 self.config.set_key('io_dir', os.path.dirname(fileName), True)
550 def getSaveFileName(self, title, filename, filter = None):
551 directory = self.config.get('io_dir', os.path.expanduser('~'))
552 path = os.path.join( directory, filename )
553 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
554 if fileName and directory != os.path.dirname(fileName):
555 self.config.set_key('io_dir', os.path.dirname(fileName), True)
561 QMainWindow.close(self)
562 self.run_hook('close_main_window')
564 def connect_slots(self, sender):
565 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
566 self.previous_payto_e=''
568 def timer_actions(self):
569 if self.need_update.is_set():
571 self.need_update.clear()
572 self.run_hook('timer_actions')
574 def format_amount(self, x, is_diff=False, whitespaces=False):
575 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point, whitespaces)
577 def read_amount(self, x):
578 if x in['.', '']: return None
579 p = pow(10, self.decimal_point)
580 return int( p * Decimal(x) )
583 assert self.decimal_point in [5,8]
584 return "BTC" if self.decimal_point == 8 else "mBTC"
586 def update_status(self):
587 if self.wallet.interface and self.wallet.interface.is_connected:
588 if not self.wallet.up_to_date:
589 text = _("Synchronizing...")
590 icon = QIcon(":icons/status_waiting.png")
592 c, u = self.wallet.get_account_balance(self.current_account)
593 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
594 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
595 text += self.create_quote_text(Decimal(c+u)/100000000)
596 self.tray.setToolTip(text)
597 icon = QIcon(":icons/status_connected.png")
599 text = _("Not connected")
600 icon = QIcon(":icons/status_disconnected.png")
602 self.balance_label.setText(text)
603 self.status_button.setIcon( icon )
605 def update_wallet(self):
607 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
608 self.update_history_tab()
609 self.update_receive_tab()
610 self.update_contacts_tab()
611 self.update_completions()
614 def create_quote_text(self, btc_balance):
615 quote_currency = self.config.get("currency", "None")
616 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
617 if quote_balance is None:
620 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
623 def create_history_tab(self):
624 self.history_list = l = MyTreeWidget(self)
626 for i,width in enumerate(self.column_widths['history']):
627 l.setColumnWidth(i, width)
628 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
629 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
630 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
632 l.setContextMenuPolicy(Qt.CustomContextMenu)
633 l.customContextMenuRequested.connect(self.create_history_menu)
637 def create_history_menu(self, position):
638 self.history_list.selectedIndexes()
639 item = self.history_list.currentItem()
641 tx_hash = str(item.data(0, Qt.UserRole).toString())
642 if not tx_hash: return
644 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
645 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
646 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
647 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
650 def show_tx_details(self, tx):
651 dialog = QDialog(self)
653 dialog.setWindowTitle(_("Transaction Details"))
655 dialog.setLayout(vbox)
656 dialog.setMinimumSize(600,300)
659 if tx_hash in self.wallet.transactions.keys():
660 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
661 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
663 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
669 vbox.addWidget(QLabel("Transaction ID:"))
670 e = QLineEdit(tx_hash)
674 vbox.addWidget(QLabel("Date: %s"%time_str))
675 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
678 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
679 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
681 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
682 vbox.addWidget(QLabel("Transaction fee: unknown"))
684 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
686 vbox.addWidget( self.generate_transaction_information_widget(tx) )
688 ok_button = QPushButton(_("Close"))
689 ok_button.setDefault(True)
690 ok_button.clicked.connect(dialog.accept)
694 hbox.addWidget(ok_button)
698 def tx_label_clicked(self, item, column):
699 if column==2 and item.isSelected():
701 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
702 self.history_list.editItem( item, column )
703 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
706 def tx_label_changed(self, item, column):
710 tx_hash = str(item.data(0, Qt.UserRole).toString())
711 tx = self.wallet.transactions.get(tx_hash)
712 text = unicode( item.text(2) )
713 self.set_label(tx_hash, text)
715 item.setForeground(2, QBrush(QColor('black')))
717 text = self.wallet.get_default_label(tx_hash)
718 item.setText(2, text)
719 item.setForeground(2, QBrush(QColor('gray')))
723 def edit_label(self, is_recv):
724 l = self.receive_list if is_recv else self.contacts_list
725 item = l.currentItem()
726 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
727 l.editItem( item, 1 )
728 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
732 def address_label_clicked(self, item, column, l, column_addr, column_label):
733 if column == column_label and item.isSelected():
734 is_editable = item.data(0, 32).toBool()
737 addr = unicode( item.text(column_addr) )
738 label = unicode( item.text(column_label) )
739 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
740 l.editItem( item, column )
741 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
744 def address_label_changed(self, item, column, l, column_addr, column_label):
745 if column == column_label:
746 addr = unicode( item.text(column_addr) )
747 text = unicode( item.text(column_label) )
748 is_editable = item.data(0, 32).toBool()
752 changed = self.set_label(addr, text)
754 self.update_history_tab()
755 self.update_completions()
757 self.current_item_changed(item)
759 self.run_hook('item_changed', item, column)
762 def current_item_changed(self, a):
763 self.run_hook('current_item_changed', a)
767 def update_history_tab(self):
769 self.history_list.clear()
770 for item in self.wallet.get_tx_history(self.current_account):
771 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
774 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
779 time_str = 'unverified'
780 icon = QIcon(":icons/unconfirmed.png")
783 icon = QIcon(":icons/unconfirmed.png")
785 icon = QIcon(":icons/clock%d.png"%conf)
787 icon = QIcon(":icons/confirmed.png")
789 if value is not None:
790 v_str = self.format_amount(value, True, whitespaces=True)
794 balance_str = self.format_amount(balance, whitespaces=True)
797 label, is_default_label = self.wallet.get_label(tx_hash)
799 label = _('Pruned transaction outputs')
800 is_default_label = False
802 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
803 item.setFont(2, QFont(MONOSPACE_FONT))
804 item.setFont(3, QFont(MONOSPACE_FONT))
805 item.setFont(4, QFont(MONOSPACE_FONT))
807 item.setForeground(3, QBrush(QColor("#BC1E1E")))
809 item.setData(0, Qt.UserRole, tx_hash)
810 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
812 item.setForeground(2, QBrush(QColor('grey')))
814 item.setIcon(0, icon)
815 self.history_list.insertTopLevelItem(0,item)
818 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
821 def create_send_tab(self):
826 grid.setColumnMinimumWidth(3,300)
827 grid.setColumnStretch(5,1)
830 self.payto_e = QLineEdit()
831 grid.addWidget(QLabel(_('Pay to')), 1, 0)
832 grid.addWidget(self.payto_e, 1, 1, 1, 3)
834 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)
836 completer = QCompleter()
837 completer.setCaseSensitivity(False)
838 self.payto_e.setCompleter(completer)
839 completer.setModel(self.completions)
841 self.message_e = QLineEdit()
842 grid.addWidget(QLabel(_('Description')), 2, 0)
843 grid.addWidget(self.message_e, 2, 1, 1, 3)
844 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)
846 self.amount_e = AmountEdit(self.base_unit)
847 grid.addWidget(QLabel(_('Amount')), 3, 0)
848 grid.addWidget(self.amount_e, 3, 1, 1, 2)
849 grid.addWidget(HelpButton(
850 _('Amount to be sent.') + '\n\n' \
851 + _('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.') \
852 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
854 self.fee_e = AmountEdit(self.base_unit)
855 grid.addWidget(QLabel(_('Fee')), 4, 0)
856 grid.addWidget(self.fee_e, 4, 1, 1, 2)
857 grid.addWidget(HelpButton(
858 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
859 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
860 + _('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)
863 self.send_button = EnterButton(_("Send"), self.do_send)
864 grid.addWidget(self.send_button, 6, 1)
866 b = EnterButton(_("Clear"),self.do_clear)
867 grid.addWidget(b, 6, 2)
869 self.payto_sig = QLabel('')
870 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
872 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
873 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
882 def entry_changed( is_fee ):
883 self.funds_error = False
885 if self.amount_e.is_shortcut:
886 self.amount_e.is_shortcut = False
887 c, u = self.wallet.get_account_balance(self.current_account)
888 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
889 fee = self.wallet.estimated_fee(inputs)
891 self.amount_e.setText( self.format_amount(amount) )
892 self.fee_e.setText( self.format_amount( fee ) )
895 amount = self.read_amount(str(self.amount_e.text()))
896 fee = self.read_amount(str(self.fee_e.text()))
898 if not is_fee: fee = None
901 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
903 self.fee_e.setText( self.format_amount( fee ) )
906 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
910 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
911 self.funds_error = True
912 text = _( "Not enough funds" )
913 c, u = self.wallet.get_frozen_balance()
914 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
916 self.statusBar().showMessage(text)
917 self.amount_e.setPalette(palette)
918 self.fee_e.setPalette(palette)
920 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
921 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
923 self.run_hook('create_send_tab', grid)
927 def update_completions(self):
929 for addr,label in self.wallet.labels.items():
930 if addr in self.wallet.addressbook:
931 l.append( label + ' <' + addr + '>')
933 self.run_hook('update_completions', l)
934 self.completions.setStringList(l)
938 return lambda s, *args: s.do_protect(func, args)
943 label = unicode( self.message_e.text() )
944 r = unicode( self.payto_e.text() )
947 # label or alias, with address in brackets
948 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
949 to_address = m.group(2) if m else r
951 if not is_valid(to_address):
952 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
956 amount = self.read_amount(unicode( self.amount_e.text()))
958 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
961 fee = self.read_amount(unicode( self.fee_e.text()))
963 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
966 confirm_amount = self.config.get('confirm_amount', 100000000)
967 if amount >= confirm_amount:
968 if not self.question("send %s to %s?"%(self.format_amount(amount) + ' '+ self.base_unit(), to_address)):
971 self.send_tx(to_address, amount, fee, label)
975 def send_tx(self, to_address, amount, fee, label, password):
978 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
979 except BaseException, e:
980 traceback.print_exc(file=sys.stdout)
981 self.show_message(str(e))
984 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
985 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
988 self.run_hook('send_tx', tx)
991 self.set_label(tx.hash(), label)
994 h = self.wallet.send_tx(tx)
995 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
996 status, msg = self.wallet.receive_tx( h )
998 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
1000 self.update_contacts_tab()
1002 QMessageBox.warning(self, _('Error'), msg, _('OK'))
1004 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
1006 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
1007 with open(fileName,'w') as f:
1008 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1009 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
1011 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
1016 def set_url(self, url):
1017 address, amount, label, message, signature, identity, url = util.parse_url(url)
1018 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
1020 if label and self.wallet.labels.get(address) != label:
1021 if self.question('Give label "%s" to address %s ?'%(label,address)):
1022 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
1023 self.wallet.addressbook.append(address)
1024 self.set_label(address, label)
1026 self.run_hook('set_url', url, self.show_message, self.question)
1028 self.tabs.setCurrentIndex(1)
1029 label = self.wallet.labels.get(address)
1030 m_addr = label + ' <'+ address +'>' if label else address
1031 self.payto_e.setText(m_addr)
1033 self.message_e.setText(message)
1034 self.amount_e.setText(amount)
1036 self.set_frozen(self.payto_e,True)
1037 self.set_frozen(self.amount_e,True)
1038 self.set_frozen(self.message_e,True)
1039 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
1041 self.payto_sig.setVisible(False)
1044 self.payto_sig.setVisible(False)
1045 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1047 self.set_frozen(e,False)
1048 self.update_status()
1050 def set_frozen(self,entry,frozen):
1052 entry.setReadOnly(True)
1053 entry.setFrame(False)
1054 palette = QPalette()
1055 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1056 entry.setPalette(palette)
1058 entry.setReadOnly(False)
1059 entry.setFrame(True)
1060 palette = QPalette()
1061 palette.setColor(entry.backgroundRole(), QColor('white'))
1062 entry.setPalette(palette)
1065 def toggle_freeze(self,addr):
1067 if addr in self.wallet.frozen_addresses:
1068 self.wallet.unfreeze(addr)
1070 self.wallet.freeze(addr)
1071 self.update_receive_tab()
1073 def toggle_priority(self,addr):
1075 if addr in self.wallet.prioritized_addresses:
1076 self.wallet.unprioritize(addr)
1078 self.wallet.prioritize(addr)
1079 self.update_receive_tab()
1082 def create_list_tab(self, headers):
1083 "generic tab creation method"
1084 l = MyTreeWidget(self)
1085 l.setColumnCount( len(headers) )
1086 l.setHeaderLabels( headers )
1089 vbox = QVBoxLayout()
1096 vbox.addWidget(buttons)
1098 hbox = QHBoxLayout()
1101 buttons.setLayout(hbox)
1106 def create_receive_tab(self):
1107 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1108 l.setContextMenuPolicy(Qt.CustomContextMenu)
1109 l.customContextMenuRequested.connect(self.create_receive_menu)
1110 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1111 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1112 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1113 self.receive_list = l
1114 self.receive_buttons_hbox = hbox
1119 def receive_tab_set_mode(self, i):
1120 self.save_column_widths()
1121 self.expert_mode = (i == 1)
1122 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1123 self.update_receive_tab()
1126 def save_column_widths(self):
1127 if not self.expert_mode:
1128 widths = [ self.receive_list.columnWidth(0) ]
1131 for i in range(self.receive_list.columnCount() -1):
1132 widths.append(self.receive_list.columnWidth(i))
1133 self.column_widths["receive"][self.expert_mode] = widths
1135 self.column_widths["history"] = []
1136 for i in range(self.history_list.columnCount() - 1):
1137 self.column_widths["history"].append(self.history_list.columnWidth(i))
1139 self.column_widths["contacts"] = []
1140 for i in range(self.contacts_list.columnCount() - 1):
1141 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1143 self.config.set_key("column_widths", self.column_widths, True)
1146 def create_contacts_tab(self):
1147 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1148 l.setContextMenuPolicy(Qt.CustomContextMenu)
1149 l.customContextMenuRequested.connect(self.create_contact_menu)
1150 for i,width in enumerate(self.column_widths['contacts']):
1151 l.setColumnWidth(i, width)
1153 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1154 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1155 self.contacts_list = l
1156 self.contacts_buttons_hbox = hbox
1161 def delete_imported_key(self, addr):
1162 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1163 self.wallet.delete_imported_key(addr)
1164 self.update_receive_tab()
1165 self.update_history_tab()
1168 def create_receive_menu(self, position):
1169 # fixme: this function apparently has a side effect.
1170 # if it is not called the menu pops up several times
1171 #self.receive_list.selectedIndexes()
1173 item = self.receive_list.itemAt(position)
1175 addr = unicode(item.text(0))
1176 if not is_valid(addr):
1177 item.setExpanded(not item.isExpanded())
1180 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1181 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1182 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1183 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1184 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1185 if addr in self.wallet.imported_keys:
1186 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1188 if self.expert_mode:
1189 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1190 menu.addAction(t, lambda: self.toggle_freeze(addr))
1191 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1192 menu.addAction(t, lambda: self.toggle_priority(addr))
1194 self.run_hook('receive_menu', menu)
1195 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1198 def payto(self, addr):
1200 label = self.wallet.labels.get(addr)
1201 m_addr = label + ' <' + addr + '>' if label else addr
1202 self.tabs.setCurrentIndex(1)
1203 self.payto_e.setText(m_addr)
1204 self.amount_e.setFocus()
1207 def delete_contact(self, x):
1208 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1209 self.wallet.delete_contact(x)
1210 self.set_label(x, None)
1211 self.update_history_tab()
1212 self.update_contacts_tab()
1213 self.update_completions()
1216 def create_contact_menu(self, position):
1217 item = self.contacts_list.itemAt(position)
1219 addr = unicode(item.text(0))
1220 label = unicode(item.text(1))
1221 is_editable = item.data(0,32).toBool()
1222 payto_addr = item.data(0,33).toString()
1224 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1225 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1226 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1228 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1229 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1231 self.run_hook('create_contact_menu', menu, item)
1232 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1235 def update_receive_item(self, item):
1236 item.setFont(0, QFont(MONOSPACE_FONT))
1237 address = str(item.data(0,0).toString())
1238 label = self.wallet.labels.get(address,'')
1239 item.setData(1,0,label)
1240 item.setData(0,32, True) # is editable
1242 self.run_hook('update_receive_item', address, item)
1244 c, u = self.wallet.get_addr_balance(address)
1245 balance = self.format_amount(c + u)
1246 item.setData(2,0,balance)
1248 if self.expert_mode:
1249 if address in self.wallet.frozen_addresses:
1250 item.setBackgroundColor(0, QColor('lightblue'))
1251 elif address in self.wallet.prioritized_addresses:
1252 item.setBackgroundColor(0, QColor('lightgreen'))
1255 def update_receive_tab(self):
1256 l = self.receive_list
1259 l.setColumnHidden(2, not self.expert_mode)
1260 l.setColumnHidden(3, not self.expert_mode)
1261 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1262 l.setColumnWidth(i, width)
1264 if self.current_account is None:
1265 account_items = self.wallet.accounts.items()
1266 elif self.current_account != -1:
1267 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1271 for k, account in account_items:
1272 name = self.wallet.labels.get(k, 'unnamed account')
1273 c,u = self.wallet.get_account_balance(k)
1274 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1275 l.addTopLevelItem(account_item)
1276 account_item.setExpanded(True)
1278 for is_change in ([0,1] if self.expert_mode else [0]):
1279 if self.expert_mode:
1280 name = "Receiving" if not is_change else "Change"
1281 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1282 account_item.addChild(seq_item)
1283 if not is_change: seq_item.setExpanded(True)
1285 seq_item = account_item
1289 for address in account.get_addresses(is_change):
1290 h = self.wallet.history.get(address,[])
1294 if gap > self.wallet.gap_limit:
1299 num_tx = '*' if h == ['*'] else "%d"%len(h)
1300 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1301 self.update_receive_item(item)
1303 item.setBackgroundColor(1, QColor('red'))
1304 seq_item.addChild(item)
1307 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1308 c,u = self.wallet.get_imported_balance()
1309 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1310 l.addTopLevelItem(account_item)
1311 account_item.setExpanded(True)
1312 for address in self.wallet.imported_keys.keys():
1313 item = QTreeWidgetItem( [ address, '', '', ''] )
1314 self.update_receive_item(item)
1315 account_item.addChild(item)
1318 # we use column 1 because column 0 may be hidden
1319 l.setCurrentItem(l.topLevelItem(0),1)
1322 def update_contacts_tab(self):
1323 l = self.contacts_list
1326 for address in self.wallet.addressbook:
1327 label = self.wallet.labels.get(address,'')
1328 n = self.wallet.get_num_tx(address)
1329 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1330 item.setFont(0, QFont(MONOSPACE_FONT))
1331 # 32 = label can be edited (bool)
1332 item.setData(0,32, True)
1334 item.setData(0,33, address)
1335 l.addTopLevelItem(item)
1337 self.run_hook('update_contacts_tab', l)
1338 l.setCurrentItem(l.topLevelItem(0))
1342 def create_console_tab(self):
1343 from qt_console import Console
1344 self.console = console = Console()
1348 def update_console(self):
1349 console = self.console
1350 console.history = self.config.get("console-history",[])
1351 console.history_index = len(console.history)
1353 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1354 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1356 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1358 def mkfunc(f, method):
1359 return lambda *args: apply( f, (method, args, self.password_dialog ))
1361 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1362 methods[m] = mkfunc(c._run, m)
1364 console.updateNamespace(methods)
1367 def change_account(self,s):
1368 if s == _("All accounts"):
1369 self.current_account = None
1371 accounts = self.wallet.get_accounts()
1372 for k, v in accounts.items():
1374 self.current_account = k
1375 self.update_history_tab()
1376 self.update_status()
1377 self.update_receive_tab()
1379 def create_status_bar(self):
1382 sb.setFixedHeight(35)
1383 qtVersion = qVersion()
1385 self.balance_label = QLabel("")
1386 sb.addWidget(self.balance_label)
1388 update_notification = UpdateLabel(self.config)
1389 if(update_notification.new_version):
1390 sb.addPermanentWidget(update_notification)
1392 self.account_selector = QComboBox()
1393 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1394 sb.addPermanentWidget(self.account_selector)
1396 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1397 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1399 self.lock_icon = QIcon()
1400 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1401 sb.addPermanentWidget( self.password_button )
1403 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1404 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1405 sb.addPermanentWidget( self.seed_button )
1406 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1407 sb.addPermanentWidget( self.status_button )
1409 self.run_hook('create_status_bar', (sb,))
1411 self.setStatusBar(sb)
1414 def update_lock_icon(self):
1415 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1416 self.password_button.setIcon( icon )
1419 def update_buttons_on_seed(self):
1420 if self.wallet.seed:
1421 self.seed_button.show()
1422 self.password_button.show()
1423 self.send_button.setText(_("Send"))
1425 self.password_button.hide()
1426 self.seed_button.hide()
1427 self.send_button.setText(_("Create unsigned transaction"))
1430 def change_password_dialog(self):
1431 from password_dialog import PasswordDialog
1432 d = PasswordDialog(self.wallet, self)
1434 self.update_lock_icon()
1439 self.config.set_key('gui', 'lite', True)
1442 self.lite.mini.show()
1444 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1445 self.lite.main(None)
1448 def new_contact_dialog(self):
1449 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1450 address = unicode(text)
1452 if is_valid(address):
1453 self.wallet.add_contact(address)
1454 self.update_contacts_tab()
1455 self.update_history_tab()
1456 self.update_completions()
1458 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1461 def new_account_dialog(self):
1463 dialog = QDialog(self)
1465 dialog.setWindowTitle(_("New Account"))
1467 addr = self.wallet.new_account_address()
1468 vbox = QVBoxLayout()
1469 vbox.addWidget(QLabel(_("To create a new account, please send coins to the first address of that account:")))
1474 ok_button = QPushButton(_("OK"))
1475 ok_button.setDefault(True)
1476 ok_button.clicked.connect(dialog.accept)
1478 hbox = QHBoxLayout()
1480 hbox.addWidget(ok_button)
1481 vbox.addLayout(hbox)
1483 dialog.setLayout(vbox)
1488 def show_master_public_key(self):
1489 dialog = QDialog(self)
1491 dialog.setWindowTitle(_("Master Public Key"))
1493 main_text = QTextEdit()
1494 main_text.setText(self.wallet.get_master_public_key())
1495 main_text.setReadOnly(True)
1496 main_text.setMaximumHeight(170)
1497 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1499 ok_button = QPushButton(_("OK"))
1500 ok_button.setDefault(True)
1501 ok_button.clicked.connect(dialog.accept)
1503 main_layout = QGridLayout()
1504 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1506 main_layout.addWidget(main_text, 1, 0)
1507 main_layout.addWidget(qrw, 1, 1 )
1509 vbox = QVBoxLayout()
1510 vbox.addLayout(main_layout)
1511 hbox = QHBoxLayout()
1513 hbox.addWidget(ok_button)
1514 vbox.addLayout(hbox)
1516 dialog.setLayout(vbox)
1521 def show_seed_dialog(self, password):
1522 if not self.wallet.seed:
1523 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1526 seed = self.wallet.decode_seed(password)
1528 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1531 from seed_dialog import SeedDialog
1532 d = SeedDialog(self)
1533 d.show_seed(seed, self.wallet.imported_keys)
1537 def show_qrcode(self, data, title = "QR code"):
1541 d.setWindowTitle(title)
1542 d.setMinimumSize(270, 300)
1543 vbox = QVBoxLayout()
1544 qrw = QRCodeWidget(data)
1545 vbox.addWidget(qrw, 1)
1546 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1547 hbox = QHBoxLayout()
1551 filename = "qrcode.bmp"
1552 bmp.save_qrcode(qrw.qr, filename)
1553 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1555 b = QPushButton(_("Save"))
1557 b.clicked.connect(print_qr)
1559 b = QPushButton(_("Close"))
1561 b.clicked.connect(d.accept)
1564 vbox.addLayout(hbox)
1569 def do_protect(self, func, args):
1570 if self.wallet.use_encryption:
1571 password = self.password_dialog()
1577 if args != (False,):
1578 args = (self,) + args + (password,)
1580 args = (self,password)
1585 def show_private_key(self, address, password):
1586 if not address: return
1588 pk = self.wallet.get_private_key(address, password)
1589 except BaseException, e:
1590 self.show_message(str(e))
1592 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1596 def do_sign(self, address, message, signature, password):
1598 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1599 signature.setText(sig)
1600 except BaseException, e:
1601 self.show_message(str(e))
1603 def sign_message(self, address):
1604 if not address: return
1607 d.setWindowTitle(_('Sign Message'))
1608 d.setMinimumSize(410, 290)
1610 tab_widget = QTabWidget()
1612 layout = QGridLayout(tab)
1614 sign_address = QLineEdit()
1616 sign_address.setText(address)
1617 layout.addWidget(QLabel(_('Address')), 1, 0)
1618 layout.addWidget(sign_address, 1, 1)
1620 sign_message = QTextEdit()
1621 layout.addWidget(QLabel(_('Message')), 2, 0)
1622 layout.addWidget(sign_message, 2, 1)
1623 layout.setRowStretch(2,3)
1625 sign_signature = QTextEdit()
1626 layout.addWidget(QLabel(_('Signature')), 3, 0)
1627 layout.addWidget(sign_signature, 3, 1)
1628 layout.setRowStretch(3,1)
1631 hbox = QHBoxLayout()
1632 b = QPushButton(_("Sign"))
1634 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1635 b = QPushButton(_("Close"))
1636 b.clicked.connect(d.accept)
1638 layout.addLayout(hbox, 4, 1)
1639 tab_widget.addTab(tab, _("Sign"))
1643 layout = QGridLayout(tab)
1645 verify_address = QLineEdit()
1646 layout.addWidget(QLabel(_('Address')), 1, 0)
1647 layout.addWidget(verify_address, 1, 1)
1649 verify_message = QTextEdit()
1650 layout.addWidget(QLabel(_('Message')), 2, 0)
1651 layout.addWidget(verify_message, 2, 1)
1652 layout.setRowStretch(2,3)
1654 verify_signature = QTextEdit()
1655 layout.addWidget(QLabel(_('Signature')), 3, 0)
1656 layout.addWidget(verify_signature, 3, 1)
1657 layout.setRowStretch(3,1)
1660 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1661 self.show_message(_("Signature verified"))
1663 self.show_message(_("Error: wrong signature"))
1665 hbox = QHBoxLayout()
1666 b = QPushButton(_("Verify"))
1667 b.clicked.connect(do_verify)
1669 b = QPushButton(_("Close"))
1670 b.clicked.connect(d.accept)
1672 layout.addLayout(hbox, 4, 1)
1673 tab_widget.addTab(tab, _("Verify"))
1675 vbox = QVBoxLayout()
1676 vbox.addWidget(tab_widget)
1683 def question(self, msg):
1684 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1686 def show_message(self, msg):
1687 QMessageBox.information(self, _('Message'), msg, _('OK'))
1689 def password_dialog(self ):
1696 vbox = QVBoxLayout()
1697 msg = _('Please enter your password')
1698 vbox.addWidget(QLabel(msg))
1700 grid = QGridLayout()
1702 grid.addWidget(QLabel(_('Password')), 1, 0)
1703 grid.addWidget(pw, 1, 1)
1704 vbox.addLayout(grid)
1706 vbox.addLayout(ok_cancel_buttons(d))
1709 self.run_hook('password_dialog', pw, grid, 1)
1710 if not d.exec_(): return
1711 return unicode(pw.text())
1718 def generate_transaction_information_widget(self, tx):
1719 tabs = QTabWidget(self)
1722 grid_ui = QGridLayout(tab1)
1723 grid_ui.setColumnStretch(0,1)
1724 tabs.addTab(tab1, _('Outputs') )
1726 tree_widget = MyTreeWidget(self)
1727 tree_widget.setColumnCount(2)
1728 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1729 tree_widget.setColumnWidth(0, 300)
1730 tree_widget.setColumnWidth(1, 50)
1732 for address, value in tx.outputs:
1733 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1734 tree_widget.addTopLevelItem(item)
1736 tree_widget.setMaximumHeight(100)
1738 grid_ui.addWidget(tree_widget)
1741 grid_ui = QGridLayout(tab2)
1742 grid_ui.setColumnStretch(0,1)
1743 tabs.addTab(tab2, _('Inputs') )
1745 tree_widget = MyTreeWidget(self)
1746 tree_widget.setColumnCount(2)
1747 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1749 for input_line in tx.inputs:
1750 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1751 tree_widget.addTopLevelItem(item)
1753 tree_widget.setMaximumHeight(100)
1755 grid_ui.addWidget(tree_widget)
1759 def tx_dict_from_text(self, txt):
1761 tx_dict = json.loads(str(txt))
1762 assert "hex" in tx_dict.keys()
1763 assert "complete" in tx_dict.keys()
1764 if not tx_dict["complete"]:
1765 assert "input_info" in tx_dict.keys()
1767 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1772 def read_tx_from_file(self):
1773 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1777 with open(fileName, "r") as f:
1778 file_content = f.read()
1779 except (ValueError, IOError, os.error), reason:
1780 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1782 return self.tx_dict_from_text(file_content)
1786 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1788 self.wallet.signrawtransaction(tx, input_info, [], password)
1790 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1792 with open(fileName, "w+") as f:
1793 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1794 self.show_message(_("Transaction saved successfully"))
1797 except BaseException, e:
1798 self.show_message(str(e))
1801 def send_raw_transaction(self, raw_tx, dialog = ""):
1802 result, result_message = self.wallet.sendtx( raw_tx )
1804 self.show_message("Transaction successfully sent: %s" % (result_message))
1808 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1810 def do_process_from_text(self):
1811 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1814 tx_dict = self.tx_dict_from_text(text)
1816 self.create_process_transaction_window(tx_dict)
1818 def do_process_from_file(self):
1819 tx_dict = self.read_tx_from_file()
1821 self.create_process_transaction_window(tx_dict)
1823 def create_process_transaction_window(self, tx_dict):
1824 tx = Transaction(tx_dict["hex"])
1826 dialog = QDialog(self)
1827 dialog.setMinimumWidth(500)
1828 dialog.setWindowTitle(_('Process raw transaction'))
1834 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1835 l.addWidget(QLabel(_("Actions")), 4,0)
1837 if tx_dict["complete"] == False:
1838 l.addWidget(QLabel(_("Unsigned")), 3,1)
1839 if self.wallet.seed :
1840 b = QPushButton("Sign transaction")
1841 input_info = json.loads(tx_dict["input_info"])
1842 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1843 l.addWidget(b, 4, 1)
1845 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1847 l.addWidget(QLabel(_("Signed")), 3,1)
1848 b = QPushButton("Broadcast transaction")
1849 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1852 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1853 cancelButton = QPushButton(_("Cancel"))
1854 cancelButton.clicked.connect(lambda: dialog.done(0))
1855 l.addWidget(cancelButton, 4,2)
1861 def do_export_privkeys(self, password):
1862 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.")))
1865 select_export = _('Select file to export your private keys to')
1866 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1868 with open(fileName, "w+") as csvfile:
1869 transaction = csv.writer(csvfile)
1870 transaction.writerow(["address", "private_key"])
1873 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1874 transaction.writerow(["%34s"%addr,pk])
1876 self.show_message(_("Private keys exported."))
1878 except (IOError, os.error), reason:
1879 export_error_label = _("Electrum was unable to produce a private key-export.")
1880 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1882 except BaseException, e:
1883 self.show_message(str(e))
1887 def do_import_labels(self):
1888 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1889 if not labelsFile: return
1891 f = open(labelsFile, 'r')
1894 for key, value in json.loads(data).items():
1895 self.wallet.labels[key] = value
1897 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1898 except (IOError, os.error), reason:
1899 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1902 def do_export_labels(self):
1903 labels = self.wallet.labels
1905 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1907 with open(fileName, 'w+') as f:
1908 json.dump(labels, f)
1909 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1910 except (IOError, os.error), reason:
1911 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1914 def do_export_history(self):
1915 from gui_lite import csv_transaction
1916 csv_transaction(self.wallet)
1920 def do_import_privkey(self, password):
1921 if not self.wallet.imported_keys:
1922 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1923 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1924 + _('Are you sure you understand what you are doing?'), 3, 4)
1927 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1930 text = str(text).split()
1935 addr = self.wallet.import_key(key, password)
1936 except BaseException as e:
1942 addrlist.append(addr)
1944 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1946 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1947 self.update_receive_tab()
1948 self.update_history_tab()
1951 def settings_dialog(self):
1953 d.setWindowTitle(_('Electrum Settings'))
1955 vbox = QVBoxLayout()
1957 tabs = QTabWidget(self)
1958 self.settings_tab = tabs
1959 vbox.addWidget(tabs)
1962 grid_ui = QGridLayout(tab1)
1963 grid_ui.setColumnStretch(0,1)
1964 tabs.addTab(tab1, _('Display') )
1966 nz_label = QLabel(_('Display zeros'))
1967 grid_ui.addWidget(nz_label, 0, 0)
1968 nz_e = AmountEdit(None,True)
1969 nz_e.setText("%d"% self.wallet.num_zeros)
1970 grid_ui.addWidget(nz_e, 0, 1)
1971 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1972 grid_ui.addWidget(HelpButton(msg), 0, 2)
1973 if not self.config.is_modifiable('num_zeros'):
1974 for w in [nz_e, nz_label]: w.setEnabled(False)
1976 lang_label=QLabel(_('Language') + ':')
1977 grid_ui.addWidget(lang_label, 1, 0)
1978 lang_combo = QComboBox()
1979 from i18n import languages
1980 lang_combo.addItems(languages.values())
1982 index = languages.keys().index(self.config.get("language",''))
1985 lang_combo.setCurrentIndex(index)
1986 grid_ui.addWidget(lang_combo, 1, 1)
1987 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1988 if not self.config.is_modifiable('language'):
1989 for w in [lang_combo, lang_label]: w.setEnabled(False)
1991 currencies = self.exchanger.get_currencies()
1992 currencies.insert(0, "None")
1994 cur_label=QLabel(_('Currency') + ':')
1995 grid_ui.addWidget(cur_label , 2, 0)
1996 cur_combo = QComboBox()
1997 cur_combo.addItems(currencies)
1999 index = currencies.index(self.config.get('currency', "None"))
2002 cur_combo.setCurrentIndex(index)
2003 grid_ui.addWidget(cur_combo, 2, 1)
2004 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
2006 expert_cb = QCheckBox(_('Expert mode'))
2007 expert_cb.setChecked(self.expert_mode)
2008 grid_ui.addWidget(expert_cb, 3, 0)
2009 hh = _('In expert mode, your client will:') + '\n' \
2010 + _(' - Show change addresses in the Receive tab') + '\n' \
2011 + _(' - Display the balance of each address') + '\n' \
2012 + _(' - Add freeze/prioritize actions to addresses.')
2013 grid_ui.addWidget(HelpButton(hh), 3, 2)
2014 grid_ui.setRowStretch(4,1)
2018 grid_wallet = QGridLayout(tab2)
2019 grid_wallet.setColumnStretch(0,1)
2020 tabs.addTab(tab2, _('Wallet') )
2022 fee_label = QLabel(_('Transaction fee'))
2023 grid_wallet.addWidget(fee_label, 0, 0)
2024 fee_e = AmountEdit(self.base_unit)
2025 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2026 grid_wallet.addWidget(fee_e, 0, 2)
2027 msg = _('Fee per kilobyte of transaction.') + ' ' \
2028 + _('Recommended value') + ': ' + self.format_amount(50000)
2029 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2030 if not self.config.is_modifiable('fee_per_kb'):
2031 for w in [fee_e, fee_label]: w.setEnabled(False)
2033 usechange_cb = QCheckBox(_('Use change addresses'))
2034 usechange_cb.setChecked(self.wallet.use_change)
2035 grid_wallet.addWidget(usechange_cb, 1, 0)
2036 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2037 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2039 gap_label = QLabel(_('Gap limit'))
2040 grid_wallet.addWidget(gap_label, 2, 0)
2041 gap_e = AmountEdit(None,True)
2042 gap_e.setText("%d"% self.wallet.gap_limit)
2043 grid_wallet.addWidget(gap_e, 2, 2)
2044 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2045 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2046 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2047 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2048 + _('Warning') + ': ' \
2049 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2050 + _('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'
2051 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2052 if not self.config.is_modifiable('gap_limit'):
2053 for w in [gap_e, gap_label]: w.setEnabled(False)
2055 units = ['BTC', 'mBTC']
2056 unit_label = QLabel(_('Base unit'))
2057 grid_wallet.addWidget(unit_label, 3, 0)
2058 unit_combo = QComboBox()
2059 unit_combo.addItems(units)
2060 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2061 grid_wallet.addWidget(unit_combo, 3, 2)
2062 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2063 + '\n1BTC=1000mBTC.\n' \
2064 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2065 grid_wallet.setRowStretch(4,1)
2069 tab5 = QScrollArea()
2070 tab5.setEnabled(True)
2071 tab5.setWidgetResizable(True)
2073 grid_plugins = QGridLayout()
2074 grid_plugins.setColumnStretch(0,1)
2077 w.setLayout(grid_plugins)
2080 w.setMinimumHeight(len(self.plugins)*35)
2082 tabs.addTab(tab5, _('Plugins') )
2083 def mk_toggle(cb, p):
2084 return lambda: cb.setChecked(p.toggle())
2085 for i, p in enumerate(self.plugins):
2087 cb = QCheckBox(p.fullname())
2088 cb.setDisabled(not p.is_available())
2089 cb.setChecked(p.is_enabled())
2090 cb.clicked.connect(mk_toggle(cb,p))
2091 grid_plugins.addWidget(cb, i, 0)
2092 if p.requires_settings():
2093 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2094 grid_plugins.addWidget(HelpButton(p.description()), i, 2)
2096 print_msg("Error: cannot display plugin", p)
2097 traceback.print_exc(file=sys.stdout)
2098 grid_plugins.setRowStretch(i+1,1)
2100 self.run_hook('create_settings_tab', tabs)
2102 vbox.addLayout(ok_cancel_buttons(d))
2106 if not d.exec_(): return
2108 fee = unicode(fee_e.text())
2110 fee = self.read_amount(fee)
2112 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2115 self.wallet.set_fee(fee)
2117 nz = unicode(nz_e.text())
2122 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2125 if self.wallet.num_zeros != nz:
2126 self.wallet.num_zeros = nz
2127 self.config.set_key('num_zeros', nz, True)
2128 self.update_history_tab()
2129 self.update_receive_tab()
2131 usechange_result = usechange_cb.isChecked()
2132 if self.wallet.use_change != usechange_result:
2133 self.wallet.use_change = usechange_result
2134 self.config.set_key('use_change', self.wallet.use_change, True)
2136 unit_result = units[unit_combo.currentIndex()]
2137 if self.base_unit() != unit_result:
2138 self.decimal_point = 8 if unit_result == 'BTC' else 5
2139 self.config.set_key('decimal_point', self.decimal_point, True)
2140 self.update_history_tab()
2141 self.update_status()
2144 n = int(gap_e.text())
2146 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2149 if self.wallet.gap_limit != n:
2150 r = self.wallet.change_gap_limit(n)
2152 self.update_receive_tab()
2153 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2155 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2157 need_restart = False
2159 lang_request = languages.keys()[lang_combo.currentIndex()]
2160 if lang_request != self.config.get('language'):
2161 self.config.set_key("language", lang_request, True)
2164 cur_request = str(currencies[cur_combo.currentIndex()])
2165 if cur_request != self.config.get('currency', "None"):
2166 self.config.set_key('currency', cur_request, True)
2167 self.update_wallet()
2169 self.run_hook('close_settings_dialog')
2172 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2174 self.receive_tab_set_mode(expert_cb.isChecked())
2176 def run_network_dialog(self):
2177 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2179 def closeEvent(self, event):
2181 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2182 self.save_column_widths()
2183 self.config.set_key("console-history", self.console.history[-50:], True)
2186 class OpenFileEventFilter(QObject):
2187 def __init__(self, windows):
2188 self.windows = windows
2189 super(OpenFileEventFilter, self).__init__()
2191 def eventFilter(self, obj, event):
2192 if event.type() == QtCore.QEvent.FileOpen:
2193 if len(self.windows) >= 1:
2194 self.windows[0].set_url(event.url().toString())
2203 def __init__(self, config, interface, app=None):
2204 self.interface = interface
2205 self.config = config
2207 self.efilter = OpenFileEventFilter(self.windows)
2209 self.app = QApplication(sys.argv)
2210 self.app.installEventFilter(self.efilter)
2213 def main(self, url):
2215 found = self.config.wallet_file_exists
2217 import installwizard
2218 wizard = installwizard.InstallWizard(self.config, self.interface)
2219 wallet = wizard.run()
2223 wallet = Wallet(self.config)
2225 wallet.interface = self.interface
2227 verifier = WalletVerifier(self.interface, self.config)
2229 wallet.set_verifier(verifier)
2230 synchronizer = WalletSynchronizer(wallet, self.config)
2231 synchronizer.start()
2235 w = ElectrumWindow(self.config)
2236 w.load_wallet(wallet)
2238 self.windows.append(w)
2239 if url: w.set_url(url)