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.'))
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))
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) ) ]
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
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))
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))