From b28b3994c7c879d43325faa8db8f4691be1f920f Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 20 Dec 2020 14:39:15 +0100 Subject: [PATCH] qt: move window.get{Open,Save}FileName to util Sometimes we want its "remember path" behaviour but it does not make sense to parent the dialog from main window. When so, caller code no longer needs to get a reference to a main window. Also rm last usages of get_parent_main_window(). --- electrum/gui/qt/address_dialog.py | 4 +- electrum/gui/qt/completion_text_edit.py | 4 +- electrum/gui/qt/main_window.py | 74 ++++++++------------- electrum/gui/qt/qrcodewidget.py | 18 ++++-- electrum/gui/qt/qrtextedit.py | 19 ++++-- electrum/gui/qt/seed_dialog.py | 2 +- electrum/gui/qt/transaction_dialog.py | 16 +++-- electrum/gui/qt/util.py | 86 ++++++++++++++++++------- electrum/plugins/coldcard/qt.py | 24 +++++-- electrum/plugins/safe_t/qt.py | 9 ++- electrum/plugins/trezor/qt.py | 9 ++- 11 files changed, 157 insertions(+), 108 deletions(-) diff --git a/electrum/gui/qt/address_dialog.py b/electrum/gui/qt/address_dialog.py index c78a18ae8..2e2b58237 100644 --- a/electrum/gui/qt/address_dialog.py +++ b/electrum/gui/qt/address_dialog.py @@ -87,14 +87,14 @@ class AddressDialog(WindowModalDialog): redeem_script = self.wallet.get_redeem_script(address) if redeem_script: vbox.addWidget(QLabel(_("Redeem Script") + ':')) - redeem_e = ShowQRTextEdit(text=redeem_script) + redeem_e = ShowQRTextEdit(text=redeem_script, config=self.config) redeem_e.addCopyButton(self.app) vbox.addWidget(redeem_e) witness_script = self.wallet.get_witness_script(address) if witness_script: vbox.addWidget(QLabel(_("Witness Script") + ':')) - witness_e = ShowQRTextEdit(text=witness_script) + witness_e = ShowQRTextEdit(text=witness_script, config=self.config) witness_e.addCopyButton(self.app) vbox.addWidget(witness_e) diff --git a/electrum/gui/qt/completion_text_edit.py b/electrum/gui/qt/completion_text_edit.py index 05830b9a1..b34d976c6 100644 --- a/electrum/gui/qt/completion_text_edit.py +++ b/electrum/gui/qt/completion_text_edit.py @@ -32,8 +32,8 @@ from .util import ButtonsTextEdit class CompletionTextEdit(ButtonsTextEdit): - def __init__(self, parent=None): - super(CompletionTextEdit, self).__init__(parent) + def __init__(self): + ButtonsTextEdit.__init__(self) self.completer = None self.moveCursor(QTextCursor.End) self.disable_suggestions() diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index c3ed880e6..7066dfd1d 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -89,7 +89,8 @@ from .util import (read_QIcon, ColorScheme, text_dialog, icon_path, WaitingDialo CloseButton, HelpButton, MessageBoxMixin, EnterButton, import_meta_gui, export_meta_gui, filename_field, address_field, char_width_in_lineedit, webopen, - TRANSACTION_FILE_EXTENSION_FILTER_ANY, MONOSPACE_FONT) + TRANSACTION_FILE_EXTENSION_FILTER_ANY, MONOSPACE_FONT, + getOpenFileName, getSaveFileName) from .util import ButtonsTextEdit, ButtonsLineEdit from .installwizard import WIF_HELP_TEXT from .history_list import HistoryList, HistoryModel @@ -841,38 +842,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): except TypeError: self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000) - - - # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user - def getOpenFileName(self, title, filter = ""): - directory = self.config.get('io_dir', os.path.expanduser('~')) - fileName, __ = QFileDialog.getOpenFileName(self, title, directory, filter) - if fileName and directory != os.path.dirname(fileName): - self.config.set_key('io_dir', os.path.dirname(fileName), True) - return fileName - - def getSaveFileName(self, title, filename, filter="", - *, default_extension: str = None, - default_filter: str = None) -> Optional[str]: - directory = self.config.get('io_dir', os.path.expanduser('~')) - path = os.path.join(directory, filename) - - file_dialog = QFileDialog(self, title, path, filter) - file_dialog.setAcceptMode(QFileDialog.AcceptSave) - if default_extension: - # note: on MacOS, the selected filter's first extension seems to have priority over this... - file_dialog.setDefaultSuffix(default_extension) - if default_filter: - assert default_filter in filter, f"default_filter={default_filter!r} does not appear in filter={filter!r}" - file_dialog.selectNameFilter(default_filter) - if file_dialog.exec() != QDialog.Accepted: - return None - - selected_path = file_dialog.selectedFiles()[0] - if selected_path and directory != os.path.dirname(selected_path): - self.config.set_key('io_dir', os.path.dirname(selected_path), True) - return selected_path - def timer_actions(self): self.request_list.refresh_status() # Note this runs in the GUI thread @@ -2077,12 +2046,18 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): def do_export(): key = pr.get_id() name = str(key) + '.bip70' - fn = self.getSaveFileName(_("Save invoice to file"), name, filter="*.bip70") + fn = getSaveFileName( + parent=self, + title=_("Save invoice to file"), + filename=name, + filter="*.bip70", + config=self.config, + ) if not fn: return with open(fn, 'wb') as f: data = f.write(pr.raw) - self.show_message(_('BIP70 invoice saved as' + ' ' + fn)) + self.show_message(_('BIP70 invoice saved as {}').format(fn)) exportButton = EnterButton(_('Export'), do_export) buttons = Buttons(exportButton, CloseButton(d)) else: @@ -2113,7 +2088,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): grid.addWidget(QLabel(_("Expires") + ':'), 4, 0) grid.addWidget(QLabel(format_time(invoice.time + invoice.exp)), 4, 1) vbox.addLayout(grid) - invoice_e = ShowQRTextEdit() + invoice_e = ShowQRTextEdit(config=self.config) invoice_e.addCopyButton(self.app) invoice_e.setText(invoice.invoice) vbox.addWidget(invoice_e) @@ -2392,7 +2367,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): ks_vbox.setContentsMargins(0, 0, 0, 0) ks_w.setLayout(ks_vbox) - mpk_text = ShowQRTextEdit(ks.get_master_public_key()) + mpk_text = ShowQRTextEdit(ks.get_master_public_key(), config=self.config) mpk_text.setMaximumHeight(150) mpk_text.addCopyButton(self.app) run_hook('show_xpub_button', mpk_text, ks) @@ -2461,8 +2436,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): help_text=None, show_copy_text_btn=False): if not data: return - d = QRDialog(data, parent or self, title, help_text=help_text, - show_copy_text_btn=show_copy_text_btn) + d = QRDialog( + data=data, + parent=parent or self, + title=title, + help_text=help_text, + show_copy_text_btn=show_copy_text_btn, + config=self.config, + ) d.exec_() @protected @@ -2482,14 +2463,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): vbox.addWidget(QLabel(_("Address") + ': ' + address)) vbox.addWidget(QLabel(_("Script type") + ': ' + xtype)) vbox.addWidget(QLabel(_("Private key") + ':')) - keys_e = ShowQRTextEdit(text=pk) + keys_e = ShowQRTextEdit(text=pk, config=self.config) keys_e.addCopyButton(self.app) vbox.addWidget(keys_e) - # if redeem_script: - # vbox.addWidget(QLabel(_("Redeem Script") + ':')) - # rds_e = ShowQRTextEdit(text=redeem_script) - # rds_e.addCopyButton(self.app) - # vbox.addWidget(rds_e) vbox.addLayout(Buttons(CloseButton(d))) d.setLayout(vbox) d.exec_() @@ -2701,8 +2677,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): self.show_transaction(tx) def read_tx_from_file(self) -> Optional[Transaction]: - fileName = self.getOpenFileName(_("Select your transaction file"), - TRANSACTION_FILE_EXTENSION_FILTER_ANY) + fileName = getOpenFileName( + parent=self, + title=_("Select your transaction file"), + filter=TRANSACTION_FILE_EXTENSION_FILTER_ANY, + config=self.config, + ) if not fileName: return try: diff --git a/electrum/gui/qt/qrcodewidget.py b/electrum/gui/qt/qrcodewidget.py index 0c9a49e57..567f6ce87 100644 --- a/electrum/gui/qt/qrcodewidget.py +++ b/electrum/gui/qt/qrcodewidget.py @@ -9,8 +9,9 @@ from PyQt5.QtWidgets import ( ) from electrum.i18n import _ +from electrum.simple_config import SimpleConfig -from .util import WindowModalDialog, get_parent_main_window, WWLabel +from .util import WindowModalDialog, WWLabel, getSaveFileName class QRCodeWidget(QWidget): @@ -95,15 +96,17 @@ class QRDialog(WindowModalDialog): def __init__( self, + *, data, parent=None, title="", show_text=False, - *, help_text=None, show_copy_text_btn=False, + config: SimpleConfig, ): WindowModalDialog.__init__(self, parent, title) + self.config = config vbox = QVBoxLayout() @@ -122,11 +125,12 @@ class QRDialog(WindowModalDialog): hbox.addStretch(1) def print_qr(): - main_window = get_parent_main_window(self) - if main_window: - filename = main_window.getSaveFileName(_("Select where to save file"), "qrcode.png") - else: - filename, __ = QFileDialog.getSaveFileName(self, _("Select where to save file"), "qrcode.png") + filename = getSaveFileName( + parent=self, + title=_("Select where to save file"), + filename="qrcode.png", + config=self.config, + ) if not filename: return p = qrw.grab() diff --git a/electrum/gui/qt/qrtextedit.py b/electrum/gui/qt/qrtextedit.py index b596ffad8..453b3a445 100644 --- a/electrum/gui/qt/qrtextedit.py +++ b/electrum/gui/qt/qrtextedit.py @@ -4,14 +4,15 @@ from electrum.i18n import _ from electrum.plugin import run_hook from electrum.simple_config import SimpleConfig -from .util import ButtonsTextEdit, MessageBoxMixin, ColorScheme +from .util import ButtonsTextEdit, MessageBoxMixin, ColorScheme, getOpenFileName class ShowQRTextEdit(ButtonsTextEdit): - def __init__(self, text=None): + def __init__(self, text=None, *, config: SimpleConfig): ButtonsTextEdit.__init__(self, text) - self.setReadOnly(1) + self.config = config + self.setReadOnly(True) icon = "qrcode_white.png" if ColorScheme.dark_scheme else "qrcode.png" self.addButton(icon, self.qr_show, _("Show as QR code")) @@ -23,7 +24,11 @@ class ShowQRTextEdit(ButtonsTextEdit): s = str(self.toPlainText()) except: s = self.toPlainText() - QRDialog(s, parent=self).exec_() + QRDialog( + data=s, + parent=self, + config=self.config, + ).exec_() def contextMenuEvent(self, e): m = self.createStandardContextMenu() @@ -44,7 +49,11 @@ class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin): run_hook('scan_text_edit', self) def file_input(self): - fileName, __ = QFileDialog.getOpenFileName(self, 'select file') + fileName = getOpenFileName( + parent=self, + title='select file', + config=self.config, + ) if not fileName: return try: diff --git a/electrum/gui/qt/seed_dialog.py b/electrum/gui/qt/seed_dialog.py index 0c2683a9f..280ed73c5 100644 --- a/electrum/gui/qt/seed_dialog.py +++ b/electrum/gui/qt/seed_dialog.py @@ -105,7 +105,7 @@ class SeedLayout(QVBoxLayout): if for_seed_words: self.seed_e = ButtonsTextEdit() else: # e.g. xpub - self.seed_e = ShowQRTextEdit() + self.seed_e = ShowQRTextEdit(config=self.config) self.seed_e.setReadOnly(True) self.seed_e.setText(seed) else: # we expect user to enter text diff --git a/electrum/gui/qt/transaction_dialog.py b/electrum/gui/qt/transaction_dialog.py index 590ba6f59..5aec59af5 100644 --- a/electrum/gui/qt/transaction_dialog.py +++ b/electrum/gui/qt/transaction_dialog.py @@ -53,7 +53,7 @@ from .util import (MessageBoxMixin, read_QIcon, Buttons, icon_path, char_width_in_lineedit, TRANSACTION_FILE_EXTENSION_FILTER_SEPARATE, TRANSACTION_FILE_EXTENSION_FILTER_ONLY_COMPLETE_TX, TRANSACTION_FILE_EXTENSION_FILTER_ONLY_PARTIAL_TX, - BlockingWaitingDialog) + BlockingWaitingDialog, getSaveFileName) from .fee_slider import FeeSlider, FeeComboBox from .confirm_tx_dialog import TxEditor @@ -331,11 +331,15 @@ class BaseTxDialog(QDialog, MessageBoxMixin): extension = 'psbt' default_filter = TRANSACTION_FILE_EXTENSION_FILTER_ONLY_PARTIAL_TX name = f'{name}.{extension}' - fileName = self.main_window.getSaveFileName(_("Select where to save your transaction"), - name, - TRANSACTION_FILE_EXTENSION_FILTER_SEPARATE, - default_extension=extension, - default_filter=default_filter) + fileName = getSaveFileName( + parent=self, + title=_("Select where to save your transaction"), + filename=name, + filter=TRANSACTION_FILE_EXTENSION_FILTER_SEPARATE, + default_extension=extension, + default_filter=default_filter, + config=self.config, + ) if not fileName: return if tx.is_complete(): # network tx hex diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index 5c45b1599..e0adb72e7 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -458,8 +458,14 @@ def filename_field(parent, config, defaultname, select_msg): def func(): text = filename_e.text() - _filter = "*.csv" if text.endswith(".csv") else "*.json" if text.endswith(".json") else None - p, __ = QFileDialog.getSaveFileName(None, select_msg, text, _filter) + _filter = "*.csv" if defaultname.endswith(".csv") else "*.json" if defaultname.endswith(".json") else None + p = getSaveFileName( + parent=None, + title=select_msg, + filename=text, + filter=_filter, + config=config, + ) if p: filename_e.setText(p) @@ -935,9 +941,14 @@ class AcceptFileDragDrop: raise NotImplementedError() -def import_meta_gui(electrum_window, title, importer, on_success): +def import_meta_gui(electrum_window: 'ElectrumWindow', title, importer, on_success): filter_ = "JSON (*.json);;All files (*)" - filename = electrum_window.getOpenFileName(_("Open {} file").format(title), filter_) + filename = getOpenFileName( + parent=electrum_window, + title=_("Open {} file").format(title), + filter=filter_, + config=electrum_window.config, + ) if not filename: return try: @@ -949,10 +960,15 @@ def import_meta_gui(electrum_window, title, importer, on_success): on_success() -def export_meta_gui(electrum_window, title, exporter): +def export_meta_gui(electrum_window: 'ElectrumWindow', title, exporter): filter_ = "JSON (*.json);;All files (*)" - filename = electrum_window.getSaveFileName(_("Select file to save your {}").format(title), - 'electrum_{}.json'.format(title), filter_) + filename = getSaveFileName( + parent=electrum_window, + title=_("Select file to save your {}").format(title), + filename='electrum_{}.json'.format(title), + filter=filter_, + config=electrum_window.config, + ) if not filename: return try: @@ -964,24 +980,44 @@ def export_meta_gui(electrum_window, title, exporter): .format(title, str(filename))) -def get_parent_main_window( - widget, *, allow_wizard: bool = False, -) -> Union[None, 'ElectrumWindow', 'InstallWizard']: - """Returns a reference to the ElectrumWindow this widget belongs to.""" - from .main_window import ElectrumWindow - from .transaction_dialog import TxDialog - from .installwizard import InstallWizard - for _ in range(100): - if widget is None: - return None - if isinstance(widget, ElectrumWindow): - return widget - if isinstance(widget, TxDialog): - return widget.main_window - if isinstance(widget, InstallWizard) and allow_wizard: - return widget - widget = widget.parentWidget() - return None +def getOpenFileName(*, parent, title, filter="", config: 'SimpleConfig') -> Optional[str]: + """Custom wrapper for getOpenFileName that remembers the path selected by the user.""" + directory = config.get('io_dir', os.path.expanduser('~')) + fileName, __ = QFileDialog.getOpenFileName(parent, title, directory, filter) + if fileName and directory != os.path.dirname(fileName): + config.set_key('io_dir', os.path.dirname(fileName), True) + return fileName + + +def getSaveFileName( + *, + parent, + title, + filename, + filter="", + default_extension: str = None, + default_filter: str = None, + config: 'SimpleConfig', +) -> Optional[str]: + """Custom wrapper for getSaveFileName that remembers the path selected by the user.""" + directory = config.get('io_dir', os.path.expanduser('~')) + path = os.path.join(directory, filename) + + file_dialog = QFileDialog(parent, title, path, filter) + file_dialog.setAcceptMode(QFileDialog.AcceptSave) + if default_extension: + # note: on MacOS, the selected filter's first extension seems to have priority over this... + file_dialog.setDefaultSuffix(default_extension) + if default_filter: + assert default_filter in filter, f"default_filter={default_filter!r} does not appear in filter={filter!r}" + file_dialog.selectNameFilter(default_filter) + if file_dialog.exec() != QDialog.Accepted: + return None + + selected_path = file_dialog.selectedFiles()[0] + if selected_path and directory != os.path.dirname(selected_path): + config.set_key('io_dir', os.path.dirname(selected_path), True) + return selected_path def icon_path(icon_basename): diff --git a/electrum/plugins/coldcard/qt.py b/electrum/plugins/coldcard/qt.py index 0ce585036..7a6f4058a 100644 --- a/electrum/plugins/coldcard/qt.py +++ b/electrum/plugins/coldcard/qt.py @@ -5,7 +5,8 @@ import copy from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtWidgets import QPushButton, QLabel, QVBoxLayout, QWidget, QGridLayout -from electrum.gui.qt.util import WindowModalDialog, CloseButton, Buttons +from electrum.gui.qt.util import (WindowModalDialog, CloseButton, Buttons, getOpenFileName, + getSaveFileName) from electrum.gui.qt.transaction_dialog import TxDialog from electrum.gui.qt.main_window import ElectrumWindow @@ -64,8 +65,13 @@ class Plugin(ColdcardPlugin, QtPluginBase): basename = wallet.basename().rsplit('.', 1)[0] # trim .json name = f'{basename}-cc-export.txt'.replace(' ', '-') - fileName = main_window.getSaveFileName(_("Select where to save the setup file"), - name, "*.txt") + fileName = getSaveFileName( + parent=main_window, + title=_("Select where to save the setup file"), + filename=name, + filter="*.txt", + config=self.config, + ) if fileName: with open(fileName, "wt") as f: ColdcardPlugin.export_ms_wallet(wallet, f, basename) @@ -191,10 +197,14 @@ class CKCCSettingsDialog(WindowModalDialog): def start_upgrade(self, client): # ask for a filename (must have already downloaded it) - mw = self.window dev = client.dev - fileName = mw.getOpenFileName("Select upgraded firmware file", "*.dfu") + fileName = getOpenFileName( + parent=self, + title="Select upgraded firmware file", + filter="*.dfu", + config=self.window.config, + ) if not fileName: return @@ -220,7 +230,7 @@ class CKCCSettingsDialog(WindowModalDialog): if magic != FW_HEADER_MAGIC: raise ValueError("Bad magic") except Exception as exc: - mw.show_error("Does not appear to be a Coldcard firmware file.\n\n%s" % exc) + self.window.show_error("Does not appear to be a Coldcard firmware file.\n\n%s" % exc) return # TODO: @@ -228,7 +238,7 @@ class CKCCSettingsDialog(WindowModalDialog): # - warn them about the reboot? # - length checks # - add progress local bar - mw.show_message("Ready to Upgrade.\n\nBe patient. Unit will reboot itself when complete.") + self.window.show_message("Ready to Upgrade.\n\nBe patient. Unit will reboot itself when complete.") def doit(): dlen, _ = dev.upload_file(firmware, verify=True) diff --git a/electrum/plugins/safe_t/qt.py b/electrum/plugins/safe_t/qt.py index 6d3e9399e..92e7b3ac8 100644 --- a/electrum/plugins/safe_t/qt.py +++ b/electrum/plugins/safe_t/qt.py @@ -9,7 +9,7 @@ from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton, QMessageBox, QFileDialog, QSlider, QTabWidget) from electrum.gui.qt.util import (WindowModalDialog, WWLabel, Buttons, CancelButton, - OkButton, CloseButton) + OkButton, CloseButton, getOpenFileName) from electrum.i18n import _ from electrum.plugin import hook from electrum.util import bh2u @@ -276,8 +276,11 @@ class SettingsDialog(WindowModalDialog): invoke_client('toggle_passphrase', unpair_after=currently_enabled) def change_homescreen(): - dialog = QFileDialog(self, _("Choose Homescreen")) - filename, __ = dialog.getOpenFileName() + filename = getOpenFileName( + parent=self, + title=_("Choose Homescreen"), + config=config, + ) if not filename: return # user cancelled diff --git a/electrum/plugins/trezor/qt.py b/electrum/plugins/trezor/qt.py index bf8911c05..72674267e 100644 --- a/electrum/plugins/trezor/qt.py +++ b/electrum/plugins/trezor/qt.py @@ -8,7 +8,7 @@ from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton, QMessageBox, QFileDialog, QSlider, QTabWidget) from electrum.gui.qt.util import (WindowModalDialog, WWLabel, Buttons, CancelButton, - OkButton, CloseButton, PasswordLineEdit) + OkButton, CloseButton, PasswordLineEdit, getOpenFileName) from electrum.i18n import _ from electrum.plugin import hook from electrum.util import bh2u @@ -542,8 +542,11 @@ class SettingsDialog(WindowModalDialog): invoke_client('toggle_passphrase', unpair_after=currently_enabled) def change_homescreen(): - dialog = QFileDialog(self, _("Choose Homescreen")) - filename, __ = dialog.getOpenFileName() + filename = getOpenFileName( + parent=self, + title=_("Choose Homescreen"), + config=config, + ) if not filename: return # user cancelled