Browse Source

gui: show incoming lightning requests, add on-chain icon

dependabot/pip/contrib/deterministic-build/ecdsa-0.13.3
ThomasV 6 years ago
parent
commit
7bb4ea150f
  1. 51
      electrum/gui/qt/invoice_list.py
  2. 6
      electrum/gui/qt/main_window.py
  3. 56
      electrum/gui/qt/request_list.py
  4. 3
      electrum/gui/qt/util.py
  5. 3
      electrum/paymentrequest.py
  6. BIN
      icons/bitcoin.png

51
electrum/gui/qt/invoice_list.py

@ -30,7 +30,9 @@ from PyQt5.QtGui import QStandardItemModel, QStandardItem, QFont
from PyQt5.QtWidgets import QHeaderView, QMenu from PyQt5.QtWidgets import QHeaderView, QMenu
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import format_time from electrum.util import format_time, pr_tooltips, PR_UNPAID
from electrum.lnutil import lndecode
from electrum.bitcoin import COIN
from .util import (MyTreeView, read_QIcon, MONOSPACE_FONT, PR_UNPAID, from .util import (MyTreeView, read_QIcon, MONOSPACE_FONT, PR_UNPAID,
pr_tooltips, import_meta_gui, export_meta_gui, pr_icons) pr_tooltips, import_meta_gui, export_meta_gui, pr_icons)
@ -40,26 +42,23 @@ class InvoiceList(MyTreeView):
class Columns(IntEnum): class Columns(IntEnum):
DATE = 0 DATE = 0
REQUESTOR = 1 DESCRIPTION = 1
DESCRIPTION = 2 AMOUNT = 2
AMOUNT = 3 STATUS = 3
STATUS = 4
headers = { headers = {
Columns.DATE: _('Expires'), Columns.DATE: _('Expires'),
Columns.REQUESTOR: _('Requestor'),
Columns.DESCRIPTION: _('Description'), Columns.DESCRIPTION: _('Description'),
Columns.AMOUNT: _('Amount'), Columns.AMOUNT: _('Amount'),
Columns.STATUS: _('Status'), Columns.STATUS: _('Status'),
} }
filter_columns = [Columns.DATE, Columns.REQUESTOR, Columns.DESCRIPTION, Columns.AMOUNT] filter_columns = [Columns.DATE, Columns.DESCRIPTION, Columns.AMOUNT]
def __init__(self, parent): def __init__(self, parent):
super().__init__(parent, self.create_menu, super().__init__(parent, self.create_menu,
stretch_column=self.Columns.DESCRIPTION, stretch_column=self.Columns.DESCRIPTION,
editable_columns=[]) editable_columns=[])
self.setSortingEnabled(True) self.setSortingEnabled(True)
self.setColumnWidth(self.Columns.REQUESTOR, 200)
self.setModel(QStandardItemModel(self)) self.setModel(QStandardItemModel(self))
self.update() self.update()
@ -67,26 +66,50 @@ class InvoiceList(MyTreeView):
inv_list = self.parent.invoices.unpaid_invoices() inv_list = self.parent.invoices.unpaid_invoices()
self.model().clear() self.model().clear()
self.update_headers(self.__class__.headers) self.update_headers(self.__class__.headers)
self.header().setSectionResizeMode(self.Columns.REQUESTOR, QHeaderView.Interactive)
for idx, pr in enumerate(inv_list): for idx, pr in enumerate(inv_list):
key = pr.get_id() key = pr.get_id()
status = self.parent.invoices.get_status(key) status = self.parent.invoices.get_status(key)
if status is None: if status is None:
continue continue
requestor = pr.get_requestor() requestor = pr.get_requestor()
exp = pr.get_expiration_date() exp = pr.get_time()
date_str = format_time(exp) if exp else _('Never') date_str = format_time(exp) if exp else _('Never')
labels = [date_str, requestor, pr.memo, self.parent.format_amount(pr.get_amount(), whitespaces=True), pr_tooltips.get(status,'')] labels = [date_str, '[%s] '%requestor + pr.memo, self.parent.format_amount(pr.get_amount(), whitespaces=True), pr_tooltips.get(status,'')]
items = [QStandardItem(e) for e in labels] items = [QStandardItem(e) for e in labels]
self.set_editability(items) self.set_editability(items)
items[self.Columns.DATE].setIcon(read_QIcon('bitcoin.png'))
items[self.Columns.STATUS].setIcon(read_QIcon(pr_icons.get(status))) items[self.Columns.STATUS].setIcon(read_QIcon(pr_icons.get(status)))
items[self.Columns.DATE].setData(key, role=Qt.UserRole) items[self.Columns.DATE].setData(key, role=Qt.UserRole)
items[self.Columns.REQUESTOR].setFont(QFont(MONOSPACE_FONT))
items[self.Columns.AMOUNT].setFont(QFont(MONOSPACE_FONT))
self.model().insertRow(idx, items) self.model().insertRow(idx, items)
lnworker = self.parent.wallet.lnworker
for key, (preimage_hex, invoice, is_received, pay_timestamp) in lnworker.invoices.items():
if is_received:
continue
status = lnworker.get_invoice_status(key)
lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
amount_sat = lnaddr.amount*COIN if lnaddr.amount else None
amount_str = self.parent.format_amount(amount_sat) if amount_sat else ''
description = ''
for k,v in lnaddr.tags:
if k == 'd':
description = v
break
date_str = format_time(lnaddr.date)
labels = [date_str, description, amount_str, pr_tooltips.get(status,'')]
items = [QStandardItem(e) for e in labels]
#items[0].setData(REQUEST_TYPE_LN, ROLE_REQUEST_TYPE)
#items[0].setData(key, ROLE_RHASH_OR_ADDR)
items[0].setIcon(self.icon_cache.get(':icons/lightning.png'))
items[3].setIcon(self.icon_cache.get(pr_icons.get(status)))
self.model().insertRow(self.model().rowCount(), items)
self.selectionModel().select(self.model().index(0,0), QItemSelectionModel.SelectCurrent) self.selectionModel().select(self.model().index(0,0), QItemSelectionModel.SelectCurrent)
# sort requests by date
self.model().sort(0)
# hide list if empty
if self.parent.isVisible(): if self.parent.isVisible():
b = len(inv_list) > 0 b = self.model().rowCount() > 0
self.setVisible(b) self.setVisible(b)
self.parent.invoices_label.setVisible(b) self.parent.invoices_label.setVisible(b)
self.filter() self.filter()

