コード例 #1
0
    def on_update(self) -> None:
        if self._send_view._account_id is None:
            return

        self._stop_timer()

        current_id = None
        if self._send_view._payment_request is not None:
            current_id = self._send_view._payment_request.get_id()
        if current_id is None:
            current_item = self.currentItem()
            current_id = current_item.data(
                COL_RECEIVED, Qt.UserRole) if current_item else None

        self.clear()

        current_item = None
        current_time = time.time()
        nearest_expiry_time = float("inf")

        for row in self._send_view._account.invoices.get_invoices():
            flags = row.flags & PaymentFlag.STATE_MASK
            if flags & PaymentFlag.UNPAID and row.date_expires:
                if row.date_expires <= current_time + 5:
                    flags = (row.flags
                             & ~PaymentFlag.UNPAID) | PaymentFlag.EXPIRED
                else:
                    nearest_expiry_time = min(nearest_expiry_time,
                                              row.date_expires)

            requestor_uri = urllib.parse.urlparse(row.payment_uri)
            requestor_text = requestor_uri.netloc
            received_text = format_time(row.date_created, _("Unknown"))
            expires_text = format_time(
                row.date_expires,
                _("Unknown") if row.date_expires else _('Never'))
            item = QTreeWidgetItem([
                received_text,
                expires_text,
                requestor_text,
                row.description,
                app_state.format_amount(row.value, whitespaces=True),
                # The tooltip text should be None to ensure the icon does not have extra RHS space.
                pr_tooltips.get(flags, None)
            ])
            icon_entry = pr_icons.get(flags)
            if icon_entry:
                item.setIcon(COL_STATUS, read_QIcon(icon_entry))
            if row.invoice_id == current_id:
                current_item = item
            item.setData(COL_RECEIVED, Qt.UserRole, row.invoice_id)
            item.setFont(COL_DESCRIPTION, self._monospace_font)
            item.setFont(COL_AMOUNT, self._monospace_font)
            self.addTopLevelItem(item)

        if current_item is not None:
            self.setCurrentItem(current_item)

        if nearest_expiry_time != float("inf"):
            self._start_timer(nearest_expiry_time)
コード例 #2
0
    def _on_update_utxo_list(self):
        if self._account_id is None:
            return

        prev_selection = self.get_selected(
        )  # cache previous selection, if any
        self.clear()

        for utxo in self._account.get_utxos():
            metadata = self._account.get_transaction_metadata(utxo.tx_hash)
            prevout_str = utxo.key_str()
            prevout_str = prevout_str[0:10] + '...' + prevout_str[-2:]
            label = self._wallet.get_transaction_label(utxo.tx_hash)
            amount = app_state.format_amount(utxo.value, whitespaces=True)
            utxo_item = SortableTreeWidgetItem(
                [prevout_str, label, amount,
                 str(metadata.height)])
            # set this here to avoid sorting based on Qt.UserRole+1
            utxo_item.DataRole = Qt.UserRole + 100
            for col in (0, 2):
                utxo_item.setFont(col, self._monospace_font)
            utxo_item.setData(0, Qt.UserRole + 2, utxo)
            if self._account.is_frozen_utxo(utxo):
                utxo_item.setBackground(0, ColorScheme.BLUE.as_color(True))
            self.addChild(utxo_item)
            if utxo in prev_selection:
                # NB: This needs to be here after the item is added to the widget. See #979.
                utxo_item.setSelected(True)  # restore previous selection
コード例 #3
0
    def _update_transactions_tab_summary(self) -> None:
        local_count = 0
        local_value = 0

        if self._account_id is not None:
            wallet = self._account.get_wallet()
            with wallet.get_transaction_delta_table() as table:
                _account_id, local_value, local_count = table.read_balance(
                    self._account_id, mask=TxFlags.STATE_UNCLEARED_MASK)

        if local_count == 0:
            self._local_summary_label.setVisible(False)
            return

        value_text = app_state.format_amount(
            local_value) + " " + app_state.base_unit()
        if local_count == 1:
            text = _(
                "The Transactions tab has <b>1</b> transaction containing <b>{balance}</b> "
                "in allocated coins.").format(balance=value_text)
        else:
            text = _(
                "The Transactions tab has <b>{count}</b> transactions containing "
                "<b>{balance}</b> in allocated coins.").format(
                    count=local_count, balance=value_text)
        self._local_summary_label.setText(text)
        self._local_summary_label.setVisible(True)
コード例 #4
0
ファイル: qrwindow.py プロジェクト: wagnehc/electrumsv
 def set_content(self, address_text, amount, message, url):
     self._address_edit.setPlainText(address_text)
     if amount:
         amount_text = '{} {}'.format(app_state.format_amount(amount),
                                      app_state.base_unit())
     else:
         amount_text = ''
     self._amount_edit.setText(amount_text)
     self._message_edit.setPlainText(message)
     self.qrw.setData(url)
