def toc_clicked(self, index, force=False): if force or QApplication.mouseButtons() & Qt.LeftButton: item = self.toc_model.itemFromIndex(index) if item.abspath is not None: if not os.path.exists(item.abspath): return error_dialog(self, _('No such location'), _('The location pointed to by this item' ' does not exist.'), det_msg=item.abspath, show=True) url = QUrl.fromLocalFile(item.abspath) if item.fragment: url.setFragment(item.fragment) self.link_clicked(url) self.view.setFocus(Qt.OtherFocusReason)
def annotation_toc_clicked(self, index, force=False): from calibre.gui2 import error_dialog if force or QApplication.mouseButtons() & Qt.LeftButton: item = self._view.annotation_toc_model.itemFromIndex(index) if item.bookmark: self._view.goto_bookmark(item.bookmark) else: return error_dialog(None, _('No such location'), _('The location pointed to by this item' ' does not exist.'), det_msg=item.fragment or "no location saved", show=True) self._view.setFocus(Qt.OtherFocusReason)
def item_pressed(self, item): if QApplication.mouseButtons() & Qt.LeftButton: QTimer.singleShot(0, self.emit_navigate)
def pressed(self, index): if QApplication.mouseButtons() & Qt.LeftButton: self.activated(index)
class QTestHelper(QObject): # pragma: no cover """Helper class for testing GUI""" _interactive: bool = True _debug: bool = False _in_test: bool = False _tests_delay: float = 1.0 # delay in seconds _tests_min_delay: float = 0.0001 # min delay in seconds _tests_delay_key: float = 0.05 # delay between key clicks (in seconds) # ------------------------------------------------------------------------- def __new__(cls): """Singleton class""" if not hasattr(cls, '_instance'): cls._instance = super(QTestHelper, cls).__new__(cls) # read settings: DEBUG and INTERACTIVE test_ini_file = f"{get_res_dir()}/test.ini" with open(test_ini_file, "r", encoding="utf-8") as f: data = json.load(f) cls._interactive = data["INTERACTIVE"] cls._debug = data["DEBUG"] return cls._instance # ------------------------------------------------------------------------- def __init__(self): QObject.__init__(self) if not QApplication.instance(): # pragma: no cover os.environ['XDG_SESSION_TYPE'] = "" self._app = QApplication(sys.argv) install_translators() else: self._app = QApplication.instance() # ------------------------------------------------------------------------- @staticmethod def is_interactive() -> bool: """Return mode: if False, the forms are not shown""" if not QTestHelper._in_test: return True return QTestHelper._interactive # ------------------------------------------------------------------------- @staticmethod def is_debug() -> bool: """Return mode: if True, some tests are skipped""" return QTestHelper._debug # ------------------------------------------------------------------------- @staticmethod def start_tests() -> bool: """Return mode: if True, some tests are skipped""" QTestHelper._in_test = True return QTestHelper._debug # ------------------------------------------------------------------------- @staticmethod def set_interactive(mode: bool) -> None: """Set mode: if False, the forms are not shown""" QTestHelper._interactive = mode QTestHelper._save_settings() # ------------------------------------------------------------------------- @staticmethod def set_debug(mode: bool) -> None: """Set mode: if True, some tests are skipped""" QTestHelper._debug = mode QTestHelper._save_settings() # ------------------------------------------------------------------------- @staticmethod def _save_settings() -> None: """Save settings: DEBUG and INTERACTIVE""" test_ini_file = f"{get_res_dir()}/test.ini" with open(test_ini_file, "w", encoding="utf-8") as f: json.dump( { "DEBUG": QTestHelper._debug, "INTERACTIVE": QTestHelper._interactive }, f, indent=4, sort_keys=True) # ------------------------------------------------------------------------- def sleep(self, delay: float = 1) -> None: """The delay between the actions in an interactive mode""" self._app.processEvents() time.sleep(delay * self._tests_delay if self.is_interactive( ) else self._tests_min_delay) # ------------------------------------------------------------------------- def key_click(self, widget: QWidget, key: int = 0, txt: str = "", modifier: int = Qt.NoModifier, delay: float = 0) -> None: """Send press key event to widget""" widget.setFocus() key_press = QKeyEvent(QEvent.KeyPress, key, modifier, txt, False) key_release = QKeyEvent(QEvent.KeyRelease, key, modifier, txt, False) QCoreApplication.postEvent(widget, key_press) QCoreApplication.postEvent(widget, key_release) self.sleep(delay) # ------------------------------------------------------------------------- def qtest_key_click(self, key: int = 0, modifier: int = Qt.NoModifier, delay: float = 0) -> None: # noinspection PyTypeChecker,PyCallByClass QTest.keyClick(None, key, modifier) self.sleep(delay) # ------------------------------------------------------------------------- def key_clicks(self, widget: QWidget, txt: str = "", modifier: int = Qt.NoModifier, delay: float = 0) -> None: """Send string to widget""" timeout: float = self._tests_delay_key if delay else 0 if not self.is_interactive(): timeout = 0 for char in txt: key = QKeyEvent(QEvent.KeyPress, 0, modifier, char, False) QCoreApplication.postEvent(widget, key) if timeout: self.sleep(timeout) self.sleep(delay) # ------------------------------------------------------------------------- @staticmethod def mouse_move(widget: QWidget, pos: QPoint) -> None: """Move mouse ono widget""" # noinspection PyCallByClass,PyTypeChecker QTest.mouseMove(widget, pos) # ------------------------------------------------------------------------- def _mouse_event(self, mtype: int, pos: QPoint, btn: int) -> QMouseEvent: """Create mouse event""" return QMouseEvent(mtype, pos, btn, self._app.mouseButtons(), self._app.keyboardModifiers()) # ------------------------------------------------------------------------- def mouse_click(self, widget: QWidget, pos: QPoint = QPoint(0, 0), btn: int = Qt.LeftButton, delay: float = 0) -> None: """Send mouse click to widget""" # TODO: if delete a comment from the next line, the program crashes # Process finished with exit code 139 (interrupted signal 11: SIGSEGV) # widget.setFocus() # !!! find the cause of the error !!! mouse_press = self._mouse_event(QEvent.MouseButtonPress, pos, btn) mouse_release = self._mouse_event(QEvent.MouseButtonRelease, pos, btn) QCoreApplication.postEvent(widget, mouse_press) QCoreApplication.postEvent(widget, mouse_release) self.sleep(delay) # ------------------------------------------------------------------------- def mouse_dbl_click(self, widget: QWidget, pos: QPoint = QPoint(0, 0), btn: int = Qt.LeftButton, delay: float = 0) -> None: """Send mouse double click to widget""" widget.setFocus() mouse_dbl = self._mouse_event(QEvent.MouseButtonDblClick, pos, btn) QCoreApplication.postEvent(widget, mouse_dbl) self.sleep(delay) # ------------------------------------------------------------------------- def show_and_wait_for_active(self, widget: QWidget, timeout: int = 5000) -> None: """Wait for active widget""" widget.show() if self.is_interactive(): # noinspection PyTypeChecker,PyCallByClass QTest.qWaitForWindowActive(widget, timeout) # ------------------------------------------------------------------------- @staticmethod def get_xy_for_word(text: QTextEdit, word: str) -> QPoint: """ Returns the pixel coordinates of the word on the QTextEdit canvas. If QTextEdit does not contain the specified word or it is invisible, returns 0, 0. """ _text = text.toPlainText() x, y = 0, 0 if word and word in _text: idx = _text.index(word) cursor = text.textCursor() cursor.movePosition(QTextCursor.Start) for _ in range(idx): cursor.movePosition(QTextCursor.NextCharacter) rect = text.cursorRect(cursor) x = rect.x() + rect.width() y = rect.y() + rect.height() // 2 if x < 0 or y < 0: # pragma: no cover x, y = 0, 0 return QPoint(x, y) # ------------------------------------------------------------------------- def handle_modal_widget(self, func: TypeFuncAfter) -> None: """Listens for a modal widget""" thread = threading.Thread(target=self._close_modal, args=(func, )) thread.start() # ------------------------------------------------------------------------- def _close_modal(self, func: TypeFuncAfter, timeout: float = 10) -> None: """Endlessly waits for a modal widget""" modal_widget: TypeWidget = None start: float = time.time() while modal_widget is None and time.time() - start < timeout: modal_widget = self._app.activeModalWidget() time.sleep(0.01) func(modal_widget) # ------------------------------------------------------------------------- def handle_popup_widget(self, func: TypeFuncAfter) -> None: """Listens for a popup widget""" thread = threading.Thread(target=self._close_popup, args=(func, )) thread.start() # ------------------------------------------------------------------------- def _close_popup(self, func: TypeFuncAfter, timeout: float = 10) -> None: """Endlessly waits for a popup widget""" modal_widget: TypeWidget = None start: float = time.time() while modal_widget is None and time.time() - start < timeout: modal_widget = self._app.activePopupWidget() time.sleep(0.01) func(modal_widget) # ------------------------------------------------------------------------- def _mouse_click(self, te: QTextEdit, word: str, delay: float, dbl: bool) -> None: pos = self.get_xy_for_word(te, word) func = self.mouse_dbl_click if dbl else self.mouse_click func(te.viewport(), pos, delay=delay) # ------------------------------------------------------------------------- def wrd_click(self, te: QTextEdit, word: str, delay: float = 1) -> None: """Mouse click on word in QTextEdit""" self._mouse_click(te, word, delay, False) # ------------------------------------------------------------------------- def wrd_d_click(self, te: QTextEdit, word: str, delay: float = 1) -> None: """Mouse double click on word in QTextEdit""" self._mouse_click(te, word, delay, True)