Updates for exchange_rate plugin

Have AmountEdit return an int if is_int, otherwise a Decimal
Set the tray tooltip unconditionally.
More verbose logging for exchage_rate plugin.
Get rate_float from Coindesk as rate can have commas.
Plugin tracks windows itself, and doesn't create its own
members in the window objects.
Clean up the edit handling.
This commit is contained in:
Neil Booth 2015-09-06 13:42:40 +09:00
parent 95d3f6da1f
commit 34ce6d1821
4 changed files with 77 additions and 90 deletions

View File

@ -63,10 +63,9 @@ class AmountEdit(MyLineEdit):
def get_amount(self): def get_amount(self):
try: try:
x = int(str(self.text())) return (int if self.is_int else Decimal)(str(self.text()))
except: except:
return None return None
return x
class BTCAmountEdit(AmountEdit): class BTCAmountEdit(AmountEdit):

View File

@ -15,7 +15,6 @@ from electrum import util
import seed_dialog import seed_dialog
from network_dialog import NetworkDialog from network_dialog import NetworkDialog
from util import * from util import *
from amountedit import AmountEdit
from electrum.plugins import always_hook, run_hook from electrum.plugins import always_hook, run_hook
from electrum.mnemonic import prepare_seed from electrum.mnemonic import prepare_seed

View File

@ -45,7 +45,7 @@ from electrum import SimpleConfig, Wallet, WalletStorage
from electrum import Imported_Wallet from electrum import Imported_Wallet
from electrum import paymentrequest from electrum import paymentrequest
from amountedit import AmountEdit, BTCAmountEdit, MyLineEdit, BTCkBEdit from amountedit import BTCAmountEdit, MyLineEdit, BTCkBEdit
from network_dialog import NetworkDialog from network_dialog import NetworkDialog
from qrcodewidget import QRCodeWidget, QRDialog from qrcodewidget import QRCodeWidget, QRDialog
from qrtextedit import ScanQRTextEdit, ShowQRTextEdit from qrtextedit import ScanQRTextEdit, ShowQRTextEdit
@ -523,14 +523,12 @@ class ElectrumWindow(QMainWindow):
quote = r.get(0) quote = r.get(0)
if quote: if quote:
text += "%s"%quote text += "%s"%quote
if self.tray:
self.tray.setToolTip("%s (%s)" % (text, self.wallet.basename()))
icon = QIcon(":icons/status_connected.png") icon = QIcon(":icons/status_connected.png")
else: else:
text = _("Not connected") text = _("Not connected")
icon = QIcon(":icons/status_disconnected.png") icon = QIcon(":icons/status_disconnected.png")
self.tray.setToolTip("%s (%s)" % (text, self.wallet.basename()))
self.balance_label.setText(text) self.balance_label.setText(text)
self.status_button.setIcon( icon ) self.status_button.setIcon( icon )

View File