コード例 #5
0
    def redraw_from_list(self) -> None:
        self._from_list.clear()
        self._from_label.setHidden(len(self.pay_from) == 0)
        self._from_list.setHidden(len(self.pay_from) == 0)

        def format_utxo(utxo: UTXO) -> str:
            h = hash_to_hex_str(utxo.tx_hash)
            return '{}...{}:{:d}\t{}'.format(h[0:10], h[-10:], utxo.out_index,
                                             utxo.address)

        for utxo in self.pay_from:
            self._from_list.addTopLevelItem(
                QTreeWidgetItem(
                    [format_utxo(utxo),
                     app_state.format_amount(utxo.value)]))

        update_fixed_tree_height(self._from_list)
コード例 #6
0
    def _on_entry_changed(self) -> None:
        text = ""
        if self._not_enough_funds:
            amt_color = ColorScheme.RED
            text = _("Not enough funds")
            c, u, x = self._account.get_frozen_balance()
            if c + u + x:
                text += (' (' + app_state.format_amount(c + u + x).strip() +
                         ' ' + app_state.base_unit() + ' ' + _("are frozen") +
                         ')')

        if self.amount_e.isModified():
            amt_color = ColorScheme.DEFAULT
        else:
            amt_color = ColorScheme.BLUE

        self._main_window.statusBar().showMessage(text)
        self.amount_e.setStyleSheet(amt_color.as_stylesheet())
コード例 #7
0
ファイル: request_list.py プロジェクト: wagnehc/electrumsv
    def on_update(self) -> None:
        if self._account_id is None:
            return

        wallet = self._account._wallet
        with wallet.get_payment_request_table() as table:
            rows = table.read(self._account_id, flags=PaymentFlag.NONE,
                mask=PaymentFlag.ARCHIVED)

        # update the receive address if necessary
        current_key_id = self._receive_view.get_receive_key_id()
        if current_key_id is None:
            return

        keyinstance = None
        if self._account.is_deterministic():
            keyinstance = self._account.get_fresh_keys(RECEIVING_SUBPATH, 1)[0]
        if keyinstance is not None:
            self._receive_view.set_receive_key(keyinstance)
            self._receive_view.set_new_button_enabled(current_key_id != keyinstance.keyinstance_id)

        # clear the list and fill it again
        self.clear()
        for row in rows:
            date = format_time(row.date_created, _("Unknown"))
            amount_str = app_state.format_amount(row.value, whitespaces=True) if row.value else ""

            script_template = self._account.get_script_template_for_id(row.keyinstance_id)
            address_text = script_template_to_string(script_template)

            state = row.state & sum(pr_icons.keys())
            item = QTreeWidgetItem([date, address_text, '', row.description or "",
                amount_str, pr_tooltips.get(state,'')])
            item.setData(0, Qt.UserRole, row.paymentrequest_id)
            if state != PaymentFlag.UNKNOWN:
                icon_name = pr_icons.get(state)
                if icon_name is not None:
                    item.setIcon(6, read_QIcon(icon_name))
            item.setFont(4, self._monospace_font)
            self.addTopLevelItem(item)
