Beispiel #1
0
 def open_be_url(link):
     if link:
         try:
             kind, thing = link.split(':')
             url = web.BE_URL(self.main_window.config, kind, thing)
         except:
             url = None
         if url:
             webopen( url )
         else:
             self.show_error(_('Unable to open in block explorer. Please be sure your block explorer is configured correctly in preferences.'))
Beispiel #2
0
    def create_menu(self, position):
        item = self.currentItem()
        if not item:
            return
        column = self.currentColumn()
        tx_hash = item.data(0, Qt.UserRole)
        if not tx_hash:
            return
        if column == 0:
            column_title = "ID"
            column_data = tx_hash
        else:
            column_title = self.headerItem().text(column)
            column_data = item.text(column)

        tx_URL = web.BE_URL(self.config, 'tx', tx_hash)
        height, conf, timestamp = self.wallet.get_tx_height(tx_hash)
        tx = self.wallet.transactions.get(tx_hash)
        if not tx:
            return  # this happens sometimes on wallet synch when first starting up.
        is_unconfirmed = height <= 0
        pr_key = self.wallet.invoices.paid.get(tx_hash)

        menu = QMenu()

        menu.addAction(
            _("&Copy {}").format(column_title),
            lambda: self.parent.app.clipboard().setText(column_data.strip()))
        if column in self.editable_columns:
            # We grab a fresh reference to the current item, as it has been deleted in a reported issue.
            menu.addAction(
                _("&Edit {}").format(column_title),
                lambda: self.currentItem() and self.editItem(
                    self.currentItem(), column))
        label = self.wallet.get_label(tx_hash) or None
        menu.addAction(_("&Details"),
                       lambda: self.parent.show_transaction(tx, label))
        if is_unconfirmed and tx:
            child_tx = self.wallet.cpfp(tx, 0)
            if child_tx:
                menu.addAction(_("Child pays for parent"),
                               lambda: self.parent.cpfp(tx, child_tx))
        if pr_key:
            menu.addAction(self.invoiceIcon, _("View invoice"),
                           lambda: self.parent.show_invoice(pr_key))
        if tx_URL:
            menu.addAction(_("View on block explorer"),
                           lambda: webopen(tx_URL))

        run_hook("history_list_context_menu_setup", self, menu, item,
                 tx_hash)  # Plugins can modify menu

        menu.exec_(self.viewport().mapToGlobal(position))
Beispiel #3
0
 def _add_addr_to_io_menu_lists_for_widget(self, addr, show_list, copy_list, widget):
     if hasattr(addr, 'to_ui_string'):
         addr_text = addr.to_ui_string()
         if isinstance(addr, Address) and self.wallet.is_mine(addr):
             show_list += [ ( _("Address Details"), lambda: self._open_internal_link(addr_text) ) ]
             addr_URL = web.BE_URL(self.main_window.config, 'addr', addr)
             if addr_URL:
                 show_list += [ ( _("View on block explorer"), lambda: webopen(addr_URL) ) ]
         if isinstance(addr, ScriptOutput):
             action_text = _("Copy Script Text")
         elif isinstance(addr, PublicKey):
             action_text = _("Copy Public Key")
         else:
             action_text = _("Copy Address")
         copy_list += [ ( action_text, lambda: self._copy_to_clipboard(addr_text, widget) ) ]
         # also add script hex copy to clipboard
         if isinstance(addr, ScriptOutput):
             hex_text = addr.to_script().hex() or ''
             if hex_text:
                 copy_list += [ ( _("Copy Script Hex"), lambda: self._copy_to_clipboard(hex_text, widget) ) ]
Beispiel #4
0
 def open_link(link):
     if Address.is_valid(link):
         addr = Address.from_string(link)
         if wallet.is_mine(addr):
             parent.show_address(addr)
         else:
             addr_URL = web.BE_URL(parent.config, 'addr', addr)
             if addr_URL:
                 webopen(addr_URL)
         return
     if link.startswith('http'):
         webopen(link)
     elif len(link) == 64:  # 64 character txid
         tx = wallet.transactions.get(link)
         if tx:
             parent.show_transaction(tx, tx_desc=wallet.get_label(link))
         else:
             parent.do_process_from_txid(txid=link,
                                         tx_desc=wallet.get_label(link))
         return
