URI: 
       trequest_list.py - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
       trequest_list.py (9422B)
       ---
            1 #!/usr/bin/env python
            2 #
            3 # Electrum - lightweight Bitcoin client
            4 # Copyright (C) 2015 Thomas Voegtlin
            5 #
            6 # Permission is hereby granted, free of charge, to any person
            7 # obtaining a copy of this software and associated documentation files
            8 # (the "Software"), to deal in the Software without restriction,
            9 # including without limitation the rights to use, copy, modify, merge,
           10 # publish, distribute, sublicense, and/or sell copies of the Software,
           11 # and to permit persons to whom the Software is furnished to do so,
           12 # subject to the following conditions:
           13 #
           14 # The above copyright notice and this permission notice shall be
           15 # included in all copies or substantial portions of the Software.
           16 #
           17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
           18 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
           19 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
           20 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
           21 # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
           22 # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
           23 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
           24 # SOFTWARE.
           25 
           26 from enum import IntEnum
           27 from typing import Optional, TYPE_CHECKING
           28 
           29 from PyQt5.QtGui import QStandardItemModel, QStandardItem
           30 from PyQt5.QtWidgets import QMenu, QAbstractItemView
           31 from PyQt5.QtCore import Qt, QItemSelectionModel, QModelIndex
           32 
           33 from electrum.i18n import _
           34 from electrum.util import format_time
           35 from electrum.invoices import PR_TYPE_ONCHAIN, PR_TYPE_LN, LNInvoice, OnchainInvoice
           36 from electrum.plugin import run_hook
           37 from electrum.invoices import Invoice
           38 
           39 from .util import MyTreeView, pr_icons, read_QIcon, webopen, MySortModel
           40 
           41 if TYPE_CHECKING:
           42     from .main_window import ElectrumWindow
           43 
           44 
           45 ROLE_REQUEST_TYPE = Qt.UserRole
           46 ROLE_KEY = Qt.UserRole + 1
           47 ROLE_SORT_ORDER = Qt.UserRole + 2
           48 
           49 
           50 class RequestList(MyTreeView):
           51 
           52     class Columns(IntEnum):
           53         DATE = 0
           54         DESCRIPTION = 1
           55         AMOUNT = 2
           56         STATUS = 3
           57 
           58     headers = {
           59         Columns.DATE: _('Date'),
           60         Columns.DESCRIPTION: _('Description'),
           61         Columns.AMOUNT: _('Amount'),
           62         Columns.STATUS: _('Status'),
           63     }
           64     filter_columns = [Columns.DATE, Columns.DESCRIPTION, Columns.AMOUNT]
           65 
           66     def __init__(self, parent: 'ElectrumWindow'):
           67         super().__init__(parent, self.create_menu,
           68                          stretch_column=self.Columns.DESCRIPTION,
           69                          editable_columns=[])
           70         self.wallet = self.parent.wallet
           71         self.std_model = QStandardItemModel(self)
           72         self.proxy = MySortModel(self, sort_role=ROLE_SORT_ORDER)
           73         self.proxy.setSourceModel(self.std_model)
           74         self.setModel(self.proxy)
           75         self.setSortingEnabled(True)
           76         self.selectionModel().currentRowChanged.connect(self.item_changed)
           77         self.setSelectionMode(QAbstractItemView.ExtendedSelection)
           78         self.update()
           79 
           80     def select_key(self, key):
           81         for i in range(self.model().rowCount()):
           82             item = self.model().index(i, self.Columns.DATE)
           83             row_key = item.data(ROLE_KEY)
           84             if key == row_key:
           85                 self.selectionModel().setCurrentIndex(item, QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows)
           86                 break
           87 
           88     def item_changed(self, idx: Optional[QModelIndex]):
           89         if idx is None:
           90             self.parent.receive_payreq_e.setText('')
           91             self.parent.receive_address_e.setText('')
           92             return
           93         if not idx.isValid():
           94             return
           95         # TODO use siblingAtColumn when min Qt version is >=5.11
           96         item = self.item_from_index(idx.sibling(idx.row(), self.Columns.DATE))
           97         key = item.data(ROLE_KEY)
           98         req = self.wallet.get_request(key)
           99         if req is None:
          100             self.update()
          101             return
          102         if req.is_lightning():
          103             self.parent.receive_payreq_e.setText(req.invoice)  # TODO maybe prepend "lightning:" ??
          104             self.parent.receive_address_e.setText(req.invoice)
          105         else:
          106             self.parent.receive_payreq_e.setText(self.parent.wallet.get_request_URI(req))
          107             self.parent.receive_address_e.setText(req.get_address())
          108         self.parent.receive_payreq_e.repaint()  # macOS hack (similar to #4777)
          109         self.parent.receive_address_e.repaint()  # macOS hack (similar to #4777)
          110 
          111     def clearSelection(self):
          112         super().clearSelection()
          113         self.selectionModel().clearCurrentIndex()
          114 
          115     def refresh_status(self):
          116         m = self.std_model
          117         for r in range(m.rowCount()):
          118             idx = m.index(r, self.Columns.STATUS)
          119             date_idx = idx.sibling(idx.row(), self.Columns.DATE)
          120             date_item = m.itemFromIndex(date_idx)
          121             status_item = m.itemFromIndex(idx)
          122             key = date_item.data(ROLE_KEY)
          123             req = self.wallet.get_request(key)
          124             if req:
          125                 status = self.parent.wallet.get_request_status(key)
          126                 status_str = req.get_status_str(status)
          127                 status_item.setText(status_str)
          128                 status_item.setIcon(read_QIcon(pr_icons.get(status)))
          129 
          130     def update_item(self, key, invoice: Invoice):
          131         model = self.std_model
          132         for row in range(0, model.rowCount()):
          133             item = model.item(row, 0)
          134             if item.data(ROLE_KEY) == key:
          135                 break
          136         else:
          137             return
          138         status_item = model.item(row, self.Columns.STATUS)
          139         status = self.parent.wallet.get_request_status(key)
          140         status_str = invoice.get_status_str(status)
          141         status_item.setText(status_str)
          142         status_item.setIcon(read_QIcon(pr_icons.get(status)))
          143 
          144     def update(self):
          145         # not calling maybe_defer_update() as it interferes with conditional-visibility
          146         self.parent.update_receive_address_styling()
          147         self.proxy.setDynamicSortFilter(False)  # temp. disable re-sorting after every change
          148         self.std_model.clear()
          149         self.update_headers(self.__class__.headers)
          150         for req in self.wallet.get_unpaid_requests():
          151             key = self.wallet.get_key_for_receive_request(req)
          152             status = self.parent.wallet.get_request_status(key)
          153             status_str = req.get_status_str(status)
          154             request_type = req.type
          155             timestamp = req.time
          156             amount = req.get_amount_sat()
          157             message = req.message
          158             date = format_time(timestamp)
          159             amount_str = self.parent.format_amount(amount) if amount else ""
          160             labels = [date, message, amount_str, status_str]
          161             if req.is_lightning():
          162                 icon = read_QIcon("lightning.png")
          163                 tooltip = 'lightning request'
          164             else:
          165                 icon = read_QIcon("bitcoin.png")
          166                 tooltip = 'onchain request'
          167             items = [QStandardItem(e) for e in labels]
          168             self.set_editability(items)
          169             items[self.Columns.DATE].setData(request_type, ROLE_REQUEST_TYPE)
          170             items[self.Columns.DATE].setData(key, ROLE_KEY)
          171             items[self.Columns.DATE].setData(timestamp, ROLE_SORT_ORDER)
          172             items[self.Columns.DATE].setIcon(icon)
          173             items[self.Columns.STATUS].setIcon(read_QIcon(pr_icons.get(status)))
          174             items[self.Columns.DATE].setToolTip(tooltip)
          175             self.std_model.insertRow(self.std_model.rowCount(), items)
          176         self.filter()
          177         self.proxy.setDynamicSortFilter(True)
          178         # sort requests by date
          179         self.sortByColumn(self.Columns.DATE, Qt.DescendingOrder)
          180         # hide list if empty
          181         if self.parent.isVisible():
          182             b = self.std_model.rowCount() > 0
          183             self.setVisible(b)
          184             self.parent.receive_requests_label.setVisible(b)
          185             if not b:
          186                 # list got hidden, so selected item should also be cleared:
          187                 self.item_changed(None)
          188 
          189     def create_menu(self, position):
          190         items = self.selected_in_column(0)
          191         if len(items)>1:
          192             keys = [ item.data(ROLE_KEY)  for item in items]
          193             menu = QMenu(self)
          194             menu.addAction(_("Delete requests"), lambda: self.parent.delete_requests(keys))
          195             menu.exec_(self.viewport().mapToGlobal(position))
          196             return
          197         idx = self.indexAt(position)
          198         # TODO use siblingAtColumn when min Qt version is >=5.11
          199         item = self.item_from_index(idx.sibling(idx.row(), self.Columns.DATE))
          200         if not item:
          201             return
          202         key = item.data(ROLE_KEY)
          203         req = self.wallet.get_request(key)
          204         if req is None:
          205             self.update()
          206             return
          207         menu = QMenu(self)
          208         self.add_copy_menu(menu, idx)
          209         if req.is_lightning():
          210             menu.addAction(_("Copy Request"), lambda: self.parent.do_copy(req.invoice, title='Lightning Request'))
          211         else:
          212             URI = self.wallet.get_request_URI(req)
          213             menu.addAction(_("Copy Request"), lambda: self.parent.do_copy(URI, title='Bitcoin URI'))
          214             menu.addAction(_("Copy Address"), lambda: self.parent.do_copy(req.get_address(), title='Bitcoin Address'))
          215         #if 'view_url' in req:
          216         #    menu.addAction(_("View in web browser"), lambda: webopen(req['view_url']))
          217         menu.addAction(_("Delete"), lambda: self.parent.delete_requests([key]))
          218         run_hook('receive_list_menu', self.parent, menu, key)
          219         menu.exec_(self.viewport().mapToGlobal(position))