コード例 #8
0
    def _update_io(self, i_table: MyTreeWidget, o_table: MyTreeWidget) -> None:
        def get_xtxoutput_account(
                output: XTxOutput) -> Tuple[Optional[AbstractAccount], int]:
            if output.x_pubkeys:
                for x_pubkey in output.x_pubkeys:
                    result = self._main_window._wallet.resolve_xpubkey(
                        x_pubkey)
                    if result is not None:
                        account, keyinstance_id = result
                        if account.get_script_for_id(
                                keyinstance_id) == output.script_pubkey:
                            return account, keyinstance_id
                        # TODO: Document when this happens
                        break
            return None, -1

        def get_keyinstance_id(account: AbstractAccount,
                               txo_key: TxoKeyType) -> Optional[int]:
            utxo = account._utxos.get(txo_key)
            if utxo is not None:
                return utxo.keyinstance_id
            stxo_keyinstance_id = account._stxos.get(txo_key)
            if stxo_keyinstance_id is not None:
                return stxo_keyinstance_id
            return None

        def compare_key_path(account: AbstractAccount, keyinstance_id: int,
                             leading_path: Sequence[int]) -> bool:
            key_path = account.get_derivation_path(keyinstance_id)
            return key_path is not None and key_path[:len(leading_path
                                                          )] == leading_path

        def name_for_account(account: AbstractAccount) -> str:
            name = account.display_name()
            return f"{account.get_id()}: {name}"

        is_tx_complete = self.tx.is_complete()
        is_tx_known = self._account and self._account.have_transaction_data(
            self._tx_hash)

        prev_txos = self._coin_service.get_outputs([
            TxoKeyType(txin.prev_hash, txin.prev_idx)
            for txin in self.tx.inputs
        ])
        prev_txo_dict = {
            TxoKeyType(r.tx_hash, r.tx_index): r
            for r in prev_txos
        }
        self._spent_value_label.setText(
            _("Spent input value") + ": " +
            app_state.format_amount(sum(r.value for r in prev_txos)))

        for tx_index, txin in enumerate(self.tx.inputs):
            account_name = ""
            source_text = ""
            amount_text = ""
            is_receiving = is_change = is_broken = False
            txo_key = TxoKeyType(txin.prev_hash, txin.prev_idx)

            if txin.is_coinbase():
                source_text = "<coinbase>"
            else:
                prev_hash_hex = hash_to_hex_str(txin.prev_hash)
                source_text = f"{prev_hash_hex}:{txin.prev_idx}"
                # There are only certain kinds of transactions that have values on the inputs,
                # likely deserialised incomplete transactions from cosigners. Others?
                value = txin.value
                if self._account is not None:
                    keyinstance_id = get_keyinstance_id(self._account, txo_key)
                    is_receiving = compare_key_path(self._account,
                                                    keyinstance_id,
                                                    RECEIVING_SUBPATH)
                    is_change = compare_key_path(self._account, keyinstance_id,
                                                 CHANGE_SUBPATH)
                    account_name = name_for_account(self._account)
                    prev_txo = prev_txo_dict.get(txo_key, None)
                    if prev_txo is not None and is_tx_complete:
                        value = prev_txo.value
                        if is_tx_known:
                            # The transaction has been added to the account.
                            is_broken = (prev_txo.flags
                                         & TransactionOutputFlag.IS_SPENT) == 0
                        else:
                            # The transaction was most likely loaded from external source and is
                            # being viewed but has not been added to the account.
                            is_broken = (prev_txo.flags
                                         & TransactionOutputFlag.IS_SPENT) != 0
                amount_text = app_state.format_amount(value, whitespaces=True)

            item = QTreeWidgetItem(
                [str(tx_index), account_name, source_text, amount_text])
            item.setData(InputColumns.INDEX, Roles.TX_HASH, txin.prev_hash)
            item.setData(InputColumns.INDEX, Roles.IS_MINE, is_change
                         or is_receiving)
            if is_receiving:
                item.setBackground(InputColumns.SOURCE, self._receiving_brush)
            if is_change:
                item.setBackground(InputColumns.SOURCE, self._change_brush)
            if is_broken:
                item.setBackground(InputColumns.SOURCE, self._broken_brush)
            item.setTextAlignment(InputColumns.AMOUNT,
                                  Qt.AlignRight | Qt.AlignVCenter)
            item.setFont(InputColumns.AMOUNT, self._monospace_font)
            i_table.addTopLevelItem(item)

        # TODO: Rewrite this to be lot simpler when we have better TXO management. At this time
        # we do not track UTXOs except when a transaction is known to the network as we rely on
        # the server state to map key usage to transactions or something. We need to completely
        # rewrite that and then rewrite this. Anyway, that is why signed tx outputs do not get
        # identified and colourised.

        received_value = 0
        for tx_index, tx_output in enumerate(self.tx.outputs):
            text, _kind = tx_output_to_display_text(tx_output)
            if isinstance(_kind, Unknown_Output):
                text = script_bytes_to_asm(tx_output.script_pubkey)

            # In the longer run we will have some form of abstraction for incomplete transactions
            # that maps where the keys come from, but for now we manually map them to the limited
            # key hierarchy that currently exists.
            xtxo_account, xtxo_keyinstance_id = get_xtxoutput_account(
                tx_output)
            accounts: List[AbstractAccount] = []
            if xtxo_account is not None:
                accounts.append(xtxo_account)
            if self._account is not None and xtxo_account is not self._account:
                accounts.append(self._account)

            account_id: Optional[int] = None
            account_name = ""
            keyinstance_id: Optional[int] = None
            is_receiving = is_change = False
            txo_key = TxoKeyType(self._tx_hash, tx_index)
            for account in accounts:
                if is_tx_complete:
                    keyinstance_id = get_keyinstance_id(account, txo_key)
                elif account is xtxo_account and xtxo_keyinstance_id != -1:
                    keyinstance_id = xtxo_keyinstance_id

                if keyinstance_id is not None:
                    account_id = account.get_id()
                    is_receiving = compare_key_path(account, keyinstance_id,
                                                    RECEIVING_SUBPATH)
                    is_change = compare_key_path(account, keyinstance_id,
                                                 CHANGE_SUBPATH)
                    account_name = name_for_account(account)
                    received_value += tx_output.value
                    break

            amount_text = app_state.format_amount(tx_output.value,
                                                  whitespaces=True)

            item = QTreeWidgetItem(
                [str(tx_index), account_name, text, amount_text])
            item.setData(OutputColumns.INDEX, Roles.IS_MINE, is_change
                         or is_receiving)
            item.setData(OutputColumns.INDEX, Roles.ACCOUNT_ID, account_id)
            item.setData(OutputColumns.INDEX, Roles.KEY_ID, keyinstance_id)
            if is_receiving:
                item.setBackground(OutputColumns.DESTINATION,
                                   self._receiving_brush)
            if is_change:
                item.setBackground(OutputColumns.DESTINATION,
                                   self._change_brush)
            item.setTextAlignment(OutputColumns.AMOUNT,
                                  Qt.AlignRight | Qt.AlignVCenter)
            item.setFont(OutputColumns.AMOUNT, self._monospace_font)
            o_table.addTopLevelItem(item)

        self._received_value_label.setText(
            _("Received output value") + ": " +
            app_state.format_amount(received_value))
