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
46 from electrum import SimpleConfig, Wallet, WalletStorage
49 import bmp, pyqrnative
52 from amountedit import AmountEdit
53 from network_dialog import NetworkDialog
54 from qrcodewidget import QRCodeWidget
56 from decimal import Decimal
64 if platform.system() == 'Windows':
65 MONOSPACE_FONT = 'Lucida Console'
66 elif platform.system() == 'Darwin':
67 MONOSPACE_FONT = 'Monaco'
69 MONOSPACE_FONT = 'monospace'
71 from electrum import ELECTRUM_VERSION
76 class UpdateLabel(QLabel):
77 def __init__(self, config, parent=None):
78 QLabel.__init__(self, parent)
79 self.new_version = False
82 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
83 con.request("GET", "/version")
84 res = con.getresponse()
85 except socket.error as msg:
86 print_error("Could not retrieve version information")
90 self.latest_version = res.read()
91 self.latest_version = self.latest_version.replace("\n","")
92 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
94 self.current_version = ELECTRUM_VERSION
95 if(self.compare_versions(self.latest_version, self.current_version) == 1):
96 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
97 if(self.compare_versions(self.latest_version, latest_seen) == 1):
98 self.new_version = True
99 self.setText(_("New version available") + ": " + self.latest_version)
102 def compare_versions(self, version1, version2):
104 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
105 return cmp(normalize(version1), normalize(version2))
107 def ignore_this_version(self):
109 self.config.set_key("last_seen_version", self.latest_version, True)
110 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
113 def ignore_all_version(self):
115 self.config.set_key("last_seen_version", "9.9.9", True)
116 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
119 def open_website(self):
120 webbrowser.open("http://electrum.org/download.html")
123 def mouseReleaseEvent(self, event):
124 dialog = QDialog(self)
125 dialog.setWindowTitle(_('Electrum update'))
128 main_layout = QGridLayout()
129 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
131 ignore_version = QPushButton(_("Ignore this version"))
132 ignore_version.clicked.connect(self.ignore_this_version)
134 ignore_all_versions = QPushButton(_("Ignore all versions"))
135 ignore_all_versions.clicked.connect(self.ignore_all_version)
137 open_website = QPushButton(_("Goto download page"))
138 open_website.clicked.connect(self.open_website)
140 main_layout.addWidget(ignore_version, 1, 0)
141 main_layout.addWidget(ignore_all_versions, 1, 1)
142 main_layout.addWidget(open_website, 1, 2)
144 dialog.setLayout(main_layout)
148 if not dialog.exec_(): return
153 class MyTreeWidget(QTreeWidget):
154 def __init__(self, parent):
155 QTreeWidget.__init__(self, parent)
158 for i in range(0,self.viewport().height()/5):
159 if self.itemAt(QPoint(0,i*5)) == item:
163 for j in range(0,30):
164 if self.itemAt(QPoint(0,i*5 + j)) != item:
166 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
168 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
173 class StatusBarButton(QPushButton):
174 def __init__(self, icon, tooltip, func):
175 QPushButton.__init__(self, icon, '')
176 self.setToolTip(tooltip)
178 self.setMaximumWidth(25)
179 self.clicked.connect(func)
182 def keyPressEvent(self, e):
183 if e.key() == QtCore.Qt.Key_Return:
195 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive":[[370], [370,200,130]] }
197 class ElectrumWindow(QMainWindow):
198 def changeEvent(self, event):
199 flags = self.windowFlags();
200 if event and event.type() == QtCore.QEvent.WindowStateChange:
201 if self.windowState() & QtCore.Qt.WindowMinimized:
202 self.build_menu(True)
203 # The only way to toggle the icon in the window managers taskbar is to use the Qt.Tooltip flag
204 # The problem is that it somehow creates an (in)visible window that will stay active and prevent
205 # Electrum from closing.
206 # As for now I have no clue how to implement a proper 'hide to tray' functionality.
207 # self.setWindowFlags(flags & ~Qt.ToolTip)
208 elif event.oldState() & QtCore.Qt.WindowMinimized:
209 self.build_menu(False)
210 #self.setWindowFlags(flags | Qt.ToolTip)
212 def build_menu(self, is_hidden = False):
214 if self.isMinimized():
215 m.addAction(_("Show"), self.showNormal)
217 m.addAction(_("Hide"), self.showMinimized)
220 m.addAction(_("Exit Electrum"), self.close)
221 self.tray.setContextMenu(m)
223 def tray_activated(self, reason):
224 if reason == QSystemTrayIcon.DoubleClick:
228 def __init__(self, config):
229 QMainWindow.__init__(self)
234 self._close_electrum = False
236 self.current_account = self.config.get("current_account", None)
238 self.icon = QIcon(os.getcwd() + '/icons/electrum.png')
239 self.tray = QSystemTrayIcon(self.icon, self)
240 self.tray.setToolTip('Electrum')
241 self.tray.activated.connect(self.tray_activated)
245 self.create_status_bar()
247 self.need_update = threading.Event()
249 self.expert_mode = config.get('classic_expert_mode', False)
250 self.decimal_point = config.get('decimal_point', 8)
251 self.num_zeros = int(config.get('num_zeros',0))
253 set_language(config.get('language'))
255 self.funds_error = False
256 self.completions = QStringListModel()
258 self.tabs = tabs = QTabWidget(self)
259 self.column_widths = self.config.get("column_widths", default_column_widths )
260 tabs.addTab(self.create_history_tab(), _('History') )
261 tabs.addTab(self.create_send_tab(), _('Send') )
262 tabs.addTab(self.create_receive_tab(), _('Receive') )
263 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
264 tabs.addTab(self.create_console_tab(), _('Console') )
265 tabs.setMinimumSize(600, 400)
266 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
267 self.setCentralWidget(tabs)
269 g = self.config.get("winpos-qt",[100, 100, 840, 400])
270 self.setGeometry(g[0], g[1], g[2], g[3])
274 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
275 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
276 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
277 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
278 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
280 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
281 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
282 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
283 self.history_list.setFocus(True)
285 self.exchanger = exchange_rate.Exchanger(self)
286 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
288 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
289 if platform.system() == 'Windows':
290 n = 3 if self.wallet.seed else 2
291 tabs.setCurrentIndex (n)
292 tabs.setCurrentIndex (0)
294 # plugins that need to change the GUI do it here
295 self.run_hook('init')
300 def load_wallet(self, wallet):
304 self.wallet.interface.register_callback('updated', lambda: self.need_update.set())
305 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
306 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
307 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
308 self.wallet.interface.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
309 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
310 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
311 self.setWindowTitle( title )
313 # set initial message
314 self.console.showMessage(self.wallet.interface.banner)
315 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
316 self.notify_transactions()
319 accounts = self.wallet.get_accounts()
320 self.account_selector.clear()
321 if len(accounts) > 1:
322 self.account_selector.addItems([_("All accounts")] + accounts.values())
323 self.account_selector.setCurrentIndex(0)
324 self.account_selector.show()
326 self.account_selector.hide()
328 self.update_lock_icon()
329 self.update_buttons_on_seed()
330 self.update_console()
333 def select_wallet_file(self):
334 wallet_folder = self.wallet.storage.path
335 re.sub("(\/\w*.dat)$", "", wallet_folder)
336 file_name = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat") )
340 def open_wallet(self):
342 filename = self.select_wallet_file()
346 storage = WalletStorage({'wallet_path': filename})
347 if not storage.file_exists:
348 self.show_message("file not found "+ filename)
351 interface = self.wallet.interface
352 blockchain = self.wallet.verifier.blockchain
353 self.wallet.stop_threads()
356 wallet = Wallet(storage)
357 wallet.start_threads(interface, blockchain)
359 self.load_wallet(wallet)
362 def new_wallet(self):
365 wallet_folder = self.wallet.storage.path
366 re.sub("(\/\w*.dat)$", "", wallet_folder)
367 filename = self.getSaveFileName("Select your wallet file", wallet_folder, "*.dat")
369 storage = WalletStorage({'wallet_path': filename})
370 assert not storage.file_exists
372 wizard = installwizard.InstallWizard(self.config, self.wallet.interface, self.wallet.verifier.blockchain, storage)
373 wallet = wizard.run()
375 self.load_wallet(wallet)
379 def init_menubar(self):
382 file_menu = menubar.addMenu(_("&File"))
383 open_wallet_action = file_menu.addAction(_("&Open"))
384 open_wallet_action.triggered.connect(self.open_wallet)
386 new_wallet_action = file_menu.addAction(_("&Create/Restore"))
387 new_wallet_action.triggered.connect(self.new_wallet)
389 wallet_backup = file_menu.addAction(_("&Copy"))
390 wallet_backup.triggered.connect(lambda: backup_wallet(self.wallet.storage.path))
392 quit_item = file_menu.addAction(_("&Close"))
393 quit_item.triggered.connect(self.close)
395 wallet_menu = menubar.addMenu(_("&Wallet"))
397 # Settings / Preferences are all reserved keywords in OSX using this as work around
398 preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
399 preferences_menu = wallet_menu.addAction(preferences_name)
400 preferences_menu.triggered.connect(self.settings_dialog)
402 wallet_menu.addSeparator()
404 raw_transaction_menu = wallet_menu.addMenu(_("&Load raw transaction"))
406 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
407 raw_transaction_file.triggered.connect(self.do_process_from_file)
409 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
410 raw_transaction_text.triggered.connect(self.do_process_from_text)
412 wallet_menu.addSeparator()
414 show_menu = wallet_menu.addMenu(_("Show"))
416 #if self.wallet.seed:
417 show_seed = show_menu.addAction(_("&Seed"))
418 show_seed.triggered.connect(self.show_seed_dialog)
420 show_mpk = show_menu.addAction(_("&Master Public Key"))
421 show_mpk.triggered.connect(self.show_master_public_key)
423 wallet_menu.addSeparator()
424 new_contact = wallet_menu.addAction(_("&New contact"))
425 new_contact.triggered.connect(self.new_contact_dialog)
427 new_account = wallet_menu.addAction(_("&New account"))
428 new_account.triggered.connect(self.new_account_dialog)
430 import_menu = menubar.addMenu(_("&Import"))
431 in_labels = import_menu.addAction(_("&Labels"))
432 in_labels.triggered.connect(self.do_import_labels)
434 in_private_keys = import_menu.addAction(_("&Private keys"))
435 in_private_keys.triggered.connect(self.do_import_privkey)
437 export_menu = menubar.addMenu(_("&Export"))
438 ex_private_keys = export_menu.addAction(_("&Private keys"))
439 ex_private_keys.triggered.connect(self.do_export_privkeys)
441 ex_history = export_menu.addAction(_("&History"))
442 ex_history.triggered.connect(self.do_export_history)
444 ex_labels = export_menu.addAction(_("&Labels"))
445 ex_labels.triggered.connect(self.do_export_labels)
447 help_menu = menubar.addMenu(_("&Help"))
448 doc_open = help_menu.addAction(_("&Documentation"))
449 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
450 web_open = help_menu.addAction(_("&Official website"))
451 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
453 self.setMenuBar(menubar)
457 def notify_transactions(self):
458 print_error("Notifying GUI")
459 if len(self.wallet.interface.pending_transactions_for_notifications) > 0:
460 # Combine the transactions if there are more then three
461 tx_amount = len(self.wallet.interface.pending_transactions_for_notifications)
464 for tx in self.wallet.interface.pending_transactions_for_notifications:
465 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
469 self.notify("%s new transactions received. Total amount received in the new transactions %s %s" \
470 % (tx_amount, self.format_amount(total_amount), self.base_unit()))
472 self.wallet.interface.pending_transactions_for_notifications = []
474 for tx in self.wallet.interface.pending_transactions_for_notifications:
476 self.wallet.interface.pending_transactions_for_notifications.remove(tx)
477 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
479 self.notify("New transaction received. %s %s" % (self.format_amount(v), self.base_unit()))
481 def notify(self, message):
482 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
485 def init_plugins(self):
486 import imp, pkgutil, __builtin__
487 if __builtin__.use_local_modules:
488 fp, pathname, description = imp.find_module('plugins')
489 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
490 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
491 imp.load_module('electrum_plugins', fp, pathname, description)
492 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
494 import electrum_plugins
495 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
496 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
499 for name, p in zip(plugin_names, plugins):
501 self.plugins.append( p.Plugin(self, name) )
503 print_msg("Error:cannot initialize plugin",p)
504 traceback.print_exc(file=sys.stdout)
507 def run_hook(self, name, *args):
508 for p in self.plugins:
509 if not p.is_enabled():
518 print_error("Plugin error")
519 traceback.print_exc(file=sys.stdout)
525 def set_label(self, name, text = None):
527 old_text = self.wallet.labels.get(name)
530 self.wallet.labels[name] = text
531 self.wallet.storage.put('labels', self.wallet.labels)
535 self.wallet.labels.pop(name)
537 self.run_hook('set_label', name, text, changed)
541 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
542 def getOpenFileName(self, title, filter = None):
543 directory = self.config.get('io_dir', os.path.expanduser('~'))
544 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
545 if fileName and directory != os.path.dirname(fileName):
546 self.config.set_key('io_dir', os.path.dirname(fileName), True)
549 def getSaveFileName(self, title, filename, filter = None):
550 directory = self.config.get('io_dir', os.path.expanduser('~'))
551 path = os.path.join( directory, filename )
552 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
553 if fileName and directory != os.path.dirname(fileName):
554 self.config.set_key('io_dir', os.path.dirname(fileName), True)
560 QMainWindow.close(self)
561 self.run_hook('close_main_window')
563 def connect_slots(self, sender):
564 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
565 self.previous_payto_e=''
567 def timer_actions(self):
568 if self.need_update.is_set():
570 self.need_update.clear()
571 self.run_hook('timer_actions')
573 def format_amount(self, x, is_diff=False, whitespaces=False):
574 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
576 def read_amount(self, x):
577 if x in['.', '']: return None
578 p = pow(10, self.decimal_point)
579 return int( p * Decimal(x) )
582 assert self.decimal_point in [5,8]
583 return "BTC" if self.decimal_point == 8 else "mBTC"
585 def update_status(self):
586 if self.wallet.interface and self.wallet.interface.is_connected:
587 if not self.wallet.up_to_date:
588 text = _("Synchronizing...")
589 icon = QIcon(":icons/status_waiting.png")
591 c, u = self.wallet.get_account_balance(self.current_account)
592 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
593 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
594 text += self.create_quote_text(Decimal(c+u)/100000000)
595 self.tray.setToolTip(text)
596 icon = QIcon(":icons/status_connected.png")
598 text = _("Not connected")
599 icon = QIcon(":icons/status_disconnected.png")
601 self.balance_label.setText(text)
602 self.status_button.setIcon( icon )
604 def update_wallet(self):
606 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
607 self.update_history_tab()
608 self.update_receive_tab()
609 self.update_contacts_tab()
610 self.update_completions()
613 def create_quote_text(self, btc_balance):
614 quote_currency = self.config.get("currency", "None")
615 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
616 if quote_balance is None:
619 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
622 def create_history_tab(self):
623 self.history_list = l = MyTreeWidget(self)
625 for i,width in enumerate(self.column_widths['history']):
626 l.setColumnWidth(i, width)
627 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
628 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
629 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
631 l.setContextMenuPolicy(Qt.CustomContextMenu)
632 l.customContextMenuRequested.connect(self.create_history_menu)
636 def create_history_menu(self, position):
637 self.history_list.selectedIndexes()
638 item = self.history_list.currentItem()
640 tx_hash = str(item.data(0, Qt.UserRole).toString())
641 if not tx_hash: return
643 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
644 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
645 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
646 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
649 def show_tx_details(self, tx):
650 dialog = QDialog(self)
652 dialog.setWindowTitle(_("Transaction Details"))
654 dialog.setLayout(vbox)
655 dialog.setMinimumSize(600,300)
658 if tx_hash in self.wallet.transactions.keys():
659 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
660 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
662 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
668 vbox.addWidget(QLabel("Transaction ID:"))
669 e = QLineEdit(tx_hash)
673 vbox.addWidget(QLabel("Date: %s"%time_str))
674 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
677 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
678 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
680 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
681 vbox.addWidget(QLabel("Transaction fee: unknown"))
683 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
685 vbox.addWidget( self.generate_transaction_information_widget(tx) )
687 ok_button = QPushButton(_("Close"))
688 ok_button.setDefault(True)
689 ok_button.clicked.connect(dialog.accept)
693 hbox.addWidget(ok_button)
697 def tx_label_clicked(self, item, column):
698 if column==2 and item.isSelected():
700 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
701 self.history_list.editItem( item, column )
702 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
705 def tx_label_changed(self, item, column):
709 tx_hash = str(item.data(0, Qt.UserRole).toString())
710 tx = self.wallet.transactions.get(tx_hash)
711 text = unicode( item.text(2) )
712 self.set_label(tx_hash, text)
714 item.setForeground(2, QBrush(QColor('black')))
716 text = self.wallet.get_default_label(tx_hash)
717 item.setText(2, text)
718 item.setForeground(2, QBrush(QColor('gray')))
722 def edit_label(self, is_recv):
723 l = self.receive_list if is_recv else self.contacts_list
724 item = l.currentItem()
725 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
726 l.editItem( item, 1 )
727 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
731 def address_label_clicked(self, item, column, l, column_addr, column_label):
732 if column == column_label and item.isSelected():
733 is_editable = item.data(0, 32).toBool()
736 addr = unicode( item.text(column_addr) )
737 label = unicode( item.text(column_label) )
738 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
739 l.editItem( item, column )
740 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
743 def address_label_changed(self, item, column, l, column_addr, column_label):
744 if column == column_label:
745 addr = unicode( item.text(column_addr) )
746 text = unicode( item.text(column_label) )
747 is_editable = item.data(0, 32).toBool()
751 changed = self.set_label(addr, text)
753 self.update_history_tab()
754 self.update_completions()
756 self.current_item_changed(item)
758 self.run_hook('item_changed', item, column)
761 def current_item_changed(self, a):
762 self.run_hook('current_item_changed', a)
766 def update_history_tab(self):
768 self.history_list.clear()
769 for item in self.wallet.get_tx_history(self.current_account):
770 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
773 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
778 time_str = 'unverified'
779 icon = QIcon(":icons/unconfirmed.png")
782 icon = QIcon(":icons/unconfirmed.png")
784 icon = QIcon(":icons/clock%d.png"%conf)
786 icon = QIcon(":icons/confirmed.png")
788 if value is not None:
789 v_str = self.format_amount(value, True, whitespaces=True)
793 balance_str = self.format_amount(balance, whitespaces=True)
796 label, is_default_label = self.wallet.get_label(tx_hash)
798 label = _('Pruned transaction outputs')
799 is_default_label = False
801 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
802 item.setFont(2, QFont(MONOSPACE_FONT))
803 item.setFont(3, QFont(MONOSPACE_FONT))
804 item.setFont(4, QFont(MONOSPACE_FONT))
806 item.setForeground(3, QBrush(QColor("#BC1E1E")))
808 item.setData(0, Qt.UserRole, tx_hash)
809 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
811 item.setForeground(2, QBrush(QColor('grey')))
813 item.setIcon(0, icon)
814 self.history_list.insertTopLevelItem(0,item)
817 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
820 def create_send_tab(self):
825 grid.setColumnMinimumWidth(3,300)
826 grid.setColumnStretch(5,1)
829 self.payto_e = QLineEdit()
830 grid.addWidget(QLabel(_('Pay to')), 1, 0)
831 grid.addWidget(self.payto_e, 1, 1, 1, 3)
833 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)
835 completer = QCompleter()
836 completer.setCaseSensitivity(False)
837 self.payto_e.setCompleter(completer)
838 completer.setModel(self.completions)
840 self.message_e = QLineEdit()
841 grid.addWidget(QLabel(_('Description')), 2, 0)
842 grid.addWidget(self.message_e, 2, 1, 1, 3)
843 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)
845 self.amount_e = AmountEdit(self.base_unit)
846 grid.addWidget(QLabel(_('Amount')), 3, 0)
847 grid.addWidget(self.amount_e, 3, 1, 1, 2)
848 grid.addWidget(HelpButton(
849 _('Amount to be sent.') + '\n\n' \
850 + _('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.') \
851 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
853 self.fee_e = AmountEdit(self.base_unit)
854 grid.addWidget(QLabel(_('Fee')), 4, 0)
855 grid.addWidget(self.fee_e, 4, 1, 1, 2)
856 grid.addWidget(HelpButton(
857 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
858 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
859 + _('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)
862 self.send_button = EnterButton(_("Send"), self.do_send)
863 grid.addWidget(self.send_button, 6, 1)
865 b = EnterButton(_("Clear"),self.do_clear)
866 grid.addWidget(b, 6, 2)
868 self.payto_sig = QLabel('')
869 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
871 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
872 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
881 def entry_changed( is_fee ):
882 self.funds_error = False
884 if self.amount_e.is_shortcut:
885 self.amount_e.is_shortcut = False
886 c, u = self.wallet.get_account_balance(self.current_account)
887 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
888 fee = self.wallet.estimated_fee(inputs)
890 self.amount_e.setText( self.format_amount(amount) )
891 self.fee_e.setText( self.format_amount( fee ) )
894 amount = self.read_amount(str(self.amount_e.text()))
895 fee = self.read_amount(str(self.fee_e.text()))
897 if not is_fee: fee = None
900 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
902 self.fee_e.setText( self.format_amount( fee ) )
905 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
909 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
910 self.funds_error = True
911 text = _( "Not enough funds" )
912 c, u = self.wallet.get_frozen_balance()
913 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
915 self.statusBar().showMessage(text)
916 self.amount_e.setPalette(palette)
917 self.fee_e.setPalette(palette)
919 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
920 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
922 self.run_hook('create_send_tab', grid)
926 def update_completions(self):
928 for addr,label in self.wallet.labels.items():
929 if addr in self.wallet.addressbook:
930 l.append( label + ' <' + addr + '>')
932 self.run_hook('update_completions', l)
933 self.completions.setStringList(l)
937 return lambda s, *args: s.do_protect(func, args)
942 label = unicode( self.message_e.text() )
943 r = unicode( self.payto_e.text() )
946 # label or alias, with address in brackets
947 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
948 to_address = m.group(2) if m else r
950 if not is_valid(to_address):
951 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
955 amount = self.read_amount(unicode( self.amount_e.text()))
957 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
960 fee = self.read_amount(unicode( self.fee_e.text()))
962 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
965 confirm_amount = self.config.get('confirm_amount', 100000000)
966 if amount >= confirm_amount:
967 if not self.question("send %s to %s?"%(self.format_amount(amount) + ' '+ self.base_unit(), to_address)):
970 self.send_tx(to_address, amount, fee, label)
974 def send_tx(self, to_address, amount, fee, label, password):
977 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
978 except BaseException, e:
979 traceback.print_exc(file=sys.stdout)
980 self.show_message(str(e))
983 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
984 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
987 self.run_hook('send_tx', tx)
990 self.set_label(tx.hash(), label)
993 h = self.wallet.send_tx(tx)
994 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
995 status, msg = self.wallet.receive_tx( h )
997 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
999 self.update_contacts_tab()
1001 QMessageBox.warning(self, _('Error'), msg, _('OK'))
1003 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
1005 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
1006 with open(fileName,'w') as f:
1007 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1008 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
1010 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
1015 def set_url(self, url):
1016 address, amount, label, message, signature, identity, url = util.parse_url(url)
1017 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
1019 if label and self.wallet.labels.get(address) != label:
1020 if self.question('Give label "%s" to address %s ?'%(label,address)):
1021 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
1022 self.wallet.addressbook.append(address)
1023 self.set_label(address, label)
1025 self.run_hook('set_url', url, self.show_message, self.question)
1027 self.tabs.setCurrentIndex(1)
1028 label = self.wallet.labels.get(address)
1029 m_addr = label + ' <'+ address +'>' if label else address
1030 self.payto_e.setText(m_addr)
1032 self.message_e.setText(message)
1033 self.amount_e.setText(amount)
1035 self.set_frozen(self.payto_e,True)
1036 self.set_frozen(self.amount_e,True)
1037 self.set_frozen(self.message_e,True)
1038 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
1040 self.payto_sig.setVisible(False)
1043 self.payto_sig.setVisible(False)
1044 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1046 self.set_frozen(e,False)
1047 self.update_status()
1049 def set_frozen(self,entry,frozen):
1051 entry.setReadOnly(True)
1052 entry.setFrame(False)
1053 palette = QPalette()
1054 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1055 entry.setPalette(palette)
1057 entry.setReadOnly(False)
1058 entry.setFrame(True)
1059 palette = QPalette()
1060 palette.setColor(entry.backgroundRole(), QColor('white'))
1061 entry.setPalette(palette)
1064 def toggle_freeze(self,addr):
1066 if addr in self.wallet.frozen_addresses:
1067 self.wallet.unfreeze(addr)
1069 self.wallet.freeze(addr)
1070 self.update_receive_tab()
1072 def toggle_priority(self,addr):
1074 if addr in self.wallet.prioritized_addresses:
1075 self.wallet.unprioritize(addr)
1077 self.wallet.prioritize(addr)
1078 self.update_receive_tab()
1081 def create_list_tab(self, headers):
1082 "generic tab creation method"
1083 l = MyTreeWidget(self)
1084 l.setColumnCount( len(headers) )
1085 l.setHeaderLabels( headers )
1088 vbox = QVBoxLayout()
1095 vbox.addWidget(buttons)
1097 hbox = QHBoxLayout()
1100 buttons.setLayout(hbox)
1105 def create_receive_tab(self):
1106 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1107 l.setContextMenuPolicy(Qt.CustomContextMenu)
1108 l.customContextMenuRequested.connect(self.create_receive_menu)
1109 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1110 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1111 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1112 self.receive_list = l
1113 self.receive_buttons_hbox = hbox
1118 def receive_tab_set_mode(self, i):
1119 self.save_column_widths()
1120 self.expert_mode = (i == 1)
1121 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1122 self.update_receive_tab()
1125 def save_column_widths(self):
1126 if not self.expert_mode:
1127 widths = [ self.receive_list.columnWidth(0) ]
1130 for i in range(self.receive_list.columnCount() -1):
1131 widths.append(self.receive_list.columnWidth(i))
1132 self.column_widths["receive"][self.expert_mode] = widths
1134 self.column_widths["history"] = []
1135 for i in range(self.history_list.columnCount() - 1):
1136 self.column_widths["history"].append(self.history_list.columnWidth(i))
1138 self.column_widths["contacts"] = []
1139 for i in range(self.contacts_list.columnCount() - 1):
1140 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1142 self.config.set_key("column_widths", self.column_widths, True)
1145 def create_contacts_tab(self):
1146 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1147 l.setContextMenuPolicy(Qt.CustomContextMenu)
1148 l.customContextMenuRequested.connect(self.create_contact_menu)
1149 for i,width in enumerate(self.column_widths['contacts']):
1150 l.setColumnWidth(i, width)
1152 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1153 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1154 self.contacts_list = l
1155 self.contacts_buttons_hbox = hbox
1160 def delete_imported_key(self, addr):
1161 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1162 self.wallet.delete_imported_key(addr)
1163 self.update_receive_tab()
1164 self.update_history_tab()
1167 def create_receive_menu(self, position):
1168 # fixme: this function apparently has a side effect.
1169 # if it is not called the menu pops up several times
1170 #self.receive_list.selectedIndexes()
1172 item = self.receive_list.itemAt(position)
1174 addr = unicode(item.text(0))
1175 if not is_valid(addr):
1176 item.setExpanded(not item.isExpanded())
1179 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1180 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1181 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1182 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1183 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1184 if addr in self.wallet.imported_keys:
1185 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1187 if self.expert_mode:
1188 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1189 menu.addAction(t, lambda: self.toggle_freeze(addr))
1190 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1191 menu.addAction(t, lambda: self.toggle_priority(addr))
1193 self.run_hook('receive_menu', menu)
1194 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1197 def payto(self, addr):
1199 label = self.wallet.labels.get(addr)
1200 m_addr = label + ' <' + addr + '>' if label else addr
1201 self.tabs.setCurrentIndex(1)
1202 self.payto_e.setText(m_addr)
1203 self.amount_e.setFocus()
1206 def delete_contact(self, x):
1207 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1208 self.wallet.delete_contact(x)
1209 self.set_label(x, None)
1210 self.update_history_tab()
1211 self.update_contacts_tab()
1212 self.update_completions()
1215 def create_contact_menu(self, position):
1216 item = self.contacts_list.itemAt(position)
1218 addr = unicode(item.text(0))
1219 label = unicode(item.text(1))
1220 is_editable = item.data(0,32).toBool()
1221 payto_addr = item.data(0,33).toString()
1223 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1224 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1225 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1227 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1228 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1230 self.run_hook('create_contact_menu', menu, item)
1231 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1234 def update_receive_item(self, item):
1235 item.setFont(0, QFont(MONOSPACE_FONT))
1236 address = str(item.data(0,0).toString())
1237 label = self.wallet.labels.get(address,'')
1238 item.setData(1,0,label)
1239 item.setData(0,32, True) # is editable
1241 self.run_hook('update_receive_item', address, item)
1243 c, u = self.wallet.get_addr_balance(address)
1244 balance = self.format_amount(c + u)
1245 item.setData(2,0,balance)
1247 if self.expert_mode:
1248 if address in self.wallet.frozen_addresses:
1249 item.setBackgroundColor(0, QColor('lightblue'))
1250 elif address in self.wallet.prioritized_addresses:
1251 item.setBackgroundColor(0, QColor('lightgreen'))
1254 def update_receive_tab(self):
1255 l = self.receive_list
1258 l.setColumnHidden(2, not self.expert_mode)
1259 l.setColumnHidden(3, not self.expert_mode)
1260 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1261 l.setColumnWidth(i, width)
1263 if self.current_account is None:
1264 account_items = self.wallet.accounts.items()
1265 elif self.current_account != -1:
1266 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1270 for k, account in account_items:
1271 name = self.wallet.labels.get(k, 'unnamed account')
1272 c,u = self.wallet.get_account_balance(k)
1273 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1274 l.addTopLevelItem(account_item)
1275 account_item.setExpanded(True)
1277 for is_change in ([0,1] if self.expert_mode else [0]):
1278 if self.expert_mode:
1279 name = "Receiving" if not is_change else "Change"
1280 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1281 account_item.addChild(seq_item)
1282 if not is_change: seq_item.setExpanded(True)
1284 seq_item = account_item
1288 for address in account.get_addresses(is_change):
1289 h = self.wallet.history.get(address,[])
1293 if gap > self.wallet.gap_limit:
1298 num_tx = '*' if h == ['*'] else "%d"%len(h)
1299 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1300 self.update_receive_item(item)
1302 item.setBackgroundColor(1, QColor('red'))
1303 seq_item.addChild(item)
1306 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1307 c,u = self.wallet.get_imported_balance()
1308 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1309 l.addTopLevelItem(account_item)
1310 account_item.setExpanded(True)
1311 for address in self.wallet.imported_keys.keys():
1312 item = QTreeWidgetItem( [ address, '', '', ''] )
1313 self.update_receive_item(item)
1314 account_item.addChild(item)
1317 # we use column 1 because column 0 may be hidden
1318 l.setCurrentItem(l.topLevelItem(0),1)
1321 def update_contacts_tab(self):
1322 l = self.contacts_list
1325 for address in self.wallet.addressbook:
1326 label = self.wallet.labels.get(address,'')
1327 n = self.wallet.get_num_tx(address)
1328 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1329 item.setFont(0, QFont(MONOSPACE_FONT))
1330 # 32 = label can be edited (bool)
1331 item.setData(0,32, True)
1333 item.setData(0,33, address)
1334 l.addTopLevelItem(item)
1336 self.run_hook('update_contacts_tab', l)
1337 l.setCurrentItem(l.topLevelItem(0))
1341 def create_console_tab(self):
1342 from qt_console import Console
1343 self.console = console = Console()
1347 def update_console(self):
1348 console = self.console
1349 console.history = self.config.get("console-history",[])
1350 console.history_index = len(console.history)
1352 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1353 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1355 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1357 def mkfunc(f, method):
1358 return lambda *args: apply( f, (method, args, self.password_dialog ))
1360 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1361 methods[m] = mkfunc(c._run, m)
1363 console.updateNamespace(methods)
1366 def change_account(self,s):
1367 if s == _("All accounts"):
1368 self.current_account = None
1370 accounts = self.wallet.get_accounts()
1371 for k, v in accounts.items():
1373 self.current_account = k
1374 self.update_history_tab()
1375 self.update_status()
1376 self.update_receive_tab()
1378 def create_status_bar(self):
1381 sb.setFixedHeight(35)
1382 qtVersion = qVersion()
1384 self.balance_label = QLabel("")
1385 sb.addWidget(self.balance_label)
1387 update_notification = UpdateLabel(self.config)
1388 if(update_notification.new_version):
1389 sb.addPermanentWidget(update_notification)
1391 self.account_selector = QComboBox()
1392 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1393 sb.addPermanentWidget(self.account_selector)
1395 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1396 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1398 self.lock_icon = QIcon()
1399 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1400 sb.addPermanentWidget( self.password_button )
1402 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1403 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1404 sb.addPermanentWidget( self.seed_button )
1405 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1406 sb.addPermanentWidget( self.status_button )
1408 self.run_hook('create_status_bar', (sb,))
1410 self.setStatusBar(sb)
1413 def update_lock_icon(self):
1414 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1415 self.password_button.setIcon( icon )
1418 def update_buttons_on_seed(self):
1419 if self.wallet.seed:
1420 self.seed_button.show()
1421 self.password_button.show()
1422 self.send_button.setText(_("Send"))
1424 self.password_button.hide()
1425 self.seed_button.hide()
1426 self.send_button.setText(_("Create unsigned transaction"))
1429 def change_password_dialog(self):
1430 from password_dialog import PasswordDialog
1431 d = PasswordDialog(self.wallet, self)
1433 self.update_lock_icon()
1438 self.config.set_key('gui', 'lite', True)
1441 self.lite.mini.show()
1443 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1444 self.lite.main(None)
1447 def new_contact_dialog(self):
1448 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1449 address = unicode(text)
1451 if is_valid(address):
1452 self.wallet.add_contact(address)
1453 self.update_contacts_tab()
1454 self.update_history_tab()
1455 self.update_completions()
1457 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1460 def new_account_dialog(self):
1462 dialog = QDialog(self)
1464 dialog.setWindowTitle(_("New Account"))
1466 addr = self.wallet.new_account_address()
1467 vbox = QVBoxLayout()
1468 vbox.addWidget(QLabel(_("To create a new account, please send coins to the first address of that account:")))
1473 ok_button = QPushButton(_("OK"))
1474 ok_button.setDefault(True)
1475 ok_button.clicked.connect(dialog.accept)
1477 hbox = QHBoxLayout()
1479 hbox.addWidget(ok_button)
1480 vbox.addLayout(hbox)
1482 dialog.setLayout(vbox)
1487 def show_master_public_key(self):
1488 dialog = QDialog(self)
1490 dialog.setWindowTitle(_("Master Public Key"))
1492 main_text = QTextEdit()
1493 main_text.setText(self.wallet.get_master_public_key())
1494 main_text.setReadOnly(True)
1495 main_text.setMaximumHeight(170)
1496 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1498 ok_button = QPushButton(_("OK"))
1499 ok_button.setDefault(True)
1500 ok_button.clicked.connect(dialog.accept)
1502 main_layout = QGridLayout()
1503 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1505 main_layout.addWidget(main_text, 1, 0)
1506 main_layout.addWidget(qrw, 1, 1 )
1508 vbox = QVBoxLayout()
1509 vbox.addLayout(main_layout)
1510 hbox = QHBoxLayout()
1512 hbox.addWidget(ok_button)
1513 vbox.addLayout(hbox)
1515 dialog.setLayout(vbox)
1520 def show_seed_dialog(self, password):
1521 if not self.wallet.seed:
1522 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1525 seed = self.wallet.decode_seed(password)
1527 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1530 from seed_dialog import SeedDialog
1531 d = SeedDialog(self)
1532 d.show_seed(seed, self.wallet.imported_keys)
1536 def show_qrcode(self, data, title = "QR code"):
1540 d.setWindowTitle(title)
1541 d.setMinimumSize(270, 300)
1542 vbox = QVBoxLayout()
1543 qrw = QRCodeWidget(data)
1544 vbox.addWidget(qrw, 1)
1545 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1546 hbox = QHBoxLayout()
1550 filename = "qrcode.bmp"
1551 bmp.save_qrcode(qrw.qr, filename)
1552 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1554 b = QPushButton(_("Save"))
1556 b.clicked.connect(print_qr)
1558 b = QPushButton(_("Close"))
1560 b.clicked.connect(d.accept)
1563 vbox.addLayout(hbox)
1568 def do_protect(self, func, args):
1569 if self.wallet.use_encryption:
1570 password = self.password_dialog()
1576 if args != (False,):
1577 args = (self,) + args + (password,)
1579 args = (self,password)
1584 def show_private_key(self, address, password):
1585 if not address: return
1587 pk = self.wallet.get_private_key(address, password)
1588 except BaseException, e:
1589 self.show_message(str(e))
1591 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1595 def do_sign(self, address, message, signature, password):
1597 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1598 signature.setText(sig)
1599 except BaseException, e:
1600 self.show_message(str(e))
1602 def sign_message(self, address):
1603 if not address: return
1606 d.setWindowTitle(_('Sign Message'))
1607 d.setMinimumSize(410, 290)
1609 tab_widget = QTabWidget()
1611 layout = QGridLayout(tab)
1613 sign_address = QLineEdit()
1615 sign_address.setText(address)
1616 layout.addWidget(QLabel(_('Address')), 1, 0)
1617 layout.addWidget(sign_address, 1, 1)
1619 sign_message = QTextEdit()
1620 layout.addWidget(QLabel(_('Message')), 2, 0)
1621 layout.addWidget(sign_message, 2, 1)
1622 layout.setRowStretch(2,3)
1624 sign_signature = QTextEdit()
1625 layout.addWidget(QLabel(_('Signature')), 3, 0)
1626 layout.addWidget(sign_signature, 3, 1)
1627 layout.setRowStretch(3,1)
1630 hbox = QHBoxLayout()
1631 b = QPushButton(_("Sign"))
1633 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1634 b = QPushButton(_("Close"))
1635 b.clicked.connect(d.accept)
1637 layout.addLayout(hbox, 4, 1)
1638 tab_widget.addTab(tab, _("Sign"))
1642 layout = QGridLayout(tab)
1644 verify_address = QLineEdit()
1645 layout.addWidget(QLabel(_('Address')), 1, 0)
1646 layout.addWidget(verify_address, 1, 1)
1648 verify_message = QTextEdit()
1649 layout.addWidget(QLabel(_('Message')), 2, 0)
1650 layout.addWidget(verify_message, 2, 1)
1651 layout.setRowStretch(2,3)
1653 verify_signature = QTextEdit()
1654 layout.addWidget(QLabel(_('Signature')), 3, 0)
1655 layout.addWidget(verify_signature, 3, 1)
1656 layout.setRowStretch(3,1)
1659 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1660 self.show_message(_("Signature verified"))
1662 self.show_message(_("Error: wrong signature"))
1664 hbox = QHBoxLayout()
1665 b = QPushButton(_("Verify"))
1666 b.clicked.connect(do_verify)
1668 b = QPushButton(_("Close"))
1669 b.clicked.connect(d.accept)
1671 layout.addLayout(hbox, 4, 1)
1672 tab_widget.addTab(tab, _("Verify"))
1674 vbox = QVBoxLayout()
1675 vbox.addWidget(tab_widget)
1682 def question(self, msg):
1683 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1685 def show_message(self, msg):
1686 QMessageBox.information(self, _('Message'), msg, _('OK'))
1688 def password_dialog(self ):
1695 vbox = QVBoxLayout()
1696 msg = _('Please enter your password')
1697 vbox.addWidget(QLabel(msg))
1699 grid = QGridLayout()
1701 grid.addWidget(QLabel(_('Password')), 1, 0)
1702 grid.addWidget(pw, 1, 1)
1703 vbox.addLayout(grid)
1705 vbox.addLayout(ok_cancel_buttons(d))
1708 self.run_hook('password_dialog', pw, grid, 1)
1709 if not d.exec_(): return
1710 return unicode(pw.text())
1717 def generate_transaction_information_widget(self, tx):
1718 tabs = QTabWidget(self)
1721 grid_ui = QGridLayout(tab1)
1722 grid_ui.setColumnStretch(0,1)
1723 tabs.addTab(tab1, _('Outputs') )
1725 tree_widget = MyTreeWidget(self)
1726 tree_widget.setColumnCount(2)
1727 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1728 tree_widget.setColumnWidth(0, 300)
1729 tree_widget.setColumnWidth(1, 50)
1731 for address, value in tx.outputs:
1732 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1733 tree_widget.addTopLevelItem(item)
1735 tree_widget.setMaximumHeight(100)
1737 grid_ui.addWidget(tree_widget)
1740 grid_ui = QGridLayout(tab2)
1741 grid_ui.setColumnStretch(0,1)
1742 tabs.addTab(tab2, _('Inputs') )
1744 tree_widget = MyTreeWidget(self)
1745 tree_widget.setColumnCount(2)
1746 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1748 for input_line in tx.inputs:
1749 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1750 tree_widget.addTopLevelItem(item)
1752 tree_widget.setMaximumHeight(100)
1754 grid_ui.addWidget(tree_widget)
1758 def tx_dict_from_text(self, txt):
1760 tx_dict = json.loads(str(txt))
1761 assert "hex" in tx_dict.keys()
1762 assert "complete" in tx_dict.keys()
1763 if not tx_dict["complete"]:
1764 assert "input_info" in tx_dict.keys()
1766 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1771 def read_tx_from_file(self):
1772 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1776 with open(fileName, "r") as f:
1777 file_content = f.read()
1778 except (ValueError, IOError, os.error), reason:
1779 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1781 return self.tx_dict_from_text(file_content)
1785 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1787 self.wallet.signrawtransaction(tx, input_info, [], password)
1789 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1791 with open(fileName, "w+") as f:
1792 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1793 self.show_message(_("Transaction saved successfully"))
1796 except BaseException, e:
1797 self.show_message(str(e))
1800 def send_raw_transaction(self, raw_tx, dialog = ""):
1801 result, result_message = self.wallet.sendtx( raw_tx )
1803 self.show_message("Transaction successfully sent: %s" % (result_message))
1807 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1809 def do_process_from_text(self):
1810 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1813 tx_dict = self.tx_dict_from_text(text)
1815 self.create_process_transaction_window(tx_dict)
1817 def do_process_from_file(self):
1818 tx_dict = self.read_tx_from_file()
1820 self.create_process_transaction_window(tx_dict)
1822 def create_process_transaction_window(self, tx_dict):
1823 tx = Transaction(tx_dict["hex"])
1825 dialog = QDialog(self)
1826 dialog.setMinimumWidth(500)
1827 dialog.setWindowTitle(_('Process raw transaction'))
1833 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1834 l.addWidget(QLabel(_("Actions")), 4,0)
1836 if tx_dict["complete"] == False:
1837 l.addWidget(QLabel(_("Unsigned")), 3,1)
1838 if self.wallet.seed :
1839 b = QPushButton("Sign transaction")
1840 input_info = json.loads(tx_dict["input_info"])
1841 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1842 l.addWidget(b, 4, 1)
1844 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1846 l.addWidget(QLabel(_("Signed")), 3,1)
1847 b = QPushButton("Broadcast transaction")
1848 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1851 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1852 cancelButton = QPushButton(_("Cancel"))
1853 cancelButton.clicked.connect(lambda: dialog.done(0))
1854 l.addWidget(cancelButton, 4,2)
1860 def do_export_privkeys(self, password):
1861 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.")))
1864 select_export = _('Select file to export your private keys to')
1865 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1867 with open(fileName, "w+") as csvfile:
1868 transaction = csv.writer(csvfile)
1869 transaction.writerow(["address", "private_key"])
1872 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1873 transaction.writerow(["%34s"%addr,pk])
1875 self.show_message(_("Private keys exported."))
1877 except (IOError, os.error), reason:
1878 export_error_label = _("Electrum was unable to produce a private key-export.")
1879 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1881 except BaseException, e:
1882 self.show_message(str(e))
1886 def do_import_labels(self):
1887 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1888 if not labelsFile: return
1890 f = open(labelsFile, 'r')
1893 for key, value in json.loads(data).items():
1894 self.wallet.labels[key] = value
1896 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1897 except (IOError, os.error), reason:
1898 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1901 def do_export_labels(self):
1902 labels = self.wallet.labels
1904 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1906 with open(fileName, 'w+') as f:
1907 json.dump(labels, f)
1908 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1909 except (IOError, os.error), reason:
1910 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1913 def do_export_history(self):
1914 from gui_lite import csv_transaction
1915 csv_transaction(self.wallet)
1919 def do_import_privkey(self, password):
1920 if not self.wallet.imported_keys:
1921 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1922 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1923 + _('Are you sure you understand what you are doing?'), 3, 4)
1926 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1929 text = str(text).split()
1934 addr = self.wallet.import_key(key, password)
1935 except BaseException as e:
1941 addrlist.append(addr)
1943 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1945 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1946 self.update_receive_tab()
1947 self.update_history_tab()
1950 def settings_dialog(self):
1952 d.setWindowTitle(_('Electrum Settings'))
1954 vbox = QVBoxLayout()
1956 tabs = QTabWidget(self)
1957 self.settings_tab = tabs
1958 vbox.addWidget(tabs)
1961 grid_ui = QGridLayout(tab1)
1962 grid_ui.setColumnStretch(0,1)
1963 tabs.addTab(tab1, _('Display') )
1965 nz_label = QLabel(_('Display zeros'))
1966 grid_ui.addWidget(nz_label, 0, 0)
1967 nz_e = AmountEdit(None,True)
1968 nz_e.setText("%d"% self.num_zeros)
1969 grid_ui.addWidget(nz_e, 0, 1)
1970 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1971 grid_ui.addWidget(HelpButton(msg), 0, 2)
1972 if not self.config.is_modifiable('num_zeros'):
1973 for w in [nz_e, nz_label]: w.setEnabled(False)
1975 lang_label=QLabel(_('Language') + ':')
1976 grid_ui.addWidget(lang_label, 1, 0)
1977 lang_combo = QComboBox()
1978 from i18n import languages
1979 lang_combo.addItems(languages.values())
1981 index = languages.keys().index(self.config.get("language",''))
1984 lang_combo.setCurrentIndex(index)
1985 grid_ui.addWidget(lang_combo, 1, 1)
1986 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1987 if not self.config.is_modifiable('language'):
1988 for w in [lang_combo, lang_label]: w.setEnabled(False)
1990 currencies = self.exchanger.get_currencies()
1991 currencies.insert(0, "None")
1993 cur_label=QLabel(_('Currency') + ':')
1994 grid_ui.addWidget(cur_label , 2, 0)
1995 cur_combo = QComboBox()
1996 cur_combo.addItems(currencies)
1998 index = currencies.index(self.config.get('currency', "None"))
2001 cur_combo.setCurrentIndex(index)
2002 grid_ui.addWidget(cur_combo, 2, 1)
2003 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
2005 expert_cb = QCheckBox(_('Expert mode'))
2006 expert_cb.setChecked(self.expert_mode)
2007 grid_ui.addWidget(expert_cb, 3, 0)
2008 hh = _('In expert mode, your client will:') + '\n' \
2009 + _(' - Show change addresses in the Receive tab') + '\n' \
2010 + _(' - Display the balance of each address') + '\n' \
2011 + _(' - Add freeze/prioritize actions to addresses.')
2012 grid_ui.addWidget(HelpButton(hh), 3, 2)
2013 grid_ui.setRowStretch(4,1)
2017 grid_wallet = QGridLayout(tab2)
2018 grid_wallet.setColumnStretch(0,1)
2019 tabs.addTab(tab2, _('Wallet') )
2021 fee_label = QLabel(_('Transaction fee'))
2022 grid_wallet.addWidget(fee_label, 0, 0)
2023 fee_e = AmountEdit(self.base_unit)
2024 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2025 grid_wallet.addWidget(fee_e, 0, 2)
2026 msg = _('Fee per kilobyte of transaction.') + ' ' \
2027 + _('Recommended value') + ': ' + self.format_amount(50000)
2028 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2029 if not self.config.is_modifiable('fee_per_kb'):
2030 for w in [fee_e, fee_label]: w.setEnabled(False)
2032 usechange_cb = QCheckBox(_('Use change addresses'))
2033 usechange_cb.setChecked(self.wallet.use_change)
2034 grid_wallet.addWidget(usechange_cb, 1, 0)
2035 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2036 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2038 gap_label = QLabel(_('Gap limit'))
2039 grid_wallet.addWidget(gap_label, 2, 0)
2040 gap_e = AmountEdit(None,True)
2041 gap_e.setText("%d"% self.wallet.gap_limit)
2042 grid_wallet.addWidget(gap_e, 2, 2)
2043 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2044 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2045 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2046 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2047 + _('Warning') + ': ' \
2048 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2049 + _('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'
2050 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2051 if not self.config.is_modifiable('gap_limit'):
2052 for w in [gap_e, gap_label]: w.setEnabled(False)
2054 units = ['BTC', 'mBTC']
2055 unit_label = QLabel(_('Base unit'))
2056 grid_wallet.addWidget(unit_label, 3, 0)
2057 unit_combo = QComboBox()
2058 unit_combo.addItems(units)
2059 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2060 grid_wallet.addWidget(unit_combo, 3, 2)
2061 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2062 + '\n1BTC=1000mBTC.\n' \
2063 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2064 grid_wallet.setRowStretch(4,1)
2068 tab5 = QScrollArea()
2069 tab5.setEnabled(True)
2070 tab5.setWidgetResizable(True)
2072 grid_plugins = QGridLayout()
2073 grid_plugins.setColumnStretch(0,1)
2076 w.setLayout(grid_plugins)
2079 w.setMinimumHeight(len(self.plugins)*35)
2081 tabs.addTab(tab5, _('Plugins') )
2082 def mk_toggle(cb, p):
2083 return lambda: cb.setChecked(p.toggle())
2084 for i, p in enumerate(self.plugins):
2086 cb = QCheckBox(p.fullname())
2087 cb.setDisabled(not p.is_available())
2088 cb.setChecked(p.is_enabled())
2089 cb.clicked.connect(mk_toggle(cb,p))
2090 grid_plugins.addWidget(cb, i, 0)
2091 if p.requires_settings():
2092 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2093 grid_plugins.addWidget(HelpButton(p.description()), i, 2)
2095 print_msg("Error: cannot display plugin", p)
2096 traceback.print_exc(file=sys.stdout)
2097 grid_plugins.setRowStretch(i+1,1)
2099 self.run_hook('create_settings_tab', tabs)
2101 vbox.addLayout(ok_cancel_buttons(d))
2105 if not d.exec_(): return
2107 fee = unicode(fee_e.text())
2109 fee = self.read_amount(fee)
2111 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2114 self.wallet.set_fee(fee)
2116 nz = unicode(nz_e.text())
2121 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2124 if self.num_zeros != nz:
2126 self.config.set_key('num_zeros', nz, True)
2127 self.update_history_tab()
2128 self.update_receive_tab()
2130 usechange_result = usechange_cb.isChecked()
2131 if self.wallet.use_change != usechange_result:
2132 self.wallet.use_change = usechange_result
2133 self.config.set_key('use_change', self.wallet.use_change, True)
2135 unit_result = units[unit_combo.currentIndex()]
2136 if self.base_unit() != unit_result:
2137 self.decimal_point = 8 if unit_result == 'BTC' else 5
2138 self.config.set_key('decimal_point', self.decimal_point, True)
2139 self.update_history_tab()
2140 self.update_status()
2143 n = int(gap_e.text())
2145 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2148 if self.wallet.gap_limit != n:
2149 r = self.wallet.change_gap_limit(n)
2151 self.update_receive_tab()
2152 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2154 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2156 need_restart = False
2158 lang_request = languages.keys()[lang_combo.currentIndex()]
2159 if lang_request != self.config.get('language'):
2160 self.config.set_key("language", lang_request, True)
2163 cur_request = str(currencies[cur_combo.currentIndex()])
2164 if cur_request != self.config.get('currency', "None"):
2165 self.config.set_key('currency', cur_request, True)
2166 self.update_wallet()
2168 self.run_hook('close_settings_dialog')
2171 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2173 self.receive_tab_set_mode(expert_cb.isChecked())
2175 def run_network_dialog(self):
2176 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2178 def closeEvent(self, event):
2180 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2181 self.save_column_widths()
2182 self.config.set_key("console-history", self.console.history[-50:], True)
2185 class OpenFileEventFilter(QObject):
2186 def __init__(self, windows):
2187 self.windows = windows
2188 super(OpenFileEventFilter, self).__init__()
2190 def eventFilter(self, obj, event):
2191 if event.type() == QtCore.QEvent.FileOpen:
2192 if len(self.windows) >= 1:
2193 self.windows[0].set_url(event.url().toString())
2202 def __init__(self, config, interface, blockchain, app=None):
2203 self.interface = interface
2204 self.config = config
2205 self.blockchain = blockchain
2207 self.efilter = OpenFileEventFilter(self.windows)
2209 self.app = QApplication(sys.argv)
2210 self.app.installEventFilter(self.efilter)
2213 def main(self, url):
2215 storage = WalletStorage(self.config)
2216 if not storage.file_exists:
2217 import installwizard
2218 wizard = installwizard.InstallWizard(self.config, self.interface, self.blockchain, storage)
2219 wallet = wizard.run()
2223 wallet = Wallet(storage)
2225 wallet.start_threads(self.interface, self.blockchain)
2229 w = ElectrumWindow(self.config)
2230 w.load_wallet(wallet)
2232 self.windows.append(w)
2233 if url: w.set_url(url)
2241 wallet.stop_threads()