@ -9,6 +9,7 @@ from threading import Thread
import time import time
import traceback import traceback
from decimal import Decimal from decimal import Decimal
from functools import partial
from electrum.bitcoin import COIN from electrum.bitcoin import COIN
from electrum.plugins import BasePlugin, hook from electrum.plugins import BasePlugin, hook
@ -36,7 +37,9 @@ class ExchangeBase:
return self.__class__.__name__ return self.__class__.__name__
def update(self, ccy): def update(self, ccy):
self.print_error("getting fx quotes for", ccy)
self.quotes = self.get_rates(ccy) self.quotes = self.get_rates(ccy)
self.print_error("received fx quotes")
self.sig.emit(SIGNAL('fx_quotes')) self.sig.emit(SIGNAL('fx_quotes'))
return self.quotes return self.quotes
@ -46,6 +49,7 @@ class ExchangeBase:
def set_history(self, ccy, history): def set_history(self, ccy, history):
'''History is a map of "%Y-%m-%d" strings to values''' '''History is a map of "%Y-%m-%d" strings to values'''
self.history[ccy] = history self.history[ccy] = history
self.print_error("received fx history for", ccy)
self.sig.emit(SIGNAL("fx_history")) self.sig.emit(SIGNAL("fx_history"))
def get_historical_rates(self, ccy): def get_historical_rates(self, ccy):
@ -131,7 +135,7 @@ class CoinDesk(ExchangeBase):
'/v1/bpi/currentprice/%s.json' % ccy) '/v1/bpi/currentprice/%s.json' % ccy)
ccys = [d['currency'] for d in dicts] ccys = [d['currency'] for d in dicts]
result = dict.fromkeys(ccys) result = dict.fromkeys(ccys)
result[ccy] = Decimal(json['bpi'][ccy]['rate']) result[ccy] = Decimal(json['bpi'][ccy]['rate_float'])
return result return result
def history_starts(self): def history_starts(self):
@ -191,6 +195,7 @@ class Plugin(BasePlugin, ThreadJob):
self.history_used_spot = False self.history_used_spot = False
self.ccy_combo = None self.ccy_combo = None
self.hist_checkbox = None self.hist_checkbox = None
self.windows = dict()
is_exchange = lambda obj: (inspect.isclass(obj) is_exchange = lambda obj: (inspect.isclass(obj)
and issubclass(obj, ExchangeBase) and issubclass(obj, ExchangeBase)
@ -206,10 +211,9 @@ class Plugin(BasePlugin, ThreadJob):
def run(self): def run(self):
# This runs from the network thread which catches exceptions # This runs from the network thread which catches exceptions
if self.parent.windows and self.timeout <= time.time(): if self.windows and self.timeout <= time.time():
self.timeout = time.time() + 150 self.timeout = time.time() + 150
rates = self.exchange.update(self.ccy) self.exchange.update(self.ccy)
self.refresh_fields()
def config_ccy(self): def config_ccy(self):
'''Use when dynamic fetching is needed''' '''Use when dynamic fetching is needed'''
@ -236,24 +240,81 @@ class Plugin(BasePlugin, ThreadJob):
def update_status_bars(self): def update_status_bars(self):
'''Update status bar fiat balance in all windows''' '''Update status bar fiat balance in all windows'''
for window in self.parent.windows: for window in self.windows:
window.update_status() window.update_status()
def on_new_window(self, window): def on_new_window(self, window):
window.fx_fields = {} # Additional send and receive edit boxes
self.add_send_edit(window) send_e = AmountEdit(self.config_ccy)
self.add_receive_edit(window) window.send_grid.addWidget(send_e, 4, 3, Qt.AlignHCenter)
window.amount_e.frozen.connect(
lambda: send_e.setFrozen(window.amount_e.isReadOnly()))
receive_e = AmountEdit(self.config_ccy)
window.receive_grid.addWidget(receive_e, 2, 3, Qt.AlignHCenter)
self.windows[window] = {'edits': (send_e, receive_e),
'last_edited': {}}
self.connect_fields(window, window.amount_e, send_e, window.fee_e)
self.connect_fields(window, window.receive_amount_e, receive_e, None)
window.update_status()
def connect_fields(self, window, btc_e, fiat_e, fee_e):
last_edited = self.windows[window]['last_edited']
def edit_changed(edit):
edit.setStyleSheet(BLACK_FG)
last_edited[(fiat_e, btc_e)] = edit
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.setAmount(int(amount / Decimal(rate) * COIN))
if fee_e: window.update_fee()
btc_e.setStyleSheet(BLUE_FG)
else:
fiat_e.setText("%.2f" % (amount * Decimal(rate) / COIN))
fiat_e.setStyleSheet(BLUE_FG)
fiat_e.textEdited.connect(partial(edit_changed, fiat_e))
btc_e.textEdited.connect(partial(edit_changed, btc_e))
last_edited[(fiat_e, btc_e)] = btc_e
@hook
def do_clear(self, window):
self.windows[window]['edits'][0].setText('')
def on_close_window(self, window):
self.windows.pop(window)
def close(self):
# Get rid of hooks before updating status bars.
BasePlugin.close(self)
self.update_status_bars()
for window, data in self.windows.items():
for edit in data['edits']:
edit.hide()
window.update_status() window.update_status()
def on_fx_history(self): def on_fx_history(self):
'''Called when historical fx quotes are updated''' '''Called when historical fx quotes are updated'''
for window in self.parent.windows: for window in self.windows:
window.update_history_tab() window.update_history_tab()
def on_fx_quotes(self): def on_fx_quotes(self):
'''Called when fresh spot fx quotes come in''' '''Called when fresh spot fx quotes come in'''
self.update_status_bars() self.update_status_bars()
self.populate_ccy_combo() self.populate_ccy_combo()
# Refresh edits with the new rate
for window, data in self.windows.items():
for edit in data['last_edited'].values():
edit.textEdited.emit(edit.text())
# History tab needs updating if it used spot # History tab needs updating if it used spot
if self.history_used_spot: if self.history_used_spot:
self.on_fx_history() self.on_fx_history()
@ -284,14 +345,6 @@ class Plugin(BasePlugin, ThreadJob):
combo.blockSignals(False) combo.blockSignals(False)
combo.setCurrentIndex(combo.findText(self.ccy)) combo.setCurrentIndex(combo.findText(self.ccy))
def close(self):
BasePlugin.close(self)
for window in self.parent.windows:
window.send_fiat_e.hide()
window.receive_fiat_e.hide()
window.update_history_tab()
window.update_status()
def exchange_rate(self): def exchange_rate(self):
'''Returns None, or the exchange rate as a Decimal''' '''Returns None, or the exchange rate as a Decimal'''
rate = self.exchange.quotes.get(self.ccy) rate = self.exchange.quotes.get(self.ccy)
@ -405,9 +458,9 @@ class Plugin(BasePlugin, ThreadJob):
self.config.set_key('history_rates', 'unchecked') self.config.set_key('history_rates', 'unchecked')
def ok_clicked(): def ok_clicked():
if self.exchange in ["CoinDesk", "itBit"]:
self.timeout = 0 self.timeout = 0
d.accept(); self.ccy_combo = None
d.accept()
combo_ex = QComboBox() combo_ex = QComboBox()
combo_ex.addItems(sorted(self.exchanges.keys())) combo_ex.addItems(sorted(self.exchanges.keys()))
@ -426,66 +479,4 @@ class Plugin(BasePlugin, ThreadJob):
layout.addWidget(self.hist_checkbox,2,1) layout.addWidget(self.hist_checkbox,2,1)
layout.addWidget(ok_button,3,1) layout.addWidget(ok_button,3,1)
result = d.exec_() return d.exec_()
self.ccy_combo = None
return result
def refresh_fields(self):
'''Update the display at the new rate'''
for window in self.parent.windows:
for field in window.fx_fields.values():
field.textEdited.emit(field.text())
def add_send_edit(self, window):
window.send_fiat_e = AmountEdit(self.config_ccy)
self.connect_fields(window, True)
window.send_grid.addWidget(window.send_fiat_e, 4, 3, Qt.AlignHCenter)
window.amount_e.frozen.connect(lambda: window.send_fiat_e.setFrozen(window.amount_e.isReadOnly()))
def add_receive_edit(self, window):
window.receive_fiat_e = AmountEdit(self.config_ccy)
self.connect_fields(window, False)
window.receive_grid.addWidget(window.receive_fiat_e, 2, 3, Qt.AlignHCenter)
def connect_fields(self, window, send):
if send:
btc_e, fiat_e, fee_e = (window.amount_e, window.send_fiat_e,
window.fee_e)
else:
btc_e, fiat_e, fee_e = (window.receive_amount_e,
window.receive_fiat_e, None)
def fiat_changed():
fiat_e.setStyleSheet(BLACK_FG)
window.fx_fields[(fiat_e, btc_e)] = fiat_e
try:
fiat_amount = Decimal(str(fiat_e.text()))
except:
btc_e.setText("")
if fee_e: fee_e.setText("")
return
exchange_rate = self.exchange_rate()
if exchange_rate is not None:
btc_amount = fiat_amount/exchange_rate
btc_e.setAmount(int(btc_amount*Decimal(COIN)))
btc_e.setStyleSheet(BLUE_FG)
if fee_e: window.update_fee()
fiat_e.textEdited.connect(fiat_changed)
def btc_changed():
btc_e.setStyleSheet(BLACK_FG)
window.fx_fields[(fiat_e, btc_e)] = btc_e
btc_amount = btc_e.get_amount()
rate = self.exchange_rate()
if rate is None or btc_amount is None:
fiat_e.setText("")
else:
fiat_amount = rate * Decimal(btc_amount) / Decimal(COIN)
pos = fiat_e.cursorPosition()
fiat_e.setText("%.2f"%fiat_amount)
fiat_e.setCursorPosition(pos)
fiat_e.setStyleSheet(BLUE_FG)
btc_e.textEdited.connect(btc_changed)
window.fx_fields[(fiat_e, btc_e)] = btc_e
@hook
def do_clear(self, window):
window.send_fiat_e.setText('')