def send_click(view, pos, button=Qt.LeftButton, double_click=False): if double_click: ev = QMouseEvent(QEvent.MouseButtonDblClick, pos, button, button, QApplication.keyboardModifiers()) QApplication.postEvent(view.viewport(), ev) return ev = QMouseEvent(QEvent.MouseButtonPress, pos, button, button, QApplication.keyboardModifiers()) QApplication.postEvent(view.viewport(), ev) ev = QMouseEvent(QEvent.MouseButtonRelease, pos, button, button, QApplication.keyboardModifiers()) QApplication.postEvent(view.viewport(), ev)
def eventFilter(self, obj, ev): if ev.type() == ev.MouseButtonPress and ev.button() == Qt.LeftButton: mods = QApplication.keyboardModifiers() if mods & Qt.ControlModifier or mods & Qt.ShiftModifier: self.show_marked() return True return False
def handle_mouse_press_event(self, ev): if QApplication.keyboardModifiers() & Qt.ShiftModifier: # Shift-Click in QListView is broken. It selects extra items in # various circumstances, for example, click on some item in the # middle of a row then click on an item in the next row, all items # in the first row will be selected instead of only items after the # middle item. index = self.indexAt(ev.pos()) if not index.isValid(): return ci = self.currentIndex() sm = self.selectionModel() sm.setCurrentIndex(index, sm.NoUpdate) if not ci.isValid(): return if not sm.hasSelection(): sm.select(index, sm.ClearAndSelect) return cr = ci.row() tgt = index.row() top = self.model().index(min(cr, tgt), 0) bottom = self.model().index(max(cr, tgt), 0) sm.select(QItemSelection(top, bottom), sm.Select) else: return QListView.mousePressEvent(self, ev)
def createEditor(self, parent, option, index): if self.db and hasattr(self.db, self.items_func_name): m = index.model() col = m.column_map[index.column()] # If shifted, bring up the tag editor instead of the line editor. # Don't do this for people-name columns because order will be lost if QApplication.keyboardModifiers() == Qt.ShiftModifier and self.sep == ',': key = col if m.is_custom_column(col) else None d = TagEditor(parent, self.db, m.id(index.row()), key=key) if d.exec_() == TagEditor.Accepted: m.setData(index, self.sep.join(d.tags), Qt.EditRole) return None editor = EditWithComplete(parent) editor.set_separator(self.sep) editor.set_space_before_sep(self.space_before_sep) if self.sep == '&': editor.set_add_separator(tweaks['authors_completer_append_separator']) if not m.is_custom_column(col): all_items = getattr(self.db, self.items_func_name)() else: all_items = list(self.db.all_custom( label=self.db.field_metadata.key_to_label(col))) editor.update_items_cache(all_items) else: editor = EnLineEdit(parent) return editor
def createEditor(self, parent, option, index): if self.db and hasattr(self.db, self.items_func_name): m = index.model() col = m.column_map[index.column()] # If shifted, bring up the tag editor instead of the line editor. if QApplication.keyboardModifiers( ) == Qt.ShiftModifier and col != 'authors': key = col if m.is_custom_column(col) else None d = TagEditor(parent, self.db, m.id(index.row()), key=key) if d.exec_() == TagEditor.Accepted: m.setData(index, self.sep.join(d.tags), Qt.EditRole) return None editor = EditWithComplete(parent) editor.set_separator(self.sep) editor.set_space_before_sep(self.space_before_sep) if self.sep == '&': editor.set_add_separator( tweaks['authors_completer_append_separator']) if not m.is_custom_column(col): all_items = getattr(self.db, self.items_func_name)() else: all_items = list( self.db.all_custom( label=self.db.field_metadata.key_to_label(col))) editor.update_items_cache(all_items) else: editor = EnLineEdit(parent) return editor
def select_book(self, row): book_id = int(self.books_table.item(row, self.title_column).data(Qt.UserRole)) self.view.select_rows([book_id]) modifiers = int(QApplication.keyboardModifiers()) if modifiers in (Qt.CTRL, Qt.SHIFT): em = find_plugin('Edit Metadata') if em is not None: em.actual_plugin_.edit_metadata(None)
def _toggle(self, index, set_to): ''' set_to: if None, advance the state. Otherwise must be one of the values in TAG_SEARCH_STATES ''' modifiers = int(QApplication.keyboardModifiers()) exclusive = modifiers not in (Qt.CTRL, Qt.SHIFT) if self._model.toggle(index, exclusive, set_to=set_to): self.tags_marked.emit(self.search_string)
def toolbar_button_clicked(self): if not self.duplicate_finder.has_results(): return self.find_book_duplicates() # If the user control-clicks on this button/menu, reverse the direction of search forward = True mods = QApplication.keyboardModifiers() if mods & Qt.ControlModifier or mods & Qt.ShiftModifier: forward = False self.show_next_result(forward)
def select_book(self, row): book_id = int( self.books_table.item(row, self.title_column).data(Qt.UserRole)) self.view.select_rows([book_id]) modifiers = int(QApplication.keyboardModifiers()) if modifiers in (Qt.CTRL, Qt.SHIFT): em = find_plugin('Edit Metadata') if em is not None: em.actual_plugin_.edit_metadata(None)
def end_pressed(self): if self.prompt_frame is not None: mods = QApplication.keyboardModifiers() ctrl = bool(int(mods & Qt.CTRL)) if ctrl: self.cursor_pos = (len(list(self.prompt()))-1, self.prompt_len) c = self.cursor c.movePosition(c.EndOfLine) self.setTextCursor(c) self.ensureCursorVisible()
def check_for_plugin_updates(self): # Get the user to choose a plugin to install initial_filter = FILTER_UPDATE_AVAILABLE mods = QApplication.keyboardModifiers() if mods & Qt.ControlModifier or mods & Qt.ShiftModifier: initial_filter = FILTER_ALL d = PluginUpdaterDialog(self.gui, initial_filter=initial_filter) d.exec_() if d.do_restart: self.gui.quit(restart=True)
def end_pressed(self): if self.prompt_frame is not None: mods = QApplication.keyboardModifiers() ctrl = bool(int(mods & Qt.CTRL)) if ctrl: self.cursor_pos = (len(list(self.prompt())) - 1, self.prompt_len) c = self.cursor c.movePosition(c.EndOfLine) self.setTextCursor(c) self.ensureCursorVisible()
def home_pressed(self): if self.prompt_frame is not None: mods = QApplication.keyboardModifiers() ctrl = bool(int(mods & Qt.CTRL)) if ctrl: self.cursor_pos = (0, self.prompt_len) else: c = self.cursor c.movePosition(c.StartOfLine) c.movePosition(c.NextCharacter, n=self.prompt_len) self.setTextCursor(c) self.ensureCursorVisible()
def handle_hovered(link, title, content): """ When hovered, if ALT is pressed, show message label; hide otherwise """ if ((QApplication.keyboardModifiers() & Qt.AltModifier) and (link or title or content)): # ugly hack to ensure proper resizing; find a better way? self.message_label.hide() self.message_label.setText(link + " " + title + " " + content) self.message_label.show() else: self.message_label.hide()
def canvasClickedWithPicker(self, point, button): # button is the MouseButton #coordinatorLog(type(button).__name__) if QApplication.keyboardModifiers() and Qt.ControlModifier: self.setInputCrs(self.canvas.mapSettings().destinationCrs()) # coordinatorLog("Current Canvas Transform is %s -> %s (we do the reverse to get input)" # % (self._canvasTransform.sourceCrs().authid(), self._canvasTransform.destinationCrs().authid()) # ) point = self._canvasTransform.transform( point, QgsCoordinateTransform.ReverseTransform) self.dockwidget.setInputPoint(point)
def handle_hovered(link, title, content): """ When hovered, if ALT is pressed, show message label; hide otherwise """ if ((QApplication.keyboardModifiers() & Qt.AltModifier) and (link or title or content)): # ugly hack to ensure proper resizing; find a better way? self.message_label.hide() self.message_label.setText( link + " " + title + " " + content) self.message_label.show() else: self.message_label.hide()
def char_selected(self, c): if QApplication.keyboardModifiers() & Qt.CTRL: self.hide() if self.parent() is None or self.parent().focusWidget() is None: QApplication.clipboard().setText(c) return self.parent().activateWindow() w = self.parent().focusWidget() e = QInputMethodEvent('', []) e.setCommitString(c) if hasattr(w, 'no_popup'): oval = w.no_popup w.no_popup = True QApplication.sendEvent(w, e) if hasattr(w, 'no_popup'): w.no_popup = oval
def select_book(self, row, column): ''' row and column both refer the qv table. In particular, column is not the logical column in the book list. ''' item = self.books_table.item(row, column) if item is None: return book_id = int(self.books_table.item(row, column).data(Qt.UserRole)) key = self.column_order[column] modifiers = int(QApplication.keyboardModifiers()) if modifiers in (Qt.CTRL, Qt.SHIFT): self.view.select_rows([book_id]) em = find_plugin('Edit Metadata') if em and em.actual_plugin_: em.actual_plugin_.edit_metadata(None) else: self.view.select_cell(self.db.data.id_to_index(book_id), self.view.column_map.index(key))
def select_book_and_qv(self, row, column): ''' row and column both refer the qv table. In particular, column is not the logical column in the book list. ''' item = self.books_table.item(row, column) if item is None: return book_id = int(self.books_table.item(row, column).data(Qt.UserRole)) if not self.book_displayed_in_library_view(book_id): self.book_not_in_view_error() return key = self.column_order[column] modifiers = int(QApplication.keyboardModifiers()) if modifiers in (Qt.CTRL, Qt.SHIFT): self.edit_metadata(book_id) else: self.view.select_cell(self.db.data.id_to_index(book_id), self.view.column_map.index(key))
def select_book(self, row, column): ''' row and column both refer the qv table. In particular, column is not the logical column in the book list. ''' item = self.books_table.item(row, column) if item is None: return book_id = int(self.books_table.item(row, column).data(Qt.UserRole)) key = self.column_order[column] modifiers = int(QApplication.keyboardModifiers()) if modifiers in (Qt.CTRL, Qt.SHIFT): self.view.select_rows([book_id]) em = find_plugin('Edit Metadata') if em is not None: em.actual_plugin_.edit_metadata(None) else: self.view.select_cell(self.db.data.id_to_index(book_id), self.view.column_map.index(key))
def wheelEvent(self, event: QWheelEvent): """ wheelEvent Parameters ---------- event : QWheelEvent """ if QApplication.keyboardModifiers() == Qt.ControlModifier: delta = event.angleDelta() if delta.y() == 0: event.ignore() return d = delta.y() / abs(delta.y()) if d > 0.0: self.scale_up() else: self.scale_down() else: super().wheelEvent(event)
def mouseDoubleClickEvent(self, event): ''' @param: event QMouseEvent ''' super().mouseDoubleClickEvent(event) if len(self.selectionModel().selectedRows()) == 1: # QModelIndex index = self.indexAt(event.pos()) if index.isValid(): item = self._model.item(self._filter.mapToSource(index)) # Qt::MouseButtons buttons = event.buttons() # Qt::KeyboardModifiers modifiers = QApplication.keyboardModifiers() if buttons == Qt.LeftButton and modifiers == Qt.NoModifier: self.bookmarkActivated.emit(item) elif buttons == Qt.LeftButton and modifiers == Qt.ShiftModifier: self.bookmarkShiftActivated.emit(item)
def event_has_mods(self, event=None): mods = event.modifiers() if event is not None else \ QApplication.keyboardModifiers() return mods & Qt.ControlModifier or mods & Qt.ShiftModifier
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)
def check_key_modifier(which_modifier): v = int(QApplication.keyboardModifiers() & (Qt.ControlModifier + Qt.ShiftModifier)) return v == which_modifier