3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re, threading
20 from i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
29 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
31 from PyQt4.QtGui import *
32 from PyQt4.QtCore import *
33 import PyQt4.QtCore as QtCore
35 from electrum.bitcoin import MIN_RELAY_TX_FEE
40 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'")
42 from electrum.wallet import format_satoshis
43 from electrum.bitcoin import Transaction, is_valid
44 from electrum import mnemonic
45 from electrum import util, bitcoin, commands, Interface, Wallet, WalletVerifier, WalletSynchronizer
47 import bmp, pyqrnative
50 from amountedit import AmountEdit
51 from network_dialog import NetworkDialog
52 from qrcodewidget import QRCodeWidget
54 from decimal import Decimal
62 if platform.system() == 'Windows':
63 MONOSPACE_FONT = 'Lucida Console'
64 elif platform.system() == 'Darwin':
65 MONOSPACE_FONT = 'Monaco'
67 MONOSPACE_FONT = 'monospace'
69 from electrum import ELECTRUM_VERSION
74 class UpdateLabel(QLabel):
75 def __init__(self, config, parent=None):
76 QLabel.__init__(self, parent)
77 self.new_version = False
80 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
81 con.request("GET", "/version")
82 res = con.getresponse()
83 except socket.error as msg:
84 print_error("Could not retrieve version information")
88 self.latest_version = res.read()
89 self.latest_version = self.latest_version.replace("\n","")
90 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
92 self.current_version = ELECTRUM_VERSION
93 if(self.compare_versions(self.latest_version, self.current_version) == 1):
94 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
95 if(self.compare_versions(self.latest_version, latest_seen) == 1):
96 self.new_version = True
97 self.setText(_("New version available") + ": " + self.latest_version)
100 def compare_versions(self, version1, version2):
102 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
103 return cmp(normalize(version1), normalize(version2))
105 def ignore_this_version(self):
107 self.config.set_key("last_seen_version", self.latest_version, True)
108 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
111 def ignore_all_version(self):
113 self.config.set_key("last_seen_version", "9.9.9", True)
114 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
117 def open_website(self):
118 webbrowser.open("http://electrum.org/download.html")
121 def mouseReleaseEvent(self, event):
122 dialog = QDialog(self)
123 dialog.setWindowTitle(_('Electrum update'))
126 main_layout = QGridLayout()
127 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
129 ignore_version = QPushButton(_("Ignore this version"))
130 ignore_version.clicked.connect(self.ignore_this_version)
132 ignore_all_versions = QPushButton(_("Ignore all versions"))
133 ignore_all_versions.clicked.connect(self.ignore_all_version)
135 open_website = QPushButton(_("Goto download page"))
136 open_website.clicked.connect(self.open_website)
138 main_layout.addWidget(ignore_version, 1, 0)
139 main_layout.addWidget(ignore_all_versions, 1, 1)
140 main_layout.addWidget(open_website, 1, 2)
142 dialog.setLayout(main_layout)
146 if not dialog.exec_(): return
150 class Timer(QtCore.QThread):
153 self.emit(QtCore.SIGNAL('timersignal'))
156 class HelpButton(QPushButton):
157 def __init__(self, text):
158 QPushButton.__init__(self, '?')
159 self.setFocusPolicy(Qt.NoFocus)
160 self.setFixedWidth(20)
161 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
164 class EnterButton(QPushButton):
165 def __init__(self, text, func):
166 QPushButton.__init__(self, text)
168 self.clicked.connect(func)
170 def keyPressEvent(self, e):
171 if e.key() == QtCore.Qt.Key_Return:
174 class MyTreeWidget(QTreeWidget):
175 def __init__(self, parent):
176 QTreeWidget.__init__(self, parent)
179 for i in range(0,self.viewport().height()/5):
180 if self.itemAt(QPoint(0,i*5)) == item:
184 for j in range(0,30):
185 if self.itemAt(QPoint(0,i*5 + j)) != item:
187 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
189 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
194 class StatusBarButton(QPushButton):
195 def __init__(self, icon, tooltip, func):
196 QPushButton.__init__(self, icon, '')
197 self.setToolTip(tooltip)
199 self.setMaximumWidth(25)
200 self.clicked.connect(func)
203 def keyPressEvent(self, e):
204 if e.key() == QtCore.Qt.Key_Return:
211 def waiting_dialog(f):
217 w.setWindowTitle('Electrum')
227 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
236 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive":[[370], [370,200,130]] }
238 class ElectrumWindow(QMainWindow):
239 def changeEvent(self, event):
240 flags = self.windowFlags();
241 if event and event.type() == QtCore.QEvent.WindowStateChange:
242 if self.windowState() & QtCore.Qt.WindowMinimized:
243 self.build_menu(True)
244 # The only way to toggle the icon in the window managers taskbar is to use the Qt.Tooltip flag
245 # The problem is that it somehow creates an (in)visible window that will stay active and prevent
246 # Electrum from closing.
247 # As for now I have no clue how to implement a proper 'hide to tray' functionality.
248 # self.setWindowFlags(flags & ~Qt.ToolTip)
249 elif event.oldState() & QtCore.Qt.WindowMinimized:
250 self.build_menu(False)
251 #self.setWindowFlags(flags | Qt.ToolTip)
253 def build_menu(self, is_hidden = False):
255 if self.isMinimized():
256 m.addAction(_("Show"), self.showNormal)
258 m.addAction(_("Hide"), self.showMinimized)
261 m.addAction(_("Exit Electrum"), self.close)
262 self.tray.setContextMenu(m)
264 def tray_activated(self, reason):
265 if reason == QSystemTrayIcon.DoubleClick:
269 def __init__(self, wallet, config):
270 QMainWindow.__init__(self)
271 self._close_electrum = False
275 self.current_account = self.config.get("current_account", None)
277 self.icon = QIcon(os.getcwd() + '/icons/electrum.png')
278 self.tray = QSystemTrayIcon(self.icon, self)
279 self.tray.setToolTip('Electrum')
280 self.tray.activated.connect(self.tray_activated)
286 self.create_status_bar()
288 self.need_update = threading.Event()
289 self.wallet.interface.register_callback('updated', lambda: self.need_update.set())
290 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
291 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
292 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
293 self.wallet.interface.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
295 self.expert_mode = config.get('classic_expert_mode', False)
296 self.decimal_point = config.get('decimal_point', 8)
298 set_language(config.get('language'))
300 self.funds_error = False
301 self.completions = QStringListModel()
303 self.tabs = tabs = QTabWidget(self)
304 self.column_widths = self.config.get("column_widths", default_column_widths )
305 tabs.addTab(self.create_history_tab(), _('History') )
306 tabs.addTab(self.create_send_tab(), _('Send') )
307 tabs.addTab(self.create_receive_tab(), _('Receive') )
308 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
309 tabs.addTab(self.create_console_tab(), _('Console') )
310 tabs.setMinimumSize(600, 400)
311 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
312 self.setCentralWidget(tabs)
314 g = self.config.get("winpos-qt",[100, 100, 840, 400])
315 self.setGeometry(g[0], g[1], g[2], g[3])
316 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
317 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
318 self.setWindowTitle( title )
322 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
323 QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
324 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
325 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
326 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
328 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
329 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
330 self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
331 self.history_list.setFocus(True)
333 self.exchanger = exchange_rate.Exchanger(self)
334 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
336 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
337 if platform.system() == 'Windows':
338 n = 3 if self.wallet.seed else 2
339 tabs.setCurrentIndex (n)
340 tabs.setCurrentIndex (0)
342 # set initial message
343 self.console.showMessage(self.wallet.interface.banner)
345 # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
346 self.notify_transactions()
348 # plugins that need to change the GUI do it here
349 self.run_hook('init')
352 def select_wallet_file(self):
353 wallet_folder = self.wallet.config.path
354 re.sub("(\/\w*.dat)$", "", wallet_folder)
355 file_name = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat")
358 def open_wallet(self):
359 n = self.select_wallet_file()
363 def new_wallet(self):
364 n = self.getOpenFileName("Select wallet file")
366 wizard = installwizard.InstallWizard(self.config, self.interface)
367 wallet = wizard.run()
369 self.load_wallet(wallet)
373 def init_menubar(self):
376 electrum_menu = menubar.addMenu(_("&File"))
377 open_wallet_action = electrum_menu.addAction(_("Open wallet"))
378 open_wallet_action.triggered.connect(self.open_wallet)
380 new_wallet_action = electrum_menu.addAction(_("New wallet"))
381 new_wallet_action.triggered.connect(self.new_wallet)
383 preferences_name = _("Preferences")
384 if sys.platform == 'darwin':
385 preferences_name = _("Electrum preferences") # Settings / Preferences are all reserved keywords in OSX using this as work around
387 preferences_menu = electrum_menu.addAction(preferences_name)
388 preferences_menu.triggered.connect(self.settings_dialog)
389 electrum_menu.addSeparator()
391 raw_transaction_menu = electrum_menu.addMenu(_("&Load raw transaction"))
393 raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
394 raw_transaction_file.triggered.connect(self.do_process_from_file)
396 raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
397 raw_transaction_text.triggered.connect(self.do_process_from_text)
399 electrum_menu.addSeparator()
400 quit_item = electrum_menu.addAction(_("&Close"))
401 quit_item.triggered.connect(self.close)
403 wallet_menu = menubar.addMenu(_("&Wallet"))
404 wallet_backup = wallet_menu.addAction(_("&Create backup"))
405 wallet_backup.triggered.connect(lambda: backup_wallet(self.config.path))
407 show_menu = wallet_menu.addMenu(_("Show"))
410 show_seed = show_menu.addAction(_("&Seed"))
411 show_seed.triggered.connect(self.show_seed_dialog)
413 show_mpk = show_menu.addAction(_("&Master Public Key"))
414 show_mpk.triggered.connect(self.show_master_public_key)
416 wallet_menu.addSeparator()
417 new_contact = wallet_menu.addAction(_("&New contact"))
418 new_contact.triggered.connect(self.new_contact_dialog)
420 new_account = wallet_menu.addAction(_("&New account"))
421 new_account.triggered.connect(self.new_account_dialog)
423 import_menu = menubar.addMenu(_("&Import"))
424 in_labels = import_menu.addAction(_("&Labels"))
425 in_labels.triggered.connect(self.do_import_labels)
427 in_private_keys = import_menu.addAction(_("&Private keys"))
428 in_private_keys.triggered.connect(self.do_import_privkey)
430 export_menu = menubar.addMenu(_("&Export"))
431 ex_private_keys = export_menu.addAction(_("&Private keys"))
432 ex_private_keys.triggered.connect(self.do_export_privkeys)
434 ex_history = export_menu.addAction(_("&History"))
435 ex_history.triggered.connect(self.do_export_history)
437 ex_labels = export_menu.addAction(_("&Labels"))
438 ex_labels.triggered.connect(self.do_export_labels)
440 help_menu = menubar.addMenu(_("&Help"))
441 doc_open = help_menu.addAction(_("&Documentation"))
442 doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
443 web_open = help_menu.addAction(_("&Official website"))
444 web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
446 self.setMenuBar(menubar)
449 def load_wallet(self, filename):
452 config = electrum.SimpleConfig({'wallet_path': filename})
453 if not config.wallet_file_exists:
454 self.show_message("file not found "+ filename)
457 #self.wallet.verifier.stop()
458 interface = self.wallet.interface
459 verifier = self.wallet.verifier
460 self.wallet.synchronizer.stop()
463 self.wallet = electrum.Wallet(self.config)
464 self.wallet.interface = interface
465 self.wallet.verifier = verifier
467 synchronizer = electrum.WalletSynchronizer(self.wallet, self.config)
472 def notify_transactions(self):
473 print_error("Notifying GUI")
474 if len(self.wallet.interface.pending_transactions_for_notifications) > 0:
475 # Combine the transactions if there are more then three
476 tx_amount = len(self.wallet.interface.pending_transactions_for_notifications)
479 for tx in self.wallet.interface.pending_transactions_for_notifications:
480 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
484 self.notify("%s new transactions received. Total amount received in the new transactions %s %s" \
485 % (tx_amount, self.format_amount(total_amount), self.base_unit()))
487 self.wallet.interface.pending_transactions_for_notifications = []
489 for tx in self.wallet.interface.pending_transactions_for_notifications:
491 self.wallet.interface.pending_transactions_for_notifications.remove(tx)
492 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
494 self.notify("New transaction received. %s %s" % (self.format_amount(v), self.base_unit()))
496 def notify(self, message):
497 self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
500 def init_plugins(self):
501 import imp, pkgutil, __builtin__
502 if __builtin__.use_local_modules:
503 fp, pathname, description = imp.find_module('plugins')
504 plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
505 plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
506 imp.load_module('electrum_plugins', fp, pathname, description)
507 plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
509 import electrum_plugins
510 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
511 plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
514 for name, p in zip(plugin_names, plugins):
516 self.plugins.append( p.Plugin(self, name) )
518 print_msg("Error:cannot initialize plugin",p)
519 traceback.print_exc(file=sys.stdout)
522 def run_hook(self, name, *args):
523 for p in self.plugins:
524 if not p.is_enabled():
533 print_error("Plugin error")
534 traceback.print_exc(file=sys.stdout)
539 def set_label(self, name, text = None):
541 old_text = self.wallet.labels.get(name)
544 self.wallet.labels[name] = text
545 self.wallet.config.set_key('labels', self.wallet.labels)
549 self.wallet.labels.pop(name)
551 self.run_hook('set_label', name, text, changed)
555 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
556 def getOpenFileName(self, title, filter = None):
557 directory = self.config.get('io_dir', os.path.expanduser('~'))
558 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
559 if fileName and directory != os.path.dirname(fileName):
560 self.config.set_key('io_dir', os.path.dirname(fileName), True)
563 def getSaveFileName(self, title, filename, filter = None):
564 directory = self.config.get('io_dir', os.path.expanduser('~'))
565 path = os.path.join( directory, filename )
566 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
567 if fileName and directory != os.path.dirname(fileName):
568 self.config.set_key('io_dir', os.path.dirname(fileName), True)
574 QMainWindow.close(self)
575 self.run_hook('close_main_window')
577 def connect_slots(self, sender):
578 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
579 self.previous_payto_e=''
581 def timer_actions(self):
582 if self.need_update.is_set():
584 self.need_update.clear()
585 self.run_hook('timer_actions')
587 def format_amount(self, x, is_diff=False, whitespaces=False):
588 return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point, whitespaces)
590 def read_amount(self, x):
591 if x in['.', '']: return None
592 p = pow(10, self.decimal_point)
593 return int( p * Decimal(x) )
596 assert self.decimal_point in [5,8]
597 return "BTC" if self.decimal_point == 8 else "mBTC"
599 def update_status(self):
600 if self.wallet.interface and self.wallet.interface.is_connected:
601 if not self.wallet.up_to_date:
602 text = _("Synchronizing...")
603 icon = QIcon(":icons/status_waiting.png")
605 c, u = self.wallet.get_account_balance(self.current_account)
606 text = _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
607 if u: text += " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
608 text += self.create_quote_text(Decimal(c+u)/100000000)
609 self.tray.setToolTip(text)
610 icon = QIcon(":icons/status_connected.png")
612 text = _("Not connected")
613 icon = QIcon(":icons/status_disconnected.png")
615 self.balance_label.setText(text)
616 self.status_button.setIcon( icon )
618 def update_wallet(self):
620 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
621 self.update_history_tab()
622 self.update_receive_tab()
623 self.update_contacts_tab()
624 self.update_completions()
627 def create_quote_text(self, btc_balance):
628 quote_currency = self.config.get("currency", "None")
629 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
630 if quote_balance is None:
633 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
636 def create_history_tab(self):
637 self.history_list = l = MyTreeWidget(self)
639 for i,width in enumerate(self.column_widths['history']):
640 l.setColumnWidth(i, width)
641 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
642 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
643 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
645 l.setContextMenuPolicy(Qt.CustomContextMenu)
646 l.customContextMenuRequested.connect(self.create_history_menu)
650 def create_history_menu(self, position):
651 self.history_list.selectedIndexes()
652 item = self.history_list.currentItem()
654 tx_hash = str(item.data(0, Qt.UserRole).toString())
655 if not tx_hash: return
657 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
658 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
659 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
660 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
663 def show_tx_details(self, tx):
664 dialog = QDialog(self)
666 dialog.setWindowTitle(_("Transaction Details"))
668 dialog.setLayout(vbox)
669 dialog.setMinimumSize(600,300)
672 if tx_hash in self.wallet.transactions.keys():
673 is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
674 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
676 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
682 vbox.addWidget(QLabel("Transaction ID:"))
683 e = QLineEdit(tx_hash)
687 vbox.addWidget(QLabel("Date: %s"%time_str))
688 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
691 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
692 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
694 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
695 vbox.addWidget(QLabel("Transaction fee: unknown"))
697 vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
699 vbox.addWidget( self.generate_transaction_information_widget(tx) )
701 ok_button = QPushButton(_("Close"))
702 ok_button.setDefault(True)
703 ok_button.clicked.connect(dialog.accept)
707 hbox.addWidget(ok_button)
711 def tx_label_clicked(self, item, column):
712 if column==2 and item.isSelected():
714 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
715 self.history_list.editItem( item, column )
716 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
719 def tx_label_changed(self, item, column):
723 tx_hash = str(item.data(0, Qt.UserRole).toString())
724 tx = self.wallet.transactions.get(tx_hash)
725 text = unicode( item.text(2) )
726 self.set_label(tx_hash, text)
728 item.setForeground(2, QBrush(QColor('black')))
730 text = self.wallet.get_default_label(tx_hash)
731 item.setText(2, text)
732 item.setForeground(2, QBrush(QColor('gray')))
736 def edit_label(self, is_recv):
737 l = self.receive_list if is_recv else self.contacts_list
738 item = l.currentItem()
739 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
740 l.editItem( item, 1 )
741 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
745 def address_label_clicked(self, item, column, l, column_addr, column_label):
746 if column == column_label and item.isSelected():
747 is_editable = item.data(0, 32).toBool()
750 addr = unicode( item.text(column_addr) )
751 label = unicode( item.text(column_label) )
752 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
753 l.editItem( item, column )
754 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
757 def address_label_changed(self, item, column, l, column_addr, column_label):
758 if column == column_label:
759 addr = unicode( item.text(column_addr) )
760 text = unicode( item.text(column_label) )
761 is_editable = item.data(0, 32).toBool()
765 changed = self.set_label(addr, text)
767 self.update_history_tab()
768 self.update_completions()
770 self.current_item_changed(item)
772 self.run_hook('item_changed', item, column)
775 def current_item_changed(self, a):
776 self.run_hook('current_item_changed', a)
780 def update_history_tab(self):
782 self.history_list.clear()
783 for item in self.wallet.get_tx_history(self.current_account):
784 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
787 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
792 time_str = 'unverified'
793 icon = QIcon(":icons/unconfirmed.png")
796 icon = QIcon(":icons/unconfirmed.png")
798 icon = QIcon(":icons/clock%d.png"%conf)
800 icon = QIcon(":icons/confirmed.png")
802 if value is not None:
803 v_str = self.format_amount(value, True, whitespaces=True)
807 balance_str = self.format_amount(balance, whitespaces=True)
810 label, is_default_label = self.wallet.get_label(tx_hash)
812 label = _('Pruned transaction outputs')
813 is_default_label = False
815 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
816 item.setFont(2, QFont(MONOSPACE_FONT))
817 item.setFont(3, QFont(MONOSPACE_FONT))
818 item.setFont(4, QFont(MONOSPACE_FONT))
820 item.setForeground(3, QBrush(QColor("#BC1E1E")))
822 item.setData(0, Qt.UserRole, tx_hash)
823 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
825 item.setForeground(2, QBrush(QColor('grey')))
827 item.setIcon(0, icon)
828 self.history_list.insertTopLevelItem(0,item)
831 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
834 def create_send_tab(self):
839 grid.setColumnMinimumWidth(3,300)
840 grid.setColumnStretch(5,1)
843 self.payto_e = QLineEdit()
844 grid.addWidget(QLabel(_('Pay to')), 1, 0)
845 grid.addWidget(self.payto_e, 1, 1, 1, 3)
847 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)
849 completer = QCompleter()
850 completer.setCaseSensitivity(False)
851 self.payto_e.setCompleter(completer)
852 completer.setModel(self.completions)
854 self.message_e = QLineEdit()
855 grid.addWidget(QLabel(_('Description')), 2, 0)
856 grid.addWidget(self.message_e, 2, 1, 1, 3)
857 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)
859 self.amount_e = AmountEdit(self.base_unit)
860 grid.addWidget(QLabel(_('Amount')), 3, 0)
861 grid.addWidget(self.amount_e, 3, 1, 1, 2)
862 grid.addWidget(HelpButton(
863 _('Amount to be sent.') + '\n\n' \
864 + _('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.') \
865 + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
867 self.fee_e = AmountEdit(self.base_unit)
868 grid.addWidget(QLabel(_('Fee')), 4, 0)
869 grid.addWidget(self.fee_e, 4, 1, 1, 2)
870 grid.addWidget(HelpButton(
871 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
872 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
873 + _('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)
876 b = EnterButton(_("Send"), self.do_send)
878 b = EnterButton(_("Create unsigned transaction"), self.do_send)
879 grid.addWidget(b, 6, 1)
881 b = EnterButton(_("Clear"),self.do_clear)
882 grid.addWidget(b, 6, 2)
884 self.payto_sig = QLabel('')
885 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
887 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
888 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
897 def entry_changed( is_fee ):
898 self.funds_error = False
900 if self.amount_e.is_shortcut:
901 self.amount_e.is_shortcut = False
902 c, u = self.wallet.get_account_balance(self.current_account)
903 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
904 fee = self.wallet.estimated_fee(inputs)
906 self.amount_e.setText( self.format_amount(amount) )
907 self.fee_e.setText( self.format_amount( fee ) )
910 amount = self.read_amount(str(self.amount_e.text()))
911 fee = self.read_amount(str(self.fee_e.text()))
913 if not is_fee: fee = None
916 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
918 self.fee_e.setText( self.format_amount( fee ) )
921 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
925 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
926 self.funds_error = True
927 text = _( "Not enough funds" )
928 c, u = self.wallet.get_frozen_balance()
929 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
931 self.statusBar().showMessage(text)
932 self.amount_e.setPalette(palette)
933 self.fee_e.setPalette(palette)
935 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
936 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
938 self.run_hook('create_send_tab', grid)
942 def update_completions(self):
944 for addr,label in self.wallet.labels.items():
945 if addr in self.wallet.addressbook:
946 l.append( label + ' <' + addr + '>')
948 self.run_hook('update_completions', l)
949 self.completions.setStringList(l)
953 return lambda s, *args: s.do_protect(func, args)
958 label = unicode( self.message_e.text() )
959 r = unicode( self.payto_e.text() )
962 # label or alias, with address in brackets
963 m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
964 to_address = m.group(2) if m else r
966 if not is_valid(to_address):
967 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
971 amount = self.read_amount(unicode( self.amount_e.text()))
973 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
976 fee = self.read_amount(unicode( self.fee_e.text()))
978 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
981 confirm_amount = self.config.get('confirm_amount', 100000000)
982 if amount >= confirm_amount:
983 if not self.question("send %s to %s?"%(self.format_amount(amount) + ' '+ self.base_unit(), to_address)):
986 self.send_tx(to_address, amount, fee, label)
990 def send_tx(self, to_address, amount, fee, label, password):
993 tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
994 except BaseException, e:
995 traceback.print_exc(file=sys.stdout)
996 self.show_message(str(e))
999 if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
1000 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
1003 self.run_hook('send_tx', tx)
1006 self.set_label(tx.hash(), label)
1009 h = self.wallet.send_tx(tx)
1010 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
1011 status, msg = self.wallet.receive_tx( h )
1013 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
1015 self.update_contacts_tab()
1017 QMessageBox.warning(self, _('Error'), msg, _('OK'))
1019 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
1021 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
1022 with open(fileName,'w') as f:
1023 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1024 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
1026 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
1031 def set_url(self, url):
1032 address, amount, label, message, signature, identity, url = util.parse_url(url)
1033 if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
1035 if label and self.wallet.labels.get(address) != label:
1036 if self.question('Give label "%s" to address %s ?'%(label,address)):
1037 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
1038 self.wallet.addressbook.append(address)
1039 self.set_label(address, label)
1041 self.run_hook('set_url', url, self.show_message, self.question)
1043 self.tabs.setCurrentIndex(1)
1044 label = self.wallet.labels.get(address)
1045 m_addr = label + ' <'+ address +'>' if label else address
1046 self.payto_e.setText(m_addr)
1048 self.message_e.setText(message)
1049 self.amount_e.setText(amount)
1051 self.set_frozen(self.payto_e,True)
1052 self.set_frozen(self.amount_e,True)
1053 self.set_frozen(self.message_e,True)
1054 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
1056 self.payto_sig.setVisible(False)
1059 self.payto_sig.setVisible(False)
1060 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1062 self.set_frozen(e,False)
1063 self.update_status()
1065 def set_frozen(self,entry,frozen):
1067 entry.setReadOnly(True)
1068 entry.setFrame(False)
1069 palette = QPalette()
1070 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
1071 entry.setPalette(palette)
1073 entry.setReadOnly(False)
1074 entry.setFrame(True)
1075 palette = QPalette()
1076 palette.setColor(entry.backgroundRole(), QColor('white'))
1077 entry.setPalette(palette)
1080 def toggle_freeze(self,addr):
1082 if addr in self.wallet.frozen_addresses:
1083 self.wallet.unfreeze(addr)
1085 self.wallet.freeze(addr)
1086 self.update_receive_tab()
1088 def toggle_priority(self,addr):
1090 if addr in self.wallet.prioritized_addresses:
1091 self.wallet.unprioritize(addr)
1093 self.wallet.prioritize(addr)
1094 self.update_receive_tab()
1097 def create_list_tab(self, headers):
1098 "generic tab creation method"
1099 l = MyTreeWidget(self)
1100 l.setColumnCount( len(headers) )
1101 l.setHeaderLabels( headers )
1104 vbox = QVBoxLayout()
1111 vbox.addWidget(buttons)
1113 hbox = QHBoxLayout()
1116 buttons.setLayout(hbox)
1121 def create_receive_tab(self):
1122 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1123 l.setContextMenuPolicy(Qt.CustomContextMenu)
1124 l.customContextMenuRequested.connect(self.create_receive_menu)
1125 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1126 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1127 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1128 self.receive_list = l
1129 self.receive_buttons_hbox = hbox
1134 def receive_tab_set_mode(self, i):
1135 self.save_column_widths()
1136 self.expert_mode = (i == 1)
1137 self.config.set_key('classic_expert_mode', self.expert_mode, True)
1138 self.update_receive_tab()
1141 def save_column_widths(self):
1142 if not self.expert_mode:
1143 widths = [ self.receive_list.columnWidth(0) ]
1146 for i in range(self.receive_list.columnCount() -1):
1147 widths.append(self.receive_list.columnWidth(i))
1148 self.column_widths["receive"][self.expert_mode] = widths
1150 self.column_widths["history"] = []
1151 for i in range(self.history_list.columnCount() - 1):
1152 self.column_widths["history"].append(self.history_list.columnWidth(i))
1154 self.column_widths["contacts"] = []
1155 for i in range(self.contacts_list.columnCount() - 1):
1156 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1158 self.config.set_key("column_widths", self.column_widths, True)
1161 def create_contacts_tab(self):
1162 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1163 l.setContextMenuPolicy(Qt.CustomContextMenu)
1164 l.customContextMenuRequested.connect(self.create_contact_menu)
1165 for i,width in enumerate(self.column_widths['contacts']):
1166 l.setColumnWidth(i, width)
1168 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1169 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1170 self.contacts_list = l
1171 self.contacts_buttons_hbox = hbox
1176 def delete_imported_key(self, addr):
1177 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1178 self.wallet.delete_imported_key(addr)
1179 self.update_receive_tab()
1180 self.update_history_tab()
1183 def create_receive_menu(self, position):
1184 # fixme: this function apparently has a side effect.
1185 # if it is not called the menu pops up several times
1186 #self.receive_list.selectedIndexes()
1188 item = self.receive_list.itemAt(position)
1190 addr = unicode(item.text(0))
1191 if not is_valid(addr):
1192 item.setExpanded(not item.isExpanded())
1195 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1196 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1197 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1198 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1199 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1200 if addr in self.wallet.imported_keys:
1201 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1203 if self.expert_mode:
1204 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1205 menu.addAction(t, lambda: self.toggle_freeze(addr))
1206 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1207 menu.addAction(t, lambda: self.toggle_priority(addr))
1209 self.run_hook('receive_menu', menu)
1210 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1213 def payto(self, addr):
1215 label = self.wallet.labels.get(addr)
1216 m_addr = label + ' <' + addr + '>' if label else addr
1217 self.tabs.setCurrentIndex(1)
1218 self.payto_e.setText(m_addr)
1219 self.amount_e.setFocus()
1222 def delete_contact(self, x):
1223 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1224 self.wallet.delete_contact(x)
1225 self.set_label(x, None)
1226 self.update_history_tab()
1227 self.update_contacts_tab()
1228 self.update_completions()
1231 def create_contact_menu(self, position):
1232 item = self.contacts_list.itemAt(position)
1234 addr = unicode(item.text(0))
1235 label = unicode(item.text(1))
1236 is_editable = item.data(0,32).toBool()
1237 payto_addr = item.data(0,33).toString()
1239 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1240 menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1241 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1243 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1244 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1246 self.run_hook('create_contact_menu', menu, item)
1247 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1250 def update_receive_item(self, item):
1251 item.setFont(0, QFont(MONOSPACE_FONT))
1252 address = str(item.data(0,0).toString())
1253 label = self.wallet.labels.get(address,'')
1254 item.setData(1,0,label)
1255 item.setData(0,32, True) # is editable
1257 self.run_hook('update_receive_item', address, item)
1259 c, u = self.wallet.get_addr_balance(address)
1260 balance = self.format_amount(c + u)
1261 item.setData(2,0,balance)
1263 if self.expert_mode:
1264 if address in self.wallet.frozen_addresses:
1265 item.setBackgroundColor(0, QColor('lightblue'))
1266 elif address in self.wallet.prioritized_addresses:
1267 item.setBackgroundColor(0, QColor('lightgreen'))
1270 def update_receive_tab(self):
1271 l = self.receive_list
1274 l.setColumnHidden(2, not self.expert_mode)
1275 l.setColumnHidden(3, not self.expert_mode)
1276 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1277 l.setColumnWidth(i, width)
1279 if self.current_account is None:
1280 account_items = self.wallet.accounts.items()
1281 elif self.current_account != -1:
1282 account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1286 for k, account in account_items:
1287 name = self.wallet.labels.get(k, 'unnamed account')
1288 c,u = self.wallet.get_account_balance(k)
1289 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1290 l.addTopLevelItem(account_item)
1291 account_item.setExpanded(True)
1293 for is_change in ([0,1] if self.expert_mode else [0]):
1294 if self.expert_mode:
1295 name = "Receiving" if not is_change else "Change"
1296 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1297 account_item.addChild(seq_item)
1298 if not is_change: seq_item.setExpanded(True)
1300 seq_item = account_item
1304 for address in account.get_addresses(is_change):
1305 h = self.wallet.history.get(address,[])
1309 if gap > self.wallet.gap_limit:
1314 num_tx = '*' if h == ['*'] else "%d"%len(h)
1315 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1316 self.update_receive_item(item)
1318 item.setBackgroundColor(1, QColor('red'))
1319 seq_item.addChild(item)
1322 if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1323 c,u = self.wallet.get_imported_balance()
1324 account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1325 l.addTopLevelItem(account_item)
1326 account_item.setExpanded(True)
1327 for address in self.wallet.imported_keys.keys():
1328 item = QTreeWidgetItem( [ address, '', '', ''] )
1329 self.update_receive_item(item)
1330 account_item.addChild(item)
1333 # we use column 1 because column 0 may be hidden
1334 l.setCurrentItem(l.topLevelItem(0),1)
1337 def update_contacts_tab(self):
1338 l = self.contacts_list
1341 for address in self.wallet.addressbook:
1342 label = self.wallet.labels.get(address,'')
1343 n = self.wallet.get_num_tx(address)
1344 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1345 item.setFont(0, QFont(MONOSPACE_FONT))
1346 # 32 = label can be edited (bool)
1347 item.setData(0,32, True)
1349 item.setData(0,33, address)
1350 l.addTopLevelItem(item)
1352 self.run_hook('update_contacts_tab', l)
1353 l.setCurrentItem(l.topLevelItem(0))
1357 def create_console_tab(self):
1358 from qt_console import Console
1359 self.console = console = Console()
1360 self.console.history = self.config.get("console-history",[])
1361 self.console.history_index = len(self.console.history)
1363 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1364 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1366 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1368 def mkfunc(f, method):
1369 return lambda *args: apply( f, (method, args, self.password_dialog ))
1371 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1372 methods[m] = mkfunc(c._run, m)
1374 console.updateNamespace(methods)
1377 def change_account(self,s):
1378 if s == _("All accounts"):
1379 self.current_account = None
1381 accounts = self.wallet.get_accounts()
1382 for k, v in accounts.items():
1384 self.current_account = k
1385 self.update_history_tab()
1386 self.update_status()
1387 self.update_receive_tab()
1389 def create_status_bar(self):
1392 sb.setFixedHeight(35)
1393 qtVersion = qVersion()
1395 self.balance_label = QLabel("")
1396 sb.addWidget(self.balance_label)
1398 update_notification = UpdateLabel(self.config)
1399 if(update_notification.new_version):
1400 sb.addPermanentWidget(update_notification)
1402 accounts = self.wallet.get_accounts()
1403 if len(accounts) > 1:
1404 from_combo = QComboBox()
1405 from_combo.addItems([_("All accounts")] + accounts.values())
1406 from_combo.setCurrentIndex(0)
1407 self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account)
1408 sb.addPermanentWidget(from_combo)
1410 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1411 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1412 if self.wallet.seed:
1413 self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1414 self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1415 sb.addPermanentWidget( self.password_button )
1416 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1417 if self.wallet.seed:
1418 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1419 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1420 sb.addPermanentWidget( self.status_button )
1422 self.run_hook('create_status_bar', (sb,))
1424 self.setStatusBar(sb)
1427 def change_password_dialog(self):
1428 from password_dialog import PasswordDialog
1429 d = PasswordDialog(self.wallet, self)
1435 self.config.set_key('gui', 'lite', True)
1438 self.lite.mini.show()
1440 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1441 self.lite.main(None)
1443 def new_contact_dialog(self):
1444 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1445 address = unicode(text)
1447 if is_valid(address):
1448 self.wallet.add_contact(address)
1449 self.update_contacts_tab()
1450 self.update_history_tab()
1451 self.update_completions()
1453 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1456 def new_account_dialog(self):
1457 text, ok = QInputDialog.getText(self, _('New Account'), _('Name') + ':')
1458 if not ok or not text:
1460 name = unicode(text)
1462 self.create_new_account(name)
1464 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1467 def create_new_account(self, name, password):
1468 self.wallet.create_new_account(name, password)
1469 self.wallet.synchronize()
1470 self.update_receive_tab()
1471 self.update_history_tab()
1472 self.update_completions()
1474 def show_master_public_key(self):
1475 dialog = QDialog(self)
1477 dialog.setWindowTitle(_("Master Public Key"))
1479 main_text = QTextEdit()
1480 main_text.setText(self.wallet.get_master_public_key())
1481 main_text.setReadOnly(True)
1482 main_text.setMaximumHeight(170)
1483 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1485 ok_button = QPushButton(_("OK"))
1486 ok_button.setDefault(True)
1487 ok_button.clicked.connect(dialog.accept)
1489 main_layout = QGridLayout()
1490 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1492 main_layout.addWidget(main_text, 1, 0)
1493 main_layout.addWidget(qrw, 1, 1 )
1495 vbox = QVBoxLayout()
1496 vbox.addLayout(main_layout)
1497 hbox = QHBoxLayout()
1499 hbox.addWidget(ok_button)
1500 vbox.addLayout(hbox)
1502 dialog.setLayout(vbox)
1507 def show_seed_dialog(self, password):
1508 if not self.wallet.seed:
1509 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1512 seed = self.wallet.decode_seed(password)
1514 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1517 from seed_dialog import SeedDialog
1518 d = SeedDialog(self)
1519 d.show_seed(seed, self.wallet.imported_keys)
1523 def show_qrcode(self, data, title = "QR code"):
1527 d.setWindowTitle(title)
1528 d.setMinimumSize(270, 300)
1529 vbox = QVBoxLayout()
1530 qrw = QRCodeWidget(data)
1531 vbox.addWidget(qrw, 1)
1532 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1533 hbox = QHBoxLayout()
1537 filename = "qrcode.bmp"
1538 bmp.save_qrcode(qrw.qr, filename)
1539 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1541 b = QPushButton(_("Save"))
1543 b.clicked.connect(print_qr)
1545 b = QPushButton(_("Close"))
1547 b.clicked.connect(d.accept)
1550 vbox.addLayout(hbox)
1555 def do_protect(self, func, args):
1556 if self.wallet.use_encryption:
1557 password = self.password_dialog()
1563 if args != (False,):
1564 args = (self,) + args + (password,)
1566 args = (self,password)
1571 def show_private_key(self, address, password):
1572 if not address: return
1574 pk = self.wallet.get_private_key(address, password)
1575 except BaseException, e:
1576 self.show_message(str(e))
1578 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1582 def do_sign(self, address, message, signature, password):
1584 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1585 signature.setText(sig)
1586 except BaseException, e:
1587 self.show_message(str(e))
1589 def sign_message(self, address):
1590 if not address: return
1593 d.setWindowTitle(_('Sign Message'))
1594 d.setMinimumSize(410, 290)
1596 tab_widget = QTabWidget()
1598 layout = QGridLayout(tab)
1600 sign_address = QLineEdit()
1602 sign_address.setText(address)
1603 layout.addWidget(QLabel(_('Address')), 1, 0)
1604 layout.addWidget(sign_address, 1, 1)
1606 sign_message = QTextEdit()
1607 layout.addWidget(QLabel(_('Message')), 2, 0)
1608 layout.addWidget(sign_message, 2, 1)
1609 layout.setRowStretch(2,3)
1611 sign_signature = QTextEdit()
1612 layout.addWidget(QLabel(_('Signature')), 3, 0)
1613 layout.addWidget(sign_signature, 3, 1)
1614 layout.setRowStretch(3,1)
1617 hbox = QHBoxLayout()
1618 b = QPushButton(_("Sign"))
1620 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1621 b = QPushButton(_("Close"))
1622 b.clicked.connect(d.accept)
1624 layout.addLayout(hbox, 4, 1)
1625 tab_widget.addTab(tab, _("Sign"))
1629 layout = QGridLayout(tab)
1631 verify_address = QLineEdit()
1632 layout.addWidget(QLabel(_('Address')), 1, 0)
1633 layout.addWidget(verify_address, 1, 1)
1635 verify_message = QTextEdit()
1636 layout.addWidget(QLabel(_('Message')), 2, 0)
1637 layout.addWidget(verify_message, 2, 1)
1638 layout.setRowStretch(2,3)
1640 verify_signature = QTextEdit()
1641 layout.addWidget(QLabel(_('Signature')), 3, 0)
1642 layout.addWidget(verify_signature, 3, 1)
1643 layout.setRowStretch(3,1)
1646 if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1647 self.show_message(_("Signature verified"))
1649 self.show_message(_("Error: wrong signature"))
1651 hbox = QHBoxLayout()
1652 b = QPushButton(_("Verify"))
1653 b.clicked.connect(do_verify)
1655 b = QPushButton(_("Close"))
1656 b.clicked.connect(d.accept)
1658 layout.addLayout(hbox, 4, 1)
1659 tab_widget.addTab(tab, _("Verify"))
1661 vbox = QVBoxLayout()
1662 vbox.addWidget(tab_widget)
1669 def question(self, msg):
1670 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1672 def show_message(self, msg):
1673 QMessageBox.information(self, _('Message'), msg, _('OK'))
1675 def password_dialog(self ):
1682 vbox = QVBoxLayout()
1683 msg = _('Please enter your password')
1684 vbox.addWidget(QLabel(msg))
1686 grid = QGridLayout()
1688 grid.addWidget(QLabel(_('Password')), 1, 0)
1689 grid.addWidget(pw, 1, 1)
1690 vbox.addLayout(grid)
1692 vbox.addLayout(ok_cancel_buttons(d))
1695 self.run_hook('password_dialog', pw, grid, 1)
1696 if not d.exec_(): return
1697 return unicode(pw.text())
1704 def generate_transaction_information_widget(self, tx):
1705 tabs = QTabWidget(self)
1708 grid_ui = QGridLayout(tab1)
1709 grid_ui.setColumnStretch(0,1)
1710 tabs.addTab(tab1, _('Outputs') )
1712 tree_widget = MyTreeWidget(self)
1713 tree_widget.setColumnCount(2)
1714 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1715 tree_widget.setColumnWidth(0, 300)
1716 tree_widget.setColumnWidth(1, 50)
1718 for address, value in tx.outputs:
1719 item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1720 tree_widget.addTopLevelItem(item)
1722 tree_widget.setMaximumHeight(100)
1724 grid_ui.addWidget(tree_widget)
1727 grid_ui = QGridLayout(tab2)
1728 grid_ui.setColumnStretch(0,1)
1729 tabs.addTab(tab2, _('Inputs') )
1731 tree_widget = MyTreeWidget(self)
1732 tree_widget.setColumnCount(2)
1733 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1735 for input_line in tx.inputs:
1736 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1737 tree_widget.addTopLevelItem(item)
1739 tree_widget.setMaximumHeight(100)
1741 grid_ui.addWidget(tree_widget)
1745 def tx_dict_from_text(self, txt):
1747 tx_dict = json.loads(str(txt))
1748 assert "hex" in tx_dict.keys()
1749 assert "complete" in tx_dict.keys()
1750 if not tx_dict["complete"]:
1751 assert "input_info" in tx_dict.keys()
1753 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1758 def read_tx_from_file(self):
1759 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1763 with open(fileName, "r") as f:
1764 file_content = f.read()
1765 except (ValueError, IOError, os.error), reason:
1766 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1768 return self.tx_dict_from_text(file_content)
1772 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1774 self.wallet.signrawtransaction(tx, input_info, [], password)
1776 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1778 with open(fileName, "w+") as f:
1779 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1780 self.show_message(_("Transaction saved successfully"))
1783 except BaseException, e:
1784 self.show_message(str(e))
1787 def send_raw_transaction(self, raw_tx, dialog = ""):
1788 result, result_message = self.wallet.sendtx( raw_tx )
1790 self.show_message("Transaction successfully sent: %s" % (result_message))
1794 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1796 def do_process_from_text(self):
1797 text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1800 tx_dict = self.tx_dict_from_text(text)
1802 self.create_process_transaction_window(tx_dict)
1804 def do_process_from_file(self):
1805 tx_dict = self.read_tx_from_file()
1807 self.create_process_transaction_window(tx_dict)
1809 def create_process_transaction_window(self, tx_dict):
1810 tx = Transaction(tx_dict["hex"])
1812 dialog = QDialog(self)
1813 dialog.setMinimumWidth(500)
1814 dialog.setWindowTitle(_('Process raw transaction'))
1820 l.addWidget(QLabel(_("Transaction status:")), 3,0)
1821 l.addWidget(QLabel(_("Actions")), 4,0)
1823 if tx_dict["complete"] == False:
1824 l.addWidget(QLabel(_("Unsigned")), 3,1)
1825 if self.wallet.seed :
1826 b = QPushButton("Sign transaction")
1827 input_info = json.loads(tx_dict["input_info"])
1828 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1829 l.addWidget(b, 4, 1)
1831 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1833 l.addWidget(QLabel(_("Signed")), 3,1)
1834 b = QPushButton("Broadcast transaction")
1835 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1838 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1839 cancelButton = QPushButton(_("Cancel"))
1840 cancelButton.clicked.connect(lambda: dialog.done(0))
1841 l.addWidget(cancelButton, 4,2)
1847 def do_export_privkeys(self, password):
1848 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.")))
1851 select_export = _('Select file to export your private keys to')
1852 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1854 with open(fileName, "w+") as csvfile:
1855 transaction = csv.writer(csvfile)
1856 transaction.writerow(["address", "private_key"])
1859 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1860 transaction.writerow(["%34s"%addr,pk])
1862 self.show_message(_("Private keys exported."))
1864 except (IOError, os.error), reason:
1865 export_error_label = _("Electrum was unable to produce a private key-export.")
1866 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1868 except BaseException, e:
1869 self.show_message(str(e))
1873 def do_import_labels(self):
1874 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1875 if not labelsFile: return
1877 f = open(labelsFile, 'r')
1880 for key, value in json.loads(data).items():
1881 self.wallet.labels[key] = value
1883 QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1884 except (IOError, os.error), reason:
1885 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1888 def do_export_labels(self):
1889 labels = self.wallet.labels
1891 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1893 with open(fileName, 'w+') as f:
1894 json.dump(labels, f)
1895 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1896 except (IOError, os.error), reason:
1897 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1900 def do_export_history(self):
1901 from gui_lite import csv_transaction
1902 csv_transaction(self.wallet)
1906 def do_import_privkey(self, password):
1907 if not self.wallet.imported_keys:
1908 r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1909 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1910 + _('Are you sure you understand what you are doing?'), 3, 4)
1913 text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1916 text = str(text).split()
1921 addr = self.wallet.import_key(key, password)
1922 except BaseException as e:
1928 addrlist.append(addr)
1930 QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1932 QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1933 self.update_receive_tab()
1934 self.update_history_tab()
1937 def settings_dialog(self):
1939 d.setWindowTitle(_('Electrum Settings'))
1941 vbox = QVBoxLayout()
1943 tabs = QTabWidget(self)
1944 self.settings_tab = tabs
1945 vbox.addWidget(tabs)
1948 grid_ui = QGridLayout(tab1)
1949 grid_ui.setColumnStretch(0,1)
1950 tabs.addTab(tab1, _('Display') )
1952 nz_label = QLabel(_('Display zeros'))
1953 grid_ui.addWidget(nz_label, 0, 0)
1954 nz_e = AmountEdit(None,True)
1955 nz_e.setText("%d"% self.wallet.num_zeros)
1956 grid_ui.addWidget(nz_e, 0, 1)
1957 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1958 grid_ui.addWidget(HelpButton(msg), 0, 2)
1959 if not self.config.is_modifiable('num_zeros'):
1960 for w in [nz_e, nz_label]: w.setEnabled(False)
1962 lang_label=QLabel(_('Language') + ':')
1963 grid_ui.addWidget(lang_label, 1, 0)
1964 lang_combo = QComboBox()
1965 from i18n import languages
1966 lang_combo.addItems(languages.values())
1968 index = languages.keys().index(self.config.get("language",''))
1971 lang_combo.setCurrentIndex(index)
1972 grid_ui.addWidget(lang_combo, 1, 1)
1973 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1974 if not self.config.is_modifiable('language'):
1975 for w in [lang_combo, lang_label]: w.setEnabled(False)
1977 currencies = self.exchanger.get_currencies()
1978 currencies.insert(0, "None")
1980 cur_label=QLabel(_('Currency') + ':')
1981 grid_ui.addWidget(cur_label , 2, 0)
1982 cur_combo = QComboBox()
1983 cur_combo.addItems(currencies)
1985 index = currencies.index(self.config.get('currency', "None"))
1988 cur_combo.setCurrentIndex(index)
1989 grid_ui.addWidget(cur_combo, 2, 1)
1990 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1992 expert_cb = QCheckBox(_('Expert mode'))
1993 expert_cb.setChecked(self.expert_mode)
1994 grid_ui.addWidget(expert_cb, 3, 0)
1995 hh = _('In expert mode, your client will:') + '\n' \
1996 + _(' - Show change addresses in the Receive tab') + '\n' \
1997 + _(' - Display the balance of each address') + '\n' \
1998 + _(' - Add freeze/prioritize actions to addresses.')
1999 grid_ui.addWidget(HelpButton(hh), 3, 2)
2000 grid_ui.setRowStretch(4,1)
2004 grid_wallet = QGridLayout(tab2)
2005 grid_wallet.setColumnStretch(0,1)
2006 tabs.addTab(tab2, _('Wallet') )
2008 fee_label = QLabel(_('Transaction fee'))
2009 grid_wallet.addWidget(fee_label, 0, 0)
2010 fee_e = AmountEdit(self.base_unit)
2011 fee_e.setText(self.format_amount(self.wallet.fee).strip())
2012 grid_wallet.addWidget(fee_e, 0, 2)
2013 msg = _('Fee per kilobyte of transaction.') + ' ' \
2014 + _('Recommended value') + ': ' + self.format_amount(50000)
2015 grid_wallet.addWidget(HelpButton(msg), 0, 3)
2016 if not self.config.is_modifiable('fee_per_kb'):
2017 for w in [fee_e, fee_label]: w.setEnabled(False)
2019 usechange_cb = QCheckBox(_('Use change addresses'))
2020 usechange_cb.setChecked(self.wallet.use_change)
2021 grid_wallet.addWidget(usechange_cb, 1, 0)
2022 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2023 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2025 gap_label = QLabel(_('Gap limit'))
2026 grid_wallet.addWidget(gap_label, 2, 0)
2027 gap_e = AmountEdit(None,True)
2028 gap_e.setText("%d"% self.wallet.gap_limit)
2029 grid_wallet.addWidget(gap_e, 2, 2)
2030 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2031 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2032 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2033 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2034 + _('Warning') + ': ' \
2035 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2036 + _('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'
2037 grid_wallet.addWidget(HelpButton(msg), 2, 3)
2038 if not self.config.is_modifiable('gap_limit'):
2039 for w in [gap_e, gap_label]: w.setEnabled(False)
2041 units = ['BTC', 'mBTC']
2042 unit_label = QLabel(_('Base unit'))
2043 grid_wallet.addWidget(unit_label, 3, 0)
2044 unit_combo = QComboBox()
2045 unit_combo.addItems(units)
2046 unit_combo.setCurrentIndex(units.index(self.base_unit()))
2047 grid_wallet.addWidget(unit_combo, 3, 2)
2048 grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2049 + '\n1BTC=1000mBTC.\n' \
2050 + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2051 grid_wallet.setRowStretch(4,1)
2055 tab5 = QScrollArea()
2056 tab5.setEnabled(True)
2057 tab5.setWidgetResizable(True)
2059 grid_plugins = QGridLayout()
2060 grid_plugins.setColumnStretch(0,1)
2063 w.setLayout(grid_plugins)
2066 w.setMinimumHeight(len(self.plugins)*35)
2068 tabs.addTab(tab5, _('Plugins') )
2069 def mk_toggle(cb, p):
2070 return lambda: cb.setChecked(p.toggle())
2071 for i, p in enumerate(self.plugins):
2073 cb = QCheckBox(p.fullname())
2074 cb.setDisabled(not p.is_available())
2075 cb.setChecked(p.is_enabled())
2076 cb.clicked.connect(mk_toggle(cb,p))
2077 grid_plugins.addWidget(cb, i, 0)
2078 if p.requires_settings():
2079 grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2080 grid_plugins.addWidget(HelpButton(p.description()), i, 2)
2082 print_msg("Error: cannot display plugin", p)
2083 traceback.print_exc(file=sys.stdout)
2084 grid_plugins.setRowStretch(i+1,1)
2086 self.run_hook('create_settings_tab', tabs)
2088 vbox.addLayout(ok_cancel_buttons(d))
2092 if not d.exec_(): return
2094 fee = unicode(fee_e.text())
2096 fee = self.read_amount(fee)
2098 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2101 self.wallet.set_fee(fee)
2103 nz = unicode(nz_e.text())
2108 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2111 if self.wallet.num_zeros != nz:
2112 self.wallet.num_zeros = nz
2113 self.config.set_key('num_zeros', nz, True)
2114 self.update_history_tab()
2115 self.update_receive_tab()
2117 usechange_result = usechange_cb.isChecked()
2118 if self.wallet.use_change != usechange_result:
2119 self.wallet.use_change = usechange_result
2120 self.config.set_key('use_change', self.wallet.use_change, True)
2122 unit_result = units[unit_combo.currentIndex()]
2123 if self.base_unit() != unit_result:
2124 self.decimal_point = 8 if unit_result == 'BTC' else 5
2125 self.config.set_key('decimal_point', self.decimal_point, True)
2126 self.update_history_tab()
2127 self.update_status()
2130 n = int(gap_e.text())
2132 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2135 if self.wallet.gap_limit != n:
2136 r = self.wallet.change_gap_limit(n)
2138 self.update_receive_tab()
2139 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2141 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2143 need_restart = False
2145 lang_request = languages.keys()[lang_combo.currentIndex()]
2146 if lang_request != self.config.get('language'):
2147 self.config.set_key("language", lang_request, True)
2150 cur_request = str(currencies[cur_combo.currentIndex()])
2151 if cur_request != self.config.get('currency', "None"):
2152 self.config.set_key('currency', cur_request, True)
2153 self.update_wallet()
2155 self.run_hook('close_settings_dialog')
2158 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2160 self.receive_tab_set_mode(expert_cb.isChecked())
2162 def run_network_dialog(self):
2163 NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2165 def closeEvent(self, event):
2167 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2168 self.save_column_widths()
2169 self.config.set_key("console-history",self.console.history[-50:])
2172 class OpenFileEventFilter(QObject):
2173 def __init__(self, windows):
2174 self.windows = windows
2175 super(OpenFileEventFilter, self).__init__()
2177 def eventFilter(self, obj, event):
2178 if event.type() == QtCore.QEvent.FileOpen:
2179 if len(self.windows) >= 1:
2180 self.windows[0].set_url(event.url().toString())
2189 def __init__(self, config, app=None):
2190 self.interface = Interface(config, True)
2191 self.config = config
2193 self.efilter = OpenFileEventFilter(self.windows)
2195 self.app = QApplication(sys.argv)
2196 self.app.installEventFilter(self.efilter)
2199 def main(self, url):
2201 found = self.config.wallet_file_exists
2203 import installwizard
2204 wizard = installwizard.InstallWizard(self.config, self.interface)
2205 wallet = wizard.run()
2209 wallet = Wallet(self.config)
2211 self.wallet = wallet
2213 self.interface.start(wait = False)
2214 self.interface.send([('server.peers.subscribe',[])])
2215 wallet.interface = self.interface
2217 verifier = WalletVerifier(self.interface, self.config)
2219 wallet.set_verifier(verifier)
2220 synchronizer = WalletSynchronizer(wallet, self.config)
2221 synchronizer.start()
2226 w = ElectrumWindow(self.wallet, self.config)
2227 self.windows.append(w)
2228 if url: w.set_url(url)
2238 self.interface.stop()