コード例 #9
0
    def __init__(self, main_window: ElectrumWindow) -> None:
        super().__init__(main_window)
        self._logger = logs.get_logger("key-view")

        self._main_window = weakref.proxy(main_window)
        self._account: AbstractAccount = None
        self._account_id: Optional[int] = None

        self._update_lock = threading.Lock()

        self._headers = COLUMN_NAMES

        self.verticalHeader().setVisible(False)
        self.setAlternatingRowColors(True)

        self._pending_state: Dict[int, EventFlags] = {}
        self._pending_actions = set([ListActions.RESET])
        self._main_window.keys_created_signal.connect(self._on_keys_created)
        self._main_window.keys_updated_signal.connect(self._on_keys_updated)
        self._main_window.account_change_signal.connect(
            self._on_account_change)

        model = _ItemModel(self, self._headers)
        model.set_data(self._account_id, [])
        self._base_model = model

        # If the underlying model changes, observe it in the sort.
        self._proxy_model = proxy_model = _SortFilterProxyModel()
        proxy_model.setDynamicSortFilter(True)
        proxy_model.setSortRole(QT_SORT_ROLE)
        proxy_model.setSourceModel(model)
        self.setModel(proxy_model)

        fx = app_state.fx
        self._set_fiat_columns_enabled(fx and fx.get_fiat_address_config())

        # Sort by type then by index, by making sure the initial sort is our type column.
        self.sortByColumn(BALANCE_COLUMN, Qt.DescendingOrder)
        self.setSortingEnabled(True)

        defaultFontMetrics = QFontMetrics(app_state.app.font())

        def fw(s: str) -> int:
            return defaultFontMetrics.boundingRect(s).width() + 10

        self._monospace_font = QFont(platform.monospace_font)
        monospaceFontMetrics = QFontMetrics(self._monospace_font)

        def mw(s: str) -> int:
            return monospaceFontMetrics.boundingRect(s).width() + 10

        # We set the columm widths so that rendering is instant rather than taking a second or two
        # because ResizeToContents does not scale for thousands of rows.
        horizontalHeader = self.horizontalHeader()
        horizontalHeader.setMinimumSectionSize(20)
        horizontalHeader.resizeSection(TYPE_COLUMN,
                                       fw(COLUMN_NAMES[TYPE_COLUMN]))
        horizontalHeader.resizeSection(STATE_COLUMN,
                                       fw(COLUMN_NAMES[STATE_COLUMN]))
        horizontalHeader.resizeSection(KEY_COLUMN, fw("1442:01:m/000/1392"))
        horizontalHeader.resizeSection(SCRIPT_COLUMN,
                                       fw("MULTISIG_ACCUMULATOR"))
        horizontalHeader.setSectionResizeMode(LABEL_COLUMN,
                                              QHeaderView.Stretch)
        horizontalHeader.resizeSection(USAGES_COLUMN,
                                       fw(COLUMN_NAMES[USAGES_COLUMN]))
        balance_width = mw(app_state.format_amount(1.2, whitespaces=True))
        horizontalHeader.resizeSection(BALANCE_COLUMN, balance_width)

        verticalHeader = self.verticalHeader()
        verticalHeader.setSectionResizeMode(QHeaderView.Fixed)
        # This value will get pushed out if the contents are larger, so it does not have to be
        # correct, it just has to be minimal.
        lineHeight = defaultFontMetrics.height()
        verticalHeader.setDefaultSectionSize(lineHeight)

        self.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        # New selections clear existing selections, unless the user holds down control.
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)

        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self._event_create_menu)

        app_state.app.base_unit_changed.connect(
            self._on_balance_display_change)
        app_state.app.fiat_balance_changed.connect(
            self._on_fiat_balance_display_change)
        app_state.app.fiat_ccy_changed.connect(
            self._on_fiat_balance_display_change)
        app_state.app.labels_changed_signal.connect(self.update_labels)
        app_state.app.num_zeros_changed.connect(
            self._on_balance_display_change)

        self.setEditTriggers(QAbstractItemView.DoubleClicked)
        self.doubleClicked.connect(self._event_double_clicked)

        self._last_not_synced = 0
        self._timer = QTimer(self)
        self._timer.setSingleShot(False)
        self._timer.setInterval(1000)
        self._timer.timeout.connect(self._on_update_check)