Beispiel #5
0
    def create_menu(self, position):
        if self.picker:
            # picker mode has no menu
            return
        from oregano.wallet import Multisig_Wallet
        is_multisig = isinstance(self.wallet, Multisig_Wallet)
        can_delete = self.wallet.can_delete_address()
        selected = self.selectedItems()
        multi_select = len(selected) > 1
        addrs = [item.data(0, self.DataRoles.address) for item in selected]
        if not addrs:
            return
        addrs = [addr for addr in addrs if isinstance(addr, Address)]

        menu = QMenu()

        where_to_insert_dupe_copy_cash_account = None

        def doCopy(txt):
            txt = txt.strip()
            self.parent.copy_to_clipboard(txt)

        col = self.currentColumn()
        column_title = self.headerItem().text(col)

        if not multi_select:
            item = self.itemAt(position)
            if not item:
                return
            if not addrs:
                item.setExpanded(not item.isExpanded())
                return
            addr = addrs[0]

            alt_copy_text, alt_column_title = None, None
            if col == 0:
                copy_text = addr.to_full_ui_string()
                if Address.FMT_UI == Address.FMT_LEGACY:
                    alt_copy_text, alt_column_title = addr.to_full_string(
                        Address.FMT_CASHADDR), _('Cash Address')
                else:
                    alt_copy_text, alt_column_title = addr.to_full_string(
                        Address.FMT_LEGACY), _('Legacy Address')
            else:
                copy_text = item.text(col)
            menu.addAction(
                _("Copy {}").format(column_title), lambda: doCopy(copy_text))
            if alt_copy_text and alt_column_title:
                # Add 'Copy Legacy Address' and 'Copy Cash Address' alternates if right-click is on column 0
                menu.addAction(
                    _("Copy {}").format(alt_column_title),
                    lambda: doCopy(alt_copy_text))
            a = menu.addAction(
                _('Details') + "...", lambda: self.parent.show_address(addr))
            if col == 0:
                where_to_insert_dupe_copy_cash_account = a
            if col in self.editable_columns:
                menu.addAction(
                    _("Edit {}").format(column_title),
                    lambda: self.editItem(
                        self.itemAt(
                            position
                        ),  # NB: C++ item may go away if this widget is refreshed while menu is up -- so need to re-grab and not store in lamba. See #953
                        col))
            a = menu.addAction(_("Request payment"),
                               lambda: self.parent.receive_at(addr))
            if self.wallet.get_num_tx(addr) or self.wallet.has_payment_request(
                    addr):
                # This address cannot be used for a payment request because
                # the receive tab will refuse to display it and will instead
                # create a request with a new address, if we were to call
                # self.parent.receive_at(addr). This is because the recieve tab
                # now strongly enforces no-address-reuse. See #1552.
                a.setDisabled(True)
            if self.wallet.can_export():
                menu.addAction(_("Private key"),
                               lambda: self.parent.show_private_key(addr))
            if not is_multisig and not self.wallet.is_watching_only():
                menu.addAction(
                    _("Sign/verify message") + "...",
                    lambda: self.parent.sign_verify_message(addr))
                menu.addAction(
                    _("Encrypt/decrypt message") + "...",
                    lambda: self.parent.encrypt_message(addr))
            if can_delete:
                menu.addAction(_("Remove from wallet"),
                               lambda: self.parent.remove_address(addr))
            addr_URL = web.BE_URL(self.config, 'addr', addr)
            if addr_URL:
                menu.addAction(_("View on block explorer"),
                               lambda: webopen(addr_URL))
        else:
            # multi-select
            if col > -1:
                texts, alt_copy, alt_copy_text = None, None, None
                if col == 0:  # address column
                    texts = [a.to_ui_string() for a in addrs]
                    # Add additional copy option: "Address, Balance (n)"
                    alt_copy = _("Copy {}").format(
                        _("Address") + ", " +
                        _("Balance")) + f" ({len(addrs)})"
                    alt_copy_text = "\n".join([
                        a.to_ui_string() + ", " + self.parent.format_amount(
                            sum(self.wallet.get_addr_balance(a)))
                        for a in addrs
                    ])
                else:
                    texts = [i.text(col).strip() for i in selected]
                    texts = [t for t in texts if t]  # omit empty items
                if texts:
                    copy_text = '\n'.join(texts)
                    menu.addAction(
                        _("Copy {}").format(column_title) + f" ({len(texts)})",
                        lambda: doCopy(copy_text))
                if alt_copy and alt_copy_text:
                    menu.addAction(alt_copy, lambda: doCopy(alt_copy_text))

        freeze = self.parent.set_frozen_state
        if any(self.wallet.is_frozen(addr) for addr in addrs):
            menu.addAction(_("Unfreeze"), partial(freeze, addrs, False))
        if not all(self.wallet.is_frozen(addr) for addr in addrs):
            menu.addAction(_("Freeze"), partial(freeze, addrs, True))

        coins = self.wallet.get_spendable_coins(domain=addrs,
                                                config=self.config)
        if coins:
            menu.addAction(_("Spend from"),
                           partial(self.parent.spend_coins, coins))

        run_hook('address_list_context_menu_setup', self, menu, addrs)

        # Add Cash Accounts section at the end, if relevant
        if not multi_select:
            ca_list = item.data(0, self.DataRoles.cash_accounts)
            menu.addSeparator()
            a1 = menu.addAction(_("Cash Accounts"), lambda: None)
            a1.setDisabled(True)
            if ca_list:
                ca_default = self._ca_get_default(ca_list)
                for ca_info in ca_list:
                    ca_text = self.wallet.cashacct.fmt_info(
                        ca_info, ca_info.minimal_chash)
                    ca_text_em = self.wallet.cashacct.fmt_info(
                        ca_info, ca_info.minimal_chash, emoji=True)
                    m = menu.addMenu(ca_info.emoji + " " + ca_text)
                    a_ca_copy = m.addAction(
                        _("Copy Cash Account"),
                        lambda x=None, text=ca_text_em: doCopy(text))
                    a = m.addAction(
                        _("Details") + "...",
                        lambda x=None, ca_text=ca_text: cashacctqt.
                        cash_account_detail_dialog(self.parent, ca_text))
                    a = m.addAction(_("View registration tx") + "...",
                                    lambda x=None, ca=ca_info: self.parent.
                                    do_process_from_txid(txid=ca.txid))
                    a = a_def = m.addAction(_("Make default for address"),
                                            lambda x=None, ca=ca_info: self.
                                            _ca_set_default(ca, True))
                    if ca_info == ca_default:
                        if where_to_insert_dupe_copy_cash_account and a_ca_copy:
                            # insert a dupe of "Copy Cash Account" for the default cash account for this address in the top-level menu
                            menu.insertAction(
                                where_to_insert_dupe_copy_cash_account,
                                a_ca_copy)
                        m.setTitle(m.title() + "    " + "★")
                        a_def.setDisabled(True)
                        a_def.setCheckable(True)
                        a_def.setChecked(True)
                        a_def.setText(_("Is default for address"))
            else:
                a1.setText(_("No Cash Accounts"))
            a_new = menu.addAction(_("Register new..."),
                                   lambda x=None, addr=addr: self.parent.
                                   register_new_cash_account(addr))
            a_new.setIcon(__class__._cashacct_icon)

        run_hook('receive_menu', menu, addrs, self.wallet)
        menu.exec_(self.viewport().mapToGlobal(position))