6
electrum/gui/qt/main_window.py

@ -953,8 +953,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
grid.addWidget(self.expires_label, 2, 1) grid.addWidget(self.expires_label, 2, 1)
self.create_invoice_button = QPushButton(_('On-chain')) self.create_invoice_button = QPushButton(_('On-chain'))
self.create_invoice_button.setIcon(QIcon(":icons/bitcoin.png"))
self.create_invoice_button.clicked.connect(lambda: self.create_invoice(False)) self.create_invoice_button.clicked.connect(lambda: self.create_invoice(False))
self.create_lightning_invoice_button = QPushButton(_('Lightning')) self.create_lightning_invoice_button = QPushButton(_('Lightning'))
self.create_lightning_invoice_button.setIcon(QIcon(":icons/lightning.png"))
self.create_lightning_invoice_button.clicked.connect(lambda: self.create_invoice(True)) self.create_lightning_invoice_button.clicked.connect(lambda: self.create_invoice(True))
self.receive_buttons = buttons = QHBoxLayout() self.receive_buttons = buttons = QHBoxLayout()
buttons.addStretch(1) buttons.addStretch(1)
@ -974,7 +976,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
self.receive_qr.enterEvent = lambda x: self.app.setOverrideCursor(QCursor(Qt.PointingHandCursor)) self.receive_qr.enterEvent = lambda x: self.app.setOverrideCursor(QCursor(Qt.PointingHandCursor))
self.receive_qr.leaveEvent = lambda x: self.app.setOverrideCursor(QCursor(Qt.ArrowCursor)) self.receive_qr.leaveEvent = lambda x: self.app.setOverrideCursor(QCursor(Qt.ArrowCursor))
self.receive_requests_label = QLabel(_('Requests')) self.receive_requests_label = QLabel(_('Incoming invoices'))
from .request_list import RequestList from .request_list import RequestList
self.request_list = RequestList(self) self.request_list = RequestList(self)
@ -1395,7 +1397,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
self.fee_e.textChanged.connect(entry_changed) self.fee_e.textChanged.connect(entry_changed)
self.feerate_e.textChanged.connect(entry_changed) self.feerate_e.textChanged.connect(entry_changed)
self.invoices_label = QLabel(_('Invoices')) self.invoices_label = QLabel(_('Outgoing invoices'))
from .invoice_list import InvoiceList from .invoice_list import InvoiceList
self.invoice_list = InvoiceList(self) self.invoice_list = InvoiceList(self)

