Qt: show single balance in statusbar
- details in popup dialop - piechart in status bar
This commit is contained in:
212
electrum/gui/qt/balance_dialog.py
Normal file
212
electrum/gui/qt/balance_dialog.py
Normal file
@@ -0,0 +1,212 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Electrum - lightweight Bitcoin client
|
||||
# Copyright (C) 2013 ecdsa@github
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QPixmap
|
||||
from PyQt5.QtWidgets import (QVBoxLayout, QCheckBox, QHBoxLayout, QLineEdit,
|
||||
QLabel, QCompleter, QDialog, QStyledItemDelegate,
|
||||
QScrollArea, QWidget, QPushButton, QGridLayout, QToolButton)
|
||||
|
||||
from PyQt5.QtCore import QRect, QEventLoop, Qt, pyqtSignal
|
||||
from PyQt5.QtGui import QPalette, QPen, QPainter, QPixmap
|
||||
|
||||
|
||||
from electrum.i18n import _
|
||||
|
||||
from .util import Buttons, CloseButton, WindowModalDialog, ColorScheme
|
||||
|
||||
|
||||
# Todo:
|
||||
# show lightning funds that are not usable
|
||||
# pie chart mouse interactive, to prepare a swap
|
||||
|
||||
COLOR_CONFIRMED = Qt.green
|
||||
COLOR_UNCONFIRMED = Qt.red
|
||||
COLOR_UNMATURED = Qt.magenta
|
||||
COLOR_FROZEN = ColorScheme.BLUE.as_color(True)
|
||||
COLOR_LIGHTNING = Qt.yellow
|
||||
|
||||
class PieChartObject:
|
||||
|
||||
def paintEvent(self, event):
|
||||
bgcolor = self.palette().color(QPalette.Background)
|
||||
pen = QPen(Qt.gray, 1, Qt.SolidLine)
|
||||
qp = QPainter()
|
||||
qp.begin(self)
|
||||
qp.setPen(pen)
|
||||
qp.setRenderHint(QPainter.Antialiasing)
|
||||
qp.setBrush(Qt.gray)
|
||||
total = sum([x[2] for x in self._list])
|
||||
if total == 0:
|
||||
return
|
||||
alpha = 0
|
||||
s = 0
|
||||
for name, color, amount in self._list:
|
||||
delta = int(16 * 360 * amount/total)
|
||||
qp.setBrush(color)
|
||||
qp.drawPie(self.R, alpha, delta)
|
||||
alpha += delta
|
||||
qp.end()
|
||||
|
||||
class PieChartWidget(QWidget, PieChartObject):
|
||||
|
||||
def __init__(self, size, l):
|
||||
QWidget.__init__(self)
|
||||
self.size = size
|
||||
self.R = QRect(0, 0, self.size, self.size)
|
||||
self.setGeometry(self.R)
|
||||
self.setMinimumWidth(self.size)
|
||||
self.setMaximumWidth(self.size)
|
||||
self.setMinimumHeight(self.size)
|
||||
self.setMaximumHeight(self.size)
|
||||
self._list = l # list[ (name, color, amount)]
|
||||
self.update()
|
||||
|
||||
def update_list(self, l):
|
||||
self._list = l
|
||||
self.update()
|
||||
|
||||
|
||||
class BalanceToolButton(QToolButton, PieChartObject):
|
||||
|
||||
def __init__(self):
|
||||
QToolButton.__init__(self)
|
||||
self.size = 18
|
||||
self._list = []
|
||||
self.R = QRect(6, 3, self.size, self.size)
|
||||
|
||||
def update_list(self, l):
|
||||
self._list = l
|
||||
self.update()
|
||||
|
||||
def setText(self, text):
|
||||
# this is a hack
|
||||
QToolButton.setText(self, ' ' + text)
|
||||
|
||||
def paintEvent(self, event):
|
||||
QToolButton.paintEvent(self, event)
|
||||
PieChartObject.paintEvent(self, event)
|
||||
|
||||
|
||||
class LegendWidget(QWidget):
|
||||
size = 20
|
||||
|
||||
def __init__(self, color):
|
||||
QWidget.__init__(self)
|
||||
self.color = color
|
||||
self.R = QRect(0, 0, self.size, int(self.size*0.75))
|
||||
self.setGeometry(self.R)
|
||||
self.setMinimumWidth(self.size)
|
||||
self.setMaximumWidth(self.size)
|
||||
self.setMinimumHeight(self.size)
|
||||
self.setMaximumHeight(self.size)
|
||||
|
||||
def paintEvent(self, event):
|
||||
bgcolor = self.palette().color(QPalette.Background)
|
||||
pen = QPen(Qt.gray, 1, Qt.SolidLine)
|
||||
qp = QPainter()
|
||||
qp.begin(self)
|
||||
qp.setPen(pen)
|
||||
qp.setRenderHint(QPainter.Antialiasing)
|
||||
qp.setBrush(self.color)
|
||||
qp.drawRect(self.R)
|
||||
qp.end()
|
||||
|
||||
|
||||
class BalanceDialog(WindowModalDialog):
|
||||
|
||||
def __init__(self, parent, wallet):
|
||||
|
||||
WindowModalDialog.__init__(self, parent, _("Wallet Balance"))
|
||||
self.wallet = wallet
|
||||
self.config = parent.config
|
||||
self.fx = parent.fx
|
||||
|
||||
confirmed, unconfirmed, unmatured, frozen, lightning = self.wallet.get_balances_for_piechart()
|
||||
|
||||
frozen_str = self.config.format_amount_and_units(frozen)
|
||||
confirmed_str = self.config.format_amount_and_units(confirmed)
|
||||
unconfirmed_str = self.config.format_amount_and_units(unconfirmed)
|
||||
unmatured_str = self.config.format_amount_and_units(unmatured)
|
||||
lightning_str = self.config.format_amount_and_units(lightning)
|
||||
|
||||
frozen_fiat_str = self.fx.format_amount_and_units(frozen) if self.fx else ''
|
||||
confirmed_fiat_str = self.fx.format_amount_and_units(confirmed) if self.fx else ''
|
||||
unconfirmed_fiat_str = self.fx.format_amount_and_units(unconfirmed) if self.fx else ''
|
||||
unmatured_fiat_str = self.fx.format_amount_and_units(unmatured) if self.fx else ''
|
||||
lightning_fiat_str = self.fx.format_amount_and_units(lightning) if self.fx else ''
|
||||
|
||||
piechart = PieChartWidget(120, [
|
||||
(_('Frozen'), COLOR_FROZEN, frozen),
|
||||
(_('Unmatured'), COLOR_UNMATURED, unmatured),
|
||||
(_('Unconfirmed'), COLOR_UNCONFIRMED, unconfirmed),
|
||||
(_('Confirmed'), COLOR_CONFIRMED, confirmed),
|
||||
(_('Lightning'), COLOR_LIGHTNING, lightning),
|
||||
])
|
||||
|
||||
vbox = QVBoxLayout()
|
||||
vbox.addWidget(piechart)
|
||||
grid = QGridLayout()
|
||||
#grid.addWidget(QLabel(_("Onchain") + ':'), 0, 1)
|
||||
#grid.addWidget(QLabel(onchain_str), 0, 2, alignment=Qt.AlignRight)
|
||||
#grid.addWidget(QLabel(onchain_fiat_str), 0, 3, alignment=Qt.AlignRight)
|
||||
|
||||
if frozen:
|
||||
grid.addWidget(LegendWidget(COLOR_FROZEN), 0, 0)
|
||||
grid.addWidget(QLabel(_("Frozen") + ':'), 0, 1)
|
||||
grid.addWidget(QLabel(frozen_str), 0, 2, alignment=Qt.AlignRight)
|
||||
grid.addWidget(QLabel(frozen_fiat_str), 0, 3, alignment=Qt.AlignRight)
|
||||
if unconfirmed:
|
||||
grid.addWidget(LegendWidget(COLOR_UNCONFIRMED), 2, 0)
|
||||
grid.addWidget(QLabel(_("Unconfirmed") + ':'), 2, 1)
|
||||
grid.addWidget(QLabel(unconfirmed_str), 2, 2, alignment=Qt.AlignRight)
|
||||
grid.addWidget(QLabel(unconfirmed_fiat_str), 2, 3, alignment=Qt.AlignRight)
|
||||
if unmatured:
|
||||
grid.addWidget(LegendWidget(COLOR_UNMATURED), 3, 0)
|
||||
grid.addWidget(QLabel(_("Unmatured") + ':'), 3, 1)
|
||||
grid.addWidget(QLabel(unmatured_str), 3, 2, alignment=Qt.AlignRight)
|
||||
grid.addWidget(QLabel(unmatured_fiat_str), 3, 3, alignment=Qt.AlignRight)
|
||||
if confirmed:
|
||||
grid.addWidget(LegendWidget(COLOR_CONFIRMED), 1, 0)
|
||||
grid.addWidget(QLabel(_("Confirmed") + ':'), 1, 1)
|
||||
grid.addWidget(QLabel(confirmed_str), 1, 2, alignment=Qt.AlignRight)
|
||||
grid.addWidget(QLabel(confirmed_fiat_str), 1, 3, alignment=Qt.AlignRight)
|
||||
if lightning:
|
||||
grid.addWidget(LegendWidget(COLOR_LIGHTNING), 4, 0)
|
||||
grid.addWidget(QLabel(_("Lightning") + ':'), 4, 1)
|
||||
grid.addWidget(QLabel(lightning_str), 4, 2, alignment=Qt.AlignRight)
|
||||
grid.addWidget(QLabel(lightning_fiat_str), 4, 3, alignment=Qt.AlignRight)
|
||||
|
||||
vbox.addLayout(grid)
|
||||
vbox.addStretch(1)
|
||||
btn_close = CloseButton(self)
|
||||
btns = Buttons(btn_close)
|
||||
vbox.addLayout(btns)
|
||||
self.setLayout(vbox)
|
||||
|
||||
def run(self):
|
||||
self.exec_()
|
||||
@@ -106,6 +106,7 @@ from .transaction_dialog import PreviewTxDialog
|
||||
from .rbf_dialog import BumpFeeDialog, DSCancelDialog
|
||||
from .qrreader import scan_qrcode
|
||||
from .swap_dialog import SwapDialog
|
||||
from .balance_dialog import BalanceToolButton, COLOR_FROZEN, COLOR_UNMATURED, COLOR_UNCONFIRMED, COLOR_CONFIRMED, COLOR_LIGHTNING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import ElectrumGui
|
||||
@@ -999,18 +1000,19 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
icon = read_QIcon("status_lagging%s.png"%fork_str)
|
||||
else:
|
||||
network_text = _("Connected")
|
||||
c, u, x = self.wallet.get_balance()
|
||||
balance_text = _("Balance") + ": %s "%(self.format_amount_and_units(c))
|
||||
if u:
|
||||
balance_text += " [%s unconfirmed]"%(self.format_amount(u, is_diff=True).strip())
|
||||
if x:
|
||||
balance_text += " [%s unmatured]"%(self.format_amount(x, is_diff=True).strip())
|
||||
if self.wallet.has_lightning():
|
||||
l = self.wallet.lnworker.get_balance()
|
||||
balance_text += u' \U000026a1 %s'%(self.format_amount_and_units(l).strip())
|
||||
confirmed, unconfirmed, unmatured, frozen, lightning = self.wallet.get_balances_for_piechart()
|
||||
self.balance_label.update_list([
|
||||
(_('Frozen'), COLOR_FROZEN, frozen),
|
||||
(_('Unmatured'), COLOR_UNMATURED, unmatured),
|
||||
(_('Unconfirmed'), COLOR_UNCONFIRMED, unconfirmed),
|
||||
(_('Confirmed'), COLOR_CONFIRMED, confirmed),
|
||||
(_('Lightning'), COLOR_LIGHTNING, lightning),
|
||||
])
|
||||
balance = confirmed + unconfirmed + unmatured + frozen + lightning
|
||||
balance_text = _("Balance") + ": %s "%(self.format_amount_and_units(balance))
|
||||
# append fiat balance and price
|
||||
if self.fx.is_enabled():
|
||||
balance_text += self.fx.get_fiat_status_text(c + u + x,
|
||||
balance_text += self.fx.get_fiat_status_text(balance,
|
||||
self.base_unit(), self.get_decimal_point()) or ''
|
||||
if not self.network.proxy:
|
||||
icon = read_QIcon("status_connected%s.png"%fork_str)
|
||||
@@ -2410,16 +2412,23 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
|
||||
console.updateNamespace(methods)
|
||||
|
||||
def create_status_bar(self):
|
||||
def show_balance_dialog(self):
|
||||
from .balance_dialog import BalanceDialog
|
||||
d = BalanceDialog(self, self.wallet)
|
||||
d.run()
|
||||
|
||||
def create_status_bar(self):
|
||||
sb = QStatusBar()
|
||||
sb.setFixedHeight(35)
|
||||
|
||||
self.balance_label = QLabel("Loading wallet...")
|
||||
self.balance_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
|
||||
self.balance_label.setStyleSheet("""QLabel { padding: 0 }""")
|
||||
self.balance_label = BalanceToolButton()
|
||||
self.balance_label.setText("Loading wallet...")
|
||||
self.balance_label.setAutoRaise(True)
|
||||
self.balance_label.clicked.connect(self.show_balance_dialog)
|
||||
sb.addWidget(self.balance_label)
|
||||
|
||||
# remove border of all items in status bar
|
||||
self.setStyleSheet("QStatusBar::item { border: 0px;} ")
|
||||
|
||||
self.search_box = QLineEdit()
|
||||
self.search_box.textChanged.connect(self.do_search)
|
||||
self.search_box.hide()
|
||||
|
||||
@@ -299,7 +299,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
self.invoices = db.get_dict('invoices') # type: Dict[str, Invoice]
|
||||
self._reserved_addresses = set(db.get('reserved_addresses', []))
|
||||
|
||||
self._freeze_lock = threading.Lock() # for mutating/iterating frozen_{addresses,coins}
|
||||
self._freeze_lock = threading.RLock() # for mutating/iterating frozen_{addresses,coins}
|
||||
|
||||
self._prepare_onchain_invoice_paid_detection()
|
||||
self.calc_unused_change_addresses()
|
||||
@@ -723,6 +723,24 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
)
|
||||
return c1-c2, u1-u2, x1-x2
|
||||
|
||||
def get_balances_for_piechart(self):
|
||||
# return only positive values
|
||||
# todo: add lightning frozen
|
||||
c, u, x = self.get_balance()
|
||||
fc, fu, fx = self.get_frozen_balance()
|
||||
lightning = self.lnworker.get_balance() if self.has_lightning() else 0
|
||||
# subtract frozen funds
|
||||
cc = c - fc
|
||||
uu = u - fu
|
||||
xx = x - fx
|
||||
frozen = fc + fu + fx
|
||||
# subtract unconfirmed if negative.
|
||||
# (this does not make sense if positive and negative tx cancel eachother out)
|
||||
if uu < 0:
|
||||
cc = cc + uu
|
||||
uu = 0
|
||||
return cc, uu, xx, frozen, lightning
|
||||
|
||||
def balance_at_timestamp(self, domain, target_timestamp):
|
||||
# we assume that get_history returns items ordered by block height
|
||||
# we also assume that block timestamps are monotonic (which is false...!)
|
||||
|
||||
Reference in New Issue
Block a user