Beispiel #6
0
    def create_menu(self, position):
        menu = QMenu()
        selected = self.selectedItems()
        i2c = self._i2c
        ca_unverified = self._get_ca_unverified(include_temp=False)
        if selected:
            names = [item.text(1) for item in selected]
            keys = [i2c(item) for item in selected]
            payable_keys = [k for k in keys if k.type != 'cashacct_T']
            deletable_keys = [k for k in keys if k.type in contact_types]
            needs_verif_keys = [k for k in keys if k in ca_unverified]
            column = self.currentColumn()
            column_title = self.headerItem().text(column)
            column_data = '\n'.join([item.text(column) for item in selected])
            item = self.currentItem()
            typ = i2c(item).type if item else 'unknown'
            ca_info = None
            if item and typ in ('cashacct', 'cashacct_W'):
                ca_info = self.wallet.cashacct.get_verified(i2c(item).name)
                if column == 1 and len(selected) == 1:
                    # hack .. for Cash Accounts just say "Copy Cash Account"
                    column_title = _('Cash Account')
                    if ca_info:
                        column_data = self.wallet.cashacct.fmt_info(ca_info,
                                                                    emoji=True)
            if len(selected) > 1:
                column_title += f" ({len(selected)})"
            menu.addAction(
                _("Copy {}").format(column_title),
                lambda: self.parent.app.clipboard().setText(column_data))
            if item and column in self.editable_columns and self.on_permit_edit(
                    item, column):
                key = item.data(0, self.DataRoles.Contact)
                # this key & find_item business is so we don't hold a reference
                # to the ephemeral item, which may be deleted while the
                # context menu is up.  Accessing the item after on_update runs
                # means the item is deleted and you get a C++ object deleted
                # runtime error.
                menu.addAction(
                    _("Edit {}").format(column_title),
                    lambda: self._on_edit_item(key, column))
            a = menu.addAction(
                _("Pay to"), lambda: self.parent.payto_contacts(payable_keys))
            if needs_verif_keys or not payable_keys:
                a.setDisabled(True)
            a = menu.addAction(
                _("Delete"),
                lambda: self.parent.delete_contacts(deletable_keys))
            if not deletable_keys:
                a.setDisabled(True)
            # Add sign/verify and encrypt/decrypt menu - but only if just 1 thing selected
            if len(keys) == 1 and Address.is_valid(keys[0].address):
                signAddr = Address.from_string(keys[0].address)
                a = menu.addAction(
                    _("Sign/verify message") + "...",
                    lambda: self.parent.sign_verify_message(signAddr))
                if signAddr.kind != Address.ADDR_P2PKH:
                    a.setDisabled(
                        True
                    )  # We only allow this for P2PKH since it makes no sense for P2SH (ambiguous public key)
            URLs = [
                web.BE_URL(self.config, 'addr',
                           Address.from_string(key.address)) for key in keys
                if Address.is_valid(key.address)
            ]
            a = menu.addAction(_("View on block explorer"),
                               lambda: [URL and webopen(URL) for URL in URLs])
            if not any(URLs):
                a.setDisabled(True)
            if ca_info:
                menu.addAction(
                    _("View registration tx..."),
                    lambda: self.parent.do_process_from_txid(
                        txid=ca_info.txid,
                        tx_desc=self.wallet.get_label(ca_info.txid)))
                if typ in ('cashacct_W', 'cashacct'):
                    _contact_d = i2c(item)
                    menu.addAction(
                        _("Details..."),
                        lambda: cashacctqt.cash_account_detail_dialog(
                            self.parent, _contact_d.name))
            menu.addSeparator()

        menu.addAction(self.icon_cashacct,
                       _("Add Contact") + " - " + _("Cash Account"),
                       self.new_cash_account_contact_dialog)
        menu.addAction(self.icon_contacts,
                       _("Add Contact") + " - " + _("Address"),
                       self.parent.new_contact_dialog)
        menu.addSeparator()
        menu.addAction(self.icon_cashacct, _("Register Cash Account..."),
                       self.parent.register_new_cash_account)
        menu.addSeparator()
        menu.addAction(
            QIcon(":icons/import.svg" if not ColorScheme.dark_scheme else
                  ":icons/import_dark_theme.svg"), _("Import file"),
            self.import_contacts)
        if not self.parent.contacts.empty:
            menu.addAction(
                QIcon(":icons/save.svg" if not ColorScheme.dark_scheme else
                      ":icons/save_dark_theme.svg"), _("Export file"),
                self.export_contacts)

        menu.addSeparator()
        a = menu.addAction(_("Show My Cash Accounts"),
                           self.toggle_show_my_cashaccts)
        a.setCheckable(True)
        a.setChecked(self.show_my_cashaccts)

        if ca_unverified:

            def kick_off_verify():
                bnums = set()
                for contact in ca_unverified:
                    tup = self.wallet.cashacct.parse_string(contact.name)
                    if not tup:
                        continue
                    bnums.add(tup[1])  # number
                ret = cashacctqt.verify_multiple_blocks(
                    bnums, self.parent, self.wallet)
                if ret is None:
                    # user cancel
                    return
                verified = ca_unverified - self._get_ca_unverified()
                if not verified:
                    self.parent.show_error(
                        _("Cash Account verification failure"))

            menu.addSeparator()
            num = len(ca_unverified)
            a = menu.addAction(
                self.icon_unverif,
                ngettext("Verify {count} Cash Account",
                         "Verify {count} Cash Accounts",
                         num).format(count=num), kick_off_verify)
            if not self.wallet.network:
                a.setDisabled(True)

        run_hook('create_contact_menu', menu, selected)
        menu.exec_(self.viewport().mapToGlobal(position))