56
electrum/gui/qt/request_list.py

@ -51,14 +51,12 @@ class RequestList(MyTreeView):
class Columns(IntEnum): class Columns(IntEnum):
DATE = 0 DATE = 0
TYPE = 1 DESCRIPTION = 1
DESCRIPTION = 2 AMOUNT = 2
AMOUNT = 3 STATUS = 3
STATUS = 4
headers = { headers = {
Columns.DATE: _('Date'), Columns.DATE: _('Date'),
Columns.TYPE: _('Type'),
Columns.DESCRIPTION: _('Description'), Columns.DESCRIPTION: _('Description'),
Columns.AMOUNT: _('Amount'), Columns.AMOUNT: _('Amount'),
Columns.STATUS: _('Status'), Columns.STATUS: _('Status'),
@ -68,7 +66,7 @@ class RequestList(MyTreeView):
def __init__(self, parent): def __init__(self, parent):
super().__init__(parent, self.create_menu, super().__init__(parent, self.create_menu,
stretch_column=self.Columns.DESCRIPTION, stretch_column=self.Columns.DESCRIPTION,
editable_columns=[]) editable_columns=[self.Columns.AMOUNT])
self.setModel(QStandardItemModel(self)) self.setModel(QStandardItemModel(self))
self.setSortingEnabled(True) self.setSortingEnabled(True)
self.update() self.update()
@ -76,7 +74,7 @@ class RequestList(MyTreeView):
def select_key(self, key): def select_key(self, key):
for i in range(self.model().rowCount()): for i in range(self.model().rowCount()):
item = self.model().index(i, 0) item = self.model().index(i, self.Columns.DATE)
row_key = item.data(ROLE_RHASH_OR_ADDR) row_key = item.data(ROLE_RHASH_OR_ADDR)
if item.data(ROLE_REQUEST_TYPE) == REQUEST_TYPE_LN: if item.data(ROLE_REQUEST_TYPE) == REQUEST_TYPE_LN:
row_key = self.wallet.lnworker.invoices[row_key][1] row_key = self.wallet.lnworker.invoices[row_key][1]
@ -86,7 +84,7 @@ class RequestList(MyTreeView):
def item_changed(self, idx): def item_changed(self, idx):
# TODO use siblingAtColumn when min Qt version is >=5.11 # TODO use siblingAtColumn when min Qt version is >=5.11
item = self.model().itemFromIndex(idx.sibling(idx.row(), 0)) item = self.model().itemFromIndex(idx.sibling(idx.row(), self.Columns.DATE))
request_type = item.data(ROLE_REQUEST_TYPE) request_type = item.data(ROLE_REQUEST_TYPE)
key = item.data(ROLE_RHASH_OR_ADDR) key = item.data(ROLE_RHASH_OR_ADDR)
if request_type == REQUEST_TYPE_BITCOIN: if request_type == REQUEST_TYPE_BITCOIN:
@ -104,19 +102,8 @@ class RequestList(MyTreeView):
def update(self): def update(self):
self.wallet = self.parent.wallet self.wallet = self.parent.wallet
# hide receive tab if no receive requests available
if self.parent.isVisible():
b = len(self.wallet.receive_requests) > 0 or len(self.wallet.lnworker.invoices) > 0
self.setVisible(b)
self.parent.receive_requests_label.setVisible(b)
if not b:
self.parent.expires_label.hide()
self.parent.expires_combo.show()
domain = self.wallet.get_receiving_addresses() domain = self.wallet.get_receiving_addresses()
self.parent.update_receive_address_styling() self.parent.update_receive_address_styling()
self.model().clear() self.model().clear()
self.update_headers(self.__class__.headers) self.update_headers(self.__class__.headers)
for req in self.wallet.get_sorted_requests(self.config): for req in self.wallet.get_sorted_requests(self.config):
@ -132,17 +119,18 @@ class RequestList(MyTreeView):
signature = req.get('sig') signature = req.get('sig')
requestor = req.get('name', '') requestor = req.get('name', '')
amount_str = self.parent.format_amount(amount) if amount else "" amount_str = self.parent.format_amount(amount) if amount else ""
labels = [date, 'on-chain', message, amount_str, pr_tooltips.get(status,'')] labels = [date, message, amount_str, pr_tooltips.get(status,'')]
items = [QStandardItem(e) for e in labels] items = [QStandardItem(e) for e in labels]
self.set_editability(items) self.set_editability(items)
if signature is not None: if signature is not None:
items[self.Columns.TYPE].setIcon(read_QIcon("seal.png")) items[self.Columns.DATE].setIcon(read_QIcon("seal.png"))
items[self.Columns.TYPE].setToolTip(f'signed by {requestor}') items[self.Columns.DATE].setToolTip(f'signed by {requestor}')
if status is not PR_UNKNOWN: else:
items[self.Columns.DATE].setIcon(read_QIcon("bitcoin.png"))
items[self.Columns.STATUS].setIcon(read_QIcon(pr_icons.get(status))) items[self.Columns.STATUS].setIcon(read_QIcon(pr_icons.get(status)))
self.model().insertRow(self.model().rowCount(), items) self.model().insertRow(self.model().rowCount(), items)
items[0].setData(REQUEST_TYPE_BITCOIN, ROLE_REQUEST_TYPE) items[self.Columns.DATE].setData(REQUEST_TYPE_BITCOIN, ROLE_REQUEST_TYPE)
items[0].setData(address, ROLE_RHASH_OR_ADDR) items[self.Columns.DATE].setData(address, ROLE_RHASH_OR_ADDR)
self.filter() self.filter()
# lightning # lightning
lnworker = self.wallet.lnworker lnworker = self.wallet.lnworker
@ -159,16 +147,20 @@ class RequestList(MyTreeView):
description = v description = v
break break
date = format_time(lnaddr.date) date = format_time(lnaddr.date)
labels = [date, 'lightning', description, amount_str, pr_tooltips.get(status,'')] labels = [date, description, amount_str, pr_tooltips.get(status,'')]
items = [QStandardItem(e) for e in labels] items = [QStandardItem(e) for e in labels]
items[1].setIcon(self.icon_cache.get(":icons/lightning.png")) items[self.Columns.DATE].setIcon(self.icon_cache.get(":icons/lightning.png"))
items[0].setData(REQUEST_TYPE_LN, ROLE_REQUEST_TYPE) items[self.Columns.DATE].setData(REQUEST_TYPE_LN, ROLE_REQUEST_TYPE)
items[0].setData(key, ROLE_RHASH_OR_ADDR) items[self.Columns.DATE].setData(key, ROLE_RHASH_OR_ADDR)
if status is not PR_UNKNOWN: items[self.Columns.STATUS].setIcon(self.icon_cache.get(pr_icons.get(status)))
items[4].setIcon(self.icon_cache.get(pr_icons.get(status)))
self.model().insertRow(self.model().rowCount(), items) self.model().insertRow(self.model().rowCount(), items)
# sort requests by date # sort requests by date
self.model().sort(0) self.model().sort(self.Columns.DATE)
# hide list if empty
if self.parent.isVisible():
b = self.model().rowCount() > 0
self.setVisible(b)
self.parent.receive_requests_label.setVisible(b)
def create_menu(self, position): def create_menu(self, position):
idx = self.indexAt(position) idx = self.indexAt(position)

3
electrum/gui/qt/util.py

@ -24,7 +24,7 @@ from PyQt5.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout,
from electrum.i18n import _, languages from electrum.i18n import _, languages
from electrum.util import FileImportFailed, FileExportFailed, make_aiohttp_session, PrintError, resource_path from electrum.util import FileImportFailed, FileExportFailed, make_aiohttp_session, PrintError, resource_path
from electrum.util import PR_UNPAID, PR_PAID, PR_EXPIRED, PR_INFLIGHT from electrum.util import PR_UNPAID, PR_PAID, PR_EXPIRED, PR_INFLIGHT, PR_UNKNOWN
if TYPE_CHECKING: if TYPE_CHECKING:
from .main_window import ElectrumWindow from .main_window import ElectrumWindow
@ -41,6 +41,7 @@ else:
dialogs = [] dialogs = []
pr_icons = { pr_icons = {
PR_UNKNOWN:"unpaid.png",
PR_UNPAID:"unpaid.png", PR_UNPAID:"unpaid.png",
PR_PAID:"confirmed.png", PR_PAID:"confirmed.png",
PR_EXPIRED:"expired.png", PR_EXPIRED:"expired.png",

3
electrum/paymentrequest.py

@ -246,6 +246,9 @@ class PaymentRequest:
return None return None
return self.details.expires and self.details.expires < int(time.time()) return self.details.expires and self.details.expires < int(time.time())
def get_time(self):
return self.details.time
def get_expiration_date(self): def get_expiration_date(self):
return self.details.expires return self.details.expires

BIN
icons/bitcoin.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Loading…
Cancel
Save