コード例 #10
0
    def data(self, model_index: QModelIndex, role: int) -> QVariant:
        if self._view._account_id != self._account_id:
            return None

        row = model_index.row()
        column = model_index.column()
        if row >= len(self._data):
            return None
        if column >= len(self._column_names):
            return None

        if model_index.isValid():
            line = self._data[row]

            # First check the custom sort role.
            if role == QT_SORT_ROLE:
                if column == TYPE_COLUMN:
                    return line.script_type
                elif column == STATE_COLUMN:
                    return line.flags
                elif column == KEY_COLUMN:
                    return get_key_text(line)
                elif column == SCRIPT_COLUMN:
                    return ScriptType(line.script_type).name
                elif column == LABEL_COLUMN:
                    return self._view._account.get_keyinstance_label(
                        line.keyinstance_id)
                elif column == USAGES_COLUMN:
                    return line.match_count
                elif column in (BALANCE_COLUMN, FIAT_BALANCE_COLUMN):
                    if column == BALANCE_COLUMN:
                        return line.total_value
                    elif column == FIAT_BALANCE_COLUMN:
                        fx = app_state.fx
                        rate = fx.exchange_rate()
                        return fx.value_str(line.total_value, rate)

            elif role == QT_FILTER_ROLE:
                if column == KEY_COLUMN:
                    return line

            elif role == Qt.DecorationRole:
                if column == TYPE_COLUMN:
                    # TODO(rt12) BACKLOG Need to add variation in icons.
                    return self._receive_icon

            elif role == Qt.DisplayRole:
                if column == TYPE_COLUMN:
                    return None
                elif column == STATE_COLUMN:
                    state_text = ""
                    if line.flags & KeyInstanceFlag.ALLOCATED_MASK:
                        state_text += "A"
                    if line.flags & KeyInstanceFlag.IS_PAYMENT_REQUEST:
                        state_text += "R"
                    if line.flags & KeyInstanceFlag.IS_INVOICE:
                        state_text += "I"
                    if state_text:
                        return state_text
                elif column == KEY_COLUMN:
                    return get_key_text(line)
                elif column == SCRIPT_COLUMN:
                    return ScriptType(line.script_type).name
                elif column == LABEL_COLUMN:
                    return self._view._account.get_keyinstance_label(
                        line.keyinstance_id)
                elif column == USAGES_COLUMN:
                    return line.match_count
                elif column == BALANCE_COLUMN:
                    return app_state.format_amount(line.total_value,
                                                   whitespaces=True)
                elif column == FIAT_BALANCE_COLUMN:
                    fx = app_state.fx
                    rate = fx.exchange_rate()
                    return fx.value_str(line.total_value, rate)
            elif role == Qt.FontRole:
                if column in (BALANCE_COLUMN, FIAT_BALANCE_COLUMN):
                    return self._view._monospace_font

            elif role == Qt.BackgroundRole:
                # This does not work because the CSS overrides it.
                pass
            elif role == Qt.TextAlignmentRole:
                if column in (TYPE_COLUMN, STATE_COLUMN):
                    return Qt.AlignCenter
                elif column in (BALANCE_COLUMN, FIAT_BALANCE_COLUMN,
                                USAGES_COLUMN):
                    return Qt.AlignRight | Qt.AlignVCenter
                return Qt.AlignVCenter

            elif role == Qt.ToolTipRole:
                if column == TYPE_COLUMN:
                    return _("Key")
                elif column == STATE_COLUMN:
                    if line.flags & KeyInstanceFlag.ALLOCATED_MASK:
                        return _("This is an allocated address")
                    elif not line.flags & KeyInstanceFlag.IS_ACTIVE:
                        return _("This is an inactive address")
                elif column == KEY_COLUMN:
                    key_id = line.keyinstance_id
                    masterkey_id = line.masterkey_id
                    derivation_text = self._view._account.get_derivation_path_text(
                        key_id)
                    return "\n".join([
                        f"Key instance id: {key_id}",
                        f"Master key id: {masterkey_id}",
                        f"Derivation path {derivation_text}",
                    ])

            elif role == Qt.EditRole:
                if column == LABEL_COLUMN:
                    return self._view._account.get_keyinstance_label(
                        line.keyinstance_id)
