default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
class ElectrumWindow(QMainWindow):
- def build_menu(self):
- m = QMenu()
- m.addAction(_("Show/Hide"), self.show_or_hide)
- m.addSeparator()
- m.addAction(_("Exit Electrum"), self.close)
- self.tray.setContextMenu(m)
-
- def show_or_hide(self):
- self.tray_activated(QSystemTrayIcon.DoubleClick)
-
- def tray_activated(self, reason):
- if reason == QSystemTrayIcon.DoubleClick:
- if self.isMinimized() or self.isHidden():
- self.show()
- self.raise_()
- else:
- self.hide()
- def __init__(self, config, network):
+
+
+ def __init__(self, config, network, gui_object):
QMainWindow.__init__(self)
self.config = config
self.network = network
-
- self._close_electrum = False
+ self.tray = gui_object.tray
+ self.go_lite = gui_object.go_lite
self.lite = None
- if sys.platform == 'darwin':
- self.icon = QIcon(":icons/electrum_dark_icon.png")
- #self.icon = QIcon(":icons/lock.png")
- else:
- self.icon = QIcon(':icons/electrum_light_icon.png')
-
- self.tray = QSystemTrayIcon(self.icon, self)
- self.tray.setToolTip('Electrum')
- self.tray.activated.connect(self.tray_activated)
-
- self.build_menu()
- self.tray.show()
self.create_status_bar()
-
self.need_update = threading.Event()
- self.decimal_point = config.get('decimal_point', 8)
+ self.decimal_point = config.get('decimal_point', 5)
self.num_zeros = int(config.get('num_zeros',0))
set_language(config.get('language'))
self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
self.connect(self, QtCore.SIGNAL('send_tx2'), self.send_tx2)
+ self.connect(self, QtCore.SIGNAL('send_tx3'), self.send_tx3)
self.history_list.setFocus(True)
self.console.showMessage(self.network.banner)
self.wallet = None
- self.init_lite()
-
-
- def go_full(self):
- self.config.set_key('lite_mode', False, True)
- self.mini.hide()
- self.show()
- self.raise_()
-
- def go_lite(self):
- self.config.set_key('lite_mode', True, True)
- self.hide()
- self.mini.show()
- self.mini.raise_()
-
-
- def init_lite(self):
- import lite_window
- if not self.check_qt_version():
- if self.config.get('lite_mode') is True:
- msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
- QMessageBox.warning(None, "Could not start Lite GUI.", msg)
- self.config.set_key('lite_mode', False, True)
- sys.exit(0)
- self.mini = None
- self.show()
- self.raise_()
- return
-
- actuator = lite_window.MiniActuator(self)
-
- actuator.load_theme()
-
- self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
-
- driver = lite_window.MiniDriver(self, self.mini)
-
- if self.config.get('lite_mode') is True:
- self.go_lite()
- else:
- self.go_full()
-
-
- def check_qt_version(self):
- qtVersion = qVersion()
- return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
def update_account_selector(self):
# Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
self.notify_transactions()
self.update_account_selector()
- self.new_account.setEnabled(self.wallet.seed_version>4)
+ # update menus
+ self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
+ self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
+ self.password_menu.setEnabled(not self.wallet.is_watching_only())
+ self.seed_menu.setEnabled(self.wallet.has_seed())
+ self.mpk_menu.setEnabled(self.wallet.is_deterministic())
+
self.update_lock_icon()
self.update_buttons_on_seed()
self.update_console()
wallet_menu = menubar.addMenu(_("&Wallet"))
wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
- self.new_account = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
+ self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
wallet_menu.addSeparator()
- wallet_menu.addAction(_("&Password"), self.change_password_dialog)
- wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
- wallet_menu.addAction(_("&Master Public Key"), self.show_master_public_key)
+ self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
+ self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
+ self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
wallet_menu.addSeparator()
labels_menu = wallet_menu.addMenu(_("&Labels"))
labels_menu.addAction(_("&Import"), self.do_import_labels)
labels_menu.addAction(_("&Export"), self.do_export_labels)
- keys_menu = wallet_menu.addMenu(_("&Private keys"))
- keys_menu.addAction(_("&Import"), self.do_import_privkey)
- keys_menu.addAction(_("&Export"), self.do_export_privkeys)
+ self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
+ self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
+ self.private_keys_menu.addAction(_("&Export"), self.do_export_privkeys)
wallet_menu.addAction(_("&Export History"), self.do_export_history)
return
print_error("Notifying GUI")
- if len(self.network.interface.pending_transactions_for_notifications) > 0:
+ if len(self.network.pending_transactions_for_notifications) > 0:
# Combine the transactions if there are more then three
- tx_amount = len(self.network.interface.pending_transactions_for_notifications)
+ tx_amount = len(self.network.pending_transactions_for_notifications)
if(tx_amount >= 3):
total_amount = 0
- for tx in self.network.interface.pending_transactions_for_notifications:
+ for tx in self.network.pending_transactions_for_notifications:
is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
if(v > 0):
total_amount += v
self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
% { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
- self.network.interface.pending_transactions_for_notifications = []
+ self.network.pending_transactions_for_notifications = []
else:
- for tx in self.network.interface.pending_transactions_for_notifications:
+ for tx in self.network.pending_transactions_for_notifications:
if tx:
- self.network.interface.pending_transactions_for_notifications.remove(tx)
+ self.network.pending_transactions_for_notifications.remove(tx)
is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
if(v > 0):
self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
def create_history_menu(self, position):
self.history_list.selectedIndexes()
item = self.history_list.currentItem()
+ be = self.config.get('block_explorer', 'Blockchain.info')
+ if be == 'Blockchain.info':
+ block_explorer = 'https://blockchain.info/tx/'
+ elif be == 'Blockr.io':
+ block_explorer = 'https://blockr.io/tx/info/'
+ elif be == 'Insight.is':
+ block_explorer = 'http://live.insight.is/tx/'
if not item: return
tx_hash = str(item.data(0, Qt.UserRole).toString())
if not tx_hash: return
menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
- menu.addAction(_("View on Blockchain.info"), lambda: webbrowser.open("https://blockchain.info/tx/" + tx_hash))
+ menu.addAction(_("View on block explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
# call hook to see if plugin needs gui interaction
run_hook('send_tx', tx)
+ # sign the tx
def sign_thread():
time.sleep(0.1)
keypairs = {}
self.wallet.sign_transaction(tx, keypairs, password)
self.signed_tx_data = (tx, fee, label)
self.emit(SIGNAL('send_tx2'))
-
- # sign the tx
self.tx_wait_dialog = self.waiting_dialog('Signing..')
threading.Thread(target=sign_thread).start()
if label:
self.wallet.set_label(tx.hash(), label)
- if tx.is_complete:
+ if not tx.is_complete() or self.config.get('show_before_broadcast'):
+ self.show_transaction(tx)
+ return
+
+ # broadcast the tx
+ def broadcast_thread():
+ self.tx_broadcast_result = self.wallet.sendtx(tx)
+ self.emit(SIGNAL('send_tx3'))
+ self.tx_broadcast_dialog = self.waiting_dialog('Broadcasting..')
+ threading.Thread(target=broadcast_thread).start()
- d = self.waiting_dialog('Broadcasting...')
- h = self.wallet.send_tx(tx)
- self.wallet.tx_event.wait()
- d.accept()
-
- status, msg = self.wallet.receive_tx( h, tx )
- if status:
- QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
- self.do_clear()
- self.update_contacts_tab()
- else:
- QMessageBox.warning(self, _('Error'), msg, _('OK'))
+
+ def send_tx3(self):
+ self.tx_broadcast_dialog.accept()
+ status, msg = self.tx_broadcast_result
+ if status:
+ QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
+ self.do_clear()
+ self.update_contacts_tab()
else:
+ QMessageBox.warning(self, _('Error'), msg, _('OK'))
- self.show_transaction(tx)
- def set_url(self, url):
- address, amount, label, message, signature, identity, url = util.parse_url(url)
- try:
- if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
- elif amount: amount = str(Decimal(amount))
- except Exception:
- amount = "0.0"
- QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
- if self.mini:
- self.mini.set_payment_fields(address, amount)
+ def set_send(self, address, amount, label, message):
if label and self.wallet.labels.get(address) != label:
if self.question('Give label "%s" to address %s ?'%(label,address)):
self.wallet.addressbook.append(address)
self.wallet.set_label(address, label)
- run_hook('set_url', url, self.show_message, self.question)
-
self.tabs.setCurrentIndex(1)
label = self.wallet.labels.get(address)
m_addr = label + ' <'+ address +'>' if label else address
if amount:
self.amount_e.setText(amount)
- if identity:
- self.set_frozen(self.payto_e,True)
- self.set_frozen(self.amount_e,True)
- self.set_frozen(self.message_e,True)
- self.payto_sig.setText( ' '+_('The bitcoin URI was signed by')+' ' + identity )
- else:
- self.payto_sig.setVisible(False)
def do_clear(self):
self.payto_sig.setVisible(False)
menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
menu.addAction(_("Edit label"), lambda: self.edit_label(True))
menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
- if self.wallet.seed:
+ if not self.wallet.is_watching_only():
menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
#menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
l.setColumnWidth(i, width)
if self.current_account is None:
- account_items = self.wallet.accounts.items()
+ account_items = sorted(self.wallet.accounts.items())
elif self.current_account != -1:
account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
else:
account_items = []
+ pending_accounts = self.wallet.get_pending_accounts()
+
for k, account in account_items:
- name = self.wallet.get_account_name(k)
- c,u = self.wallet.get_account_balance(k)
- account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
- l.addTopLevelItem(account_item)
- account_item.setExpanded(self.accounts_expanded.get(k, True))
- account_item.setData(0, 32, k)
- if not self.wallet.is_seeded(k):
- icon = QIcon(":icons/key.png")
- account_item.setIcon(0, icon)
+ if len(account_items) + len(pending_accounts) > 1:
+ name = self.wallet.get_account_name(k)
+ c,u = self.wallet.get_account_balance(k)
+ account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
+ l.addTopLevelItem(account_item)
+ account_item.setExpanded(self.accounts_expanded.get(k, True))
+ account_item.setData(0, 32, k)
+ else:
+ account_item = None
for is_change in ([0,1]):
name = _("Receiving") if not is_change else _("Change")
seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
- account_item.addChild(seq_item)
+ if account_item:
+ account_item.addChild(seq_item)
+ else:
+ l.addTopLevelItem(seq_item)
+
used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
used_flag = False
if not is_change: seq_item.setExpanded(True)
seq_item.addChild(item)
- for k, addr in self.wallet.get_pending_accounts():
+ for k, addr in pending_accounts:
name = self.wallet.labels.get(k,'')
account_item = QTreeWidgetItem( [ name + " [ "+_('pending account')+" ]", '', '', ''] )
self.update_receive_item(item)
def update_buttons_on_seed(self):
- if not self.wallet.is_watching_only():
+ if self.wallet.has_seed():
self.seed_button.show()
+ else:
+ self.seed_button.hide()
+
+ if not self.wallet.is_watching_only():
self.password_button.show()
self.send_button.setText(_("Send"))
else:
self.password_button.hide()
- self.seed_button.hide()
self.send_button.setText(_("Create unsigned transaction"))
name = str(e.text())
if not name: return
- self.wallet.create_pending_account('1of1', name, password)
+ self.wallet.create_pending_account(name, password)
self.update_receive_tab()
self.tabs.setCurrentIndex(2)
- def show_master_public_key_old(self):
- dialog = QDialog(self)
- dialog.setModal(1)
- dialog.setWindowTitle(_("Master Public Key"))
-
- main_text = QTextEdit()
- main_text.setText(self.wallet.get_master_public_key())
- main_text.setReadOnly(True)
- main_text.setMaximumHeight(170)
- qrw = QRCodeWidget(self.wallet.get_master_public_key())
-
- ok_button = QPushButton(_("OK"))
- ok_button.setDefault(True)
- ok_button.clicked.connect(dialog.accept)
-
- main_layout = QGridLayout()
- main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
-
- main_layout.addWidget(main_text, 1, 0)
- main_layout.addWidget(qrw, 1, 1 )
-
- vbox = QVBoxLayout()
- vbox.addLayout(main_layout)
- vbox.addLayout(close_button(dialog))
- dialog.setLayout(vbox)
- dialog.exec_()
-
-
- def show_master_public_key(self):
- if self.wallet.seed_version == 4:
- self.show_master_public_key_old()
- return
+ def show_master_public_keys(self):
dialog = QDialog(self)
dialog.setModal(1)
dialog.setWindowTitle(_("Master Public Keys"))
- mpk_text = QTextEdit()
- mpk_text.setReadOnly(True)
- mpk_text.setMaximumHeight(170)
- mpk_qrw = QRCodeWidget()
-
main_layout = QGridLayout()
-
- main_layout.addWidget(QLabel(_('Key')), 1, 0)
- main_layout.addWidget(mpk_text, 1, 1)
- main_layout.addWidget(mpk_qrw, 1, 2)
-
- def update(key):
- xpub = self.wallet.master_public_keys[str(key)]
- mpk_text.setText(xpub)
- mpk_qrw.set_addr(xpub)
- mpk_qrw.update_qr()
-
- key_selector = QComboBox()
- keys = sorted(self.wallet.master_public_keys.keys())
- key_selector.addItems(keys)
-
- main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
- main_layout.addWidget(key_selector, 0, 1)
- dialog.connect(key_selector,SIGNAL("activated(QString)"),update)
-
- update(keys[0])
+ mpk_dict = self.wallet.get_master_public_keys()
+ i = 0
+ for key, value in mpk_dict.items():
+ main_layout.addWidget(QLabel(key), i, 0)
+ mpk_text = QTextEdit()
+ mpk_text.setReadOnly(True)
+ mpk_text.setMaximumHeight(170)
+ mpk_text.setText(value)
+ main_layout.addWidget(mpk_text, i + 1, 0)
+ i += 2
vbox = QVBoxLayout()
vbox.addLayout(main_layout)
@protected
def show_seed_dialog(self, password):
- if self.wallet.is_watching_only():
- QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
+ if not self.wallet.has_seed():
+ QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
return
- if self.wallet.seed:
- try:
- mnemonic = self.wallet.get_mnemonic(password)
- except Exception:
- QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
- return
- from seed_dialog import SeedDialog
- d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
- d.exec_()
- else:
- l = {}
- for k in self.wallet.master_private_keys.keys():
- pk = self.wallet.get_master_private_key(k, password)
- l[k] = pk
- from seed_dialog import PrivateKeysDialog
- d = PrivateKeysDialog(self,l)
- d.exec_()
-
-
+ try:
+ mnemonic = self.wallet.get_mnemonic(password)
+ except Exception:
+ QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
+ return
+ from seed_dialog import SeedDialog
+ d = SeedDialog(self, mnemonic, self.wallet.imported_keys)
+ d.exec_()
def do_export_history(self):
- from lite_window import csv_transaction
- csv_transaction(self.wallet)
+ wallet = self.wallet
+ select_export = _('Select file to export your wallet transactions to')
+ fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-history.csv'), "*.csv")
+ if not fileName:
+ return
+
+ try:
+ with open(fileName, "w+") as csvfile:
+ transaction = csv.writer(csvfile)
+ transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
+ for item in wallet.get_tx_history():
+ tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
+ if confirmations:
+ if timestamp is not None:
+ try:
+ time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
+ except [RuntimeError, TypeError, NameError] as reason:
+ time_string = "unknown"
+ pass
+ else:
+ time_string = "unknown"
+ else:
+ time_string = "pending"
+
+ if value is not None:
+ value_string = format_satoshis(value, True)
+ else:
+ value_string = '--'
+
+ if fee is not None:
+ fee_string = format_satoshis(fee, True)
+ else:
+ fee_string = '0'
+
+ if tx_hash:
+ label, is_default_label = wallet.get_label(tx_hash)
+ label = label.encode('utf-8')
+ else:
+ label = ""
+
+ balance_string = format_satoshis(balance, False)
+ transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
+ QMessageBox.information(None,_("CSV Export created"), _("Your CSV export has been successfully created."))
+
+ except (IOError, os.error), reason:
+ export_error_label = _("Electrum was unable to produce a transaction export.")
+ QMessageBox.critical(None,_("Unable to create csv"), export_error_label + "\n" + str(reason))
+
@protected
grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
- grid.setRowStretch(5,1)
+ block_explorers = ['Blockchain.info', 'Blockr.io', 'Insight.is']
+ block_ex_label = QLabel(_('Online Block Explorer') + ':')
+ grid.addWidget(block_ex_label, 5, 0)
+ block_ex_combo = QComboBox()
+ block_ex_combo.addItems(block_explorers)
+ block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'Blockchain.info')))
+ grid.addWidget(block_ex_combo, 5, 1)
+ grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
+
+ show_tx = self.config.get('show_before_broadcast', False)
+ showtx_cb = QCheckBox(_('Show before broadcast'))
+ showtx_cb.setChecked(show_tx)
+ grid.addWidget(showtx_cb, 6, 0)
+ grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
vbox.addLayout(grid)
+ vbox.addStretch(1)
vbox.addLayout(ok_cancel_buttons(d))
d.setLayout(vbox)
self.wallet.use_change = usechange_result
self.wallet.storage.put('use_change', self.wallet.use_change)
+ if showtx_cb.isChecked() != show_tx:
+ self.config.set_key('show_before_broadcast', not show_tx)
+
unit_result = units[unit_combo.currentIndex()]
if self.base_unit() != unit_result:
self.decimal_point = 8 if unit_result == 'BTC' else 5
self.config.set_key("language", lang_request, True)
need_restart = True
+ be_result = block_explorers[block_ex_combo.currentIndex()]
+ self.config.set_key('block_explorer', be_result, True)
+
run_hook('close_settings_dialog')
if need_restart: