def create_menu(self, position): items = self.selected_in_column(0) if len(items) > 1: keys = [item.data(ROLE_KEY) for item in items] menu = QMenu(self) menu.addAction(_("Delete requests"), lambda: self.parent.delete_requests(keys)) menu.exec_(self.viewport().mapToGlobal(position)) return idx = self.indexAt(position) # TODO use siblingAtColumn when min Qt version is >=5.11 item = self.item_from_index(idx.sibling(idx.row(), self.Columns.DATE)) if not item: return key = item.data(ROLE_KEY) req = self.wallet.get_request(key) if req is None: self.update() return menu = QMenu(self) self.add_copy_menu(menu, idx) URI = self.wallet.get_request_URI(req) menu.addAction(_("Copy Request"), lambda: self.parent.do_copy(URI, title='Dash URI')) menu.addAction( _("Copy Address"), lambda: self.parent.do_copy(req.get_address(), title='Dash Address')) #if 'view_url' in req: # menu.addAction(_("View in web browser"), lambda: webopen(req['view_url'])) menu.addAction(_("Delete"), lambda: self.parent.delete_requests([key])) run_hook('receive_list_menu', menu, key) menu.exec_(self.viewport().mapToGlobal(position))
def __init__(self, text=None): ButtonsTextEdit.__init__(self, text) self.setReadOnly(1) icon = "qrcode.png" self.addButton(icon, self.qr_show, _("Show as QR code")) run_hook('show_text_edit', self)
def __init__(self, text=None, *, config: SimpleConfig): ButtonsTextEdit.__init__(self, text) self.config = config self.setReadOnly(True) icon = "qrcode.png" self.addButton(icon, self.qr_show, _("Show as QR code")) run_hook('show_text_edit', self)
def __init__(self, text="", allow_multi=False): ButtonsTextEdit.__init__(self, text) self.allow_multi = allow_multi self.setReadOnly(0) self.addButton("file.png", self.file_input, _("Read file")) icon = "camera_white.png" if ColorScheme.dark_scheme else "camera_dark.png" self.addButton(icon, self.qr_input, _("Read QR code")) run_hook('scan_text_edit', self)
def close_window(self, window: ElectrumWindow): if window in self.windows: self.windows.remove(window) self.build_tray_menu() # save wallet path of last open window if not self.windows: self.config.save_last_wallet(window.wallet) run_hook('on_close_window', window) self.daemon.stop_wallet(window.wallet.storage.path)
def __init__(self, parent=None, msg=None): msg = msg or _('Please enter your password') WindowModalDialog.__init__(self, parent, _("Enter Password")) self.pw = pw = PasswordLineEdit() vbox = QVBoxLayout() vbox.addWidget(QLabel(msg)) grid = QGridLayout() grid.setSpacing(8) grid.addWidget(QLabel(_('Password')), 1, 0) grid.addWidget(pw, 1, 1) vbox.addLayout(grid) vbox.addLayout(Buttons(CancelButton(self), OkButton(self))) self.setLayout(vbox) run_hook('password_dialog', pw, grid, 1)
def update_tx(self): try: # make unsigned transaction tx = self.make_tx() except NotEnoughFunds: self.warning = _("Not enough funds") self.ids.ok_button.disabled = True return except Exception as e: self.ids.ok_button.disabled = True self.app.logger.exception('') self.app.show_error(repr(e)) return self.ids.ok_button.disabled = False amount = self.amount if self.amount != '!' else tx.output_value() tx_size = tx.estimated_size() fee = tx.get_fee() self.ids.fee_label.text = self.app.format_amount_and_units(fee) feerate = Decimal(fee) / Decimal(tx_size) * 1000 # duffs/kB self.ids.feerate_label.text = f'{feerate:.1f} duffs/kB' self.ids.amount_label.text = self.app.format_amount_and_units(amount) x_fee = run_hook('get_tx_extra_fee', self.app.wallet, tx) if x_fee: x_fee_address, x_fee_amount = x_fee self.extra_fee = self.app.format_amount_and_units(x_fee_amount) else: self.extra_fee = '' fee_warning_tuple = self.app.wallet.get_tx_fee_warning( invoice_amt=amount, tx_size=tx_size, fee=fee) if fee_warning_tuple: allow_send, long_warning, short_warning = fee_warning_tuple self.warning = long_warning else: self.warning = '' self.tx = tx
def __init__(self, config: 'SimpleConfig', daemon: 'Daemon', plugins: 'Plugins'): set_language(config.get('language', get_default_language())) Logger.__init__(self) self.logger.info( f"Qt GUI starting up... Qt={QtCore.QT_VERSION_STR}, PyQt={QtCore.PYQT_VERSION_STR}" ) # Uncomment this call to verify objects are being properly # GC-ed when windows are closed #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer, # ElectrumWindow], interval=5)]) QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) if hasattr(QtCore.Qt, "AA_ShareOpenGLContexts"): QtCore.QCoreApplication.setAttribute( QtCore.Qt.AA_ShareOpenGLContexts) if hasattr(QGuiApplication, 'setDesktopFileName'): QGuiApplication.setDesktopFileName('electrum-firo.desktop') self.gui_thread = threading.current_thread() self.config = config self.daemon = daemon self.plugins = plugins self.windows = [] # type: List[ElectrumWindow] self.efilter = OpenFileEventFilter(self.windows) self.app = QElectrumApplication(sys.argv) self.app.installEventFilter(self.efilter) self.app.setWindowIcon(read_QIcon("electrum-dash.png")) # timer self.timer = QTimer(self.app) self.timer.setSingleShot(False) self.timer.setInterval(500) # msec self.network_dialog = None self.dash_net_dialog = None self.network_updated_signal_obj = QNetworkUpdatedSignalObject() self.dash_net_sobj = QDashNetSignalsObject() self._num_wizards_in_progress = 0 self._num_wizards_lock = threading.Lock() # init tray self.dark_icon = self.config.get("dark_icon", False) self.tray = QSystemTrayIcon(self.tray_icon(), None) self.tray.setToolTip('Firo Electrum') self.tray.activated.connect(self.tray_activated) self.build_tray_menu() self.tray.show() self.app.new_window_signal.connect(self.start_new_window) self.set_dark_theme_if_needed() run_hook('init_qt', self)
def create_menu(self, position): menu = QMenu() idx = self.indexAt(position) column = idx.column() or self.Columns.NAME selected_keys = [] for s_idx in self.selected_in_column(self.Columns.NAME): sel_key = self.model().itemFromIndex(s_idx).data( self.ROLE_CONTACT_KEY) selected_keys.append(sel_key) if not selected_keys or not idx.isValid(): menu.addAction(_("New contact"), lambda: self.parent.new_contact_dialog()) menu.addAction(_("Import file"), lambda: self.parent.import_contacts()) menu.addAction(_("Export file"), lambda: self.parent.export_contacts()) else: column_title = self.model().horizontalHeaderItem(column).text() column_data = '\n'.join( self.model().itemFromIndex(s_idx).text() for s_idx in self.selected_in_column(column)) menu.addAction( _("Copy {}").format(column_title), lambda: self.place_text_on_clipboard(column_data, title=column_title)) if column in self.editable_columns: item = self.model().itemFromIndex(idx) if item.isEditable(): # would not be editable if openalias persistent = QPersistentModelIndex(idx) menu.addAction( _("Edit {}").format(column_title), lambda p=persistent: self.edit(QModelIndex(p))) menu.addAction(_("Pay to"), lambda: self.parent.payto_contacts(selected_keys)) menu.addAction(_("Delete"), lambda: self.parent.delete_contacts(selected_keys)) URLs = [ block_explorer_URL(self.config, 'addr', key) for key in filter(is_address, selected_keys) ] if URLs: menu.addAction(_("View on block explorer"), lambda: [webopen(u) for u in URLs]) run_hook('create_contact_menu', menu, selected_keys) menu.exec_(self.viewport().mapToGlobal(position))
def update(self): tx = self.tx self._update_amount_label() if self.not_enough_funds: text = _("Not enough funds") c, u, x = self.wallet.get_frozen_balance() if c + u + x: text += " ({} {} {})".format( self.main_window.format_amount(c + u + x).strip(), self.main_window.base_unit(), _("are frozen")) self.toggle_send_button(False, message=text) return if not tx: return psman = self.main_window.wallet.psman if (psman.is_hw_ks and psman.is_ps_ks_inputs_in_tx(tx) and psman.is_ps_ks_encrypted()): self.password_required = True fee = tx.get_fee() self.fee_label.setText(self.main_window.format_amount_and_units(fee)) x_fee = run_hook('get_tx_extra_fee', self.wallet, tx) if x_fee: x_fee_address, x_fee_amount = x_fee self.extra_fee_label.setVisible(True) self.extra_fee_value.setVisible(True) self.extra_fee_value.setText( self.main_window.format_amount_and_units(x_fee_amount)) amount = tx.output_value( ) if self.output_value == '!' else self.output_value feerate = Decimal(fee) / Decimal( tx.estimated_size() / 1000) # duffs/kB fee_ratio = Decimal(fee) / amount if amount else 1 if feerate < self.wallet.relayfee(): msg = '\n'.join([ _("This transaction requires a higher fee, or it will not be propagated by your current server" ), _("Try to raise your transaction fee, or use a server with a lower relay fee." ) ]) self.toggle_send_button(False, message=msg) elif fee_ratio >= FEE_RATIO_HIGH_WARNING: self.toggle_send_button( True, message=_('Warning') + ': ' + _("The fee for this transaction seems unusually high.") + f'\n({fee_ratio*100:.2f}% of amount)') elif feerate > FEERATE_WARNING_HIGH_FEE: self.toggle_send_button( True, message=_('Warning') + ': ' + _("The fee for this transaction seems unusually high.") + f'\n(feerate: {feerate:.1f} duffs/kB)') else: self.toggle_send_button(True)
def update(self): if self.maybe_defer_update(): return current_key = self.current_item_user_role(col=self.Columns.NAME) self.model().clear() self.update_headers(self.__class__.headers) set_current = None for key in sorted(self.parent.contacts.keys()): contact_type, name = self.parent.contacts[key] items = [QStandardItem(x) for x in (name, key)] items[self.Columns.NAME].setEditable(contact_type != 'openalias') items[self.Columns.ADDRESS].setEditable(False) items[self.Columns.NAME].setData(key, Qt.UserRole) row_count = self.model().rowCount() self.model().insertRow(row_count, items) if key == current_key: idx = self.model().index(row_count, self.Columns.NAME) set_current = QPersistentModelIndex(idx) self.set_current_idx(set_current) # FIXME refresh loses sort order; so set "default" here: self.sortByColumn(self.Columns.NAME, Qt.AscendingOrder) self.filter() run_hook('update_contacts_tab', self)
def __init__(self, parent=None, msg=None): msg = msg or _('Please enter your password') WindowModalDialog.__init__(self, parent, _('Enter Password')) self.pw = pw = QtWidgets.QLineEdit() pw.setEchoMode(2) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(QtWidgets.QLabel(msg)) inactivity_lb = QtWidgets.QLabel(_('Inactivity timeout in minutes')) self.inactivity_sb = QtWidgets.QSpinBox() self.inactivity_sb.setMinimum(1) self.inactivity_sb.setMaximum(10) self.config = parent.config timeout = parent.config.get('console_kbd_timeout', 1) self.inactivity_sb.setValue(timeout) grid = QtWidgets.QGridLayout() grid.setSpacing(8) grid.addWidget(QtWidgets.QLabel(_('Password')), 1, 0) grid.addWidget(pw, 1, 1) grid.addWidget(inactivity_lb, 20, 0) grid.addWidget(self.inactivity_sb, 20, 1, 1, -1) vbox.addLayout(grid) vbox.addLayout(Buttons(CancelButton(self), OkButton(self))) self.setLayout(vbox) run_hook('password_dialog', pw, grid, 1)
def update(self): tx = self.tx self._update_amount_label() if self.not_enough_funds: text = self.main_window.get_text_not_enough_funds_mentioning_frozen( ) self.toggle_send_button(False, message=text) return if not tx: return psman = self.main_window.wallet.psman if (psman.is_hw_ks and psman.is_ps_ks_inputs_in_tx(tx) and psman.is_ps_ks_encrypted()): self.password_required = True self.pw_label.setVisible(self.password_required) self.pw.setVisible(self.password_required) fee = tx.get_fee() assert fee is not None self.fee_label.setText(self.main_window.format_amount_and_units(fee)) x_fee = run_hook('get_tx_extra_fee', self.wallet, tx) if x_fee: x_fee_address, x_fee_amount = x_fee self.extra_fee_label.setVisible(True) self.extra_fee_value.setVisible(True) self.extra_fee_value.setText( self.main_window.format_amount_and_units(x_fee_amount)) amount = tx.output_value( ) if self.output_value == '!' else self.output_value tx_size = tx.estimated_size() fee_warning_tuple = self.wallet.get_tx_fee_warning(invoice_amt=amount, tx_size=tx_size, fee=fee) if fee_warning_tuple: allow_send, long_warning, short_warning = fee_warning_tuple self.toggle_send_button(allow_send, message=long_warning) else: self.toggle_send_button(True)
def _do_pay_onchain(self, invoice: OnchainInvoice, is_ps=False) -> None: # make unsigned transaction outputs = invoice.outputs wallet = self.app.wallet mix_rounds = None if not is_ps else wallet.psman.mix_rounds include_ps = (mix_rounds is None) coins = wallet.get_spendable_coins(None, include_ps=include_ps, min_rounds=mix_rounds) try: tx = wallet.make_unsigned_transaction(coins=coins, outputs=outputs, min_rounds=mix_rounds) except NotEnoughFunds: self.app.show_error(_("Not enough funds")) return except Exception as e: self.logger.exception('') self.app.show_error(repr(e)) return fee = tx.get_fee() amount = sum(map(lambda x: x.value, outputs)) if '!' not in [x.value for x in outputs] else tx.output_value() msg = [ _("Amount to be sent") + ": " + self.app.format_amount_and_units(amount), _("Mining fee") + ": " + self.app.format_amount_and_units(fee), ] x_fee = run_hook('get_tx_extra_fee', self.app.wallet, tx) if x_fee: x_fee_address, x_fee_amount = x_fee msg.append(_("Additional fees") + ": " + self.app.format_amount_and_units(x_fee_amount)) feerate = Decimal(fee) / Decimal(tx.estimated_size() / 1000) # duffs/kB fee_ratio = Decimal(fee) / amount if amount else 1 if fee_ratio >= FEE_RATIO_HIGH_WARNING: msg.append(_('Warning') + ': ' + _("The fee for this transaction seems unusually high.") + f' ({fee_ratio*100:.2f}% of amount)') elif feerate > FEERATE_WARNING_HIGH_FEE: msg.append(_('Warning') + ': ' + _("The fee for this transaction seems unusually high.") + f' (feerate: {feerate:.1f} duffs/kB)') self.app.protected('\n'.join(msg), self.send_tx, (tx, invoice))
def update(self): if not self.finalized: self.update_fee_fields() self.finalize_button.setEnabled(self.can_finalize()) if self.tx is None: return self.update_io() desc = self.desc base_unit = self.main_window.base_unit() format_amount = self.main_window.format_amount format_fiat_and_units = self.main_window.format_fiat_and_units tx_details = self.wallet.get_tx_info(self.tx) tx_mined_status = tx_details.tx_mined_status exp_n = tx_details.mempool_depth_bytes amount, fee = tx_details.amount, tx_details.fee size = self.tx.estimated_size() txid = self.tx.txid() fx = self.main_window.fx tx_item_fiat = None if (self. finalized # ensures we don't use historical rates for tx being constructed *now* and txid is not None and fx.is_enabled() and amount is not None): tx_item_fiat = self.wallet.get_tx_item_fiat(tx_hash=txid, amount_sat=abs(amount), fx=fx, tx_fee=fee) self.broadcast_button.setEnabled(tx_details.can_broadcast) can_sign = not self.tx.is_complete() and \ (self.wallet.can_sign(self.tx) or bool(self.external_keypairs)) self.sign_button.setEnabled(can_sign) if self.finalized and tx_details.txid: self.tx_hash_e.setText(tx_details.txid) else: # note: when not finalized, locktime changes do not trigger # a make_tx, so the txid is unreliable, hence: self.tx_hash_e.setText(_('Unknown')) if not desc: self.tx_desc.hide() else: self.tx_desc.setText(_("Description") + ': ' + desc) self.tx_desc.show() self.status_label.setText(_('Status:') + ' ' + tx_details.status) islock = tx_details.islock timestamp = tx_mined_status.timestamp if not timestamp and islock: timestamp = islock if timestamp: dttm = datetime.datetime.fromtimestamp(timestamp) time_str = dttm.isoformat(' ')[:-3] self.date_label.setText(_("Date: {}").format(time_str)) self.date_label.show() elif exp_n is not None: text = '%.2f MB' % (exp_n / 1000000) self.date_label.setText( _('Position in mempool: {} from tip').format(text)) self.date_label.show() else: self.date_label.hide() if self.tx.locktime <= NLOCKTIME_BLOCKHEIGHT_MAX: locktime_final_str = f"LockTime: {self.tx.locktime} (height)" else: locktime_final_str = f"LockTime: {self.tx.locktime} ({datetime.datetime.fromtimestamp(self.tx.locktime)})" self.locktime_final_label.setText(locktime_final_str) if self.locktime_e.get_locktime() is None: self.locktime_e.set_locktime(self.tx.locktime) if tx_mined_status.header_hash: self.block_hash_label.setText( _("Included in block: {}").format(tx_mined_status.header_hash)) self.block_height_label.setText( _("At block height: {}").format(tx_mined_status.height)) else: self.block_hash_label.hide() self.block_height_label.hide() if amount is None: amount_str = _("Transaction unrelated to your wallet") elif amount is None: amount_str = '' else: if amount > 0: amount_str = _("Amount received:") + ' %s' % format_amount( amount) + ' ' + base_unit else: amount_str = _("Amount sent:") + ' %s' % format_amount( -amount) + ' ' + base_unit if fx.is_enabled(): if tx_item_fiat: amount_str += ' (%s)' % tx_item_fiat[ 'fiat_value'].to_ui_string() else: amount_str += ' (%s)' % format_fiat_and_units(abs(amount)) if amount_str: self.amount_label.setText(amount_str) else: self.amount_label.hide() size_str = _("Size:") + ' %d bytes' % size if fee is None: fee_str = _("Fee") + ': ' + _("unknown") else: fee_str = _("Fee") + f': {format_amount(fee)} {base_unit}' if fx.is_enabled(): if tx_item_fiat: fiat_fee_str = tx_item_fiat['fiat_fee'].to_ui_string() else: fiat_fee_str = format_fiat_and_units(fee) fee_str += f' ({fiat_fee_str})' if fee is not None: fee_rate = Decimal(fee) / size # sat/byte fee_str += ' ( %s ) ' % self.main_window.format_fee_rate( fee_rate * 1000) if isinstance(self.tx, PartialTransaction): if isinstance(self, PreviewTxDialog): invoice_amt = self.tx.output_value( ) if self.output_value == '!' else self.output_value else: invoice_amt = amount fee_warning_tuple = self.wallet.get_tx_fee_warning( invoice_amt=invoice_amt, tx_size=size, fee=fee) if fee_warning_tuple: allow_send, long_warning, short_warning = fee_warning_tuple fee_str += " - <font color={color}>{header}: {body}</font>".format( header=_('Warning'), body=short_warning, color=ColorScheme.RED.as_color().name(), ) if isinstance(self.tx, PartialTransaction): risk_of_burning_coins = ( can_sign and fee is not None and self.wallet.get_warning_for_risk_of_burning_coins_as_fees( self.tx)) self.fee_warning_icon.setToolTip(str(risk_of_burning_coins)) self.fee_warning_icon.setVisible(bool(risk_of_burning_coins)) self.fee_label.setText(fee_str) self.size_label.setText(size_str) show_psbt_only_widgets = self.finalized and isinstance( self.tx, PartialTransaction) for widget in self.psbt_only_widgets: if isinstance(widget, QMenu): widget.menuAction().setVisible(show_psbt_only_widgets) else: widget.setVisible(show_psbt_only_widgets) self.save_button.setEnabled(tx_details.can_save_as_local) if tx_details.can_save_as_local: self.save_button.setToolTip(_("Save transaction offline")) else: self.save_button.setToolTip( _("Transaction already saved or not yet signed.")) tx_type = self.tx.tx_type if tx_type == 0: self.extra_pld_lb.hide() self.extra_pld.hide() txid = self.tx.txid() if txid: tx_type, completed = self.wallet.db.get_ps_tx(txid) else: extra_payload = self.tx.extra_payload self.extra_pld.set_extra_data(tx_type, extra_payload) self.extra_pld_lb.show() self.extra_pld.show() tx_type_name = '%s: %s, ' % (_('Type'), SPEC_TX_NAMES[tx_type]) self.txid_lb.setText(tx_type_name + _('Transaction ID:')) run_hook('transaction_dialog_update', self)
def __init__(self, *, parent: 'ElectrumWindow', desc, prompt_if_unsaved, finalized: bool, external_keypairs=None): '''Transactions in the wallet will show their description. Pass desc to give a description for txs not yet in the wallet. ''' # We want to be a top-level window QDialog.__init__(self, parent=None) self.tx = None # type: Optional[Transaction] self.external_keypairs = external_keypairs self.finalized = finalized self.main_window = parent self.config = parent.config self.wallet = parent.wallet self.prompt_if_unsaved = prompt_if_unsaved self.saved = False self.desc = desc self.setMinimumWidth(640) self.resize(1200, 600) self.set_title() self.psbt_only_widgets = [] # type: List[QWidget] vbox = QVBoxLayout() self.setLayout(vbox) self.txid_lb = QLabel(_('Transaction ID:')) vbox.addWidget(self.txid_lb) self.tx_hash_e = ButtonsLineEdit() qr_show = lambda: parent.show_qrcode( str(self.tx_hash_e.text()), 'Transaction ID', parent=self) qr_icon = "qrcode.png" self.tx_hash_e.addButton(qr_icon, qr_show, _("Show as QR code")) self.tx_hash_e.setReadOnly(True) vbox.addWidget(self.tx_hash_e) self.add_tx_stats(vbox) vbox.addSpacing(10) self.inputs_header = QLabel() vbox.addWidget(self.inputs_header) self.inputs_textedit = QTextEditWithDefaultSize() vbox.addWidget(self.inputs_textedit) self.txo_color_recv = TxOutputColoring( legend=_("Receiving Address"), color=ColorScheme.GREEN, tooltip=_("Wallet receive address")) self.txo_color_change = TxOutputColoring( legend=_("Change Address"), color=ColorScheme.YELLOW, tooltip=_("Wallet change address")) outheader_hbox = QHBoxLayout() outheader_hbox.setContentsMargins(0, 0, 0, 0) vbox.addLayout(outheader_hbox) self.outputs_header = QLabel() outheader_hbox.addWidget(self.outputs_header) outheader_hbox.addStretch(2) outheader_hbox.addWidget(self.txo_color_recv.legend_label) outheader_hbox.addWidget(self.txo_color_change.legend_label) self.outputs_textedit = QTextEditWithDefaultSize() vbox.addWidget(self.outputs_textedit) self.extra_pld_lb = QLabel('Extra payload:') self.extra_pld_lb.hide() vbox.addWidget(self.extra_pld_lb) self.extra_pld = ExtraPayloadWidget() self.extra_pld.hide() vbox.addWidget(self.extra_pld) self.sign_button = b = QPushButton(_("Sign")) b.clicked.connect(self.sign) self.broadcast_button = b = QPushButton(_("Broadcast")) b.clicked.connect(self.do_broadcast) self.save_button = b = QPushButton(_("Save")) b.clicked.connect(self.save) self.cancel_button = b = QPushButton(_("Close")) b.clicked.connect(self.close) b.setDefault(True) self.export_actions_menu = export_actions_menu = QMenu() self.add_export_actions_to_menu(export_actions_menu) export_actions_menu.addSeparator() export_submenu = export_actions_menu.addMenu( _("For CoinJoin; strip privates")) self.add_export_actions_to_menu(export_submenu, gettx=self._gettx_for_coinjoin) self.psbt_only_widgets.append(export_submenu) export_submenu = export_actions_menu.addMenu( _("For hardware device; include xpubs")) self.add_export_actions_to_menu(export_submenu, gettx=self._gettx_for_hardware_device) self.psbt_only_widgets.append(export_submenu) self.export_actions_button = QToolButton() self.export_actions_button.setObjectName("blue_toolbutton") self.export_actions_button.setText(_("Export")) self.export_actions_button.setMenu(export_actions_menu) self.export_actions_button.setPopupMode(QToolButton.InstantPopup) self.finalize_button = QPushButton(_('Finalize')) self.finalize_button.clicked.connect(self.on_finalize) partial_tx_actions_menu = QMenu() ptx_merge_sigs_action = QAction(_("Merge signatures from"), self) ptx_merge_sigs_action.triggered.connect(self.merge_sigs) partial_tx_actions_menu.addAction(ptx_merge_sigs_action) self.partial_tx_actions_button = QToolButton() self.partial_tx_actions_button.setObjectName("blue_toolbutton") self.partial_tx_actions_button.setText(_("Combine")) self.partial_tx_actions_button.setMenu(partial_tx_actions_menu) self.partial_tx_actions_button.setPopupMode(QToolButton.InstantPopup) self.psbt_only_widgets.append(self.partial_tx_actions_button) # Action buttons self.buttons = [ self.partial_tx_actions_button, self.sign_button, self.broadcast_button, self.cancel_button ] # Transaction sharing buttons self.sharing_buttons = [ self.finalize_button, self.export_actions_button, self.save_button ] run_hook('transaction_dialog', self) if not self.finalized: self.create_fee_controls() vbox.addWidget(self.feecontrol_fields) self.hbox = hbox = QHBoxLayout() hbox.addLayout(Buttons(*self.sharing_buttons)) hbox.addStretch(1) hbox.addLayout(Buttons(*self.buttons)) vbox.addLayout(hbox) self.set_buttons_visibility() dialogs.append(self)
def update(self): if not self.finalized: self.update_fee_fields() self.finalize_button.setEnabled(self.can_finalize()) if self.tx is None: return self.update_io() desc = self.desc base_unit = self.main_window.base_unit() format_amount = self.main_window.format_amount tx_details = self.wallet.get_tx_info(self.tx) tx_mined_status = tx_details.tx_mined_status exp_n = tx_details.mempool_depth_bytes amount, fee = tx_details.amount, tx_details.fee size = self.tx.estimated_size() txid = self.tx.txid() self.broadcast_button.setEnabled(tx_details.can_broadcast) can_sign = not self.tx.is_complete() and \ (self.wallet.can_sign(self.tx) or bool(self.external_keypairs)) self.sign_button.setEnabled(can_sign) if self.finalized and tx_details.txid: self.tx_hash_e.setText(tx_details.txid) else: # note: when not finalized, locktime changes do not trigger # a make_tx, so the txid is unreliable, hence: self.tx_hash_e.setText(_('Unknown')) if not desc: self.tx_desc.hide() else: self.tx_desc.setText(_("Description") + ': ' + desc) self.tx_desc.show() self.status_label.setText(_('Status:') + ' ' + tx_details.status) islock = tx_details.islock timestamp = tx_mined_status.timestamp if not timestamp and islock: timestamp = islock if timestamp: dttm = datetime.datetime.fromtimestamp(timestamp) time_str = dttm.isoformat(' ')[:-3] self.date_label.setText(_("Date: {}").format(time_str)) self.date_label.show() elif exp_n is not None: text = '%.2f MB'%(exp_n/1000000) self.date_label.setText(_('Position in mempool: {} from tip').format(text)) self.date_label.show() else: self.date_label.hide() if self.tx.locktime <= NLOCKTIME_BLOCKHEIGHT_MAX: locktime_final_str = f"LockTime: {self.tx.locktime} (height)" else: locktime_final_str = f"LockTime: {self.tx.locktime} ({datetime.datetime.fromtimestamp(self.tx.locktime)})" self.locktime_final_label.setText(locktime_final_str) if self.locktime_e.get_locktime() is None: self.locktime_e.set_locktime(self.tx.locktime) if tx_mined_status.header_hash: self.block_hash_label.setText(_("Included in block: {}") .format(tx_mined_status.header_hash)) self.block_height_label.setText(_("At block height: {}") .format(tx_mined_status.height)) else: self.block_hash_label.hide() self.block_height_label.hide() if amount is None: amount_str = _("Transaction unrelated to your wallet") elif amount is None: amount_str = '' elif amount > 0: amount_str = _("Amount received:") + ' %s'% format_amount(amount) + ' ' + base_unit else: amount_str = _("Amount sent:") + ' %s'% format_amount(-amount) + ' ' + base_unit if amount_str: self.amount_label.setText(amount_str) else: self.amount_label.hide() size_str = _("Size:") + ' %d bytes'% size fee_str = _("Fee") + ': %s' % (format_amount(fee) + ' ' + base_unit if fee is not None else _('unknown')) if fee is not None: fee_rate = fee/size*1000 fee_str += ' ( %s ) ' % self.main_window.format_fee_rate(fee_rate) feerate_warning = simple_config.FEERATE_WARNING_HIGH_FEE if fee_rate > feerate_warning: fee_str += ' - ' + _('Warning') + ': ' + _("high fee") + '!' if isinstance(self.tx, PartialTransaction): risk_of_burning_coins = (can_sign and fee is not None and self.wallet.get_warning_for_risk_of_burning_coins_as_fees(self.tx)) self.fee_warning_icon.setToolTip(str(risk_of_burning_coins)) self.fee_warning_icon.setVisible(bool(risk_of_burning_coins)) self.fee_label.setText(fee_str) self.size_label.setText(size_str) show_psbt_only_widgets = self.finalized and isinstance(self.tx, PartialTransaction) for widget in self.psbt_only_widgets: if isinstance(widget, QMenu): widget.menuAction().setVisible(show_psbt_only_widgets) else: widget.setVisible(show_psbt_only_widgets) self.save_button.setEnabled(tx_details.can_save_as_local) if tx_details.can_save_as_local: self.save_button.setToolTip(_("Save transaction offline")) else: self.save_button.setToolTip(_("Transaction already saved or not yet signed.")) if self.tx.tx_type == 0: txid = self.tx.txid() tx_type, completed = self.wallet.db.get_ps_tx(txid) else: tx_type = self.tx.tx_type if tx_type: extra_payload = self.tx.extra_payload self.extra_pld.set_extra_data(tx_type, extra_payload) self.extra_pld_lb.show() self.extra_pld.show() else: self.extra_pld_lb.hide() self.extra_pld.hide() tx_type_name = '%s: %s, ' % (_('Type'), SPEC_TX_NAMES[tx_type]) self.txid_lb.setText(tx_type_name + _('Transaction ID:')) run_hook('transaction_dialog_update', self)
def create_menu(self, position): from electrum_firo.wallet import Multisig_Wallet is_multisig = isinstance(self.wallet, Multisig_Wallet) can_delete = self.wallet.can_delete_address() selected = self.selectionModel().selectedRows() if not selected: return multi_select = len(selected) > 1 addr_items = [] for idx in selected: if not idx.isValid(): return addr_items.append(idx.internalPointer()) addrs = [addr_item['addr'] for addr_item in addr_items] menu = QMenu() if not multi_select: idx = self.indexAt(position) if not idx.isValid(): return item = addr_items[0] if not item: return addr = item['addr'] is_ps = item['is_ps'] is_ps_ks = item['is_ps_ks'] hd = self.am.headerData addr_title = hd(AddrColumns.LABEL, None, Qt.DisplayRole) label_idx = idx.sibling(idx.row(), AddrColumns.LABEL) self.add_copy_menu(menu, idx) menu.addAction(_('Details'), lambda: self.parent.show_address(addr)) persistent = QPersistentModelIndex(label_idx) menu.addAction(_("Edit {}").format(addr_title), lambda p=persistent: self.edit(QModelIndex(p))) #if not is_ps and not is_ps_ks: # menu.addAction(_("Request payment"), # lambda: self.parent.receive_at(addr)) if self.wallet.can_export() or self.wallet.psman.is_ps_ks(addr): 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 = block_explorer_URL(self.config, 'addr', addr) if addr_URL: menu.addAction(_("View on block explorer"), lambda: webopen(addr_URL)) if not is_ps: def set_frozen_state(addrs, state): self.parent.set_frozen_state_of_addresses(addrs, state) if not self.wallet.is_frozen_address(addr): menu.addAction(_("Freeze"), lambda: set_frozen_state([addr], True)) else: menu.addAction(_("Unfreeze"), lambda: set_frozen_state([addr], False)) coins = self.wallet.get_spendable_coins(addrs) if coins: menu.addAction(_("Spend from"), lambda: self.parent.utxo_list.set_spend_list(coins)) run_hook('receive_menu', menu, addrs, self.wallet) menu.exec_(self.viewport().mapToGlobal(position))
def on_cb_active(self, is_checked): if self.active != is_checked: self.app.plugins.toggle(self.name) self.update() run_hook('init_kivy', self.app)