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))
252 self.fee = int(config.get('fee_per_kb',20000))
254 set_language(config.get('language'))
256 self.funds_error = False
257 self.completions = QStringListModel()
259 self.tabs = tabs = QTabWidget(self)
260 self.column_widths = self.config.get("column_widths", default_column_widths )
261 tabs.addTab(self.create_history_tab(), _('History') )
262 tabs.addTab(self.create_send_tab(), _('Send') )
263 tabs.addTab(self.create_receive_tab(), _('Receive') )
264 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
265 tabs.addTab(self.create_console_tab(), _('Console') )
266 tabs.setMinimumSize(600, 400)
267 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
268 self.setCentralWidget(tabs)
270 g = self.config.get("winpos-qt",[100, 100, 840, 400])
271 self.setGeometry(g[0], g[1], g[2], g[3])
275 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
276 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
277 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
278 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
279 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
281 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
282 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
283 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
284 self.history_list.setFocus(True)
286 self.exchanger = exchange_rate.Exchanger(self)
287 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
289 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
290 if platform.system() == 'Windows':
291 n = 3 if self.wallet.seed else 2
292 tabs.setCurrentIndex (n)
293 tabs.setCurrentIndex (0)
295 # plugins that need to change the GUI do it here
296 self.run_hook('init')
301 def load_wallet(self, wallet):
305 self.wallet.interface.register_callback('updated', lambda: self.need_update.set())
306 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
307 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
308 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
309 self.wallet.interface.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
310 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
311 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
312 self.setWindowTitle( title )
314 # set initial message
315 self.console.showMessage(self.wallet.interface.banner)
316 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
317 self.notify_transactions()
320 accounts = self.wallet.get_accounts()
321 self.account_selector.clear()
322 if len(accounts) > 1:
323 self.account_selector.addItems([_("All accounts")] + accounts.values())
324 self.account_selector.setCurrentIndex(0)
325 self.account_selector.show()
327 self.account_selector.hide()
329 self.update_lock_icon()
330 self.update_buttons_on_seed()
331 self.update_console()
334 def select_wallet_file(self):
335 wallet_folder = self.wallet.storage.path
336 re.sub("(\/\w*.dat)$", "", wallet_folder)
337 file_name = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat") )
341 def open_wallet(self):
343 filename = self.select_wallet_file()
347 storage = WalletStorage({'wallet_path': filename})
348 if not storage.file_exists:
349 self.show_message("file not found "+ filename)
352 interface = self.wallet.interface
353 blockchain = self.wallet.verifier.blockchain
354 self.wallet.stop_threads()
357 wallet = Wallet(storage)
358 wallet.start_threads(interface, blockchain)
360 self.load_wallet(wallet)
363 def new_wallet(self):
366 wallet_folder = self.wallet.storage.path
367 re.sub("(\/\w*.dat)$", "", wallet_folder)
368 filename = self.getSaveFileName("Select your wallet file", wallet_folder, "*.dat")
370 storage = WalletStorage({'wallet_path': filename})
371 assert not storage.file_exists
373 wizard = installwizard.InstallWizard(self.config, self.wallet.interface, storage)
374 wallet = wizard.run()
376 self.load_wallet(wallet)
380 def init_menubar(self):
383 file_menu = menubar.addMenu(_("&File"))
384 open_wallet_action = file_menu.addAction(_("&Open"))
385 open_wallet_action.triggered.connect(self.open_wallet)
387 new_wallet_action = file_menu.addAction(_("&Create/Restore"))
388 new_wallet_action.triggered.connect(self.new_wallet)
390 wallet_backup = file_menu.addAction(_("&Copy"))
391 wallet_backup.triggered.connect(lambda: backup_wallet(self.config.path))
393 quit_item = file_menu.addAction(_("&Close"))
394 quit_item.triggered.connect(self.close)
396 wallet_menu = menubar.addMenu(_("&Wallet"))
398 # Settings / Preferences are all reserved keywords in OSX using this as work around
399 preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
400 preferences_menu = wallet_menu.addAction(preferences_name)
401 preferences_menu.triggered.connect(self.settings_dialog)
403 wallet_menu.addSeparator()
405 raw_transaction_menu = wallet_menu.addMenu(_("&Load raw transaction"))
407 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
408 raw_transaction_file.triggered.connect(self.do_process_from_file)
410 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
411 raw_transaction_text.triggered.connect(self.do_process_from_text)
413 wallet_menu.addSeparator()
415 show_menu = wallet_menu.addMenu(_("Show"))
417 #if self.wallet.seed:
418 show_seed = show_menu.addAction(_("&Seed"))
419 show_seed.triggered.connect(self.show_seed_dialog)
421 show_mpk = show_menu.addAction(_("&Master Public Key"))
422 show_mpk.triggered.connect(self.show_master_public_key)
424 wallet_menu.addSeparator()
425 new_contact = wallet_menu.addAction(_("&New contact"))
426 new_contact.triggered.connect(self.new_contact_dialog)
428 new_account = wallet_menu.addAction(_("&New account"))
429 new_account.triggered.connect(self.new_account_dialog)
431 import_menu = menubar.addMenu(_("&Import"))
432 in_labels = import_menu.addAction(_("&Labels"))
433 in_labels.triggered.connect(self.do_import_labels)
435 in_private_keys = import_menu.addAction(_("&Private keys"))
436 in_private_keys.triggered.connect(self.do_import_privkey)
438 export_menu = menubar.addMenu(_("&Export"))
439 ex_private_keys = export_menu.addAction(_("&Private keys"))
440 ex_private_keys.triggered.connect(self.do_export_privkeys)
442 ex_history = export_menu.addAction(_("&History"))
443 ex_history.triggered.connect(self.do_export_history)
445 ex_labels = export_menu.addAction(_("&Labels"))
446 ex_labels.triggered.connect(self.do_export_labels)
448 help_menu = menubar.addMenu(_("&Help"))
449 doc_open = help_menu.addAction(_("&Documentation"))
450 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
451 web_open = help_menu.addAction(_("&Official website"))
452 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
454 self.setMenuBar(menubar)
458 def notify_transactions(self):
459 print_error("Notifying GUI")
460 if len(self.wallet.interface.pending_transactions_for_notifications) > 0:
461 # Combine the transactions if there are more then three
462 tx_amount = len(self.wallet.interface.pending_transactions_for_notifications)
465 for tx in self.wallet.interface.pending_transactions_for_notifications:
466 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
470 self.notify("%s new transactions received. Total amount received in the new transactions %s %s" \
471 % (tx_amount, self.format_amount(total_amount), self.base_unit()))
473 self.wallet.interface.pending_transactions_for_notifications = []
475 for tx in self.wallet.interface.pending_transactions_for_notifications:
477 self.wallet.interface.pending_transactions_for_notifications.remove(tx)
478 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
480 self.notify("New transaction received. %s %s" % (self.format_amount(v), self.base_unit()))
482 def notify(self, message):
483 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
486 def init_plugins(self):
487 import imp, pkgutil, __builtin__
488 if __builtin__.use_local_modules:
489 fp, pathname, description = imp.find_module('plugins')
490 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
491 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
492 imp.load_module('electrum_plugins', fp, pathname, description)
493 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
495 import electrum_plugins
496 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
497 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
500 for name, p in zip(plugin_names, plugins):
502 self.plugins.append( p.Plugin(self, name) )
504 print_msg("Error:cannot initialize plugin",p)
505 traceback.print_exc(file=sys.stdout)
508 def run_hook(self, name, *args):
509 for p in self.plugins:
510 if not p.is_enabled():
519 print_error("Plugin error")
520 traceback.print_exc(file=sys.stdout)
525 def set_fee(self, fee):
528 self.config.set_key('fee_per_kb', self.fee, True)
531 def set_label(self, name, text = None):
533 old_text = self.wallet.labels.get(name)
536 self.wallet.labels[name] = text
537 self.wallet.storage.set_key('labels', self.wallet.labels)
541 self.wallet.labels.pop(name)
543 self.run_hook('set_label', name, text, changed)
547 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
548 def getOpenFileName(self, title, filter = None):
549 directory = self.config.get('io_dir', os.path.expanduser('~'))
550 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
551 if fileName and directory != os.path.dirname(fileName):
552 self.config.set_key('io_dir', os.path.dirname(fileName), True)
555 def getSaveFileName(self, title, filename, filter = None):
556 directory = self.config.get('io_dir', os.path.expanduser('~'))
557 path = os.path.join( directory, filename )
558 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
559 if fileName and directory != os.path.dirname(fileName):
560 self.config.set_key('io_dir', os.path.dirname(fileName), True)
566 QMainWindow.close(self)
567 self.run_hook('close_main_window')
569 def connect_slots(self, sender):
570 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
571 self.previous_payto_e=''
573 def timer_actions(self):
574 if self.need_update.is_set():
576 self.need_update.clear()
577 self.run_hook('timer_actions')
579 def format_amount(self, x, is_diff=False, whitespaces=False):
580 return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
582 def read_amount(self, x):
583 if x in['.', '']: return None
584 p = pow(10, self.decimal_point)
585 return int( p * Decimal(x) )
588 assert self.decimal_point in [5,8]
589 return "BTC" if self.decimal_point == 8 else "mBTC"
591 def update_status(self):
592 if self.wallet.interface and self.wallet.interface.is_connected:
593 if not self.wallet.up_to_date:
594 text = _("Synchronizing...")
595 icon = QIcon(":icons/status_waiting.png")
597 c, u = self.wallet.get_account_balance(self.current_account)
598 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
599 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
600 text += self.create_quote_text(Decimal(c+u)/100000000)
601 self.tray.setToolTip(text)
602 icon = QIcon(":icons/status_connected.png")
604 text = _("Not connected")
605 icon = QIcon(":icons/status_disconnected.png")
607 self.balance_label.setText(text)
608 self.status_button.setIcon( icon )
610 def update_wallet(self):
612 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
613 self.update_history_tab()
614 self.update_receive_tab()
615 self.update_contacts_tab()
616 self.update_completions()
619 def create_quote_text(self, btc_balance):
620 quote_currency = self.config.get("currency", "None")
621 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
622 if quote_balance is None:
625 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
628 def create_history_tab(self):
629 self.history_list = l = MyTreeWidget(self)
631 for i,width in enumerate(self.column_widths['history']):
632 l.setColumnWidth(i, width)
633 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
634 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
635 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
637 l.setContextMenuPolicy(Qt.CustomContextMenu)
638 l.customContextMenuRequested.connect(self.create_history_menu)
642 def create_history_menu(self, position):
643 self.history_list.selectedIndexes()
644 item = self.history_list.currentItem()
646 tx_hash = str(item.data(0, Qt.UserRole).toString())
647 if not tx_hash: return
649 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
650 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
651 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
652 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
655 def show_tx_details(self, tx):
656 dialog = QDialog(self)
658 dialog.setWindowTitle(_("Transaction Details"))
660 dialog.setLayout(vbox)
661 dialog.setMinimumSize(600,300)
664 if tx_hash in self.wallet.transactions.keys():
665 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
666 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
668 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
674 vbox.addWidget(QLabel("Transaction ID:"))
675 e = QLineEdit(tx_hash)
679 vbox.addWidget(QLabel("Date: %s"%time_str))
680 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
683 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
684 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
686 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
687 vbox.addWidget(QLabel("Transaction fee: unknown"))
689 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
691 vbox.addWidget( self.generate_transaction_information_widget(tx) )
693 ok_button = QPushButton(_("Close"))
694 ok_button.setDefault(True)
695 ok_button.clicked.connect(dialog.accept)
699 hbox.addWidget(ok_button)
703 def tx_label_clicked(self, item, column):
704 if column==2 and item.isSelected():
706 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
707 self.history_list.editItem( item, column )
708 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
711 def tx_label_changed(self, item, column):
715 tx_hash = str(item.data(0, Qt.UserRole).toString())
716 tx = self.wallet.transactions.get(tx_hash)
717 text = unicode( item.text(2) )
718 self.set_label(tx_hash, text)
720 item.setForeground(2, QBrush(QColor('black')))
722 text = self.wallet.get_default_label(tx_hash)
723 item.setText(2, text)
724 item.setForeground(2, QBrush(QColor('gray')))
728 def edit_label(self, is_recv):
729 l = self.receive_list if is_recv else self.contacts_list
730 item = l.currentItem()
731 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
732 l.editItem( item, 1 )
733 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
737 def address_label_clicked(self, item, column, l, column_addr, column_label):
738 if column == column_label and item.isSelected():
739 is_editable = item.data(0, 32).toBool()
742 addr = unicode( item.text(column_addr) )
743 label = unicode( item.text(column_label) )
744 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
745 l.editItem( item, column )
746 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
749 def address_label_changed(self, item, column, l, column_addr, column_label):
750 if column == column_label:
751 addr = unicode( item.text(column_addr) )
752 text = unicode( item.text(column_label) )
753 is_editable = item.data(0, 32).toBool()
757 changed = self.set_label(addr, text)
759 self.update_history_tab()
760 self.update_completions()
762 self.current_item_changed(item)
764 self.run_hook('item_changed', item, column)
767 def current_item_changed(self, a):
768 self.run_hook('current_item_changed', a)
772 def update_history_tab(self):
774 self.history_list.clear()
775 for item in self.wallet.get_tx_history(self.current_account):
776 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
779 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
784 time_str = 'unverified'
785 icon = QIcon(":icons/unconfirmed.png")
788 icon = QIcon(":icons/unconfirmed.png")
790 icon = QIcon(":icons/clock%d.png"%conf)
792 icon = QIcon(":icons/confirmed.png")
794 if value is not None:
795 v_str = self.format_amount(value, True, whitespaces=True)
799 balance_str = self.format_amount(balance, whitespaces=True)
802 label, is_default_label = self.wallet.get_label(tx_hash)
804 label = _('Pruned transaction outputs')
805 is_default_label = False
807 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
808 item.setFont(2, QFont(MONOSPACE_FONT))
809 item.setFont(3, QFont(MONOSPACE_FONT))
810 item.setFont(4, QFont(MONOSPACE_FONT))
812 item.setForeground(3, QBrush(QColor("#BC1E1E")))
814 item.setData(0, Qt.UserRole, tx_hash)
815 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
817 item.setForeground(2, QBrush(QColor('grey')))
819 item.setIcon(0, icon)
820 self.history_list.insertTopLevelItem(0,item)
823 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
826 def create_send_tab(self):
831 grid.setColumnMinimumWidth(3,300)
832 grid.setColumnStretch(5,1)
835 self.payto_e = QLineEdit()
836 grid.addWidget(QLabel(_('Pay to')), 1, 0)
837 grid.addWidget(self.payto_e, 1, 1, 1, 3)
839 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)
841 completer = QCompleter()
842 completer.setCaseSensitivity(False)
843 self.payto_e.setCompleter(completer)
844 completer.setModel(self.completions)
846 self.message_e = QLineEdit()
847 grid.addWidget(QLabel(_('Description')), 2, 0)
848 grid.addWidget(self.message_e, 2, 1, 1, 3)
849 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)
851 self.amount_e = AmountEdit(self.base_unit)
852 grid.addWidget(QLabel(_('Amount')), 3, 0)
853 grid.addWidget(self.amount_e, 3, 1, 1, 2)
854 grid.addWidget(HelpButton(
855 _('Amount to be sent.') + '\n\n' \
856 + _('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.') \
857 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
859 self.fee_e = AmountEdit(self.base_unit)
860 grid.addWidget(QLabel(_('Fee')), 4, 0)
861 grid.addWidget(self.fee_e, 4, 1, 1, 2)
862 grid.addWidget(HelpButton(
863 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
864 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
865 + _('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)
868 self.send_button = EnterButton(_("Send"), self.do_send)
869 grid.addWidget(self.send_button, 6, 1)
871 b = EnterButton(_("Clear"),self.do_clear)
872 grid.addWidget(b, 6, 2)
874 self.payto_sig = QLabel('')
875 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
877 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
878 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
887 def entry_changed( is_fee ):
888 self.funds_error = False
890 if self.amount_e.is_shortcut:
891 self.amount_e.is_shortcut = False
892 c, u = self.wallet.get_account_balance(self.current_account)
893 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
894 fee = self.wallet.estimated_fee(inputs)
896 self.amount_e.setText( self.format_amount(amount) )
897 self.fee_e.setText( self.format_amount( fee ) )
900 amount = self.read_amount(str(self.amount_e.text()))
901 fee = self.read_amount(str(self.fee_e.text()))
903 if not is_fee: fee = None
906 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
908 self.fee_e.setText( self.format_amount( fee ) )
911 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
915 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
916 self.funds_error = True
917 text = _( "Not enough funds" )
918 c, u = self.wallet.get_frozen_balance()
919 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
921 self.statusBar().showMessage(text)
922 self.amount_e.setPalette(palette)
923 self.fee_e.setPalette(palette)
925 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
926 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
928 self.run_hook('create_send_tab', grid)
932 def update_completions(self):
934 for addr,label in self.wallet.labels.items():
935 if addr in self.wallet.addressbook:
936 l.append( label + ' <' + addr + '>')
938 self.run_hook('update_completions', l)
939 self.completions.setStringList(l)
943 return lambda s, *args: s.do_protect(func, args)
948 label = unicode( self.message_e.text() )
949 r = unicode( self.payto_e.text() )
952 # label or alias, with address in brackets
953 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
954 to_address = m.group(2) if m else r
956 if not is_valid(to_address):
957 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
961 amount = self.read_amount(unicode( self.amount_e.text()))
963 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
966 fee = self.read_amount(unicode( self.fee_e.text()))
968 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
971 confirm_amount = self.config.get('confirm_amount', 100000000)
972 if amount >= confirm_amount:
973 if not self.question("send %s to %s?"%(self.format_amount(amount) + ' '+ self.base_unit(), to_address)):
976 self.send_tx(to_address, amount, fee, label)
980 def send_tx(self, to_address, amount, fee, label, password):
983 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
984 except BaseException, e:
985 traceback.print_exc(file=sys.stdout)
986 self.show_message(str(e))
989 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
990 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
993 self.run_hook('send_tx', tx)
996 self.set_label(tx.hash(), label)
999 h = self.wallet.send_tx(tx)
1000 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
1001 status, msg = self.wallet.receive_tx( h )
1003 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
1005 self.update_contacts_tab()
1007 QMessageBox.warning(self, _('Error'), msg, _('OK'))
1009 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
1011 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
1012 with open(fileName,'w') as f:
1013 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1014 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
1016 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
1021 def set_url(self, url):
1022 address, amount, label, message, signature, identity, url = util.parse_url(url)
1023 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
1025 if label and self.wallet.labels.get(address) != label:
1026 if self.question('Give label "%s" to address %s ?'%(label,address)):
1027 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
1028 self.wallet.addressbook.append(address)
1029 self.set_label(address, label)
1031 self.run_hook('set_url', url, self.show_message, self.question)
1033 self.tabs.setCurrentIndex(1)
1034 label = self.wallet.labels.get(address)
1035 m_addr = label + ' <'+ address +'>' if label else address
1036 self.payto_e.setText(m_addr)
1038 self.message_e.setText(message)
1039 self.amount_e.setText(amount)
1041 self.set_frozen(self.payto_e,True)
1042 self.set_frozen(self.amount_e,True)
1043 self.set_frozen(self.message_e,True)
1044 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
1046 self.payto_sig.setVisible(False)
1049 self.payto_sig.setVisible(False)
1050 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1052 self.set_frozen(e,False)
1053 self.update_status()
1055 def set_frozen(self,entry,frozen):
1057 entry.setReadOnly(True)
1058 entry.setFrame(False)
1059 palette = QPalette()
1060 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1061 entry.setPalette(palette)
1063 entry.setReadOnly(False)
1064 entry.setFrame(True)
1065 palette = QPalette()
1066 palette.setColor(entry.backgroundRole(), QColor('white'))
1067 entry.setPalette(palette)
1070 def toggle_freeze(self,addr):
1072 if addr in self.wallet.frozen_addresses:
1073 self.wallet.unfreeze(addr)
1075 self.wallet.freeze(addr)
1076 self.update_receive_tab()
1078 def toggle_priority(self,addr):
1080 if addr in self.wallet.prioritized_addresses:
1081 self.wallet.unprioritize(addr)
1083 self.wallet.prioritize(addr)
1084 self.update_receive_tab()
1087 def create_list_tab(self, headers):
1088 "generic tab creation method"
1089 l = MyTreeWidget(self)
1090 l.setColumnCount( len(headers) )
1091 l.setHeaderLabels( headers )
1094 vbox = QVBoxLayout()
1101 vbox.addWidget(buttons)
1103 hbox = QHBoxLayout()
1106 buttons.setLayout(hbox)
1111 def create_receive_tab(self):
1112 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1113 l.setContextMenuPolicy(Qt.CustomContextMenu)
1114 l.customContextMenuRequested.connect(self.create_receive_menu)
1115 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1116 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1117 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1118 self.receive_list = l
1119 self.receive_buttons_hbox = hbox
1124 def receive_tab_set_mode(self, i):
1125 self.save_column_widths()
1126 self.expert_mode = (i == 1)
1127 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1128 self.update_receive_tab()
1131 def save_column_widths(self):
1132 if not self.expert_mode:
1133 widths = [ self.receive_list.columnWidth(0) ]
1136 for i in range(self.receive_list.columnCount() -1):
1137 widths.append(self.receive_list.columnWidth(i))
1138 self.column_widths["receive"][self.expert_mode] = widths
1140 self.column_widths["history"] = []
1141 for i in range(self.history_list.columnCount() - 1):
1142 self.column_widths["history"].append(self.history_list.columnWidth(i))
1144 self.column_widths["contacts"] = []
1145 for i in range(self.contacts_list.columnCount() - 1):
1146 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1148 self.config.set_key("column_widths", self.column_widths, True)
1151 def create_contacts_tab(self):
1152 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1153 l.setContextMenuPolicy(Qt.CustomContextMenu)
1154 l.customContextMenuRequested.connect(self.create_contact_menu)
1155 for i,width in enumerate(self.column_widths['contacts']):
1156 l.setColumnWidth(i, width)
1158 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1159 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1160 self.contacts_list = l
1161 self.contacts_buttons_hbox = hbox
1166 def delete_imported_key(self, addr):
1167 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1168 self.wallet.delete_imported_key(addr)
1169 self.update_receive_tab()
1170 self.update_history_tab()
1173 def create_receive_menu(self, position):
1174 # fixme: this function apparently has a side effect.
1175 # if it is not called the menu pops up several times
1176 #self.receive_list.selectedIndexes()
1178 item = self.receive_list.itemAt(position)
1180 addr = unicode(item.text(0))
1181 if not is_valid(addr):
1182 item.setExpanded(not item.isExpanded())
1185 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1186 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1187 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1188 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1189 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1190 if addr in self.wallet.imported_keys:
1191 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1193 if self.expert_mode:
1194 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1195 menu.addAction(t, lambda: self.toggle_freeze(addr))
1196 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1197 menu.addAction(t, lambda: self.toggle_priority(addr))
1199 self.run_hook('receive_menu', menu)
1200 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1203 def payto(self, addr):
1205 label = self.wallet.labels.get(addr)
1206 m_addr = label + ' <' + addr + '>' if label else addr
1207 self.tabs.setCurrentIndex(1)
1208 self.payto_e.setText(m_addr)
1209 self.amount_e.setFocus()
1212 def delete_contact(self, x):
1213 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1214 self.wallet.delete_contact(x)
1215 self.set_label(x, None)
1216 self.update_history_tab()
1217 self.update_contacts_tab()
1218 self.update_completions()
1221 def create_contact_menu(self, position):
1222 item = self.contacts_list.itemAt(position)
1224 addr = unicode(item.text(0))
1225 label = unicode(item.text(1))
1226 is_editable = item.data(0,32).toBool()
1227 payto_addr = item.data(0,33).toString()
1229 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1230 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1231 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1233 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1234 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1236 self.run_hook('create_contact_menu', menu, item)
1237 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1240 def update_receive_item(self, item):
1241 item.setFont(0, QFont(MONOSPACE_FONT))
1242 address = str(item.data(0,0).toString())
1243 label = self.wallet.labels.get(address,'')
1244 item.setData(1,0,label)
1245 item.setData(0,32, True) # is editable
1247 self.run_hook('update_receive_item', address, item)
1249 c, u = self.wallet.get_addr_balance(address)
1250 balance = self.format_amount(c + u)
1251 item.setData(2,0,balance)
1253 if self.expert_mode:
1254 if address in self.wallet.frozen_addresses:
1255 item.setBackgroundColor(0, QColor('lightblue'))
1256 elif address in self.wallet.prioritized_addresses:
1257 item.setBackgroundColor(0, QColor('lightgreen'))
1260 def update_receive_tab(self):
1261 l = self.receive_list
1264 l.setColumnHidden(2, not self.expert_mode)
1265 l.setColumnHidden(3, not self.expert_mode)
1266 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1267 l.setColumnWidth(i, width)
1269 if self.current_account is None:
1270 account_items = self.wallet.accounts.items()
1271 elif self.current_account != -1:
1272 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1276 for k, account in account_items:
1277 name = self.wallet.labels.get(k, 'unnamed account')
1278 c,u = self.wallet.get_account_balance(k)
1279 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1280 l.addTopLevelItem(account_item)
1281 account_item.setExpanded(True)
1283 for is_change in ([0,1] if self.expert_mode else [0]):
1284 if self.expert_mode:
1285 name = "Receiving" if not is_change else "Change"
1286 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1287 account_item.addChild(seq_item)
1288 if not is_change: seq_item.setExpanded(True)
1290 seq_item = account_item
1294 for address in account.get_addresses(is_change):
1295 h = self.wallet.history.get(address,[])
1299 if gap > self.wallet.gap_limit:
1304 num_tx = '*' if h == ['*'] else "%d"%len(h)
1305 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1306 self.update_receive_item(item)
1308 item.setBackgroundColor(1, QColor('red'))
1309 seq_item.addChild(item)
1312 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1313 c,u = self.wallet.get_imported_balance()
1314 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1315 l.addTopLevelItem(account_item)
1316 account_item.setExpanded(True)
1317 for address in self.wallet.imported_keys.keys():
1318 item = QTreeWidgetItem( [ address, '', '', ''] )
1319 self.update_receive_item(item)
1320 account_item.addChild(item)
1323 # we use column 1 because column 0 may be hidden
1324 l.setCurrentItem(l.topLevelItem(0),1)
1327 def update_contacts_tab(self):
1328 l = self.contacts_list
1331 for address in self.wallet.addressbook:
1332 label = self.wallet.labels.get(address,'')
1333 n = self.wallet.get_num_tx(address)
1334 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1335 item.setFont(0, QFont(MONOSPACE_FONT))
1336 # 32 = label can be edited (bool)
1337 item.setData(0,32, True)
1339 item.setData(0,33, address)
1340 l.addTopLevelItem(item)
1342 self.run_hook('update_contacts_tab', l)
1343 l.setCurrentItem(l.topLevelItem(0))
1347 def create_console_tab(self):
1348 from qt_console import Console
1349 self.console = console = Console()
1353 def update_console(self):
1354 console = self.console
1355 console.history = self.config.get("console-history",[])
1356 console.history_index = len(console.history)
1358 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1359 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1361 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1363 def mkfunc(f, method):
1364 return lambda *args: apply( f, (method, args, self.password_dialog ))
1366 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1367 methods[m] = mkfunc(c._run, m)
1369 console.updateNamespace(methods)
1372 def change_account(self,s):
1373 if s == _("All accounts"):
1374 self.current_account = None
1376 accounts = self.wallet.get_accounts()
1377 for k, v in accounts.items():
1379 self.current_account = k
1380 self.update_history_tab()
1381 self.update_status()
1382 self.update_receive_tab()
1384 def create_status_bar(self):
1387 sb.setFixedHeight(35)
1388 qtVersion = qVersion()
1390 self.balance_label = QLabel("")
1391 sb.addWidget(self.balance_label)
1393 update_notification = UpdateLabel(self.config)
1394 if(update_notification.new_version):
1395 sb.addPermanentWidget(update_notification)
1397 self.account_selector = QComboBox()
1398 self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1399 sb.addPermanentWidget(self.account_selector)
1401 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1402 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1404 self.lock_icon = QIcon()
1405 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1406 sb.addPermanentWidget( self.password_button )
1408 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1409 self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1410 sb.addPermanentWidget( self.seed_button )
1411 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1412 sb.addPermanentWidget( self.status_button )
1414 self.run_hook('create_status_bar', (sb,))
1416 self.setStatusBar(sb)
1419 def update_lock_icon(self):
1420 icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1421 self.password_button.setIcon( icon )
1424 def update_buttons_on_seed(self):
1425 if self.wallet.seed:
1426 self.seed_button.show()
1427 self.password_button.show()
1428 self.send_button.setText(_("Send"))
1430 self.password_button.hide()
1431 self.seed_button.hide()
1432 self.send_button.setText(_("Create unsigned transaction"))
1435 def change_password_dialog(self):
1436 from password_dialog import PasswordDialog
1437 d = PasswordDialog(self.wallet, self)
1439 self.update_lock_icon()
1444 self.config.set_key('gui', 'lite', True)
1447 self.lite.mini.show()
1449 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1450 self.lite.main(None)
1453 def new_contact_dialog(self):
1454 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1455 address = unicode(text)
1457 if is_valid(address):
1458 self.wallet.add_contact(address)
1459 self.update_contacts_tab()
1460 self.update_history_tab()
1461 self.update_completions()
1463 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1466 def new_account_dialog(self):
1468 dialog = QDialog(self)
1470 dialog.setWindowTitle(_("New Account"))
1472 addr = self.wallet.new_account_address()
1473 vbox = QVBoxLayout()
1474 vbox.addWidget(QLabel(_("To create a new account, please send coins to the first address of that account:")))
1479 ok_button = QPushButton(_("OK"))
1480 ok_button.setDefault(True)
1481 ok_button.clicked.connect(dialog.accept)
1483 hbox = QHBoxLayout()
1485 hbox.addWidget(ok_button)
1486 vbox.addLayout(hbox)
1488 dialog.setLayout(vbox)
1493 def show_master_public_key(self):
1494 dialog = QDialog(self)
1496 dialog.setWindowTitle(_("Master Public Key"))
1498 main_text = QTextEdit()
1499 main_text.setText(self.wallet.get_master_public_key())
1500 main_text.setReadOnly(True)
1501 main_text.setMaximumHeight(170)
1502 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1504 ok_button = QPushButton(_("OK"))
1505 ok_button.setDefault(True)
1506 ok_button.clicked.connect(dialog.accept)
1508 main_layout = QGridLayout()
1509 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1511 main_layout.addWidget(main_text, 1, 0)
1512 main_layout.addWidget(qrw, 1, 1 )
1514 vbox = QVBoxLayout()
1515 vbox.addLayout(main_layout)
1516 hbox = QHBoxLayout()
1518 hbox.addWidget(ok_button)
1519 vbox.addLayout(hbox)
1521 dialog.setLayout(vbox)
1526 def show_seed_dialog(self, password):
1527 if not self.wallet.seed:
1528 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1531 seed = self.wallet.decode_seed(password)
1533 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1536 from seed_dialog import SeedDialog
1537 d = SeedDialog(self)
1538 d.show_seed(seed, self.wallet.imported_keys)
1542 def show_qrcode(self, data, title = "QR code"):
1546 d.setWindowTitle(title)
1547 d.setMinimumSize(270, 300)
1548 vbox = QVBoxLayout()
1549 qrw = QRCodeWidget(data)
1550 vbox.addWidget(qrw, 1)
1551 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1552 hbox = QHBoxLayout()
1556 filename = "qrcode.bmp"
1557 bmp.save_qrcode(qrw.qr, filename)
1558 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1560 b = QPushButton(_("Save"))
1562 b.clicked.connect(print_qr)
1564 b = QPushButton(_("Close"))
1566 b.clicked.connect(d.accept)
1569 vbox.addLayout(hbox)
1574 def do_protect(self, func, args):
1575 if self.wallet.use_encryption:
1576 password = self.password_dialog()
1582 if args != (False,):
1583 args = (self,) + args + (password,)
1585 args = (self,password)
1590 def show_private_key(self, address, password):
1591 if not address: return
1593 pk = self.wallet.get_private_key(address, password)
1594 except BaseException, e:
1595 self.show_message(str(e))
1597 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1601 def do_sign(self, address, message, signature, password):
1603 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1604 signature.setText(sig)
1605 except BaseException, e:
1606 self.show_message(str(e))
1608 def sign_message(self, address):
1609 if not address: return
1612 d.setWindowTitle(_('Sign Message'))
1613 d.setMinimumSize(410, 290)
1615 tab_widget = QTabWidget()
1617 layout = QGridLayout(tab)
1619 sign_address = QLineEdit()
1621 sign_address.setText(address)
1622 layout.addWidget(QLabel(_('Address')), 1, 0)
1623 layout.addWidget(sign_address, 1, 1)
1625 sign_message = QTextEdit()
1626 layout.addWidget(QLabel(_('Message')), 2, 0)
1627 layout.addWidget(sign_message, 2, 1)
1628 layout.setRowStretch(2,3)
1630 sign_signature = QTextEdit()
1631 layout.addWidget(QLabel(_('Signature')), 3, 0)
1632 layout.addWidget(sign_signature, 3, 1)
1633 layout.setRowStretch(3,1)
1636 hbox = QHBoxLayout()
1637 b = QPushButton(_("Sign"))
1639 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1640 b = QPushButton(_("Close"))
1641 b.clicked.connect(d.accept)
1643 layout.addLayout(hbox, 4, 1)
1644 tab_widget.addTab(tab, _("Sign"))
1648 layout = QGridLayout(tab)
1650 verify_address = QLineEdit()
1651 layout.addWidget(QLabel(_('Address')), 1, 0)
1652 layout.addWidget(verify_address, 1, 1)
1654 verify_message = QTextEdit()
1655 layout.addWidget(QLabel(_('Message')), 2, 0)
1656 layout.addWidget(verify_message, 2, 1)
1657 layout.setRowStretch(2,3)
1659 verify_signature = QTextEdit()
1660 layout.addWidget(QLabel(_('Signature')), 3, 0)
1661 layout.addWidget(verify_signature, 3, 1)
1662 layout.setRowStretch(3,1)
1665 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1666 self.show_message(_("Signature verified"))
1668 self.show_message(_("Error: wrong signature"))
1670 hbox = QHBoxLayout()
1671 b = QPushButton(_("Verify"))
1672 b.clicked.connect(do_verify)
1674 b = QPushButton(_("Close"))
1675 b.clicked.connect(d.accept)
1677 layout.addLayout(hbox, 4, 1)
1678 tab_widget.addTab(tab, _("Verify"))
1680 vbox = QVBoxLayout()
1681 vbox.addWidget(tab_widget)
1688 def question(self, msg):
1689 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1691 def show_message(self, msg):
1692 QMessageBox.information(self, _('Message'), msg, _('OK'))
1694 def password_dialog(self ):
1701 vbox = QVBoxLayout()
1702 msg = _('Please enter your password')
1703 vbox.addWidget(QLabel(msg))
1705 grid = QGridLayout()
1707 grid.addWidget(QLabel(_('Password')), 1, 0)
1708 grid.addWidget(pw, 1, 1)
1709 vbox.addLayout(grid)
1711 vbox.addLayout(ok_cancel_buttons(d))
1714 self.run_hook('password_dialog', pw, grid, 1)
1715 if not d.exec_(): return
1716 return unicode(pw.text())
1723 def generate_transaction_information_widget(self, tx):
1724 tabs = QTabWidget(self)
1727 grid_ui = QGridLayout(tab1)
1728 grid_ui.setColumnStretch(0,1)
1729 tabs.addTab(tab1, _('Outputs') )
1731 tree_widget = MyTreeWidget(self)
1732 tree_widget.setColumnCount(2)
1733 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1734 tree_widget.setColumnWidth(0, 300)
1735 tree_widget.setColumnWidth(1, 50)
1737 for address, value in tx.outputs:
1738 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1739 tree_widget.addTopLevelItem(item)
1741 tree_widget.setMaximumHeight(100)
1743 grid_ui.addWidget(tree_widget)
1746 grid_ui = QGridLayout(tab2)
1747 grid_ui.setColumnStretch(0,1)
1748 tabs.addTab(tab2, _('Inputs') )
1750 tree_widget = MyTreeWidget(self)
1751 tree_widget.setColumnCount(2)
1752 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1754 for input_line in tx.inputs:
1755 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1756 tree_widget.addTopLevelItem(item)
1758 tree_widget.setMaximumHeight(100)
1760 grid_ui.addWidget(tree_widget)
1764 def tx_dict_from_text(self, txt):
1766 tx_dict = json.loads(str(txt))
1767 assert "hex" in tx_dict.keys()
1768 assert "complete" in tx_dict.keys()
1769 if not tx_dict["complete"]:
1770 assert "input_info" in tx_dict.keys()
1772 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1777 def read_tx_from_file(self):
1778 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1782 with open(fileName, "r") as f:
1783 file_content = f.read()
1784 except (ValueError, IOError, os.error), reason:
1785 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1787 return self.tx_dict_from_text(file_content)
1791 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1793 self.wallet.signrawtransaction(tx, input_info, [], password)
1795 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1797 with open(fileName, "w+") as f:
1798 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1799 self.show_message(_("Transaction saved successfully"))
1802 except BaseException, e:
1803 self.show_message(str(e))
1806 def send_raw_transaction(self, raw_tx, dialog = ""):
1807 result, result_message = self.wallet.sendtx( raw_tx )
1809 self.show_message("Transaction successfully sent: %s" % (result_message))
1813 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1815 def do_process_from_text(self):
1816 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1819 tx_dict = self.tx_dict_from_text(text)
1821 self.create_process_transaction_window(tx_dict)
1823 def do_process_from_file(self):
1824 tx_dict = self.read_tx_from_file()
1826 self.create_process_transaction_window(tx_dict)
1828 def create_process_transaction_window(self, tx_dict):
1829 tx = Transaction(tx_dict["hex"])
1831 dialog = QDialog(self)
1832 dialog.setMinimumWidth(500)
1833 dialog.setWindowTitle(_('Process raw transaction'))
1839 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1840 l.addWidget(QLabel(_("Actions")), 4,0)
1842 if tx_dict["complete"] == False:
1843 l.addWidget(QLabel(_("Unsigned")), 3,1)
1844 if self.wallet.seed :
1845 b = QPushButton("Sign transaction")
1846 input_info = json.loads(tx_dict["input_info"])
1847 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1848 l.addWidget(b, 4, 1)
1850 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1852 l.addWidget(QLabel(_("Signed")), 3,1)
1853 b = QPushButton("Broadcast transaction")
1854 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1857 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1858 cancelButton = QPushButton(_("Cancel"))
1859 cancelButton.clicked.connect(lambda: dialog.done(0))
1860 l.addWidget(cancelButton, 4,2)
1866 def do_export_privkeys(self, password):
1867 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.")))
1870 select_export = _('Select file to export your private keys to')
1871 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1873 with open(fileName, "w+") as csvfile:
1874 transaction = csv.writer(csvfile)
1875 transaction.writerow(["address", "private_key"])
1878 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1879 transaction.writerow(["%34s"%addr,pk])
1881 self.show_message(_("Private keys exported."))
1883 except (IOError, os.error), reason:
1884 export_error_label = _("Electrum was unable to produce a private key-export.")
1885 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1887 except BaseException, e:
1888 self.show_message(str(e))
1892 def do_import_labels(self):
1893 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1894 if not labelsFile: return
1896 f = open(labelsFile, 'r')
1899 for key, value in json.loads(data).items():
1900 self.wallet.labels[key] = value
1902 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1903 except (IOError, os.error), reason:
1904 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1907 def do_export_labels(self):
1908 labels = self.wallet.labels
1910 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1912 with open(fileName, 'w+') as f:
1913 json.dump(labels, f)
1914 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1915 except (IOError, os.error), reason:
1916 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1919 def do_export_history(self):
1920 from gui_lite import csv_transaction
1921 csv_transaction(self.wallet)
1925 def do_import_privkey(self, password):
1926 if not self.wallet.imported_keys:
1927 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1928 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1929 + _('Are you sure you understand what you are doing?'), 3, 4)
1932 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1935 text = str(text).split()
1940 addr = self.wallet.import_key(key, password)
1941 except BaseException as e:
1947 addrlist.append(addr)
1949 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1951 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1952 self.update_receive_tab()
1953 self.update_history_tab()
1956 def settings_dialog(self):
1958 d.setWindowTitle(_('Electrum Settings'))
1960 vbox = QVBoxLayout()
1962 tabs = QTabWidget(self)
1963 self.settings_tab = tabs
1964 vbox.addWidget(tabs)
1967 grid_ui = QGridLayout(tab1)
1968 grid_ui.setColumnStretch(0,1)
1969 tabs.addTab(tab1, _('Display') )
1971 nz_label = QLabel(_('Display zeros'))
1972 grid_ui.addWidget(nz_label, 0, 0)
1973 nz_e = AmountEdit(None,True)
1974 nz_e.setText("%d"% self.num_zeros)
1975 grid_ui.addWidget(nz_e, 0, 1)
1976 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1977 grid_ui.addWidget(HelpButton(msg), 0, 2)
1978 if not self.config.is_modifiable('num_zeros'):
1979 for w in [nz_e, nz_label]: w.setEnabled(False)
1981 lang_label=QLabel(_('Language') + ':')
1982 grid_ui.addWidget(lang_label, 1, 0)
1983 lang_combo = QComboBox()
1984 from i18n import languages
1985 lang_combo.addItems(languages.values())
1987 index = languages.keys().index(self.config.get("language",''))
1990 lang_combo.setCurrentIndex(index)
1991 grid_ui.addWidget(lang_combo, 1, 1)
1992 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1993 if not self.config.is_modifiable('language'):
1994 for w in [lang_combo, lang_label]: w.setEnabled(False)
1996 currencies = self.exchanger.get_currencies()
1997 currencies.insert(0, "None")
1999 cur_label=QLabel(_('Currency') + ':')
2000 grid_ui.addWidget(cur_label , 2, 0)
2001 cur_combo = QComboBox()
2002 cur_combo.addItems(currencies)
2004 index = currencies.index(self.config.get('currency', "None"))
2007 cur_combo.setCurrentIndex(index)
2008 grid_ui.addWidget(cur_combo, 2, 1)
2009 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
2011 expert_cb = QCheckBox(_('Expert mode'))
2012 expert_cb.setChecked(self.expert_mode)
2013 grid_ui.addWidget(expert_cb, 3, 0)
2014 hh = _('In expert mode, your client will:') + '\n' \
2015 + _(' - Show change addresses in the Receive tab') + '\n' \
2016 + _(' - Display the balance of each address') + '\n' \
2017 + _(' - Add freeze/prioritize actions to addresses.')
2018 grid_ui.addWidget(HelpButton(hh), 3, 2)
2019 grid_ui.setRowStretch(4,1)
2023 grid_wallet = QGridLayout(tab2)
2024 grid_wallet.setColumnStretch(0,1)
2025 tabs.addTab(tab2, _('Wallet') )
2027 fee_label = QLabel(_('Transaction fee'))
2028 grid_wallet.addWidget(fee_label, 0, 0)
2029 fee_e = AmountEdit(self.base_unit)
2030 fee_e.setText(self.format_amount(self.fee).strip())
2031 grid_wallet.addWidget(fee_e, 0, 2)
2032 msg = _('Fee per kilobyte of transaction.') + ' ' \
2033 + _('Recommended value') + ': ' + self.format_amount(50000)
2034 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2035 if not self.config.is_modifiable('fee_per_kb'):
2036 for w in [fee_e, fee_label]: w.setEnabled(False)
2038 usechange_cb = QCheckBox(_('Use change addresses'))
2039 usechange_cb.setChecked(self.wallet.use_change)
2040 grid_wallet.addWidget(usechange_cb, 1, 0)
2041 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2042 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2044 gap_label = QLabel(_('Gap limit'))
2045 grid_wallet.addWidget(gap_label, 2, 0)
2046 gap_e = AmountEdit(None,True)
2047 gap_e.setText("%d"% self.wallet.gap_limit)
2048 grid_wallet.addWidget(gap_e, 2, 2)
2049 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2050 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2051 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2052 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2053 + _('Warning') + ': ' \
2054 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2055 + _('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'
2056 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2057 if not self.config.is_modifiable('gap_limit'):
2058 for w in [gap_e, gap_label]: w.setEnabled(False)
2060 units = ['BTC', 'mBTC']
2061 unit_label = QLabel(_('Base unit'))
2062 grid_wallet.addWidget(unit_label, 3, 0)
2063 unit_combo = QComboBox()
2064 unit_combo.addItems(units)
2065 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2066 grid_wallet.addWidget(unit_combo, 3, 2)
2067 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2068 + '\n1BTC=1000mBTC.\n' \
2069 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2070 grid_wallet.setRowStretch(4,1)
2074 tab5 = QScrollArea()
2075 tab5.setEnabled(True)
2076 tab5.setWidgetResizable(True)
2078 grid_plugins = QGridLayout()
2079 grid_plugins.setColumnStretch(0,1)
2082 w.setLayout(grid_plugins)
2085 w.setMinimumHeight(len(self.plugins)*35)
2087 tabs.addTab(tab5, _('Plugins') )
2088 def mk_toggle(cb, p):
2089 return lambda: cb.setChecked(p.toggle())
2090 for i, p in enumerate(self.plugins):
2092 cb = QCheckBox(p.fullname())
2093 cb.setDisabled(not p.is_available())
2094 cb.setChecked(p.is_enabled())
2095 cb.clicked.connect(mk_toggle(cb,p))
2096 grid_plugins.addWidget(cb, i, 0)
2097 if p.requires_settings():
2098 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2099 grid_plugins.addWidget(HelpButton(p.description()), i, 2)
2101 print_msg("Error: cannot display plugin", p)
2102 traceback.print_exc(file=sys.stdout)
2103 grid_plugins.setRowStretch(i+1,1)
2105 self.run_hook('create_settings_tab', tabs)
2107 vbox.addLayout(ok_cancel_buttons(d))
2111 if not d.exec_(): return
2113 fee = unicode(fee_e.text())
2115 fee = self.read_amount(fee)
2117 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2122 nz = unicode(nz_e.text())
2127 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2130 if self.num_zeros != nz:
2132 self.config.set_key('num_zeros', nz, True)
2133 self.update_history_tab()
2134 self.update_receive_tab()
2136 usechange_result = usechange_cb.isChecked()
2137 if self.wallet.use_change != usechange_result:
2138 self.wallet.use_change = usechange_result
2139 self.config.set_key('use_change', self.wallet.use_change, True)
2141 unit_result = units[unit_combo.currentIndex()]
2142 if self.base_unit() != unit_result:
2143 self.decimal_point = 8 if unit_result == 'BTC' else 5
2144 self.config.set_key('decimal_point', self.decimal_point, True)
2145 self.update_history_tab()
2146 self.update_status()
2149 n = int(gap_e.text())
2151 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2154 if self.wallet.gap_limit != n:
2155 r = self.wallet.change_gap_limit(n)
2157 self.update_receive_tab()
2158 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2160 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2162 need_restart = False
2164 lang_request = languages.keys()[lang_combo.currentIndex()]
2165 if lang_request != self.config.get('language'):
2166 self.config.set_key("language", lang_request, True)
2169 cur_request = str(currencies[cur_combo.currentIndex()])
2170 if cur_request != self.config.get('currency', "None"):
2171 self.config.set_key('currency', cur_request, True)
2172 self.update_wallet()
2174 self.run_hook('close_settings_dialog')
2177 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2179 self.receive_tab_set_mode(expert_cb.isChecked())
2181 def run_network_dialog(self):
2182 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2184 def closeEvent(self, event):
2186 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2187 self.save_column_widths()
2188 self.config.set_key("console-history", self.console.history[-50:], True)
2191 class OpenFileEventFilter(QObject):
2192 def __init__(self, windows):
2193 self.windows = windows
2194 super(OpenFileEventFilter, self).__init__()
2196 def eventFilter(self, obj, event):
2197 if event.type() == QtCore.QEvent.FileOpen:
2198 if len(self.windows) >= 1:
2199 self.windows[0].set_url(event.url().toString())
2208 def __init__(self, config, interface, blockchain, app=None):
2209 self.interface = interface
2210 self.config = config
2211 self.blockchain = blockchain
2213 self.efilter = OpenFileEventFilter(self.windows)
2215 self.app = QApplication(sys.argv)
2216 self.app.installEventFilter(self.efilter)
2219 def main(self, url):
2221 storage = WalletStorage(self.config)
2222 if not storage.file_exists:
2223 import installwizard
2224 wizard = installwizard.InstallWizard(self.config, self.interface, storage)
2225 wallet = wizard.run()
2229 wallet = Wallet(storage)
2231 wallet.start_threads(self.interface, self.blockchain)
2235 w = ElectrumWindow(self.config)
2236 w.load_wallet(wallet)
2238 self.windows.append(w)
2239 if url: w.set_url(url)
2247 wallet.stop_threads()