コード例 #11
0
    def data(self, model_index: QModelIndex, role: int) -> QVariant:
        row = model_index.row()
        column = model_index.column()
        if row >= len(self._data):
            return None
        if column >= len(self._column_names):
            return None

        if model_index.isValid():
            line = self._data[row]

            # First check the custom sort role.
            if role == QT_SORT_ROLE:
                if column == DATE_ADDED_COLUMN:
                    return line.date_added
                elif column == DATE_UPDATED_COLUMN:
                    return line.date_updated
                elif column == STATE_COLUMN:
                    if line.flags & TxFlags.StateDispatched:
                        return 0
                    elif line.flags & TxFlags.StateReceived:
                        return 2
                    elif line.flags & TxFlags.StateSigned:
                        return 1
                    else:
                        return 3
                elif column == LABEL_COLUMN:
                    return self._view._wallet.get_transaction_label(line.hash)
                elif column in (VALUE_COLUMN, FIAT_VALUE_COLUMN):
                    return line.value

            elif role == Qt.DecorationRole:
                if column == LABEL_COLUMN and line.flags & TxFlags.PaysInvoice:
                    return self._view._invoice_icon

            elif role == Qt.DisplayRole:
                if column == DATE_ADDED_COLUMN:
                    return (format_time(line.date_added, _("unknown"))
                        if line.date_added else _("unknown"))
                elif column == DATE_UPDATED_COLUMN:
                    return (format_time(line.date_updated, _("unknown"))
                        if line.date_updated else _("unknown"))

                elif column == STATE_COLUMN:
                    if line.flags & TxFlags.StateDispatched:
                        return _("Dispatched")
                    elif line.flags & TxFlags.StateReceived:
                        return _("Received")
                    elif line.flags & TxFlags.StateSigned:
                        return _("Signed")
                    return line.flags
                elif column == LABEL_COLUMN:
                    return self._view._wallet.get_transaction_label(line.hash)
                elif column == VALUE_COLUMN:
                    return app_state.format_amount(line.value, whitespaces=True)
                elif column == FIAT_VALUE_COLUMN:
                    fx = app_state.fx
                    rate = fx.exchange_rate()
                    return fx.value_str(line.value, rate)

            elif role == Qt.FontRole:
                if column in (VALUE_COLUMN, FIAT_VALUE_COLUMN):
                    return self._monospace_font

            elif role == Qt.TextAlignmentRole:
                if column in (VALUE_COLUMN, FIAT_VALUE_COLUMN):
                    return Qt.AlignRight | Qt.AlignVCenter
                return Qt.AlignVCenter

            elif role == Qt.ToolTipRole:
                if column == LABEL_COLUMN:
                    if line.flags & TxFlags.PaysInvoice:
                        return _("This transaction is associated with an invoice.")
                elif column == STATE_COLUMN:
                    if line.flags & TxFlags.StateDispatched:
                        return _("This transaction has been sent to the network, but has not "
                            "cleared yet.")
                    elif line.flags & TxFlags.StateReceived:
                        return _("This transaction has been received from another party, but "
                            "has not been broadcast yet.")
                    elif line.flags & TxFlags.StateSigned:
                        return _("This transaction has been signed, but has not been broadcast "
                            "yet.")

            elif role == Qt.EditRole:
                if column == LABEL_COLUMN:
                    return self._view._wallet.get_transaction_label(line.hash)
コード例 #12
0
ファイル: wallet_api.py プロジェクト: wagnehc/electrumsv
 def get_base_amount(self, sv_value: int) -> str:
     return app_state.format_amount(sv_value)
コード例 #13
0
ファイル: invoice_dialog.py プロジェクト: wagnehc/electrumsv
    def __init__(self, main_window: 'ElectrumWindow', row: InvoiceRow) -> None:
        super().__init__(main_window, _("Invoice"))

        self.setMinimumWidth(400)

        self._main_window = weakref.proxy(main_window)

        self._pr = pr = PaymentRequest.from_json(row.invoice_data)

        state = row.flags & PaymentFlag.STATE_MASK
        if state & PaymentFlag.UNPAID and has_expired(row.date_expires):
            state = PaymentFlag.EXPIRED

        total_amount = 0
        for output in pr.outputs:
            total_amount += output.amount

        vbox = QVBoxLayout(self)
        form = FormSectionWidget(minimum_label_width=120)
        form.add_row(_('Type'), QLabel(_("BIP270")))
        form.add_row(_("State"), QLabel(pr_tooltips.get(state, _("Unknown"))))
        form.add_row(
            _('Amount'),
            QLabel(
                app_state.format_amount(output.amount) + " " +
                app_state.base_unit()))
        form.add_row(_('Memo'), QLabel(row.description))
        form.add_row(_('Date Created'),
                     QLabel(format_time(pr.creation_timestamp, _("Unknown"))))
        form.add_row(_('Date Received'),
                     QLabel(format_time(row.date_created, _("Unknown"))))
        if row.date_expires:
            form.add_row(_('Date Expires'),
                         QLabel(format_time(row.date_expires, _("Unknown"))))
        vbox.addWidget(form)

        self._table = table = ButtonsTableWidget()
        table.setSelectionBehavior(QAbstractItemView.SelectRows)
        table.setSelectionMode(QAbstractItemView.SingleSelection)

        vh = table.verticalHeader()
        vh.setSectionResizeMode(QHeaderView.ResizeToContents)
        vh.hide()

        table.setColumnCount(3)
        table.setContextMenuPolicy(Qt.CustomContextMenu)
        table.customContextMenuRequested.connect(self._on_table_menu)
        table.setHorizontalHeaderLabels(self._table_column_names)
        table.setRowCount(len(pr.outputs))
        # table.addButton("icons8-copy-to-clipboard-32.png", f,
        #     _("Copy all listed destinations to the clipboard"))
        # table.addButton("icons8-save-as-32-windows.png", f,
        #     _("Save the listed destinations to a file"))
        hh = table.horizontalHeader()
        hh.setStretchLastSection(True)

        for row, output in enumerate(pr.outputs):
            label = QLabel(
                app_state.format_amount(output.amount) + " " +
                app_state.base_unit())
            table.setCellWidget(row, 0, label)

            table.setCellWidget(row, 1, QLabel(output.description))

            kind = classify_output_script(output.script, Net.COIN)
            text = script_to_display_text(output.script, kind)
            table.setCellWidget(row, 2, QLabel(text))

            vbox.addWidget(table, 1)

        def do_export():
            fn = self._main_window.getSaveFileName(_("Export invoice to file"),
                                                   "*.bip270.json")
            if not fn:
                return
            with open(fn, 'w') as f:
                data = f.write(row.invoice_data)
            self._main_window.show_message(_('Invoice saved as' + ' ' + fn))

        exportButton = EnterButton(_('Export'), do_export)

        def do_delete():
            if self.question(
                    _('Are you sure you want to delete this invoice?'),
                    title=_("Delete invoice"),
                    icon=QMessageBox.Warning):
                self._main_window._send_view._invoice_list._delete_invoice(
                    row.invoice_id)
                self.close()

        deleteButton = EnterButton(_('Delete'), do_delete)

        vbox.addLayout(Buttons(exportButton, deleteButton, CloseButton(self)))
