3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re, threading
20 from i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
30 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
32 from PyQt4.QtGui import *
33 from PyQt4.QtCore import *
34 import PyQt4.QtCore as QtCore
36 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
41 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'")
43 from electrum.wallet import format_satoshis
44 from electrum import Transaction
45 from electrum import mnemonic
46 from electrum import util, bitcoin, commands, Interface, Wallet
47 from electrum import SimpleConfig, Wallet, WalletStorage
50 import bmp, pyqrnative
53 from amountedit import AmountEdit
54 from network_dialog import NetworkDialog
55 from qrcodewidget import QRCodeWidget
57 from decimal import Decimal
65 if platform.system() == 'Windows':
66 MONOSPACE_FONT = 'Lucida Console'
67 elif platform.system() == 'Darwin':
68 MONOSPACE_FONT = 'Monaco'
70 MONOSPACE_FONT = 'monospace'
72 from electrum import ELECTRUM_VERSION
78 class MyTreeWidget(QTreeWidget):
79 def __init__(self, parent):
80 QTreeWidget.__init__(self, parent)
83 for i in range(0,self.viewport().height()/5):
84 if self.itemAt(QPoint(0,i*5)) == item:
89 if self.itemAt(QPoint(0,i*5 + j)) != item:
91 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
93 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
98 class StatusBarButton(QPushButton):
99 def __init__(self, icon, tooltip, func):
100 QPushButton.__init__(self, icon, '')
101 self.setToolTip(tooltip)
103 self.setMaximumWidth(25)
104 self.clicked.connect(func)
106 self.setIconSize(QSize(25,25))
108 def keyPressEvent(self, e):
109 if e.key() == QtCore.Qt.Key_Return:
121 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive":[[370], [370,200,130]] }
123 class ElectrumWindow(QMainWindow):
124 def changeEvent(self, event):
125 flags = self.windowFlags();
126 if event and event.type() == QtCore.QEvent.WindowStateChange:
127 if self.windowState() & QtCore.Qt.WindowMinimized:
128 self.build_menu(True)
129 # The only way to toggle the icon in the window managers taskbar is to use the Qt.Tooltip flag
130 # The problem is that it somehow creates an (in)visible window that will stay active and prevent
131 # Electrum from closing.
132 # As for now I have no clue how to implement a proper 'hide to tray' functionality.
133 # self.setWindowFlags(flags & ~Qt.ToolTip)
134 elif event.oldState() & QtCore.Qt.WindowMinimized:
135 self.build_menu(False)
136 #self.setWindowFlags(flags | Qt.ToolTip)
138 def build_menu(self, is_hidden = False):
140 if self.isMinimized():
141 m.addAction(_("Show"), self.showNormal)
143 m.addAction(_("Hide"), self.showMinimized)
146 m.addAction(_("Exit Electrum"), self.close)
147 self.tray.setContextMenu(m)
149 def tray_activated(self, reason):
150 if reason == QSystemTrayIcon.DoubleClick:
154 def __init__(self, config, network):
155 QMainWindow.__init__(self)
158 self.network = network
161 self._close_electrum = False
163 self.current_account = self.config.get("current_account", None)
165 self.icon = QIcon(os.getcwd() + '/icons/electrum.png')
166 self.tray = QSystemTrayIcon(self.icon, self)
167 self.tray.setToolTip('Electrum')
168 self.tray.activated.connect(self.tray_activated)
172 self.create_status_bar()
174 self.need_update = threading.Event()
176 self.expert_mode = config.get('classic_expert_mode', False)
177 self.decimal_point = config.get('decimal_point', 8)
178 self.num_zeros = int(config.get('num_zeros',0))
180 set_language(config.get('language'))
182 self.funds_error = False
183 self.completions = QStringListModel()
185 self.tabs = tabs = QTabWidget(self)
186 self.column_widths = self.config.get("column_widths", default_column_widths )
187 tabs.addTab(self.create_history_tab(), _('History') )
188 tabs.addTab(self.create_send_tab(), _('Send') )
189 tabs.addTab(self.create_receive_tab(), _('Receive') )
190 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
191 tabs.addTab(self.create_console_tab(), _('Console') )
192 tabs.setMinimumSize(600, 400)
193 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
194 self.setCentralWidget(tabs)
196 g = self.config.get("winpos-qt",[100, 100, 840, 400])
197 self.setGeometry(g[0], g[1], g[2], g[3])
201 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
202 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
203 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
204 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
205 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
207 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
208 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
209 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
211 self.history_list.setFocus(True)
213 self.exchanger = exchange_rate.Exchanger(self)
214 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
216 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
217 if platform.system() == 'Windows':
218 n = 3 if self.wallet.seed else 2
219 tabs.setCurrentIndex (n)
220 tabs.setCurrentIndex (0)
222 # plugins that need to change the GUI do it here
223 self.run_hook('init')
228 def load_wallet(self, wallet):
232 self.network.register_callback('updated', lambda: self.need_update.set())
233 self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
234 self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
235 self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
236 self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
237 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
238 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
239 self.setWindowTitle( title )
241 # set initial message
242 self.console.showMessage(self.wallet.interface.banner)
243 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
244 self.notify_transactions()
247 accounts = self.wallet.get_account_names()
248 self.account_selector.clear()
249 if len(accounts) > 1:
250 self.account_selector.addItems([_("All accounts")] + accounts.values())
251 self.account_selector.setCurrentIndex(0)
252 self.account_selector.show()
254 self.account_selector.hide()
256 self.new_account.setEnabled(self.wallet.seed_version>4)
258 self.update_lock_icon()
259 self.update_buttons_on_seed()
260 self.update_console()
262 self.run_hook('load_wallet')
265 def select_wallet_file(self):
266 wallet_folder = self.wallet.storage.path
267 re.sub("(\/\w*.dat)$", "", wallet_folder)
268 file_name = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat") )
272 def open_wallet(self):
274 filename = self.select_wallet_file()
278 storage = WalletStorage({'wallet_path': filename})
279 if not storage.file_exists:
280 self.show_message("file not found "+ filename)
283 interface = self.wallet.interface
284 blockchain = self.wallet.verifier.blockchain
285 self.wallet.stop_threads()
288 wallet = Wallet(storage)
289 wallet.start_threads(interface, blockchain)
291 self.load_wallet(wallet)
294 def new_wallet(self):
297 wallet_folder = self.wallet.storage.path
298 re.sub("(\/\w*.dat)$", "", wallet_folder)
299 filename = self.getSaveFileName("Select your wallet file", wallet_folder, "*.dat")
301 storage = WalletStorage({'wallet_path': filename})
302 assert not storage.file_exists
304 wizard = installwizard.InstallWizard(self.config, self.wallet.interface, self.wallet.verifier.blockchain, storage)
305 wallet = wizard.run()
307 self.load_wallet(wallet)
311 def init_menubar(self):
314 file_menu = menubar.addMenu(_("&File"))
315 open_wallet_action = file_menu.addAction(_("&Open"))
316 open_wallet_action.triggered.connect(self.open_wallet)
318 new_wallet_action = file_menu.addAction(_("&Create/Restore"))
319 new_wallet_action.triggered.connect(self.new_wallet)
321 wallet_backup = file_menu.addAction(_("&Copy"))
322 wallet_backup.triggered.connect(lambda: backup_wallet(self.wallet.storage.path))
324 quit_item = file_menu.addAction(_("&Close"))
325 quit_item.triggered.connect(self.close)
327 wallet_menu = menubar.addMenu(_("&Wallet"))
329 # Settings / Preferences are all reserved keywords in OSX using this as work around
330 preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
331 preferences_menu = wallet_menu.addAction(preferences_name)
332 preferences_menu.triggered.connect(self.settings_dialog)
334 wallet_menu.addSeparator()
336 raw_transaction_menu = wallet_menu.addMenu(_("&Load raw transaction"))
338 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
339 raw_transaction_file.triggered.connect(self.do_process_from_file)
341 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
342 raw_transaction_text.triggered.connect(self.do_process_from_text)
344 csv_transaction_menu = wallet_menu.addMenu(_("&Load CSV transaction"))
346 csv_transaction_file = csv_transaction_menu.addAction(_("&From file"))
347 csv_transaction_file.triggered.connect(self.do_process_from_csv_file)
349 csv_transaction_text = csv_transaction_menu.addAction(_("&From text"))
350 csv_transaction_text.triggered.connect(self.do_process_from_csv_text)
352 wallet_menu.addSeparator()
354 show_menu = wallet_menu.addMenu(_("Show"))
356 #if self.wallet.seed:
357 show_seed = show_menu.addAction(_("&Seed"))
358 show_seed.triggered.connect(self.show_seed_dialog)
360 show_mpk = show_menu.addAction(_("&Master Public Key"))
361 show_mpk.triggered.connect(self.show_master_public_key)
363 wallet_menu.addSeparator()
364 new_contact = wallet_menu.addAction(_("&New contact"))
365 new_contact.triggered.connect(self.new_contact_dialog)
367 self.new_account = wallet_menu.addAction(_("&New account"))
368 self.new_account.triggered.connect(self.new_account_dialog)
370 import_menu = menubar.addMenu(_("&Import"))
371 in_labels = import_menu.addAction(_("&Labels"))
372 in_labels.triggered.connect(self.do_import_labels)
374 in_private_keys = import_menu.addAction(_("&Private keys"))
375 in_private_keys.triggered.connect(self.do_import_privkey)
377 export_menu = menubar.addMenu(_("&Export"))
378 ex_private_keys = export_menu.addAction(_("&Private keys"))
379 ex_private_keys.triggered.connect(self.do_export_privkeys)
381 ex_history = export_menu.addAction(_("&History"))
382 ex_history.triggered.connect(self.do_export_history)
384 ex_labels = export_menu.addAction(_("&Labels"))
385 ex_labels.triggered.connect(self.do_export_labels)
387 help_menu = menubar.addMenu(_("&Help"))
388 doc_open = help_menu.addAction(_("&Documentation"))
389 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
390 web_open = help_menu.addAction(_("&Official website"))
391 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
393 self.setMenuBar(menubar)
397 def notify_transactions(self):
398 print_error("Notifying GUI")
399 if len(self.wallet.interface.pending_transactions_for_notifications) > 0:
400 # Combine the transactions if there are more then three
401 tx_amount = len(self.wallet.interface.pending_transactions_for_notifications)
404 for tx in self.wallet.interface.pending_transactions_for_notifications:
405 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
409 self.notify("%s new transactions received. Total amount received in the new transactions %s %s" \
410 % (tx_amount, self.format_amount(total_amount), self.base_unit()))
412 self.wallet.interface.pending_transactions_for_notifications = []
414 for tx in self.wallet.interface.pending_transactions_for_notifications:
416 self.wallet.interface.pending_transactions_for_notifications.remove(tx)
417 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
419 self.notify("New transaction received. %s %s" % (self.format_amount(v), self.base_unit()))
421 def notify(self, message):
422 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
425 def init_plugins(self):
426 import imp, pkgutil, __builtin__
427 if __builtin__.use_local_modules:
428 fp, pathname, description = imp.find_module('plugins')
429 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
430 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
431 imp.load_module('electrum_plugins', fp, pathname, description)
432 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
434 import electrum_plugins
435 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
436 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
439 for name, p in zip(plugin_names, plugins):
441 self.plugins.append( p.Plugin(self, name) )
443 print_msg("Error:cannot initialize plugin",p)
444 traceback.print_exc(file=sys.stdout)
447 def run_hook(self, name, *args):
448 for p in self.plugins:
449 if not p.is_enabled():
458 print_error("Plugin error")
459 traceback.print_exc(file=sys.stdout)
465 def set_label(self, name, text = None):
467 old_text = self.wallet.labels.get(name)
470 self.wallet.labels[name] = text
471 self.wallet.storage.put('labels', self.wallet.labels)
475 self.wallet.labels.pop(name)
477 self.run_hook('set_label', name, text, changed)
481 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
482 def getOpenFileName(self, title, filter = None):
483 directory = self.config.get('io_dir', os.path.expanduser('~'))
484 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
485 if fileName and directory != os.path.dirname(fileName):
486 self.config.set_key('io_dir', os.path.dirname(fileName), True)
489 def getSaveFileName(self, title, filename, filter = None):
490 directory = self.config.get('io_dir', os.path.expanduser('~'))
491 path = os.path.join( directory, filename )
492 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
493 if fileName and directory != os.path.dirname(fileName):
494 self.config.set_key('io_dir', os.path.dirname(fileName), True)
498 QMainWindow.close(self)
499 self.run_hook('close_main_window')
501 def connect_slots(self, sender):
502 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
503 self.previous_payto_e=''
505 def timer_actions(self):
506 if self.need_update.is_set():
508 self.need_update.clear()
509 self.run_hook('timer_actions')
511 def format_amount(self, x, is_diff=False, whitespaces=False):
512 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
514 def read_amount(self, x):
515 if x in['.', '']: return None
516 p = pow(10, self.decimal_point)
517 return int( p * Decimal(x) )
520 assert self.decimal_point in [5,8]
521 return "BTC" if self.decimal_point == 8 else "mBTC"
523 def update_status(self):
524 if self.wallet.interface and self.wallet.interface.is_connected:
525 if not self.wallet.up_to_date:
526 text = _("Synchronizing...")
527 icon = QIcon(":icons/status_waiting.png")
529 c, u = self.wallet.get_account_balance(self.current_account)
530 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
531 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
532 text += self.create_quote_text(Decimal(c+u)/100000000)
533 self.tray.setToolTip(text)
534 icon = QIcon(":icons/status_connected.png")
536 text = _("Not connected")
537 icon = QIcon(":icons/status_disconnected.png")
539 self.balance_label.setText(text)
540 self.status_button.setIcon( icon )
542 def update_wallet(self):
544 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
545 self.update_history_tab()
546 self.update_receive_tab()
547 self.update_contacts_tab()
548 self.update_completions()
551 def create_quote_text(self, btc_balance):
552 quote_currency = self.config.get("currency", "None")
553 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
554 if quote_balance is None:
557 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
560 def create_history_tab(self):
561 self.history_list = l = MyTreeWidget(self)
563 for i,width in enumerate(self.column_widths['history']):
564 l.setColumnWidth(i, width)
565 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
566 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
567 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
569 l.setContextMenuPolicy(Qt.CustomContextMenu)
570 l.customContextMenuRequested.connect(self.create_history_menu)
574 def create_history_menu(self, position):
575 self.history_list.selectedIndexes()
576 item = self.history_list.currentItem()
578 tx_hash = str(item.data(0, Qt.UserRole).toString())
579 if not tx_hash: return
581 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
582 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
583 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
584 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
587 def show_tx_details(self, tx):
588 dialog = QDialog(self)
590 dialog.setWindowTitle(_("Transaction Details"))
592 dialog.setLayout(vbox)
593 dialog.setMinimumSize(600,300)
596 if tx_hash in self.wallet.transactions.keys():
597 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
598 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
600 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
606 vbox.addWidget(QLabel("Transaction ID:"))
607 e = QLineEdit(tx_hash)
611 vbox.addWidget(QLabel("Date: %s"%time_str))
612 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
615 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
616 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
618 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
619 vbox.addWidget(QLabel("Transaction fee: unknown"))
621 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
623 vbox.addWidget( self.generate_transaction_information_widget(tx) )
625 ok_button = QPushButton(_("Close"))
626 ok_button.setDefault(True)
627 ok_button.clicked.connect(dialog.accept)
631 hbox.addWidget(ok_button)
635 def tx_label_clicked(self, item, column):
636 if column==2 and item.isSelected():
638 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
639 self.history_list.editItem( item, column )
640 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
643 def tx_label_changed(self, item, column):
647 tx_hash = str(item.data(0, Qt.UserRole).toString())
648 tx = self.wallet.transactions.get(tx_hash)
649 text = unicode( item.text(2) )
650 self.set_label(tx_hash, text)
652 item.setForeground(2, QBrush(QColor('black')))
654 text = self.wallet.get_default_label(tx_hash)
655 item.setText(2, text)
656 item.setForeground(2, QBrush(QColor('gray')))
660 def edit_label(self, is_recv):
661 l = self.receive_list if is_recv else self.contacts_list
662 item = l.currentItem()
663 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
664 l.editItem( item, 1 )
665 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
669 def address_label_clicked(self, item, column, l, column_addr, column_label):
670 if column == column_label and item.isSelected():
671 is_editable = item.data(0, 32).toBool()
674 addr = unicode( item.text(column_addr) )
675 label = unicode( item.text(column_label) )
676 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
677 l.editItem( item, column )
678 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
681 def address_label_changed(self, item, column, l, column_addr, column_label):
682 if column == column_label:
683 addr = unicode( item.text(column_addr) )
684 text = unicode( item.text(column_label) )
685 is_editable = item.data(0, 32).toBool()
689 changed = self.set_label(addr, text)
691 self.update_history_tab()
692 self.update_completions()
694 self.current_item_changed(item)
696 self.run_hook('item_changed', item, column)
699 def current_item_changed(self, a):
700 self.run_hook('current_item_changed', a)
704 def update_history_tab(self):
706 self.history_list.clear()
707 for item in self.wallet.get_tx_history(self.current_account):
708 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
711 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
716 time_str = 'unverified'
717 icon = QIcon(":icons/unconfirmed.png")
720 icon = QIcon(":icons/unconfirmed.png")
722 icon = QIcon(":icons/clock%d.png"%conf)
724 icon = QIcon(":icons/confirmed.png")
726 if value is not None:
727 v_str = self.format_amount(value, True, whitespaces=True)
731 balance_str = self.format_amount(balance, whitespaces=True)
734 label, is_default_label = self.wallet.get_label(tx_hash)
736 label = _('Pruned transaction outputs')
737 is_default_label = False
739 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
740 item.setFont(2, QFont(MONOSPACE_FONT))
741 item.setFont(3, QFont(MONOSPACE_FONT))
742 item.setFont(4, QFont(MONOSPACE_FONT))
744 item.setForeground(3, QBrush(QColor("#BC1E1E")))
746 item.setData(0, Qt.UserRole, tx_hash)
747 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
749 item.setForeground(2, QBrush(QColor('grey')))
751 item.setIcon(0, icon)
752 self.history_list.insertTopLevelItem(0,item)
755 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
758 def create_send_tab(self):
763 grid.setColumnMinimumWidth(3,300)
764 grid.setColumnStretch(5,1)
767 self.payto_e = QLineEdit()
768 grid.addWidget(QLabel(_('Pay to')), 1, 0)
769 grid.addWidget(self.payto_e, 1, 1, 1, 3)
771 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)
773 completer = QCompleter()
774 completer.setCaseSensitivity(False)
775 self.payto_e.setCompleter(completer)
776 completer.setModel(self.completions)
778 self.message_e = QLineEdit()
779 grid.addWidget(QLabel(_('Description')), 2, 0)
780 grid.addWidget(self.message_e, 2, 1, 1, 3)
781 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)
783 self.amount_e = AmountEdit(self.base_unit)
784 grid.addWidget(QLabel(_('Amount')), 3, 0)
785 grid.addWidget(self.amount_e, 3, 1, 1, 2)
786 grid.addWidget(HelpButton(
787 _('Amount to be sent.') + '\n\n' \
788 + _('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.') \
789 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
791 self.fee_e = AmountEdit(self.base_unit)
792 grid.addWidget(QLabel(_('Fee')), 4, 0)
793 grid.addWidget(self.fee_e, 4, 1, 1, 2)
794 grid.addWidget(HelpButton(
795 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
796 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
797 + _('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)
800 self.send_button = EnterButton(_("Send"), self.do_send)
801 grid.addWidget(self.send_button, 6, 1)
803 b = EnterButton(_("Clear"),self.do_clear)
804 grid.addWidget(b, 6, 2)
806 self.payto_sig = QLabel('')
807 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
809 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
810 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
819 def entry_changed( is_fee ):
820 self.funds_error = False
822 if self.amount_e.is_shortcut:
823 self.amount_e.is_shortcut = False
824 c, u = self.wallet.get_account_balance(self.current_account)
825 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
826 fee = self.wallet.estimated_fee(inputs)
828 self.amount_e.setText( self.format_amount(amount) )
829 self.fee_e.setText( self.format_amount( fee ) )
832 amount = self.read_amount(str(self.amount_e.text()))
833 fee = self.read_amount(str(self.fee_e.text()))
835 if not is_fee: fee = None
838 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
840 self.fee_e.setText( self.format_amount( fee ) )
843 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
847 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
848 self.funds_error = True
849 text = _( "Not enough funds" )
850 c, u = self.wallet.get_frozen_balance()
851 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
853 self.statusBar().showMessage(text)
854 self.amount_e.setPalette(palette)
855 self.fee_e.setPalette(palette)
857 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
858 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
860 self.run_hook('create_send_tab', grid)
864 def update_completions(self):
866 for addr,label in self.wallet.labels.items():
867 if addr in self.wallet.addressbook:
868 l.append( label + ' <' + addr + '>')
870 self.run_hook('update_completions', l)
871 self.completions.setStringList(l)
875 return lambda s, *args: s.do_protect(func, args)
880 label = unicode( self.message_e.text() )
881 r = unicode( self.payto_e.text() )
884 # label or alias, with address in brackets
885 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
886 to_address = m.group(2) if m else r
888 if not is_valid(to_address):
889 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
893 amount = self.read_amount(unicode( self.amount_e.text()))
895 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
898 fee = self.read_amount(unicode( self.fee_e.text()))
900 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
903 confirm_amount = self.config.get('confirm_amount', 100000000)
904 if amount >= confirm_amount:
905 if not self.question("send %s to %s?"%(self.format_amount(amount) + ' '+ self.base_unit(), to_address)):
908 self.send_tx(to_address, amount, fee, label)
912 def send_tx(self, to_address, amount, fee, label, password):
915 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
916 except BaseException, e:
917 traceback.print_exc(file=sys.stdout)
918 self.show_message(str(e))
921 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
922 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
925 self.run_hook('send_tx', tx)
928 self.set_label(tx.hash(), label)
931 h = self.wallet.send_tx(tx)
932 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
933 status, msg = self.wallet.receive_tx( h )
935 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
937 self.update_contacts_tab()
939 QMessageBox.warning(self, _('Error'), msg, _('OK'))
941 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
943 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
944 with open(fileName,'w') as f:
945 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
946 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
948 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
950 # add recipient to addressbook
951 if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
952 self.wallet.addressbook.append(to_address)
957 def set_url(self, url):
958 address, amount, label, message, signature, identity, url = util.parse_url(url)
959 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
961 if label and self.wallet.labels.get(address) != label:
962 if self.question('Give label "%s" to address %s ?'%(label,address)):
963 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
964 self.wallet.addressbook.append(address)
965 self.set_label(address, label)
967 self.run_hook('set_url', url, self.show_message, self.question)
969 self.tabs.setCurrentIndex(1)
970 label = self.wallet.labels.get(address)
971 m_addr = label + ' <'+ address +'>' if label else address
972 self.payto_e.setText(m_addr)
974 self.message_e.setText(message)
975 self.amount_e.setText(amount)
977 self.set_frozen(self.payto_e,True)
978 self.set_frozen(self.amount_e,True)
979 self.set_frozen(self.message_e,True)
980 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
982 self.payto_sig.setVisible(False)
985 self.payto_sig.setVisible(False)
986 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
988 self.set_frozen(e,False)
991 def set_frozen(self,entry,frozen):
993 entry.setReadOnly(True)
994 entry.setFrame(False)
996 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
997 entry.setPalette(palette)
999 entry.setReadOnly(False)
1000 entry.setFrame(True)
1001 palette = QPalette()
1002 palette.setColor(entry.backgroundRole(), QColor('white'))
1003 entry.setPalette(palette)
1006 def toggle_freeze(self,addr):
1008 if addr in self.wallet.frozen_addresses:
1009 self.wallet.unfreeze(addr)
1011 self.wallet.freeze(addr)
1012 self.update_receive_tab()
1014 def toggle_priority(self,addr):
1016 if addr in self.wallet.prioritized_addresses:
1017 self.wallet.unprioritize(addr)
1019 self.wallet.prioritize(addr)
1020 self.update_receive_tab()
1023 def create_list_tab(self, headers):
1024 "generic tab creation method"
1025 l = MyTreeWidget(self)
1026 l.setColumnCount( len(headers) )
1027 l.setHeaderLabels( headers )
1030 vbox = QVBoxLayout()
1037 vbox.addWidget(buttons)
1039 hbox = QHBoxLayout()
1042 buttons.setLayout(hbox)
1047 def create_receive_tab(self):
1048 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1049 l.setContextMenuPolicy(Qt.CustomContextMenu)
1050 l.customContextMenuRequested.connect(self.create_receive_menu)
1051 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1052 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1053 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1054 self.receive_list = l
1055 self.receive_buttons_hbox = hbox
1060 def receive_tab_set_mode(self, i):
1061 self.save_column_widths()
1062 self.expert_mode = (i == 1)
1063 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1064 self.update_receive_tab()
1067 def save_column_widths(self):
1068 if not self.expert_mode:
1069 widths = [ self.receive_list.columnWidth(0) ]
1072 for i in range(self.receive_list.columnCount() -1):
1073 widths.append(self.receive_list.columnWidth(i))
1074 self.column_widths["receive"][self.expert_mode] = widths
1076 self.column_widths["history"] = []
1077 for i in range(self.history_list.columnCount() - 1):
1078 self.column_widths["history"].append(self.history_list.columnWidth(i))
1080 self.column_widths["contacts"] = []
1081 for i in range(self.contacts_list.columnCount() - 1):
1082 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1084 self.config.set_key("column_widths", self.column_widths, True)
1087 def create_contacts_tab(self):
1088 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1089 l.setContextMenuPolicy(Qt.CustomContextMenu)
1090 l.customContextMenuRequested.connect(self.create_contact_menu)
1091 for i,width in enumerate(self.column_widths['contacts']):
1092 l.setColumnWidth(i, width)
1094 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1095 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1096 self.contacts_list = l
1097 self.contacts_buttons_hbox = hbox
1102 def delete_imported_key(self, addr):
1103 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1104 self.wallet.delete_imported_key(addr)
1105 self.update_receive_tab()
1106 self.update_history_tab()
1109 def create_receive_menu(self, position):
1110 # fixme: this function apparently has a side effect.
1111 # if it is not called the menu pops up several times
1112 #self.receive_list.selectedIndexes()
1114 item = self.receive_list.itemAt(position)
1116 addr = unicode(item.text(0))
1117 if not is_valid(addr):
1118 item.setExpanded(not item.isExpanded())
1121 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1122 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1123 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1124 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1125 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1126 if addr in self.wallet.imported_keys:
1127 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1129 if self.expert_mode:
1130 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1131 menu.addAction(t, lambda: self.toggle_freeze(addr))
1132 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1133 menu.addAction(t, lambda: self.toggle_priority(addr))
1135 self.run_hook('receive_menu', menu)
1136 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1139 def payto(self, addr):
1141 label = self.wallet.labels.get(addr)
1142 m_addr = label + ' <' + addr + '>' if label else addr
1143 self.tabs.setCurrentIndex(1)
1144 self.payto_e.setText(m_addr)
1145 self.amount_e.setFocus()
1148 def delete_contact(self, x):
1149 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1150 self.wallet.delete_contact(x)
1151 self.set_label(x, None)
1152 self.update_history_tab()
1153 self.update_contacts_tab()
1154 self.update_completions()
1157 def create_contact_menu(self, position):
1158 item = self.contacts_list.itemAt(position)
1160 addr = unicode(item.text(0))
1161 label = unicode(item.text(1))
1162 is_editable = item.data(0,32).toBool()
1163 payto_addr = item.data(0,33).toString()
1165 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1166 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1167 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1169 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1170 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1172 self.run_hook('create_contact_menu', menu, item)
1173 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1176 def update_receive_item(self, item):
1177 item.setFont(0, QFont(MONOSPACE_FONT))
1178 address = str(item.data(0,0).toString())
1179 label = self.wallet.labels.get(address,'')
1180 item.setData(1,0,label)
1181 item.setData(0,32, True) # is editable
1183 self.run_hook('update_receive_item', address, item)
1185 c, u = self.wallet.get_addr_balance(address)
1186 balance = self.format_amount(c + u)
1187 item.setData(2,0,balance)
1189 if self.expert_mode:
1190 if address in self.wallet.frozen_addresses:
1191 item.setBackgroundColor(0, QColor('lightblue'))
1192 elif address in self.wallet.prioritized_addresses:
1193 item.setBackgroundColor(0, QColor('lightgreen'))
1196 def update_receive_tab(self):
1197 l = self.receive_list
1200 l.setColumnHidden(2, not self.expert_mode)
1201 l.setColumnHidden(3, not self.expert_mode)
1202 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1203 l.setColumnWidth(i, width)
1205 if self.current_account is None:
1206 account_items = self.wallet.accounts.items()
1207 elif self.current_account != -1:
1208 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1212 for k, account in account_items:
1213 name = self.wallet.get_account_name(k)
1214 c,u = self.wallet.get_account_balance(k)
1215 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1216 l.addTopLevelItem(account_item)
1217 account_item.setExpanded(True)
1219 for is_change in ([0,1] if self.expert_mode else [0]):
1220 if self.expert_mode:
1221 name = "Receiving" if not is_change else "Change"
1222 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1223 account_item.addChild(seq_item)
1224 if not is_change: seq_item.setExpanded(True)
1226 seq_item = account_item
1230 for address in account.get_addresses(is_change):
1231 h = self.wallet.history.get(address,[])
1235 if gap > self.wallet.gap_limit:
1240 num_tx = '*' if h == ['*'] else "%d"%len(h)
1241 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1242 self.update_receive_item(item)
1244 item.setBackgroundColor(1, QColor('red'))
1245 seq_item.addChild(item)
1248 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1249 c,u = self.wallet.get_imported_balance()
1250 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1251 l.addTopLevelItem(account_item)
1252 account_item.setExpanded(True)
1253 for address in self.wallet.imported_keys.keys():
1254 item = QTreeWidgetItem( [ address, '', '', ''] )
1255 self.update_receive_item(item)
1256 account_item.addChild(item)
1259 # we use column 1 because column 0 may be hidden
1260 l.setCurrentItem(l.topLevelItem(0),1)
1263 def update_contacts_tab(self):
1264 l = self.contacts_list
1267 for address in self.wallet.addressbook:
1268 label = self.wallet.labels.get(address,'')
1269 n = self.wallet.get_num_tx(address)
1270 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1271 item.setFont(0, QFont(MONOSPACE_FONT))
1272 # 32 = label can be edited (bool)
1273 item.setData(0,32, True)
1275 item.setData(0,33, address)
1276 l.addTopLevelItem(item)
1278 self.run_hook('update_contacts_tab', l)
1279 l.setCurrentItem(l.topLevelItem(0))
1283 def create_console_tab(self):
1284 from qt_console import Console
1285 self.console = console = Console()
1289 def update_console(self):
1290 console = self.console
1291 console.history = self.config.get("console-history",[])
1292 console.history_index = len(console.history)
1294 console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1295 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1297 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1299 def mkfunc(f, method):
1300 return lambda *args: apply( f, (method, args, self.password_dialog ))
1302 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1303 methods[m] = mkfunc(c._run, m)
1305 console.updateNamespace(methods)
1308 def change_account(self,s):
1309 if s == _("All accounts"):
1310 self.current_account = None
1312 accounts = self.wallet.get_account_names()
1313 for k, v in accounts.items():
1315 self.current_account = k
1316 self.update_history_tab()
1317 self.update_status()
1318 self.update_receive_tab()
1320 def create_status_bar(self):
1323 sb.setFixedHeight(35)
1324 qtVersion = qVersion()
1326 self.balance_label = QLabel("")
1327 sb.addWidget(self.balance_label)
1329 from version_getter import UpdateLabel
1330 self.updatelabel = UpdateLabel(self.config, sb)
1332 self.account_selector = QComboBox()
1333 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1334 sb.addPermanentWidget(self.account_selector)
1336 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1337 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1339 self.lock_icon = QIcon()
1340 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1341 sb.addPermanentWidget( self.password_button )
1343 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1344 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1345 sb.addPermanentWidget( self.seed_button )
1346 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1347 sb.addPermanentWidget( self.status_button )
1349 self.run_hook('create_status_bar', (sb,))
1351 self.setStatusBar(sb)
1354 def update_lock_icon(self):
1355 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1356 self.password_button.setIcon( icon )
1359 def update_buttons_on_seed(self):
1360 if self.wallet.seed:
1361 self.seed_button.show()
1362 self.password_button.show()
1363 self.send_button.setText(_("Send"))
1365 self.password_button.hide()
1366 self.seed_button.hide()
1367 self.send_button.setText(_("Create unsigned transaction"))
1370 def change_password_dialog(self):
1371 from password_dialog import PasswordDialog
1372 d = PasswordDialog(self.wallet, self)
1374 self.update_lock_icon()
1379 self.config.set_key('gui', 'lite', True)
1382 self.lite.mini.show()
1384 self.lite = gui_lite.ElectrumGui(self.config, None, None, self)
1385 self.lite.main(None)
1388 def new_contact_dialog(self):
1389 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1390 address = unicode(text)
1392 if is_valid(address):
1393 self.wallet.add_contact(address)
1394 self.update_contacts_tab()
1395 self.update_history_tab()
1396 self.update_completions()
1398 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1401 def new_account_dialog(self):
1403 dialog = QDialog(self)
1405 dialog.setWindowTitle(_("New Account"))
1407 addr = self.wallet.new_account_address()
1408 vbox = QVBoxLayout()
1409 vbox.addWidget(QLabel(_("To create a new account, please send coins to the first address of that account:")))
1414 ok_button = QPushButton(_("OK"))
1415 ok_button.setDefault(True)
1416 ok_button.clicked.connect(dialog.accept)
1418 hbox = QHBoxLayout()
1420 hbox.addWidget(ok_button)
1421 vbox.addLayout(hbox)
1423 dialog.setLayout(vbox)
1428 def show_master_public_key(self):
1429 dialog = QDialog(self)
1431 dialog.setWindowTitle(_("Master Public Key"))
1433 main_text = QTextEdit()
1434 main_text.setText(self.wallet.get_master_public_key())
1435 main_text.setReadOnly(True)
1436 main_text.setMaximumHeight(170)
1437 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1439 ok_button = QPushButton(_("OK"))
1440 ok_button.setDefault(True)
1441 ok_button.clicked.connect(dialog.accept)
1443 main_layout = QGridLayout()
1444 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1446 main_layout.addWidget(main_text, 1, 0)
1447 main_layout.addWidget(qrw, 1, 1 )
1449 vbox = QVBoxLayout()
1450 vbox.addLayout(main_layout)
1451 hbox = QHBoxLayout()
1453 hbox.addWidget(ok_button)
1454 vbox.addLayout(hbox)
1456 dialog.setLayout(vbox)
1461 def show_seed_dialog(self, password):
1462 if not self.wallet.seed:
1463 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1466 seed = self.wallet.decode_seed(password)
1468 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1471 from seed_dialog import SeedDialog
1472 d = SeedDialog(self)
1473 d.show_seed(seed, self.wallet.imported_keys)
1477 def show_qrcode(self, data, title = "QR code"):
1481 d.setWindowTitle(title)
1482 d.setMinimumSize(270, 300)
1483 vbox = QVBoxLayout()
1484 qrw = QRCodeWidget(data)
1485 vbox.addWidget(qrw, 1)
1486 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1487 hbox = QHBoxLayout()
1491 filename = "qrcode.bmp"
1492 bmp.save_qrcode(qrw.qr, filename)
1493 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1495 b = QPushButton(_("Save"))
1497 b.clicked.connect(print_qr)
1499 b = QPushButton(_("Close"))
1501 b.clicked.connect(d.accept)
1504 vbox.addLayout(hbox)
1509 def do_protect(self, func, args):
1510 if self.wallet.use_encryption:
1511 password = self.password_dialog()
1517 if args != (False,):
1518 args = (self,) + args + (password,)
1520 args = (self,password)
1525 def show_private_key(self, address, password):
1526 if not address: return
1528 pk_list = self.wallet.get_private_key(address, password)
1529 except BaseException, e:
1530 self.show_message(str(e))
1532 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + '\n'.join(pk_list), _('OK'))
1536 def do_sign(self, address, message, signature, password):
1538 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1539 signature.setText(sig)
1540 except BaseException, e:
1541 self.show_message(str(e))
1543 def sign_message(self, address):
1544 if not address: return
1547 d.setWindowTitle(_('Sign Message'))
1548 d.setMinimumSize(410, 290)
1550 tab_widget = QTabWidget()
1552 layout = QGridLayout(tab)
1554 sign_address = QLineEdit()
1556 sign_address.setText(address)
1557 layout.addWidget(QLabel(_('Address')), 1, 0)
1558 layout.addWidget(sign_address, 1, 1)
1560 sign_message = QTextEdit()
1561 layout.addWidget(QLabel(_('Message')), 2, 0)
1562 layout.addWidget(sign_message, 2, 1)
1563 layout.setRowStretch(2,3)
1565 sign_signature = QTextEdit()
1566 layout.addWidget(QLabel(_('Signature')), 3, 0)
1567 layout.addWidget(sign_signature, 3, 1)
1568 layout.setRowStretch(3,1)
1571 hbox = QHBoxLayout()
1572 b = QPushButton(_("Sign"))
1574 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1575 b = QPushButton(_("Close"))
1576 b.clicked.connect(d.accept)
1578 layout.addLayout(hbox, 4, 1)
1579 tab_widget.addTab(tab, _("Sign"))
1583 layout = QGridLayout(tab)
1585 verify_address = QLineEdit()
1586 layout.addWidget(QLabel(_('Address')), 1, 0)
1587 layout.addWidget(verify_address, 1, 1)
1589 verify_message = QTextEdit()
1590 layout.addWidget(QLabel(_('Message')), 2, 0)
1591 layout.addWidget(verify_message, 2, 1)
1592 layout.setRowStretch(2,3)
1594 verify_signature = QTextEdit()
1595 layout.addWidget(QLabel(_('Signature')), 3, 0)
1596 layout.addWidget(verify_signature, 3, 1)
1597 layout.setRowStretch(3,1)
1600 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1601 self.show_message(_("Signature verified"))
1603 self.show_message(_("Error: wrong signature"))
1605 hbox = QHBoxLayout()
1606 b = QPushButton(_("Verify"))
1607 b.clicked.connect(do_verify)
1609 b = QPushButton(_("Close"))
1610 b.clicked.connect(d.accept)
1612 layout.addLayout(hbox, 4, 1)
1613 tab_widget.addTab(tab, _("Verify"))
1615 vbox = QVBoxLayout()
1616 vbox.addWidget(tab_widget)
1623 def question(self, msg):
1624 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1626 def show_message(self, msg):
1627 QMessageBox.information(self, _('Message'), msg, _('OK'))
1629 def password_dialog(self ):
1636 vbox = QVBoxLayout()
1637 msg = _('Please enter your password')
1638 vbox.addWidget(QLabel(msg))
1640 grid = QGridLayout()
1642 grid.addWidget(QLabel(_('Password')), 1, 0)
1643 grid.addWidget(pw, 1, 1)
1644 vbox.addLayout(grid)
1646 vbox.addLayout(ok_cancel_buttons(d))
1649 self.run_hook('password_dialog', pw, grid, 1)
1650 if not d.exec_(): return
1651 return unicode(pw.text())
1658 def generate_transaction_information_widget(self, tx):
1659 tabs = QTabWidget(self)
1662 grid_ui = QGridLayout(tab1)
1663 grid_ui.setColumnStretch(0,1)
1664 tabs.addTab(tab1, _('Outputs') )
1666 tree_widget = MyTreeWidget(self)
1667 tree_widget.setColumnCount(2)
1668 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1669 tree_widget.setColumnWidth(0, 300)
1670 tree_widget.setColumnWidth(1, 50)
1672 for address, value in tx.outputs:
1673 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1674 tree_widget.addTopLevelItem(item)
1676 tree_widget.setMaximumHeight(100)
1678 grid_ui.addWidget(tree_widget)
1681 grid_ui = QGridLayout(tab2)
1682 grid_ui.setColumnStretch(0,1)
1683 tabs.addTab(tab2, _('Inputs') )
1685 tree_widget = MyTreeWidget(self)
1686 tree_widget.setColumnCount(2)
1687 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1689 for input_line in tx.inputs:
1690 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1691 tree_widget.addTopLevelItem(item)
1693 tree_widget.setMaximumHeight(100)
1695 grid_ui.addWidget(tree_widget)
1699 def tx_dict_from_text(self, txt):
1701 tx_dict = json.loads(str(txt))
1702 assert "hex" in tx_dict.keys()
1703 assert "complete" in tx_dict.keys()
1704 if not tx_dict["complete"]:
1705 assert "input_info" in tx_dict.keys()
1707 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1712 def read_tx_from_file(self):
1713 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1717 with open(fileName, "r") as f:
1718 file_content = f.read()
1719 except (ValueError, IOError, os.error), reason:
1720 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1722 return self.tx_dict_from_text(file_content)
1726 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1728 self.wallet.signrawtransaction(tx, input_info, [], password)
1730 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1732 with open(fileName, "w+") as f:
1733 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1734 self.show_message(_("Transaction saved successfully"))
1737 except BaseException, e:
1738 self.show_message(str(e))
1741 def send_raw_transaction(self, raw_tx, dialog = ""):
1742 result, result_message = self.wallet.sendtx( raw_tx )
1744 self.show_message("Transaction successfully sent: %s" % (result_message))
1748 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1750 def do_process_from_text(self):
1751 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1754 tx_dict = self.tx_dict_from_text(text)
1756 self.create_process_transaction_window(tx_dict)
1758 def do_process_from_file(self):
1759 tx_dict = self.read_tx_from_file()
1761 self.create_process_transaction_window(tx_dict)
1763 def do_process_from_csvReader(self, csvReader):
1766 for row in csvReader:
1768 amount = float(row[1])
1769 amount = int(100000000*amount)
1770 outputs.append((address, amount))
1771 except (ValueError, IOError, os.error), reason:
1772 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1776 tx = self.wallet.make_unsigned_transaction(outputs, None, None, account=self.current_account)
1777 except BaseException, e:
1778 self.show_message(str(e))
1781 tx_dict = tx.as_dict()
1782 self.create_process_transaction_window(tx_dict)
1784 def do_process_from_csv_file(self):
1785 fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1789 with open(fileName, "r") as f:
1790 csvReader = csv.reader(f)
1791 self.do_process_from_csvReader(csvReader)
1792 except (ValueError, IOError, os.error), reason:
1793 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1796 def do_process_from_csv_text(self):
1797 text = text_dialog(self, _('Input CSV'), _("CSV:"), _("Load CSV"))
1800 f = StringIO.StringIO(text)
1801 csvReader = csv.reader(f)
1802 self.do_process_from_csvReader(csvReader)
1804 def create_process_transaction_window(self, tx_dict):
1805 tx = Transaction(tx_dict["hex"])
1807 dialog = QDialog(self)
1808 dialog.setMinimumWidth(500)
1809 dialog.setWindowTitle(_('Process raw transaction'))
1815 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1816 l.addWidget(QLabel(_("Actions")), 4,0)
1818 if tx_dict["complete"] == False:
1819 l.addWidget(QLabel(_("Unsigned")), 3,1)
1820 if self.wallet.seed :
1821 b = QPushButton("Sign transaction")
1822 input_info = json.loads(tx_dict["input_info"])
1823 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1824 l.addWidget(b, 4, 1)
1826 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1828 l.addWidget(QLabel(_("Signed")), 3,1)
1829 b = QPushButton("Broadcast transaction")
1830 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1833 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1834 cancelButton = QPushButton(_("Cancel"))
1835 cancelButton.clicked.connect(lambda: dialog.done(0))
1836 l.addWidget(cancelButton, 4,2)
1842 def do_export_privkeys(self, password):
1843 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.")))
1846 select_export = _('Select file to export your private keys to')
1847 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1849 with open(fileName, "w+") as csvfile:
1850 transaction = csv.writer(csvfile)
1851 transaction.writerow(["address", "private_key"])
1853 addresses = self.wallet.addresses(True)
1855 for addr in addresses:
1856 pk = "".join(self.wallet.get_private_key(addr, password))
1857 transaction.writerow(["%34s"%addr,pk])
1859 self.show_message(_("Private keys exported."))
1861 except (IOError, os.error), reason:
1862 export_error_label = _("Electrum was unable to produce a private key-export.")
1863 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1865 except BaseException, e:
1866 self.show_message(str(e))
1870 def do_import_labels(self):
1871 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1872 if not labelsFile: return
1874 f = open(labelsFile, 'r')
1877 for key, value in json.loads(data).items():
1878 self.wallet.set_label(key, value)
1879 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1880 except (IOError, os.error), reason:
1881 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1884 def do_export_labels(self):
1885 labels = self.wallet.labels
1887 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1889 with open(fileName, 'w+') as f:
1890 json.dump(labels, f)
1891 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1892 except (IOError, os.error), reason:
1893 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1896 def do_export_history(self):
1897 from gui_lite import csv_transaction
1898 csv_transaction(self.wallet)
1902 def do_import_privkey(self, password):
1903 if not self.wallet.imported_keys:
1904 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1905 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1906 + _('Are you sure you understand what you are doing?'), 3, 4)
1909 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1912 text = str(text).split()
1917 addr = self.wallet.import_key(key, password)
1918 except BaseException as e:
1924 addrlist.append(addr)
1926 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1928 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1929 self.update_receive_tab()
1930 self.update_history_tab()
1933 def settings_dialog(self):
1935 d.setWindowTitle(_('Electrum Settings'))
1937 vbox = QVBoxLayout()
1939 tabs = QTabWidget(self)
1940 self.settings_tab = tabs
1941 vbox.addWidget(tabs)
1944 grid_ui = QGridLayout(tab1)
1945 grid_ui.setColumnStretch(0,1)
1946 tabs.addTab(tab1, _('Display') )
1948 nz_label = QLabel(_('Display zeros'))
1949 grid_ui.addWidget(nz_label, 0, 0)
1950 nz_e = AmountEdit(None,True)
1951 nz_e.setText("%d"% self.num_zeros)
1952 grid_ui.addWidget(nz_e, 0, 1)
1953 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1954 grid_ui.addWidget(HelpButton(msg), 0, 2)
1955 if not self.config.is_modifiable('num_zeros'):
1956 for w in [nz_e, nz_label]: w.setEnabled(False)
1958 lang_label=QLabel(_('Language') + ':')
1959 grid_ui.addWidget(lang_label, 1, 0)
1960 lang_combo = QComboBox()
1961 from i18n import languages
1962 lang_combo.addItems(languages.values())
1964 index = languages.keys().index(self.config.get("language",''))
1967 lang_combo.setCurrentIndex(index)
1968 grid_ui.addWidget(lang_combo, 1, 1)
1969 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1970 if not self.config.is_modifiable('language'):
1971 for w in [lang_combo, lang_label]: w.setEnabled(False)
1973 currencies = self.exchanger.get_currencies()
1974 currencies.insert(0, "None")
1976 cur_label=QLabel(_('Currency') + ':')
1977 grid_ui.addWidget(cur_label , 2, 0)
1978 cur_combo = QComboBox()
1979 cur_combo.addItems(currencies)
1981 index = currencies.index(self.config.get('currency', "None"))
1984 cur_combo.setCurrentIndex(index)
1985 grid_ui.addWidget(cur_combo, 2, 1)
1986 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1988 expert_cb = QCheckBox(_('Expert mode'))
1989 expert_cb.setChecked(self.expert_mode)
1990 grid_ui.addWidget(expert_cb, 3, 0)
1991 hh = _('In expert mode, your client will:') + '\n' \
1992 + _(' - Show change addresses in the Receive tab') + '\n' \
1993 + _(' - Display the balance of each address') + '\n' \
1994 + _(' - Add freeze/prioritize actions to addresses.')
1995 grid_ui.addWidget(HelpButton(hh), 3, 2)
1996 grid_ui.setRowStretch(4,1)
2000 grid_wallet = QGridLayout(tab2)
2001 grid_wallet.setColumnStretch(0,1)
2002 tabs.addTab(tab2, _('Wallet') )
2004 fee_label = QLabel(_('Transaction fee'))
2005 grid_wallet.addWidget(fee_label, 0, 0)
2006 fee_e = AmountEdit(self.base_unit)
2007 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2008 grid_wallet.addWidget(fee_e, 0, 2)
2009 msg = _('Fee per kilobyte of transaction.') + ' ' \
2010 + _('Recommended value') + ': ' + self.format_amount(50000)
2011 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2012 if not self.config.is_modifiable('fee_per_kb'):
2013 for w in [fee_e, fee_label]: w.setEnabled(False)
2015 usechange_cb = QCheckBox(_('Use change addresses'))
2016 usechange_cb.setChecked(self.wallet.use_change)
2017 grid_wallet.addWidget(usechange_cb, 1, 0)
2018 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2019 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2021 gap_label = QLabel(_('Gap limit'))
2022 grid_wallet.addWidget(gap_label, 2, 0)
2023 gap_e = AmountEdit(None,True)
2024 gap_e.setText("%d"% self.wallet.gap_limit)
2025 grid_wallet.addWidget(gap_e, 2, 2)
2026 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2027 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2028 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2029 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2030 + _('Warning') + ': ' \
2031 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2032 + _('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'
2033 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2034 if not self.config.is_modifiable('gap_limit'):
2035 for w in [gap_e, gap_label]: w.setEnabled(False)
2037 units = ['BTC', 'mBTC']
2038 unit_label = QLabel(_('Base unit'))
2039 grid_wallet.addWidget(unit_label, 3, 0)
2040 unit_combo = QComboBox()
2041 unit_combo.addItems(units)
2042 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2043 grid_wallet.addWidget(unit_combo, 3, 2)
2044 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2045 + '\n1BTC=1000mBTC.\n' \
2046 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2047 grid_wallet.setRowStretch(4,1)
2051 tab5 = QScrollArea()
2052 tab5.setEnabled(True)
2053 tab5.setWidgetResizable(True)
2055 grid_plugins = QGridLayout()
2056 grid_plugins.setColumnStretch(0,1)
2059 w.setLayout(grid_plugins)
2062 w.setMinimumHeight(len(self.plugins)*35)
2064 tabs.addTab(tab5, _('Plugins') )
2065 def mk_toggle(cb, p):
2066 return lambda: cb.setChecked(p.toggle())
2067 for i, p in enumerate(self.plugins):
2069 cb = QCheckBox(p.fullname())
2070 cb.setDisabled(not p.is_available())
2071 cb.setChecked(p.is_enabled())
2072 cb.clicked.connect(mk_toggle(cb,p))
2073 grid_plugins.addWidget(cb, i, 0)
2074 if p.requires_settings():
2075 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2076 grid_plugins.addWidget(HelpButton(p.description()), i, 2)
2078 print_msg("Error: cannot display plugin", p)
2079 traceback.print_exc(file=sys.stdout)
2080 grid_plugins.setRowStretch(i+1,1)
2082 self.run_hook('create_settings_tab', tabs)
2084 vbox.addLayout(ok_cancel_buttons(d))
2088 if not d.exec_(): return
2090 fee = unicode(fee_e.text())
2092 fee = self.read_amount(fee)
2094 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2097 self.wallet.set_fee(fee)
2099 nz = unicode(nz_e.text())
2104 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2107 if self.num_zeros != nz:
2109 self.config.set_key('num_zeros', nz, True)
2110 self.update_history_tab()
2111 self.update_receive_tab()
2113 usechange_result = usechange_cb.isChecked()
2114 if self.wallet.use_change != usechange_result:
2115 self.wallet.use_change = usechange_result
2116 self.config.set_key('use_change', self.wallet.use_change, True)
2118 unit_result = units[unit_combo.currentIndex()]
2119 if self.base_unit() != unit_result:
2120 self.decimal_point = 8 if unit_result == 'BTC' else 5
2121 self.config.set_key('decimal_point', self.decimal_point, True)
2122 self.update_history_tab()
2123 self.update_status()
2126 n = int(gap_e.text())
2128 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2131 if self.wallet.gap_limit != n:
2132 r = self.wallet.change_gap_limit(n)
2134 self.update_receive_tab()
2135 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2137 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2139 need_restart = False
2141 lang_request = languages.keys()[lang_combo.currentIndex()]
2142 if lang_request != self.config.get('language'):
2143 self.config.set_key("language", lang_request, True)
2146 cur_request = str(currencies[cur_combo.currentIndex()])
2147 if cur_request != self.config.get('currency', "None"):
2148 self.config.set_key('currency', cur_request, True)
2149 self.update_wallet()
2151 self.run_hook('close_settings_dialog')
2154 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2156 self.receive_tab_set_mode(expert_cb.isChecked())
2158 def run_network_dialog(self):
2159 NetworkDialog(self.wallet.network, self.config, self).do_exec()
2161 def closeEvent(self, event):
2163 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2164 self.save_column_widths()
2165 self.config.set_key("console-history", self.console.history[-50:], True)
2168 class OpenFileEventFilter(QObject):
2169 def __init__(self, windows):
2170 self.windows = windows
2171 super(OpenFileEventFilter, self).__init__()
2173 def eventFilter(self, obj, event):
2174 if event.type() == QtCore.QEvent.FileOpen:
2175 if len(self.windows) >= 1:
2176 self.windows[0].set_url(event.url().toString())
2185 def __init__(self, config, network, app=None):
2186 self.network = network
2187 #self.interface = interface
2188 self.config = config
2189 #self.blockchain = network.blockchain
2191 self.efilter = OpenFileEventFilter(self.windows)
2193 self.app = QApplication(sys.argv)
2194 self.app.installEventFilter(self.efilter)
2197 def main(self, url):
2199 storage = WalletStorage(self.config)
2200 if not storage.file_exists:
2201 import installwizard
2202 wizard = installwizard.InstallWizard(self.config, self.network, storage)
2203 wallet = wizard.run()
2207 wallet = Wallet(storage)
2209 wallet.start_threads(self.network)
2213 w = ElectrumWindow(self.config, self.network)
2214 w.load_wallet(wallet)
2216 self.windows.append(w)
2217 if url: w.set_url(url)
2225 wallet.stop_threads()