parent
21e3bb7939
commit
13678d9e13
|
@ -92,11 +92,12 @@ class ElectrumWindow(App):
|
|||
_.switch_lang(language)
|
||||
|
||||
def on_quotes(self, d):
|
||||
#Logger.info("on_quotes")
|
||||
pass
|
||||
Logger.info("on_quotes")
|
||||
if self.history_screen:
|
||||
Clock.schedule_once(lambda dt: self.history_screen.update())
|
||||
|
||||
def on_history(self, d):
|
||||
#Logger.info("on_history")
|
||||
Logger.info("on_history")
|
||||
if self.history_screen:
|
||||
Clock.schedule_once(lambda dt: self.history_screen.update())
|
||||
|
||||
|
@ -124,7 +125,7 @@ class ElectrumWindow(App):
|
|||
def btc_to_fiat(self, amount_str):
|
||||
if not amount_str:
|
||||
return ''
|
||||
rate = run_hook('exchange_rate')
|
||||
rate = self.fx.exchange_rate()
|
||||
if not rate:
|
||||
return ''
|
||||
fiat_amount = self.get_amount(amount_str + ' ' + self.base_unit) * rate / pow(10, 8)
|
||||
|
@ -133,7 +134,7 @@ class ElectrumWindow(App):
|
|||
def fiat_to_btc(self, fiat_amount):
|
||||
if not fiat_amount:
|
||||
return ''
|
||||
rate = run_hook('exchange_rate')
|
||||
rate = self.fx.exchange_rate()
|
||||
if not rate:
|
||||
return ''
|
||||
satoshis = int(pow(10,8) * Decimal(fiat_amount) / Decimal(rate))
|
||||
|
@ -198,6 +199,7 @@ class ElectrumWindow(App):
|
|||
|
||||
self.gui_object = kwargs.get('gui_object', None)
|
||||
self.daemon = self.gui_object.daemon
|
||||
self.fx = self.daemon.fx
|
||||
|
||||
self.contacts = Contacts(self.electrum_config)
|
||||
self.invoices = InvoiceStore(self.electrum_config)
|
||||
|
@ -386,6 +388,12 @@ class ElectrumWindow(App):
|
|||
self.load_wallet_by_name(self.electrum_config.get_wallet_path())
|
||||
# init plugins
|
||||
run_hook('init_kivy', self)
|
||||
|
||||
# fiat currency
|
||||
self.fiat_unit = self.fx.ccy if self.fx.is_enabled() else ''
|
||||
self.network.register_callback(self.on_quotes, ['on_quotes'])
|
||||
self.network.register_callback(self.on_history, ['on_history'])
|
||||
|
||||
# default tab
|
||||
self.switch_to('history')
|
||||
# bind intent for bitcoin: URI scheme
|
||||
|
|
|
@ -86,46 +86,35 @@ class FxDialog(Factory.Popup):
|
|||
self.app = app
|
||||
self.config = config
|
||||
self.callback = callback
|
||||
self.plugins = plugins
|
||||
p = self.plugins.get('exchange_rate')
|
||||
self.ids.enabled.active = bool(p)
|
||||
self.fx = self.app.fx
|
||||
self.ids.enabled.active = self.fx.is_enabled()
|
||||
|
||||
def on_active(self, b):
|
||||
if b:
|
||||
p = self.plugins.get('exchange_rate')
|
||||
if p is None:
|
||||
p = self.plugins.enable('exchange_rate')
|
||||
p.init_kivy(self.app)
|
||||
else:
|
||||
self.plugins.disable('exchange_rate')
|
||||
self.fx.set_enabled(b)
|
||||
Clock.schedule_once(lambda dt: self.add_currencies())
|
||||
|
||||
def add_exchanges(self):
|
||||
p = self.plugins.get('exchange_rate')
|
||||
exchanges = sorted(p.exchanges_by_ccy.get(p.get_currency())) if p else []
|
||||
mx = p.exchange.name() if p else ''
|
||||
exchanges = sorted(self.fx.exchanges_by_ccy.get(self.fx.get_currency())) if self.fx.is_enabled() else []
|
||||
mx = self.fx.exchange.name() if self.fx.is_enabled() else ''
|
||||
ex = self.ids.exchanges
|
||||
ex.values = exchanges
|
||||
ex.text = (mx if mx in exchanges else exchanges[0]) if p else ''
|
||||
ex.text = (mx if mx in exchanges else exchanges[0]) if self.fx.is_enabled() else ''
|
||||
|
||||
def on_exchange(self, text):
|
||||
if not text:
|
||||
return
|
||||
p = self.plugins.get('exchange_rate')
|
||||
if p and text != p.exchange.name():
|
||||
p.set_exchange(text)
|
||||
if self.fx.is_enabled() and text != self.fx.exchange.name():
|
||||
self.fx.set_exchange(text)
|
||||
|
||||
def add_currencies(self):
|
||||
p = self.plugins.get('exchange_rate')
|
||||
currencies = sorted(p.exchanges_by_ccy.keys()) if p else []
|
||||
my_ccy = p.get_currency() if p else ''
|
||||
currencies = sorted(self.fx.exchanges_by_ccy.keys()) if self.fx else []
|
||||
my_ccy = self.fx.get_currency() if self.fx.is_enabled() else ''
|
||||
self.ids.ccy.values = currencies
|
||||
self.ids.ccy.text = my_ccy
|
||||
|
||||
def on_currency(self, ccy):
|
||||
if ccy:
|
||||
p = self.plugins.get('exchange_rate')
|
||||
if p and ccy != p.get_currency():
|
||||
p.set_currency(ccy)
|
||||
if self.fx.is_enabled() and ccy != self.fx.get_currency():
|
||||
self.fx.set_currency(ccy)
|
||||
self.app.fiat_unit = ccy
|
||||
Clock.schedule_once(lambda dt: self.add_exchanges())
|
||||
|
|
|
@ -242,10 +242,10 @@ class SettingsDialog(Factory.Popup):
|
|||
self._rbf_dialog.open()
|
||||
|
||||
def fx_status(self):
|
||||
p = self.plugins.get('exchange_rate')
|
||||
if p:
|
||||
source = p.exchange.name()
|
||||
ccy = p.get_currency()
|
||||
fx = self.app.fx
|
||||
if fx.is_enabled():
|
||||
source = fx.exchange.name()
|
||||
ccy = fx.get_currency()
|
||||
return '%s [%s]' %(ccy, source)
|
||||
else:
|
||||
return 'Disabled'
|
||||
|
|
|
@ -20,7 +20,6 @@ from kivy.utils import platform
|
|||
from electrum.util import profiler, parse_URI, format_time, InvalidPassword, NotEnoughFunds
|
||||
from electrum import bitcoin
|
||||
from electrum.util import timestamp_to_datetime
|
||||
from electrum.plugins import run_hook
|
||||
from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
|
||||
|
||||
from context_menu import ContextMenu
|
||||
|
@ -148,9 +147,9 @@ class HistoryScreen(CScreen):
|
|||
ri.value_known = value is not None
|
||||
ri.confirmations = conf
|
||||
if self.app.fiat_unit and date:
|
||||
rate = run_hook('history_rate', date)
|
||||
rate = self.app.fx.history_rate(date)
|
||||
if rate:
|
||||
s = run_hook('value_str', value, rate)
|
||||
s = self.app.fx.value_str(value, rate)
|
||||
ri.quote_text = '' if s is None else s + ' ' + self.app.fiat_unit
|
||||
return ri
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ from util import *
|
|||
from electrum.i18n import _
|
||||
from electrum.util import block_explorer_URL, format_satoshis, format_time
|
||||
from electrum.plugins import run_hook
|
||||
from electrum.util import timestamp_to_datetime
|
||||
|
||||
|
||||
TX_ICONS = [
|
||||
|
@ -55,8 +56,10 @@ class HistoryList(MyTreeWidget):
|
|||
self.setColumnHidden(1, True)
|
||||
|
||||
def refresh_headers(self):
|
||||
ccy = self.parent.fx.ccy
|
||||
headers = ['', '', _('Date'), _('Description') , _('Amount'), _('Balance')]
|
||||
run_hook('history_tab_headers', headers)
|
||||
if self.parent.fx.show_history():
|
||||
headers.extend(['%s '%ccy + _('Amount'), '%s '%ccy + _('Balance')])
|
||||
self.update_headers(headers)
|
||||
|
||||
def get_domain(self):
|
||||
|
@ -69,7 +72,10 @@ class HistoryList(MyTreeWidget):
|
|||
item = self.currentItem()
|
||||
current_tx = item.data(0, Qt.UserRole).toString() if item else None
|
||||
self.clear()
|
||||
run_hook('history_tab_update_begin')
|
||||
|
||||
fx = self.parent.fx
|
||||
fx.history_used_spot = False
|
||||
|
||||
for h_item in h:
|
||||
tx_hash, height, conf, timestamp, value, balance = h_item
|
||||
status, status_str = self.wallet.get_tx_status(tx_hash, height, conf, timestamp)
|
||||
|
@ -78,7 +84,11 @@ class HistoryList(MyTreeWidget):
|
|||
balance_str = self.parent.format_amount(balance, whitespaces=True)
|
||||
label = self.wallet.get_label(tx_hash)
|
||||
entry = ['', tx_hash, status_str, label, v_str, balance_str]
|
||||
run_hook('history_tab_update', h_item, entry)
|
||||
if fx.show_history():
|
||||
date = timestamp_to_datetime(time.time() if conf <= 0 else timestamp)
|
||||
for amount in [value, balance]:
|
||||
text = fx.historical_value_str(amount, date)
|
||||
entry.append(text)
|
||||
item = QTreeWidgetItem(entry)
|
||||
item.setIcon(0, icon)
|
||||
for i in range(len(entry)):
|
||||
|
|
|
@ -54,7 +54,7 @@ from electrum import util, bitcoin, commands, coinchooser
|
|||
from electrum import SimpleConfig, paymentrequest
|
||||
from electrum.wallet import Wallet, Multisig_Wallet
|
||||
|
||||
from amountedit import BTCAmountEdit, MyLineEdit, BTCkBEdit
|
||||
from amountedit import AmountEdit, BTCAmountEdit, MyLineEdit, BTCkBEdit
|
||||
from network_dialog import NetworkDialog
|
||||
from qrcodewidget import QRCodeWidget, QRDialog
|
||||
from qrtextedit import ShowQRTextEdit
|
||||
|
@ -98,6 +98,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
self.gui_object = gui_object
|
||||
self.config = config = gui_object.config
|
||||
self.network = gui_object.daemon.network
|
||||
self.fx = gui_object.daemon.fx
|
||||
self.invoices = gui_object.invoices
|
||||
self.contacts = gui_object.contacts
|
||||
self.tray = gui_object.tray
|
||||
|
@ -166,10 +167,36 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
# set initial message
|
||||
self.console.showMessage(self.network.banner)
|
||||
|
||||
self.network.register_callback(self.on_quotes, ['on_quotes'])
|
||||
self.network.register_callback(self.on_history, ['on_history'])
|
||||
self.connect(self, SIGNAL('new_fx_quotes'), self.on_fx_quotes)
|
||||
self.connect(self, SIGNAL('new_fx_history'), self.on_fx_history)
|
||||
|
||||
self.load_wallet(wallet)
|
||||
self.connect_slots(gui_object.timer)
|
||||
self.fetch_alias()
|
||||
|
||||
def on_history(self, b):
|
||||
self.emit(SIGNAL('new_fx_history'))
|
||||
|
||||
def on_fx_history(self):
|
||||
self.history_list.refresh_headers()
|
||||
self.history_list.update()
|
||||
|
||||
def on_quotes(self, b):
|
||||
self.emit(SIGNAL('new_fx_quotes'))
|
||||
|
||||
def on_fx_quotes(self):
|
||||
self.update_status()
|
||||
# Refresh edits with the new rate
|
||||
edit = self.fiat_send_e if self.fiat_send_e.is_last_edited else self.amount_e
|
||||
edit.textEdited.emit(edit.text())
|
||||
edit = self.fiat_receive_e if self.fiat_receive_e.is_last_edited else self.receive_amount_e
|
||||
edit.textEdited.emit(edit.text())
|
||||
# History tab needs updating if it used spot
|
||||
if self.fx.history_used_spot:
|
||||
self.history_list.update()
|
||||
|
||||
def toggle_addresses_tab(self):
|
||||
show_addr = not self.config.get('show_addresses_tab', False)
|
||||
self.config.set_key('show_addresses_tab', show_addr)
|
||||
|
@ -528,7 +555,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
|
||||
def format_amount_and_units(self, amount):
|
||||
text = self.format_amount(amount) + ' '+ self.base_unit()
|
||||
x = run_hook('format_amount_and_units', amount)
|
||||
x = self.fx.format_amount_and_units(amount)
|
||||
if text and x:
|
||||
text += ' (%s)'%x
|
||||
return text
|
||||
|
@ -546,6 +573,43 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
return 'BTC'
|
||||
raise Exception('Unknown base unit')
|
||||
|
||||
def connect_fields(self, window, btc_e, fiat_e, fee_e):
|
||||
|
||||
def edit_changed(edit):
|
||||
if edit.follows:
|
||||
return
|
||||
edit.setStyleSheet(BLACK_FG)
|
||||
fiat_e.is_last_edited = (edit == fiat_e)
|
||||
amount = edit.get_amount()
|
||||
rate = self.fx.exchange_rate()
|
||||
if rate is None or amount is None:
|
||||
if edit is fiat_e:
|
||||
btc_e.setText("")
|
||||
if fee_e:
|
||||
fee_e.setText("")
|
||||
else:
|
||||
fiat_e.setText("")
|
||||
else:
|
||||
if edit is fiat_e:
|
||||
btc_e.follows = True
|
||||
btc_e.setAmount(int(amount / Decimal(rate) * COIN))
|
||||
btc_e.setStyleSheet(BLUE_FG)
|
||||
btc_e.follows = False
|
||||
if fee_e:
|
||||
window.update_fee()
|
||||
else:
|
||||
fiat_e.follows = True
|
||||
fiat_e.setText(self.fx.ccy_amount_str(
|
||||
amount * Decimal(rate) / COIN, False))
|
||||
fiat_e.setStyleSheet(BLUE_FG)
|
||||
fiat_e.follows = False
|
||||
|
||||
btc_e.follows = False
|
||||
fiat_e.follows = False
|
||||
fiat_e.textChanged.connect(partial(edit_changed, fiat_e))
|
||||
btc_e.textChanged.connect(partial(edit_changed, btc_e))
|
||||
fiat_e.is_last_edited = False
|
||||
|
||||
def update_status(self):
|
||||
if not self.wallet:
|
||||
return
|
||||
|
@ -573,10 +637,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
text += " [%s unconfirmed]"%(self.format_amount(u, True).strip())
|
||||
if x:
|
||||
text += " [%s unmatured]"%(self.format_amount(x, True).strip())
|
||||
# append fiat balance and price from exchange rate plugin
|
||||
rate = run_hook('get_fiat_status_text', c + u + x)
|
||||
if rate:
|
||||
text += rate
|
||||
|
||||
# append fiat balance and price
|
||||
if self.fx.is_enabled():
|
||||
text += self.fx.get_fiat_status_text(c + u + x) or ''
|
||||
icon = QIcon(":icons/status_connected.png")
|
||||
else:
|
||||
text = _("Not connected")
|
||||
|
@ -641,6 +705,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
grid.addWidget(self.receive_amount_e, 2, 1)
|
||||
self.receive_amount_e.textChanged.connect(self.update_receive_qr)
|
||||
|
||||
self.fiat_receive_e = AmountEdit(self.fx.get_currency)
|
||||
grid.addWidget(self.fiat_receive_e, 2, 2, Qt.AlignLeft)
|
||||
self.connect_fields(self, self.receive_amount_e, self.fiat_receive_e, None)
|
||||
|
||||
self.expires_combo = QComboBox()
|
||||
self.expires_combo.addItems(map(lambda x:x[0], expiration_values))
|
||||
self.expires_combo.setCurrentIndex(1)
|
||||
|
@ -893,6 +961,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
grid.addWidget(amount_label, 4, 0)
|
||||
grid.addWidget(self.amount_e, 4, 1)
|
||||
|
||||
self.fiat_send_e = AmountEdit(self.fx.get_currency)
|
||||
grid.addWidget(self.fiat_send_e, 4, 2, Qt.AlignLeft)
|
||||
self.amount_e.frozen.connect(
|
||||
lambda: self.fiat_send_e.setFrozen(self.amount_e.isReadOnly()))
|
||||
|
||||
self.max_button = EnterButton(_("Max"), self.spend_max)
|
||||
hbox = QHBoxLayout()
|
||||
hbox.addWidget(self.max_button)
|
||||
|
@ -927,6 +1000,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
# This is so that when the user blanks the fee and moves on,
|
||||
# we go back to auto-calculate mode and put a fee back.
|
||||
self.fee_e.editingFinished.connect(self.update_fee)
|
||||
self.connect_fields(self, self.amount_e, self.fiat_send_e, self.fee_e)
|
||||
|
||||
self.rbf_checkbox = QCheckBox(_('Replaceable'))
|
||||
msg = [_('If you check this box, your transaction will be marked as non-final,'),
|
||||
|
@ -1380,7 +1454,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
self.not_enough_funds = False
|
||||
self.payment_request = None
|
||||
self.payto_e.is_pr = False
|
||||
for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
|
||||
for e in [self.payto_e, self.message_e, self.amount_e, self.fiat_send_e, self.fee_e]:
|
||||
e.setText('')
|
||||
e.setFrozen(False)
|
||||
self.set_pay_from([])
|
||||
|
@ -2241,6 +2315,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
title, msg = _('Import private keys'), _("Enter private keys")
|
||||
self._do_import(title, msg, lambda x: self.wallet.import_key(x, password))
|
||||
|
||||
def update_fiat(self):
|
||||
b = self.fx.is_enabled()
|
||||
self.fiat_send_e.setVisible(b)
|
||||
self.fiat_receive_e.setVisible(b)
|
||||
self.history_list.refresh_headers()
|
||||
self.history_list.update()
|
||||
self.update_status()
|
||||
|
||||
def settings_dialog(self):
|
||||
self.need_restart = False
|
||||
d = WindowModalDialog(self, _('Preferences'))
|
||||
|
@ -2490,10 +2572,73 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
chooser_combo.currentIndexChanged.connect(on_chooser)
|
||||
tx_widgets.append((chooser_label, chooser_combo))
|
||||
|
||||
# Fiat Currency
|
||||
hist_checkbox = QCheckBox()
|
||||
ccy_combo = QComboBox()
|
||||
ex_combo = QComboBox()
|
||||
|
||||
def update_currencies():
|
||||
currencies = sorted(self.fx.exchanges_by_ccy.keys())
|
||||
ccy_combo.clear()
|
||||
ccy_combo.addItems([_('None')] + currencies)
|
||||
if self.fx.is_enabled():
|
||||
ccy_combo.setCurrentIndex(ccy_combo.findText(self.fx.get_currency()))
|
||||
|
||||
def update_history_cb():
|
||||
hist_checkbox.setChecked(self.fx.get_history_config())
|
||||
hist_checkbox.setEnabled(self.fx.is_enabled())
|
||||
|
||||
def update_exchanges():
|
||||
b = self.fx.is_enabled()
|
||||
ex_combo.setEnabled(b)
|
||||
if b:
|
||||
h = self.fx.get_history_config()
|
||||
c = self.fx.get_currency()
|
||||
exchanges = self.fx.get_exchanges_by_ccy(c, h)
|
||||
else:
|
||||
exchanges = self.fx.exchanges.keys()
|
||||
ex_combo.clear()
|
||||
ex_combo.addItems(sorted(exchanges))
|
||||
ex_combo.setCurrentIndex(ex_combo.findText(self.fx.config_exchange()))
|
||||
|
||||
def on_currency(hh):
|
||||
b = bool(ccy_combo.currentIndex())
|
||||
ccy = str(ccy_combo.currentText()) if b else None
|
||||
self.fx.set_enabled(b)
|
||||
if b and ccy != self.fx.ccy:
|
||||
self.fx.set_currency(ccy)
|
||||
update_history_cb()
|
||||
update_exchanges()
|
||||
self.update_fiat()
|
||||
|
||||
def on_exchange(idx):
|
||||
exchange = str(ex_combo.currentText())
|
||||
if self.fx.is_enabled() and exchange != self.fx.exchange.name():
|
||||
self.fx.set_exchange(exchange)
|
||||
|
||||
def on_history(checked):
|
||||
self.fx.set_history_config(checked)
|
||||
self.history_list.refresh_headers()
|
||||
if self.fx.is_enabled() and checked:
|
||||
self.fx.get_historical_rates()
|
||||
|
||||
update_currencies()
|
||||
update_history_cb()
|
||||
update_exchanges()
|
||||
ccy_combo.currentIndexChanged.connect(on_currency)
|
||||
hist_checkbox.stateChanged.connect(on_history)
|
||||
ex_combo.currentIndexChanged.connect(on_exchange)
|
||||
|
||||
fiat_widgets = []
|
||||
fiat_widgets.append((QLabel(_('Fiat currency')), ccy_combo))
|
||||
fiat_widgets.append((QLabel(_('Show history rates')), hist_checkbox))
|
||||
fiat_widgets.append((QLabel(_('Source')), ex_combo))
|
||||
|
||||
tabs_info = [
|
||||
(fee_widgets, _('Fees')),
|
||||
(tx_widgets, _('Transactions')),
|
||||
(gui_widgets, _('Appearance')),
|
||||
(fiat_widgets, _('Fiat')),
|
||||
(id_widgets, _('Identity')),
|
||||
]
|
||||
for widgets, name in tabs_info:
|
||||
|
@ -2517,12 +2662,19 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
|
||||
# run the dialog
|
||||
d.exec_()
|
||||
|
||||
if self.fx:
|
||||
self.fx.timeout = 0
|
||||
|
||||
self.disconnect(self, SIGNAL('alias_received'), set_alias_color)
|
||||
|
||||
run_hook('close_settings_dialog')
|
||||
if self.need_restart:
|
||||
self.show_warning(_('Please restart Electrum to activate the new GUI settings'), title=_('Success'))
|
||||
|
||||
|
||||
|
||||
|
||||
def run_network_dialog(self):
|
||||
if not self.network:
|
||||
self.show_warning(_('You are using Electrum in offline mode; restart Electrum if you want to get connected'), title=_('Offline'))
|
||||
|
|
|
@ -39,6 +39,7 @@ from wallet import WalletStorage, Wallet
|
|||
from commands import known_commands, Commands
|
||||
from simple_config import SimpleConfig
|
||||
from plugins import run_hook
|
||||
from exchange_rate import FxThread
|
||||
|
||||
def get_lockfile(config):
|
||||
return os.path.join(config.path, 'daemon')
|
||||
|
@ -100,14 +101,17 @@ class RequestHandler(SimpleJSONRPCRequestHandler):
|
|||
class Daemon(DaemonThread):
|
||||
|
||||
def __init__(self, config, fd):
|
||||
|
||||
DaemonThread.__init__(self)
|
||||
self.config = config
|
||||
if config.get('offline'):
|
||||
self.network = None
|
||||
self.fx = None
|
||||
else:
|
||||
self.network = Network(config)
|
||||
self.network.start()
|
||||
self.fx = FxThread(config, self.network)
|
||||
self.network.add_jobs([self.fx])
|
||||
|
||||
self.gui = None
|
||||
self.wallets = {}
|
||||
# Setup JSONRPC server
|
||||
|
|
|
@ -8,11 +8,10 @@ import traceback
|
|||
import csv
|
||||
from decimal import Decimal
|
||||
|
||||
from electrum.bitcoin import COIN
|
||||
from electrum.plugins import BasePlugin, hook
|
||||
from electrum.i18n import _
|
||||
from electrum.util import PrintError, ThreadJob
|
||||
from electrum.util import format_satoshis
|
||||
from bitcoin import COIN
|
||||
from i18n import _
|
||||
from util import PrintError, ThreadJob
|
||||
from util import format_satoshis
|
||||
|
||||
|
||||
# See https://en.wikipedia.org/wiki/ISO_4217
|
||||
|
@ -102,7 +101,7 @@ class BitcoinAverage(ExchangeBase):
|
|||
def historical_rates(self, ccy):
|
||||
history = self.get_csv('api.bitcoinaverage.com',
|
||||
"/history/%s/per_day_all_time_history.csv" % ccy)
|
||||
return dict([(h['datetime'][:10], h['average'])
|
||||
return dict([(h['DateTime'][:10], h['Average'])
|
||||
for h in history])
|
||||
|
||||
class BitcoinVenezuela(ExchangeBase):
|
||||
|
@ -298,10 +297,11 @@ def get_exchanges_by_ccy():
|
|||
|
||||
|
||||
|
||||
class FxPlugin(BasePlugin, ThreadJob):
|
||||
class FxThread(ThreadJob):
|
||||
|
||||
def __init__(self, parent, config, name):
|
||||
BasePlugin.__init__(self, parent, config, name)
|
||||
def __init__(self, config, network):
|
||||
self.config = config
|
||||
self.network = network
|
||||
self.ccy = self.get_currency()
|
||||
self.history_used_spot = False
|
||||
self.ccy_combo = None
|
||||
|
@ -310,19 +310,34 @@ class FxPlugin(BasePlugin, ThreadJob):
|
|||
self.exchanges_by_ccy = get_exchanges_by_ccy()
|
||||
self.set_exchange(self.config_exchange())
|
||||
|
||||
def get_exchanges_by_ccy(self, ccy, h):
|
||||
return self.exchanges_by_ccy.get(ccy)
|
||||
|
||||
def ccy_amount_str(self, amount, commas):
|
||||
prec = CCY_PRECISIONS.get(self.ccy, 2)
|
||||
fmt_str = "{:%s.%df}" % ("," if commas else "", max(0, prec))
|
||||
return fmt_str.format(round(amount, prec))
|
||||
|
||||
def thread_jobs(self):
|
||||
return [self]
|
||||
|
||||
def run(self):
|
||||
# This runs from the plugins thread which catches exceptions
|
||||
if self.timeout <= time.time():
|
||||
self.timeout = time.time() + 150
|
||||
self.exchange.update(self.ccy)
|
||||
if self.is_enabled():
|
||||
if self.timeout ==0 and self.show_history():
|
||||
self.exchange.get_historical_rates(self.ccy)
|
||||
if self.timeout <= time.time():
|
||||
self.timeout = time.time() + 150
|
||||
self.exchange.update(self.ccy)
|
||||
|
||||
def is_enabled(self):
|
||||
return bool(self.config.get('use_exchange_rate'))
|
||||
|
||||
def set_enabled(self, b):
|
||||
return self.config.set_key('use_exchange_rate', bool(b))
|
||||
|
||||
def get_history_config(self):
|
||||
return bool(self.config.get('history_rates'))
|
||||
|
||||
def set_history_config(self, b):
|
||||
self.config.set_key('history_rates', bool(b))
|
||||
|
||||
def get_currency(self):
|
||||
'''Use when dynamic fetching is needed'''
|
||||
|
@ -332,12 +347,12 @@ class FxPlugin(BasePlugin, ThreadJob):
|
|||
return self.config.get('use_exchange', 'BitcoinAverage')
|
||||
|
||||
def show_history(self):
|
||||
return self.ccy in self.exchange.history_ccys()
|
||||
return self.is_enabled() and self.get_history_config() and self.ccy in self.exchange.history_ccys()
|
||||
|
||||
def set_currency(self, ccy):
|
||||
self.ccy = ccy
|
||||
self.config.set_key('currency', ccy, True)
|
||||
self.get_historical_rates() # Because self.ccy changes
|
||||
self.timeout = 0 # Because self.ccy changes
|
||||
self.on_quotes()
|
||||
|
||||
def set_exchange(self, name):
|
||||
|
@ -351,39 +366,28 @@ class FxPlugin(BasePlugin, ThreadJob):
|
|||
# A new exchange means new fx quotes, initially empty. Force
|
||||
# a quote refresh
|
||||
self.timeout = 0
|
||||
self.get_historical_rates()
|
||||
|
||||
def on_quotes(self):
|
||||
pass
|
||||
self.network.trigger_callback('on_quotes')
|
||||
|
||||
def on_history(self):
|
||||
pass
|
||||
self.network.trigger_callback('on_history')
|
||||
|
||||
@hook
|
||||
def exchange_rate(self):
|
||||
'''Returns None, or the exchange rate as a Decimal'''
|
||||
rate = self.exchange.quotes.get(self.ccy)
|
||||
if rate:
|
||||
return Decimal(rate)
|
||||
|
||||
@hook
|
||||
def format_amount_and_units(self, btc_balance):
|
||||
rate = self.exchange_rate()
|
||||
return '' if rate is None else "%s %s" % (self.value_str(btc_balance, rate), self.ccy)
|
||||
|
||||
@hook
|
||||
def get_fiat_status_text(self, btc_balance):
|
||||
rate = self.exchange_rate()
|
||||
return _(" (No FX rate available)") if rate is None else "1 BTC~%s %s" % (self.value_str(COIN, rate), self.ccy)
|
||||
return _(" (No FX rate available)") if rate is None else " 1 BTC~%s %s" % (self.value_str(COIN, rate), self.ccy)
|
||||
|
||||
def get_historical_rates(self):
|
||||
if self.show_history():
|
||||
self.exchange.get_historical_rates(self.ccy)
|
||||
|
||||
def requires_settings(self):
|
||||
return True
|
||||
|
||||
@hook
|
||||
def value_str(self, satoshis, rate):
|
||||
if satoshis is None: # Can happen with incomplete history
|
||||
return _("Unknown")
|
||||
|
@ -392,7 +396,6 @@ class FxPlugin(BasePlugin, ThreadJob):
|
|||
return "%s" % (self.ccy_amount_str(value, True))
|
||||
return _("No data")
|
||||
|
||||
@hook
|
||||
def history_rate(self, d_t):
|
||||
rate = self.exchange.historical_rate(self.ccy, d_t)
|
||||
# Frequently there is no rate for today, until tomorrow :)
|
||||
|
@ -402,7 +405,6 @@ class FxPlugin(BasePlugin, ThreadJob):
|
|||
self.history_used_spot = True
|
||||
return rate
|
||||
|
||||
@hook
|
||||
def historical_value_str(self, satoshis, d_t):
|
||||
rate = self.history_rate(d_t)
|
||||
return self.value_str(satoshis, rate)
|
|
@ -63,6 +63,9 @@ class Plugins(DaemonThread):
|
|||
|
||||
def load_plugins(self):
|
||||
for loader, name, ispkg in pkgutil.iter_modules([self.pkgpath]):
|
||||
# do not load deprecated plugins
|
||||
if name in ['plot', 'exchange_rate']:
|
||||
continue
|
||||
m = loader.find_module(name).load_module(name)
|
||||
d = m.__dict__
|
||||
gui_good = self.gui_name in d.get('available_for', [])
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
from electrum.i18n import _
|
||||
|
||||
fullname = _("Exchange rates")
|
||||
description = _("Exchange rates and currency conversion tools.")
|
||||
available_for = ['qt','kivy']
|
|
@ -1,54 +0,0 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from .exchange_rate import FxPlugin
|
||||
from electrum.plugins import hook
|
||||
|
||||
|
||||
from kivy.event import EventDispatcher
|
||||
|
||||
class MyEventDispatcher(EventDispatcher):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.register_event_type('on_quotes')
|
||||
self.register_event_type('on_history')
|
||||
super(MyEventDispatcher, self).__init__(**kwargs)
|
||||
|
||||
def on_quotes(self, *args):
|
||||
pass
|
||||
|
||||
def on_history(self, *args):
|
||||
pass
|
||||
|
||||
|
||||
class Plugin(FxPlugin):
|
||||
|
||||
def __init__(self, parent, config, name):
|
||||
FxPlugin.__init__(self, parent, config, name)
|
||||
self.dispatcher = MyEventDispatcher()
|
||||
|
||||
def on_quotes(self):
|
||||
self.print_error("on_quotes", self.ccy)
|
||||
self.dispatcher.dispatch('on_quotes')
|
||||
|
||||
def on_history(self):
|
||||
self.print_error("on_history", self.ccy)
|
||||
self.dispatcher.dispatch('on_history')
|
||||
|
||||
def on_close(self):
|
||||
self.print_error("on close")
|
||||
self.window.fiat_unit = ''
|
||||
self.window.history_screen.update()
|
||||
|
||||
@hook
|
||||
def init_kivy(self, window):
|
||||
self.print_error("init_kivy")
|
||||
self.window = window
|
||||
self.dispatcher.bind(on_quotes=window.on_quotes)
|
||||
self.dispatcher.bind(on_history=window.on_history)
|
||||
self.window.fiat_unit = self.ccy
|
||||
self.dispatcher.dispatch('on_history')
|
||||
|
||||
@hook
|
||||
def load_wallet(self, wallet, window):
|
||||
self.window = window
|
||||
self.window.fiat_unit = self.ccy
|
|
@ -1,227 +0,0 @@
|
|||
import time
|
||||
from PyQt4.QtGui import *
|
||||
from PyQt4.QtCore import *
|
||||
from electrum_gui.qt.util import *
|
||||
from electrum_gui.qt.amountedit import AmountEdit
|
||||
|
||||
|
||||
from electrum.bitcoin import COIN
|
||||
from electrum.i18n import _
|
||||
from decimal import Decimal
|
||||
from functools import partial
|
||||
from electrum.plugins import hook
|
||||
from exchange_rate import FxPlugin
|
||||
from electrum.util import timestamp_to_datetime
|
||||
|
||||
class Plugin(FxPlugin, QObject):
|
||||
|
||||
def __init__(self, parent, config, name):
|
||||
FxPlugin.__init__(self, parent, config, name)
|
||||
QObject.__init__(self)
|
||||
|
||||
def connect_fields(self, window, btc_e, fiat_e, fee_e):
|
||||
|
||||
def edit_changed(edit):
|
||||
if edit.follows:
|
||||
return
|
||||
edit.setStyleSheet(BLACK_FG)
|
||||
fiat_e.is_last_edited = (edit == fiat_e)
|
||||
amount = edit.get_amount()
|
||||
rate = self.exchange_rate()
|
||||
if rate is None or amount is None:
|
||||
if edit is fiat_e:
|
||||
btc_e.setText("")
|
||||
if fee_e:
|
||||
fee_e.setText("")
|
||||
else:
|
||||
fiat_e.setText("")
|
||||
else:
|
||||
if edit is fiat_e:
|
||||
btc_e.follows = True
|
||||
btc_e.setAmount(int(amount / Decimal(rate) * COIN))
|
||||
btc_e.setStyleSheet(BLUE_FG)
|
||||
btc_e.follows = False
|
||||
if fee_e:
|
||||
window.update_fee()
|
||||
else:
|
||||
fiat_e.follows = True
|
||||
fiat_e.setText(self.ccy_amount_str(
|
||||
amount * Decimal(rate) / COIN, False))
|
||||
fiat_e.setStyleSheet(BLUE_FG)
|
||||
fiat_e.follows = False
|
||||
|
||||
btc_e.follows = False
|
||||
fiat_e.follows = False
|
||||
fiat_e.textChanged.connect(partial(edit_changed, fiat_e))
|
||||
btc_e.textChanged.connect(partial(edit_changed, btc_e))
|
||||
fiat_e.is_last_edited = False
|
||||
|
||||
@hook
|
||||
def init_qt(self, gui):
|
||||
for window in gui.windows:
|
||||
self.on_new_window(window)
|
||||
|
||||
@hook
|
||||
def do_clear(self, window):
|
||||
window.fiat_send_e.setText('')
|
||||
|
||||
def on_close(self):
|
||||
self.emit(SIGNAL('close_fx_plugin'))
|
||||
|
||||
def restore_window(self, window):
|
||||
window.update_status()
|
||||
window.history_list.refresh_headers()
|
||||
window.fiat_send_e.hide()
|
||||
window.fiat_receive_e.hide()
|
||||
|
||||
def on_quotes(self):
|
||||
self.emit(SIGNAL('new_fx_quotes'))
|
||||
|
||||
def on_history(self):
|
||||
self.emit(SIGNAL('new_fx_history'))
|
||||
|
||||
def on_fx_history(self, window):
|
||||
'''Called when historical fx quotes are updated'''
|
||||
window.history_list.update()
|
||||
|
||||
def on_fx_quotes(self, window):
|
||||
'''Called when fresh spot fx quotes come in'''
|
||||
window.update_status()
|
||||
self.populate_ccy_combo()
|
||||
# Refresh edits with the new rate
|
||||
edit = window.fiat_send_e if window.fiat_send_e.is_last_edited else window.amount_e
|
||||
edit.textEdited.emit(edit.text())
|
||||
edit = window.fiat_receive_e if window.fiat_receive_e.is_last_edited else window.receive_amount_e
|
||||
edit.textEdited.emit(edit.text())
|
||||
# History tab needs updating if it used spot
|
||||
if self.history_used_spot:
|
||||
self.on_fx_history(window)
|
||||
|
||||
def on_ccy_combo_change(self):
|
||||
'''Called when the chosen currency changes'''
|
||||
ccy = str(self.ccy_combo.currentText())
|
||||
if ccy and ccy != self.ccy:
|
||||
self.set_currency(ccy)
|
||||
self.hist_checkbox_update()
|
||||
|
||||
def hist_checkbox_update(self):
|
||||
if self.hist_checkbox:
|
||||
self.hist_checkbox.setEnabled(self.ccy in self.exchange.history_ccys())
|
||||
self.hist_checkbox.setChecked(self.config_history())
|
||||
|
||||
def populate_ccy_combo(self):
|
||||
# There should be at most one instance of the settings dialog
|
||||
combo = self.ccy_combo
|
||||
# NOTE: bool(combo) is False if it is empty. Nuts.
|
||||
if combo is not None:
|
||||
combo.blockSignals(True)
|
||||
combo.clear()
|
||||
combo.addItems(sorted(self.exchange.quotes.keys()))
|
||||
combo.blockSignals(False)
|
||||
combo.setCurrentIndex(combo.findText(self.ccy))
|
||||
|
||||
@hook
|
||||
def on_new_window(self, window):
|
||||
# Additional send and receive edit boxes
|
||||
if not hasattr(window, 'fiat_send_e'):
|
||||
send_e = AmountEdit(self.get_currency)
|
||||
window.send_grid.addWidget(send_e, 4, 2, Qt.AlignLeft)
|
||||
window.amount_e.frozen.connect(
|
||||
lambda: send_e.setFrozen(window.amount_e.isReadOnly()))
|
||||
receive_e = AmountEdit(self.get_currency)
|
||||
window.receive_grid.addWidget(receive_e, 2, 2, Qt.AlignLeft)
|
||||
window.fiat_send_e = send_e
|
||||
window.fiat_receive_e = receive_e
|
||||
self.connect_fields(window, window.amount_e, send_e, window.fee_e)
|
||||
self.connect_fields(window, window.receive_amount_e, receive_e, None)
|
||||
else:
|
||||
window.fiat_send_e.show()
|
||||
window.fiat_receive_e.show()
|
||||
window.history_list.refresh_headers()
|
||||
window.update_status()
|
||||
window.connect(self, SIGNAL('new_fx_quotes'), lambda: self.on_fx_quotes(window))
|
||||
window.connect(self, SIGNAL('new_fx_history'), lambda: self.on_fx_history(window))
|
||||
window.connect(self, SIGNAL('close_fx_plugin'), lambda: self.restore_window(window))
|
||||
window.connect(self, SIGNAL('refresh_headers'), window.history_list.refresh_headers)
|
||||
|
||||
def settings_widget(self, window):
|
||||
return EnterButton(_('Settings'), partial(self.settings_dialog, window))
|
||||
|
||||
def settings_dialog(self, window):
|
||||
d = WindowModalDialog(window, _("Exchange Rate Settings"))
|
||||
layout = QGridLayout(d)
|
||||
layout.addWidget(QLabel(_('Exchange rate API: ')), 0, 0)
|
||||
layout.addWidget(QLabel(_('Currency: ')), 1, 0)
|
||||
layout.addWidget(QLabel(_('History Rates: ')), 2, 0)
|
||||
|
||||
# Currency list
|
||||
self.ccy_combo = QComboBox()
|
||||
self.ccy_combo.currentIndexChanged.connect(self.on_ccy_combo_change)
|
||||
self.populate_ccy_combo()
|
||||
|
||||
def on_change_ex(idx):
|
||||
exchange = str(combo_ex.currentText())
|
||||
if exchange != self.exchange.name():
|
||||
self.set_exchange(exchange)
|
||||
self.hist_checkbox_update()
|
||||
|
||||
def on_change_hist(checked):
|
||||
if checked:
|
||||
self.config.set_key('history_rates', 'checked')
|
||||
self.get_historical_rates()
|
||||
else:
|
||||
self.config.set_key('history_rates', 'unchecked')
|
||||
self.emit(SIGNAL('refresh_headers'))
|
||||
|
||||
def ok_clicked():
|
||||
self.timeout = 0
|
||||
self.ccy_combo = None
|
||||
d.accept()
|
||||
|
||||
combo_ex = QComboBox()
|
||||
combo_ex.addItems(sorted(self.exchanges.keys()))
|
||||
combo_ex.setCurrentIndex(combo_ex.findText(self.config_exchange()))
|
||||
combo_ex.currentIndexChanged.connect(on_change_ex)
|
||||
|
||||
self.hist_checkbox = QCheckBox()
|
||||
self.hist_checkbox.stateChanged.connect(on_change_hist)
|
||||
self.hist_checkbox_update()
|
||||
|
||||
ok_button = QPushButton(_("OK"))
|
||||
ok_button.clicked.connect(lambda: ok_clicked())
|
||||
|
||||
layout.addWidget(self.ccy_combo,1,1)
|
||||
layout.addWidget(combo_ex,0,1)
|
||||
layout.addWidget(self.hist_checkbox,2,1)
|
||||
layout.addWidget(ok_button,3,1)
|
||||
|
||||
return d.exec_()
|
||||
|
||||
|
||||
def config_history(self):
|
||||
return self.config.get('history_rates', 'unchecked') != 'unchecked'
|
||||
|
||||
def show_history(self):
|
||||
return self.config_history() and self.ccy in self.exchange.history_ccys()
|
||||
|
||||
@hook
|
||||
def history_tab_headers(self, headers):
|
||||
if self.show_history():
|
||||
headers.extend(['%s '%self.ccy + _('Amount'), '%s '%self.ccy + _('Balance')])
|
||||
|
||||
@hook
|
||||
def history_tab_update_begin(self):
|
||||
self.history_used_spot = False
|
||||
|
||||
@hook
|
||||
def history_tab_update(self, tx, entry):
|
||||
if not self.show_history():
|
||||
return
|
||||
tx_hash, height, conf, timestamp, value, balance = tx
|
||||
if conf <= 0:
|
||||
date = timestamp_to_datetime(time.time())
|
||||
else:
|
||||
date = timestamp_to_datetime(timestamp)
|
||||
for amount in [value, balance]:
|
||||
text = self.historical_value_str(amount, date)
|
||||
entry.append(text)
|
Loading…
Reference in New Issue