# SPDX-License-Identifier: LGPL-2.1-or-later # *************************************************************************** # * * # * Copyright (c) 2022-2023 FreeCAD Project Association * # * * # * This file is part of FreeCAD. * # * * # * FreeCAD is free software: you can redistribute it and/or modify it * # * under the terms of the GNU Lesser General Public License as * # * published by the Free Software Foundation, either version 2.1 of the * # * License, or (at your option) any later version. * # * * # * FreeCAD is distributed in the hope that it will be useful, but * # * WITHOUT ANY WARRANTY; without even the implied warranty of * # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * # * Lesser General Public License for more details. * # * * # * You should have received a copy of the GNU Lesser General Public * # * License along with FreeCAD. If not, see * # * . * # * * # *************************************************************************** from PySide import QtCore, QtWidgets from AddonManagerTest.app.mocks import SignalCatcher class DialogInteractor(QtCore.QObject): """Takes the title of the dialog and a callable. The callable is passed the widget we found and can do whatever it wants to it. Whatever it does should eventually close the dialog, however.""" def __init__(self, dialog_to_watch_for, interaction): super().__init__() # Status variables for tests to check: self.dialog_found = False self.has_run = False self.button_found = False self.interaction = interaction self.dialog_to_watch_for = dialog_to_watch_for self.execution_counter = 0 self.timer = QtCore.QTimer() self.timer.timeout.connect(self.run) self.timer.start( 1 ) # At 10 this occasionally left open dialogs; less than 1 produced failed tests def run(self): widget = QtWidgets.QApplication.activeModalWidget() if widget and self._dialog_matches(widget): # Found the dialog we are looking for: now try to run the interaction if self.interaction is not None and callable(self.interaction): self.interaction(widget) self.dialog_found = True self.timer.stop() self.has_run = True self.execution_counter += 1 if self.execution_counter > 100: print("Stopped timer after 100 iterations") self.timer.stop() def _dialog_matches(self, widget) -> bool: # Is this the widget we are looking for? Only applies on Linux and Windows: macOS # doesn't set the title of a modal dialog: os = QtCore.QSysInfo.productType() # Qt5 gives "osx", Qt6 gives "macos" if os in ["osx", "macos"] or ( hasattr(widget, "windowTitle") and callable(widget.windowTitle) and widget.windowTitle() == self.dialog_to_watch_for ): return True return False class DialogWatcher(DialogInteractor): """Examine the running GUI and look for a modal dialog with a given title, containing a button with a role. Click that button, which is expected to close the dialog. Generally run on a one-shot QTimer to allow the dialog time to open up. If the specified dialog is found, but it does not contain the expected button, button_found will be false, and the dialog will be closed with a reject() slot.""" def __init__(self, dialog_to_watch_for, button=QtWidgets.QDialogButtonBox.NoButton): super().__init__(dialog_to_watch_for, self.click_button) if button != QtWidgets.QDialogButtonBox.NoButton: self.button = button else: self.button = QtWidgets.QDialogButtonBox.Cancel def click_button(self, widget): button_boxes = widget.findChildren(QtWidgets.QDialogButtonBox) if len(button_boxes) == 1: # There should be one, and only one button_to_click = button_boxes[0].button(self.button) if button_to_click: self.button_found = True button_to_click.click() else: widget.reject() else: widget.reject() class FakeWorker: def __init__(self): self.called = False self.should_continue = True def work(self): while self.should_continue: QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 100) def stop(self): self.should_continue = False class MockThread: def wait(self): pass def isRunning(self): return False class AsynchronousMonitor: """Watch for a signal to be emitted for at most some given number of milliseconds""" def __init__(self, signal): self.signal = signal self.signal_catcher = SignalCatcher() self.signal.connect(self.signal_catcher.catch_signal) self.kill_timer = QtCore.QTimer() self.kill_timer.setSingleShot(True) self.kill_timer.timeout.connect(self.signal_catcher.die) def wait_for_at_most(self, max_wait_millis) -> None: self.kill_timer.setInterval(max_wait_millis) self.kill_timer.start() while not self.signal_catcher.caught and not self.signal_catcher.killed: QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 10) self.kill_timer.stop() def good(self) -> bool: return self.signal_catcher.caught and not self.signal_catcher.killed