コード例 #14
0
    def _update_io(self, i_table: MyTreeWidget, o_table: MyTreeWidget) -> None:
        def get_xtxoutput_account(
                output: XTxOutput) -> Optional[AbstractAccount]:
            if not output.x_pubkeys:
                return None
            for x_pubkey in output.x_pubkeys:
                result = self._main_window._wallet.resolve_xpubkey(x_pubkey)
                if result is not None:
                    account, keyinstance_id = result
                    if account.get_script_for_id(
                            keyinstance_id) == output.script_pubkey:
                        return account
                    return None
            return None

        known_txos: Set[Tuple[bytes, int]] = set()
        if self._account is not None:
            known_txos = set(self._account._utxos) | set(self._account._stxos)

        def compare_key_path(account: AbstractAccount, txo_key: TxoKeyType,
                             leading_path: Sequence[int]) -> bool:
            utxo = account._utxos.get(txo_key)
            if utxo is not None:
                key_path = account.get_derivation_path(utxo.keyinstance_id)
                if key_path is not None and key_path[:len(leading_path
                                                          )] == leading_path:
                    return True
            stxo_keyinstance_id = account._stxos.get(txo_key)
            if stxo_keyinstance_id is not None:
                key_path = account.get_derivation_path(stxo_keyinstance_id)
                if key_path is not None and key_path[:len(leading_path
                                                          )] == leading_path:
                    return True
            return False

        def name_for_account(account: AbstractAccount) -> str:
            name = account.display_name()
            return f"{account.get_id()}: {name}"

        for tx_index, txin in enumerate(self.tx.inputs):
            account_name = ""
            source_text = ""
            amount_text = ""
            is_receiving = is_change = False

            if txin.is_coinbase():
                source_text = "<coinbase>"
            else:
                prev_hash_hex = hash_to_hex_str(txin.prev_hash)
                source_text = f"{prev_hash_hex}:{txin.prev_idx}"
                if self._account is not None:
                    txo_key = TxoKeyType(txin.prev_hash, txin.prev_idx)
                    is_receiving = compare_key_path(self._account, txo_key,
                                                    RECEIVING_SUBPATH)
                    is_change = compare_key_path(self._account, txo_key,
                                                 CHANGE_SUBPATH)
                    account_name = name_for_account(self._account)
                # TODO(rt12): When does a txin have a value? Loaded incomplete transactions only?
                if txin.value is not None:
                    amount_text = app_state.format_amount(txin.value,
                                                          whitespaces=True)

            item = QTreeWidgetItem(
                [str(tx_index), account_name, source_text, amount_text])
            # item.setData(0, Qt.UserRole, row.paymentrequest_id)
            if is_receiving:
                item.setBackground(2, self._receiving_brush)
            if is_change:
                item.setBackground(2, self._change_brush)
            item.setTextAlignment(3, Qt.AlignRight | Qt.AlignVCenter)
            item.setFont(3, self._monospace_font)
            i_table.addTopLevelItem(item)

        for tx_index, tx_output in enumerate(self.tx.outputs):
            text, _kind = tx_output_to_display_text(tx_output)
            if isinstance(_kind, Unknown_Output):
                text = script_bytes_to_asm(tx_output.script_pubkey)

            account = get_xtxoutput_account(tx_output)
            accounts: List[AbstractAccount] = []
            if account is not None:
                accounts.append(account)
            if self._account is not None and account is not self._account:
                accounts.append(self._account)

            account_name = ""
            is_receiving = is_change = False
            txo_key = TxoKeyType(self._tx_hash, tx_index)
            for account in accounts:
                if txo_key in account._stxos or txo_key in account._utxos:
                    is_receiving = compare_key_path(account, txo_key,
                                                    RECEIVING_SUBPATH)
                    is_change = compare_key_path(account, txo_key,
                                                 CHANGE_SUBPATH)
                    account_name = name_for_account(account)
                    break

            amount_text = app_state.format_amount(tx_output.value,
                                                  whitespaces=True)

            item = QTreeWidgetItem(
                [str(tx_index), account_name, text, amount_text])
            # item.setData(0, Qt.UserRole, row.paymentrequest_id)
            if is_receiving:
                item.setBackground(2, self._receiving_brush)
            if is_change:
                item.setBackground(2, self._change_brush)
            item.setTextAlignment(3, Qt.AlignRight | Qt.AlignVCenter)
            item.setFont(3, self._monospace_font)
            o_table.addTopLevelItem(item)
