URI: 
       taddress_list.py - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
       taddress_list.py (12334B)
       ---
            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 
           28 from PyQt5.QtCore import Qt, QPersistentModelIndex, QModelIndex
           29 from PyQt5.QtGui import QStandardItemModel, QStandardItem, QFont
           30 from PyQt5.QtWidgets import QAbstractItemView, QComboBox, QLabel, QMenu
           31 
           32 from electrum.i18n import _
           33 from electrum.util import block_explorer_URL, profiler
           34 from electrum.plugin import run_hook
           35 from electrum.bitcoin import is_address
           36 from electrum.wallet import InternalAddressCorruption
           37 
           38 from .util import MyTreeView, MONOSPACE_FONT, ColorScheme, webopen, MySortModel
           39 
           40 
           41 class AddressUsageStateFilter(IntEnum):
           42     ALL = 0
           43     UNUSED = 1
           44     FUNDED = 2
           45     USED_AND_EMPTY = 3
           46 
           47     def ui_text(self) -> str:
           48         return {
           49             self.ALL: _('All'),
           50             self.UNUSED: _('Unused'),
           51             self.FUNDED: _('Funded'),
           52             self.USED_AND_EMPTY: _('Used'),
           53         }[self]
           54 
           55 
           56 class AddressTypeFilter(IntEnum):
           57     ALL = 0
           58     RECEIVING = 1
           59     CHANGE = 2
           60 
           61     def ui_text(self) -> str:
           62         return {
           63             self.ALL: _('All'),
           64             self.RECEIVING: _('Receiving'),
           65             self.CHANGE: _('Change'),
           66         }[self]
           67 
           68 
           69 class AddressList(MyTreeView):
           70 
           71     class Columns(IntEnum):
           72         TYPE = 0
           73         ADDRESS = 1
           74         LABEL = 2
           75         COIN_BALANCE = 3
           76         FIAT_BALANCE = 4
           77         NUM_TXS = 5
           78 
           79     filter_columns = [Columns.TYPE, Columns.ADDRESS, Columns.LABEL, Columns.COIN_BALANCE]
           80 
           81     ROLE_SORT_ORDER = Qt.UserRole + 1000
           82 
           83     def __init__(self, parent):
           84         super().__init__(parent, self.create_menu, stretch_column=self.Columns.LABEL)
           85         self.wallet = self.parent.wallet
           86         self.setSelectionMode(QAbstractItemView.ExtendedSelection)
           87         self.setSortingEnabled(True)
           88         self.show_change = AddressTypeFilter.ALL  # type: AddressTypeFilter
           89         self.show_used = AddressUsageStateFilter.ALL  # type: AddressUsageStateFilter
           90         self.change_button = QComboBox(self)
           91         self.change_button.currentIndexChanged.connect(self.toggle_change)
           92         for addr_type in AddressTypeFilter.__members__.values():  # type: AddressTypeFilter
           93             self.change_button.addItem(addr_type.ui_text())
           94         self.used_button = QComboBox(self)
           95         self.used_button.currentIndexChanged.connect(self.toggle_used)
           96         for addr_usage_state in AddressUsageStateFilter.__members__.values():  # type: AddressUsageStateFilter
           97             self.used_button.addItem(addr_usage_state.ui_text())
           98         self.std_model = QStandardItemModel(self)
           99         self.proxy = MySortModel(self, sort_role=self.ROLE_SORT_ORDER)
          100         self.proxy.setSourceModel(self.std_model)
          101         self.setModel(self.proxy)
          102         self.update()
          103         self.sortByColumn(self.Columns.TYPE, Qt.AscendingOrder)
          104 
          105     def get_toolbar_buttons(self):
          106         return QLabel(_("Filter:")), self.change_button, self.used_button
          107 
          108     def on_hide_toolbar(self):
          109         self.show_change = AddressTypeFilter.ALL  # type: AddressTypeFilter
          110         self.show_used = AddressUsageStateFilter.ALL  # type: AddressUsageStateFilter
          111         self.update()
          112 
          113     def save_toolbar_state(self, state, config):
          114         config.set_key('show_toolbar_addresses', state)
          115 
          116     def refresh_headers(self):
          117         fx = self.parent.fx
          118         if fx and fx.get_fiat_address_config():
          119             ccy = fx.get_currency()
          120         else:
          121             ccy = _('Fiat')
          122         headers = {
          123             self.Columns.TYPE: _('Type'),
          124             self.Columns.ADDRESS: _('Address'),
          125             self.Columns.LABEL: _('Label'),
          126             self.Columns.COIN_BALANCE: _('Balance'),
          127             self.Columns.FIAT_BALANCE: ccy + ' ' + _('Balance'),
          128             self.Columns.NUM_TXS: _('Tx'),
          129         }
          130         self.update_headers(headers)
          131 
          132     def toggle_change(self, state: int):
          133         if state == self.show_change:
          134             return
          135         self.show_change = AddressTypeFilter(state)
          136         self.update()
          137 
          138     def toggle_used(self, state: int):
          139         if state == self.show_used:
          140             return
          141         self.show_used = AddressUsageStateFilter(state)
          142         self.update()
          143 
          144     @profiler
          145     def update(self):
          146         if self.maybe_defer_update():
          147             return
          148         current_address = self.current_item_user_role(col=self.Columns.LABEL)
          149         if self.show_change == AddressTypeFilter.RECEIVING:
          150             addr_list = self.wallet.get_receiving_addresses()
          151         elif self.show_change == AddressTypeFilter.CHANGE:
          152             addr_list = self.wallet.get_change_addresses()
          153         else:
          154             addr_list = self.wallet.get_addresses()
          155         self.proxy.setDynamicSortFilter(False)  # temp. disable re-sorting after every change
          156         self.std_model.clear()
          157         self.refresh_headers()
          158         fx = self.parent.fx
          159         set_address = None
          160         addresses_beyond_gap_limit = self.wallet.get_all_known_addresses_beyond_gap_limit()
          161         for address in addr_list:
          162             num = self.wallet.get_address_history_len(address)
          163             label = self.wallet.get_label(address)
          164             c, u, x = self.wallet.get_addr_balance(address)
          165             balance = c + u + x
          166             is_used_and_empty = self.wallet.is_used(address) and balance == 0
          167             if self.show_used == AddressUsageStateFilter.UNUSED and (balance or is_used_and_empty):
          168                 continue
          169             if self.show_used == AddressUsageStateFilter.FUNDED and balance == 0:
          170                 continue
          171             if self.show_used == AddressUsageStateFilter.USED_AND_EMPTY and not is_used_and_empty:
          172                 continue
          173             balance_text = self.parent.format_amount(balance, whitespaces=True)
          174             # create item
          175             if fx and fx.get_fiat_address_config():
          176                 rate = fx.exchange_rate()
          177                 fiat_balance = fx.value_str(balance, rate)
          178             else:
          179                 fiat_balance = ''
          180             labels = ['', address, label, balance_text, fiat_balance, "%d"%num]
          181             address_item = [QStandardItem(e) for e in labels]
          182             # align text and set fonts
          183             for i, item in enumerate(address_item):
          184                 item.setTextAlignment(Qt.AlignVCenter)
          185                 if i not in (self.Columns.TYPE, self.Columns.LABEL):
          186                     item.setFont(QFont(MONOSPACE_FONT))
          187             self.set_editability(address_item)
          188             address_item[self.Columns.FIAT_BALANCE].setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
          189             # setup column 0
          190             if self.wallet.is_change(address):
          191                 address_item[self.Columns.TYPE].setText(_('change'))
          192                 address_item[self.Columns.TYPE].setBackground(ColorScheme.YELLOW.as_color(True))
          193             else:
          194                 address_item[self.Columns.TYPE].setText(_('receiving'))
          195                 address_item[self.Columns.TYPE].setBackground(ColorScheme.GREEN.as_color(True))
          196             address_item[self.Columns.LABEL].setData(address, Qt.UserRole)
          197             address_path = self.wallet.get_address_index(address)
          198             address_item[self.Columns.TYPE].setData(address_path, self.ROLE_SORT_ORDER)
          199             address_path_str = self.wallet.get_address_path_str(address)
          200             if address_path_str is not None:
          201                 address_item[self.Columns.TYPE].setToolTip(address_path_str)
          202             address_item[self.Columns.FIAT_BALANCE].setData(balance, self.ROLE_SORT_ORDER)
          203             # setup column 1
          204             if self.wallet.is_frozen_address(address):
          205                 address_item[self.Columns.ADDRESS].setBackground(ColorScheme.BLUE.as_color(True))
          206             if address in addresses_beyond_gap_limit:
          207                 address_item[self.Columns.ADDRESS].setBackground(ColorScheme.RED.as_color(True))
          208             # add item
          209             count = self.std_model.rowCount()
          210             self.std_model.insertRow(count, address_item)
          211             address_idx = self.std_model.index(count, self.Columns.LABEL)
          212             if address == current_address:
          213                 set_address = QPersistentModelIndex(address_idx)
          214         self.set_current_idx(set_address)
          215         # show/hide columns
          216         if fx and fx.get_fiat_address_config():
          217             self.showColumn(self.Columns.FIAT_BALANCE)
          218         else:
          219             self.hideColumn(self.Columns.FIAT_BALANCE)
          220         self.filter()
          221         self.proxy.setDynamicSortFilter(True)
          222 
          223     def create_menu(self, position):
          224         from electrum.wallet import Multisig_Wallet
          225         is_multisig = isinstance(self.wallet, Multisig_Wallet)
          226         can_delete = self.wallet.can_delete_address()
          227         selected = self.selected_in_column(self.Columns.ADDRESS)
          228         if not selected:
          229             return
          230         multi_select = len(selected) > 1
          231         addrs = [self.item_from_index(item).text() for item in selected]
          232         menu = QMenu()
          233         if not multi_select:
          234             idx = self.indexAt(position)
          235             if not idx.isValid():
          236                 return
          237             item = self.item_from_index(idx)
          238             if not item:
          239                 return
          240             addr = addrs[0]
          241             addr_column_title = self.std_model.horizontalHeaderItem(self.Columns.LABEL).text()
          242             addr_idx = idx.sibling(idx.row(), self.Columns.LABEL)
          243             self.add_copy_menu(menu, idx)
          244             menu.addAction(_('Details'), lambda: self.parent.show_address(addr))
          245             persistent = QPersistentModelIndex(addr_idx)
          246             menu.addAction(_("Edit {}").format(addr_column_title), lambda p=persistent: self.edit(QModelIndex(p)))
          247             #menu.addAction(_("Request payment"), lambda: self.parent.receive_at(addr))
          248             if self.wallet.can_export():
          249                 menu.addAction(_("Private key"), lambda: self.parent.show_private_key(addr))
          250             if not is_multisig and not self.wallet.is_watching_only():
          251                 menu.addAction(_("Sign/verify message"), lambda: self.parent.sign_verify_message(addr))
          252                 menu.addAction(_("Encrypt/decrypt message"), lambda: self.parent.encrypt_message(addr))
          253             if can_delete:
          254                 menu.addAction(_("Remove from wallet"), lambda: self.parent.remove_address(addr))
          255             addr_URL = block_explorer_URL(self.config, 'addr', addr)
          256             if addr_URL:
          257                 menu.addAction(_("View on block explorer"), lambda: webopen(addr_URL))
          258 
          259             if not self.wallet.is_frozen_address(addr):
          260                 menu.addAction(_("Freeze"), lambda: self.parent.set_frozen_state_of_addresses([addr], True))
          261             else:
          262                 menu.addAction(_("Unfreeze"), lambda: self.parent.set_frozen_state_of_addresses([addr], False))
          263 
          264         coins = self.wallet.get_spendable_coins(addrs)
          265         if coins:
          266             menu.addAction(_("Spend from"), lambda: self.parent.utxo_list.set_spend_list(coins))
          267 
          268         run_hook('receive_menu', menu, addrs, self.wallet)
          269         menu.exec_(self.viewport().mapToGlobal(position))
          270 
          271     def place_text_on_clipboard(self, text: str, *, title: str = None) -> None:
          272         if is_address(text):
          273             try:
          274                 self.wallet.check_address_for_corruption(text)
          275             except InternalAddressCorruption as e:
          276                 self.parent.show_error(str(e))
          277                 raise
          278         super().place_text_on_clipboard(text, title=title)