コード例 #15
0
    def _on_update_history_list(self) -> None:
        item = self.currentItem()
        current_tx_hash = item.data(Columns.STATUS,
                                    self.TX_ROLE) if item else None
        self.clear()
        if self._account is None:
            return
        fx = app_state.fx
        if fx:
            fx.history_used_spot = False
        local_height = self._wallet.get_local_height()
        server_height = self._main_window.network.get_server_height() if self._main_window.network \
            else 0
        header_at_height = app_state.headers.header_at_height
        chain = app_state.headers.longest_chain()
        missing_header_heights = []
        items = []
        for line, balance in self._account.get_history(self.get_domain()):
            tx_id = hash_to_hex_str(line.tx_hash)
            conf = 0 if line.height <= 0 else max(
                local_height - line.height + 1, 0)
            timestamp = False
            if line.height > 0:
                try:
                    timestamp = header_at_height(chain, line.height).timestamp
                except MissingHeader:
                    if line.height <= server_height:
                        missing_header_heights.append(line.height)
                    else:
                        logger.debug("Unable to backfill header at %d (> %d)",
                                     line.height, server_height)
            status = get_tx_status(self._account, line.tx_hash, line.height,
                                   conf, timestamp)
            status_str = get_tx_desc(status, timestamp)
            v_str = app_state.format_amount(line.value_delta,
                                            True,
                                            whitespaces=True)
            balance_str = app_state.format_amount(balance, whitespaces=True)
            label = self._wallet.get_transaction_label(line.tx_hash)
            entry = [None, tx_id, status_str, label, v_str, balance_str]
            if fx and fx.show_history():
                date = timestamp_to_datetime(
                    time.time() if conf <= 0 else timestamp)
                for amount in [line.value_delta, balance]:
                    text = fx.historical_value_str(amount, date)
                    entry.append(text)

            item = SortableTreeWidgetItem(entry)
            # If there is no text,
            item.setIcon(Columns.STATUS, get_tx_icon(status))
            item.setToolTip(Columns.STATUS, get_tx_tooltip(status, conf))
            if line.tx_flags & TxFlags.PaysInvoice:
                item.setIcon(Columns.DESCRIPTION, self.invoiceIcon)
            for i in range(len(entry)):
                if i > Columns.DESCRIPTION:
                    item.setTextAlignment(i, Qt.AlignRight | Qt.AlignVCenter)
                else:
                    item.setTextAlignment(i, Qt.AlignLeft | Qt.AlignVCenter)
                if i != Columns.DATE:
                    item.setFont(i, self.monospace_font)
            if line.value_delta and line.value_delta < 0:
                item.setForeground(Columns.DESCRIPTION, self.withdrawalBrush)
                item.setForeground(Columns.AMOUNT, self.withdrawalBrush)
            item.setData(Columns.STATUS, SortableTreeWidgetItem.DataRole,
                         line.sort_key)
            item.setData(Columns.DATE, SortableTreeWidgetItem.DataRole,
                         line.sort_key)
            item.setData(Columns.STATUS, self.ACCOUNT_ROLE, self._account_id)
            item.setData(Columns.STATUS, self.TX_ROLE, line.tx_hash)

            # self.insertTopLevelItem(0, item)
            if current_tx_hash == line.tx_hash:
                self.setCurrentItem(item)

            items.append(item)

        self.addTopLevelItems(items)

        if len(missing_header_heights) and self._main_window.network:
            self._main_window.network.backfill_headers_at_heights(
                missing_header_heights)