From 9beae2bc2b370b0a9eca6e472dd1f6b9e331775a Mon Sep 17 00:00:00 2001 From: GuyTeichman <48219633+GuyTeichman@users.noreply.github.com> Date: Fri, 6 Sep 2024 18:46:40 +0300 Subject: [PATCH] transitioned RNAlysis to Qt6 --- pytest.ini | 2 +- requirements.txt | 6 +- rnalysis/gui/gui.py | 216 ++++++++++---------- rnalysis/gui/gui_graphics.py | 4 +- rnalysis/gui/gui_quickstart.py | 24 +-- rnalysis/gui/gui_style.py | 2 +- rnalysis/gui/gui_widgets.py | 50 ++--- rnalysis/gui/gui_windows.py | 350 ++++++++------------------------- tests/test_gui.py | 52 ++--- tests/test_gui_graphics.py | 6 +- tests/test_gui_quickstart.py | 44 ++--- tests/test_gui_widgets.py | 10 +- tests/test_gui_windows.py | 71 +------ 13 files changed, 301 insertions(+), 536 deletions(-) diff --git a/pytest.ini b/pytest.ini index 608ad155f..ddae59950 100644 --- a/pytest.ini +++ b/pytest.ini @@ -2,4 +2,4 @@ testpaths = tests/ python_files = *.py python_functions = test_* -qt_api=pyqt5 +qt_api=pyqt6 diff --git a/requirements.txt b/requirements.txt index 76d2bdd33..5af48976d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,8 +15,8 @@ joblib>=1.4.2 tqdm>=4.65 appdirs>=1.4.0 typing_extensions>=4.5 -PyQt5>=5.15.9 -qdarkstyle +PyQt6>=6.7 +qdarkstyle>=3 defusedxml>=0.7.1 aiohttp>=3.8.4, <3.10 aiodns>=3.0.0 @@ -26,5 +26,5 @@ tenacity>=8.2.3 mslex>=1.1.0 nest-asyncio>=1.6.0 kmedoids>=0.5.1 -polars[async,numpy,pyarrow,pandas]>=1.5.0 +polars[async,numpy,pyarrow,pandas]>=1.6.0,<1.7 pandas[performance,parquet] diff --git a/rnalysis/gui/gui.py b/rnalysis/gui/gui.py index 62400629a..841d0f6f3 100644 --- a/rnalysis/gui/gui.py +++ b/rnalysis/gui/gui.py @@ -20,7 +20,7 @@ import numpy as np import polars as pl import yaml -from PyQt5 import QtCore, QtWidgets, QtGui +from PyQt6 import QtCore, QtWidgets, QtGui from rnalysis import fastq, filtering, enrichment, __version__ from rnalysis.gui import gui_style, gui_widgets, gui_windows, gui_graphics, gui_quickstart @@ -512,7 +512,7 @@ def init_setups_ui(self): self.setups_grid.addWidget(self.stack, 1, 0) self.setups_widgets['list'] = gui_widgets.MultiChoiceListWithDelete(list(), parent=self.setups_group) self.setups_widgets['list'].itemDeleted.connect(self.remove_clustering_setup) - self.setups_grid.addWidget(QtWidgets.QLabel('Added setups'), 0, 1, QtCore.Qt.AlignCenter) + self.setups_grid.addWidget(QtWidgets.QLabel('Added setups'), 0, 1, QtCore.Qt.AlignmentFlag.AlignCenter) self.setups_grid.addWidget(self.setups_widgets['list'], 1, 1, 2, 1) self.setups_widgets['add_button'] = QtWidgets.QPushButton('Add setup') @@ -622,10 +622,10 @@ def init_basic_ui(self): self.plot_group.setVisible(False) self.stats_group.setVisible(False) - self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.scroll.setWidgetResizable(True) self.scroll.setWidget(self.scroll_widget) - self.scroll_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) + self.scroll_layout.setSizeConstraint(QtWidgets.QLayout.SizeConstraint.SetMinAndMaxSize) self.scroll_layout.addWidget(self.list_group) self.scroll_layout.addWidget(self.stats_group) @@ -994,7 +994,7 @@ def init_ui(self): self.setWindowTitle('Set Operations') self.setGeometry(600, 50, 1050, 800) self.setLayout(self.layout) - self.widgets['splitter'] = QtWidgets.QSplitter(QtCore.Qt.Horizontal) + self.widgets['splitter'] = QtWidgets.QSplitter(QtCore.Qt.Orientation.Horizontal) self.layout.addWidget(self.widgets['splitter']) self.widgets['splitter'].addWidget(self.list_group) self.widgets['splitter'].addWidget(self.operations_group) @@ -1223,7 +1223,7 @@ def init_ui(self): self.setWindowTitle('Gene Set Visualization') self.setGeometry(600, 50, 1050, 800) self.setLayout(self.layout) - self.widgets['splitter'] = QtWidgets.QSplitter(QtCore.Qt.Horizontal) + self.widgets['splitter'] = QtWidgets.QSplitter(QtCore.Qt.Orientation.Horizontal) self.layout.addWidget(self.widgets['splitter']) self.widgets['splitter'].addWidget(self.list_group) self.widgets['splitter'].addWidget(self.visualization_group) @@ -1398,18 +1398,25 @@ class TabPage(QtWidgets.QWidget): GENERAL_FUNCS = () THREADED_FUNCS = set() - def __init__(self, parent=None, undo_stack: QtWidgets.QUndoStack = None, tab_id: int = None): + def __init__(self, parent=None, undo_stack: QtGui.QUndoStack = None, tab_id: int = None): super().__init__(parent) self.tab_id = tab_id self.undo_stack = undo_stack self.sup_layout = QtWidgets.QVBoxLayout(self) - self.container = QtWidgets.QWidget(self) - self.layout = QtWidgets.QVBoxLayout(self.container) + + self.splitter = QtWidgets.QSplitter(QtCore.Qt.Orientation.Vertical) + self.sup_layout.addWidget(self.splitter) + + # initiate the splitter layout for the tab self.scroll = QtWidgets.QScrollArea() - self.scroll.setWidget(self.container) - self.scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) - self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOn) + self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.scroll.setWidgetResizable(True) + self.splitter.addWidget(self.scroll) + + self.container = QtWidgets.QWidget() + self.layout = QtWidgets.QVBoxLayout(self.container) + self.scroll.setWidget(self.container) self.name = None self.creation_time = time.time() @@ -1429,7 +1436,7 @@ def __init__(self, parent=None, undo_stack: QtWidgets.QUndoStack = None, tab_id: self.function_group = QtWidgets.QGroupBox('Apply functions') self.function_grid = QtWidgets.QGridLayout(self.function_group) self.function_widgets = {} - self.layout.insertWidget(2, self.function_group) + self.layout.addWidget(self.function_group) self.function_group.setVisible(False) self.stdout_group = QtWidgets.QGroupBox('Log') @@ -1439,13 +1446,9 @@ def __init__(self, parent=None, undo_stack: QtWidgets.QUndoStack = None, tab_id: # initiate apply button self.apply_button = QtWidgets.QPushButton('Apply') self.apply_button.clicked.connect(self.apply_function) - self.layout.insertWidget(3, self.apply_button) + self.layout.addWidget(self.apply_button) self.apply_button.setVisible(False) - # initiate the splitter layout for the tab - self.splitter = QtWidgets.QSplitter(QtCore.Qt.Vertical) - self.sup_layout.addWidget(self.splitter) - self.splitter.addWidget(self.scroll) self.splitter.addWidget(self.stdout_group) self.splitter.setStretchFactor(0, 1) @@ -1670,7 +1673,7 @@ class SetTabPage(TabPage): } def __init__(self, set_name: str, gene_set: typing.Union[set, enrichment.FeatureSet] = None, parent=None, - undo_stack: QtWidgets.QUndoStack = None, tab_id: int = None): + undo_stack: QtGui.QUndoStack = None, tab_id: int = None): super().__init__(parent, undo_stack, tab_id) if gene_set is None: gene_set = enrichment.FeatureSet(set(), set_name) @@ -1971,7 +1974,7 @@ class FilterTabPage(TabPage): startedClustering = QtCore.pyqtSignal(object, object, object) widthChanged = QtCore.pyqtSignal() - def __init__(self, parent=None, undo_stack: QtWidgets.QUndoStack = None, tab_id: int = None): + def __init__(self, parent=None, undo_stack: QtGui.QUndoStack = None, tab_id: int = None): super().__init__(parent, undo_stack, tab_id) self.filter_obj = None @@ -2051,8 +2054,8 @@ def init_overview_ui(self): self.overview_widgets['table_name_label'].setWordWrap(True) self.overview_widgets['preview'] = gui_widgets.ReactiveTableView() - self.overview_widgets['preview'].setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.overview_widgets['preview'].setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.overview_widgets['preview'].setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + self.overview_widgets['preview'].setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.overview_grid.addWidget(self.overview_widgets['table_name_label'], this_row, 0, 1, 4) this_row += 1 @@ -2158,7 +2161,7 @@ def init_basic_ui(self): self.basic_grid.setRowStretch(4, 1) self.basic_grid.setColumnStretch(4, 1) - def _check_for_special_functions(self, is_selected: bool): + def _check_for_special_functions(self, is_selected: bool=True): if not is_selected: return this_stack: FuncTypeStack = self.stack.currentWidget() @@ -2513,7 +2516,7 @@ def remove_last_function(self): err = QtWidgets.QMessageBox(self) err.setWindowTitle('Pipeline is already empty!') err.setText('Cannot remove functions from the Pipeline - it is already empty!') - err.setIcon(err.Warning) + err.setIcon(err.Icon.Warning) err.exec() def _get_pipeline_name(self): @@ -2536,9 +2539,10 @@ def closeEvent(self, event): # pragma: no cover "All unsaved progress will be lost" reply = QtWidgets.QMessageBox.question(self, "Close 'Create Pipeline' window?", - quit_msg, QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.Yes) + quit_msg, QtWidgets.QMessageBox.StandardButton.No, + QtWidgets.QMessageBox.StandardButton.Yes) - if reply == QtWidgets.QMessageBox.Yes: + if reply == QtWidgets.QMessageBox.StandardButton.Yes: event.accept() else: event.ignore() @@ -2577,7 +2581,8 @@ def __init__(self, objs: List[Union[filtering.Filter, enrichment.FeatureSet]], j self.objs[key] = obj self.files = list(self.objs.keys()) - self.button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) + self.button_box = QtWidgets.QDialogButtonBox( + QtWidgets.QDialogButtonBox.StandardButton.Ok | QtWidgets.QDialogButtonBox.StandardButton.Cancel) self.labels = dict() self.keep_marks = dict() self.names = dict() @@ -2596,7 +2601,7 @@ def init_ui(self): self.scroll.setWidgetResizable(True) self.scroll.setWidget(self.scroll_widget) self.scroll_widget.setLayout(self.scroll_layout) - self.scroll_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) + self.scroll_layout.setSizeConstraint(QtWidgets.QLayout.SizeConstraint.SetMinAndMaxSize) self.select_all.clicked.connect(self.change_all) self.button_box.accepted.connect(self.accept) @@ -2658,7 +2663,8 @@ class MultiOpenWindow(QtWidgets.QDialog): def __init__(self, files: List[str], parent=None): super().__init__(parent) self.files = files - self.button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) + self.button_box = QtWidgets.QDialogButtonBox( + QtWidgets.QDialogButtonBox.StandardButton.Ok | QtWidgets.QDialogButtonBox.StandardButton.Cancel) self.all_types_combo = QtWidgets.QComboBox(self) self.paths = dict() self.table_types = dict() @@ -2679,7 +2685,7 @@ def init_ui(self): self.scroll.setWidgetResizable(True) self.scroll.setWidget(self.scroll_widget) self.scroll_widget.setLayout(self.scroll_layout) - self.scroll_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) + self.scroll_layout.setSizeConstraint(QtWidgets.QLayout.SizeConstraint.SetMinAndMaxSize) self.button_box.accepted.connect(self.accept) self.button_box.rejected.connect(self.reject) @@ -2770,9 +2776,9 @@ def __init__(self, parent=None): self.setElideMode(QtCore.Qt.TextElideMode.ElideMiddle) def mousePressEvent(self, event: QtGui.QMouseEvent): - if event.button() == QtCore.Qt.LeftButton: + if event.button() == QtCore.Qt.MouseButton.LeftButton: super().mousePressEvent(event) - elif event.button() == QtCore.Qt.RightButton: + elif event.button() == QtCore.Qt.MouseButton.RightButton: point = event.pos() if point.isNull(): return @@ -2810,7 +2816,7 @@ def widget(self, index: int) -> Union[FilterTabPage, SetTabPage]: return super().widget(index) -class RenameCommand(QtWidgets.QUndoCommand): +class RenameCommand(QtGui.QUndoCommand): __slots__ = {'prev_name': 'previous name of the tab', 'new_name': 'new name of the tab', 'prev_id': 'previous ID', @@ -2835,7 +2841,7 @@ def redo(self): self.tab._rename(self.new_name, self.job_id) -class CloseTabCommand(QtWidgets.QUndoCommand): +class CloseTabCommand(QtGui.QUndoCommand): __slots__ = {'tab_container': 'ReactiveTabWidget containing the tabs', 'tab_index': 'index of the tab to be closed', 'tab_icon': 'icon of the tab', @@ -2868,7 +2874,7 @@ def redo(self): self.tab_container.removeTab(self.tab_index) -class InplaceCommand(QtWidgets.QUndoCommand): +class InplaceCommand(QtGui.QUndoCommand): __slots__ = {'tab': 'tab widget', 'prev_job_id': 'previous job ID', 'new_job_id': 'new job ID', @@ -2965,7 +2971,7 @@ def redo(self): self.tab.itemSpawned.emit(f"'{source_name}'\noutput", new_spawn_id, self.new_job_id, self.tab.obj()) -class PipelineInplaceCommand(QtWidgets.QUndoCommand): +class PipelineInplaceCommand(QtGui.QUndoCommand): __slots__ = {'tab': 'tab object', 'pipeline': 'Pipeline to apply', 'pipeline_name': 'Pipeline name', @@ -3016,8 +3022,8 @@ def __init__(self, gather_stdout: bool = True): self.report = None self.tabs = ReactiveTabWidget(self) - self.closed_tabs_stack = QtWidgets.QUndoStack(self) - self.undo_group = QtWidgets.QUndoGroup(self) + self.closed_tabs_stack = QtGui.QUndoStack(self) + self.undo_group = QtGui.QUndoGroup(self) self.tabs.currentChanged.connect(self._change_undo_stack) self.undo_view = QtWidgets.QUndoView(self.undo_group) @@ -3150,12 +3156,12 @@ def init_ui(self): self.command_history_dock.setWidget(self.undo_view) self.command_history_dock.setFloating(False) - self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.command_history_dock) + self.addDockWidget(QtCore.Qt.DockWidgetArea.RightDockWidgetArea, self.command_history_dock) self.setStatusBar(self.status_bar) self.task_queue_window.cancelRequested.connect(self.cancel_job) - self.tabs.setCornerWidget(self.add_tab_button, QtCore.Qt.TopRightCorner) + self.tabs.setCornerWidget(self.add_tab_button, QtCore.Qt.Corner.TopRightCorner) self.setCentralWidget(self.tabs) @QtCore.pyqtSlot() @@ -3164,11 +3170,12 @@ def clear_history(self, confirm_action: bool = True): clear_msg = """Are you sure you want to clear all command history? This cannot be undone!""" reply = QtWidgets.QMessageBox.question(self, 'Clear history', - clear_msg, QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.Yes) + clear_msg, QtWidgets.QMessageBox.StandardButton.No, + QtWidgets.QMessageBox.StandardButton.Yes) else: - reply = QtWidgets.QMessageBox.Yes + reply = QtWidgets.QMessageBox.StandardButton.Yes - if reply == QtWidgets.QMessageBox.Yes: + if reply == QtWidgets.QMessageBox.StandardButton.Yes: for stack in self.undo_group.stacks(): stack.clear() self.closed_tabs_stack.clear() @@ -3177,7 +3184,7 @@ def clear_history(self, confirm_action: bool = True): def init_tab_contextmenu(self, ind: int): self.tab_contextmenu = QtWidgets.QMenu(self) - new_to_right_action = QtWidgets.QAction("New tab to the right") + new_to_right_action = QtGui.QAction("New tab to the right") new_to_right_action.triggered.connect( functools.partial(self.add_new_tab_at, index=ind + 1, name=None, is_set=False)) self.tab_contextmenu.addAction(new_to_right_action) @@ -3186,36 +3193,36 @@ def init_tab_contextmenu(self, ind: int): color_menu = self.tab_contextmenu.addMenu("Change tab &color") actions = [] for color in gui_graphics.COLOR_ICONS: - this_action = QtWidgets.QAction(color.capitalize()) + this_action = QtGui.QAction(color.capitalize()) this_action.setIcon(gui_graphics.get_icon(color)) this_action.triggered.connect(functools.partial(self.set_tab_icon, ind, icon_name=color)) actions.append(this_action) color_menu.addAction(this_action) - reset_action = QtWidgets.QAction("Reset color") + reset_action = QtGui.QAction("Reset color") reset_action.triggered.connect(functools.partial(self.set_tab_icon, ind, icon_name=None)) color_menu.addAction(reset_action) self.tab_contextmenu.addSeparator() # sort_menu = self.tab_contextmenu.addMenu("Sort tabs") - sort_by_name = QtWidgets.QAction("Sort by tab &name") + sort_by_name = QtGui.QAction("Sort by tab &name") sort_by_name.triggered.connect(self.sort_tabs_by_name) - sort_by_time = QtWidgets.QAction("Sort by creation &time") + sort_by_time = QtGui.QAction("Sort by creation &time") sort_by_time.triggered.connect(self.sort_tabs_by_creation_time) - sort_by_type = QtWidgets.QAction("Sort by tab type") + sort_by_type = QtGui.QAction("Sort by tab type") sort_by_type.triggered.connect(self.sort_tabs_by_type) - sort_by_size = QtWidgets.QAction("Sort by number of features") + sort_by_size = QtGui.QAction("Sort by number of features") sort_by_size.triggered.connect(self.sort_tabs_by_n_features) - reverse = QtWidgets.QAction("Reverse tab order") + reverse = QtGui.QAction("Reverse tab order") reverse.triggered.connect(self.sort_reverse) self.tab_contextmenu.addActions([sort_by_name, sort_by_time, sort_by_type, sort_by_size, reverse]) self.tab_contextmenu.addSeparator() - close_this_action = QtWidgets.QAction("Close") + close_this_action = QtGui.QAction("Close") close_this_action.triggered.connect(functools.partial(self.close_tab, ind)) - close_others_action = QtWidgets.QAction("Close other tabs") + close_others_action = QtGui.QAction("Close other tabs") close_others_action.triggered.connect(functools.partial(self.close_other_tabs, ind)) - close_right_action = QtWidgets.QAction("Close tabs to the right") + close_right_action = QtGui.QAction("Close tabs to the right") close_right_action.triggered.connect(functools.partial(self.close_tabs_to_the_right, ind)) - close_left_action = QtWidgets.QAction("Close tabs to the left") + close_left_action = QtGui.QAction("Close tabs to the left") close_left_action.triggered.connect(functools.partial(self.close_tabs_to_the_left, ind)) self.tab_contextmenu.addActions([close_this_action, close_others_action, close_right_action, close_left_action]) @@ -3306,7 +3313,7 @@ def add_new_tab_at(self, index: int, name: str = None, is_set: bool = False): self.tabs.tabBar().moveTab(self.tabs.currentIndex(), index) def add_new_tab(self, name: str = None, is_set: bool = False): - new_undo_stack = QtWidgets.QUndoStack() + new_undo_stack = QtGui.QUndoStack() self.undo_group.addStack(new_undo_stack) if name is None: name = 'New Table' @@ -3354,7 +3361,7 @@ def remove_tab_asterisk(self): self.tabs.setTabText(self.tab.currentIndex(), current_name.rstrip('*')) def new_table_from_folder(self): - folder_name = QtWidgets.QFileDialog.getExistingDirectory(self, "Choose directory", str(Path.home())) + folder_name = QtWidgets.QFileDialog.getExistingDirectory(self, "Choose directory") if folder_name: filter_obj = filtering.CountFilter.from_folder(folder_name) if self.tabs.currentWidget().is_empty(): @@ -3362,13 +3369,13 @@ def new_table_from_folder(self): self.new_tab_from_filter_obj(filter_obj, JOB_COUNTER.get_id()) def new_table_from_folder_htseqcount(self): - folder_name = QtWidgets.QFileDialog.getExistingDirectory(self, "Choose directory", str(Path.home())) + folder_name = QtWidgets.QFileDialog.getExistingDirectory(self, "Choose directory") if folder_name: normalize_answer = QtWidgets.QMessageBox.question(self, 'Normalize values?', "Do you want to normalize your count table to " "reads-per-million (RPM)?", - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - to_normalize = normalize_answer == QtWidgets.QMessageBox.Yes + QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No) + to_normalize = normalize_answer == QtWidgets.QMessageBox.StandardButton.Yes filter_obj = filtering.CountFilter.from_folder_htseqcount(folder_name, norm_to_rpm=to_normalize) if self.tabs.currentWidget().is_empty(): @@ -3376,10 +3383,8 @@ def new_table_from_folder_htseqcount(self): self.new_tab_from_filter_obj(filter_obj, JOB_COUNTER.get_id()) def load_multiple_files(self): - dialog = gui_windows.MultiFileSelectionDialog() - accepted = dialog.exec() - if accepted == QtWidgets.QDialog.Accepted: - filenames = dialog.result() + filenames, _ = QtWidgets.QFileDialog.getOpenFileNames(self, "Choose files") + if filenames: if len(filenames) > 0: window = MultiOpenWindow(filenames, self) accepted = window.exec() @@ -3574,8 +3579,8 @@ def delete_pipeline(self): reply = QtWidgets.QMessageBox.question(self, 'Delete Pipeline?', "Are you sure you want to delete this Pipeline? " "This action cannot be undone!", - QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Yes) - if reply == QtWidgets.QMessageBox.Yes: + QtWidgets.QMessageBox.StandardButton.No | QtWidgets.QMessageBox.StandardButton.Yes) + if reply == QtWidgets.QMessageBox.StandardButton.Yes: self.pipelines.pop(pipeline_name) print(f"Pipeline '{pipeline_name}' deleted successfully") @@ -3626,7 +3631,7 @@ def import_pipeline(self): def import_multiple_gene_sets(self): dialog = gui_windows.MultiFileSelectionDialog() accepted = dialog.exec() - if accepted == QtWidgets.QDialog.Accepted: + if accepted == QtWidgets.QDialog.DialogCode.Accepted: filenames = dialog.result() tabs_to_close = None if len(filenames) > 0 and self.tabs.currentWidget().is_empty(): @@ -3638,11 +3643,11 @@ def import_multiple_gene_sets(self): self.tabs.removeTab(tabs_to_close) def import_gene_set(self): - filename, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Choose a file", str(Path.home()), - "Text Document (*.txt);;" - "Comma-Separated Values (*.csv);;" - "Tab-Separated Values (*.tsv);;" - "All Files (*)") + filename, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Choose a file", filter= + "Text Document (*.txt);;" + "Comma-Separated Values (*.csv);;" + "Tab-Separated Values (*.tsv);;" + "All Files (*)") if filename: tabs_to_close = None if self.tabs.currentWidget().is_empty(): @@ -3733,13 +3738,13 @@ def save_pipeline(self, pipeline_name: str, pipeline: filtering.Pipeline): response = QtWidgets.QMessageBox.question(self, 'Overwrite Pipeline?', 'A Pipeline with this name already exists. ' 'Are you sure you want to overwrite it?', - defaultButton=QtWidgets.QMessageBox.No) + defaultButton=QtWidgets.QMessageBox.StandardButton.No) else: is_new = True - response = QtWidgets.QMessageBox.Yes + response = QtWidgets.QMessageBox.StandardButton.Yes - if response == QtWidgets.QMessageBox.Yes: + if response == QtWidgets.QMessageBox.StandardButton.Yes: new_pipeline_id = JOB_COUNTER.get_id() if self._generate_report: if is_new: @@ -3754,7 +3759,7 @@ def settings(self): self.settings_window.exec() def create_action(self, name, triggered_func, checkable=False, checked=False, enabled=True, shortcut=None): - action = QtWidgets.QAction(name, self) + action = QtGui.QAction(name, self) action.triggered.connect(triggered_func) action.setCheckable(checkable) action.setChecked(checked) @@ -3923,8 +3928,8 @@ def clear_cache(self): reply = QtWidgets.QMessageBox.question(self, 'Clear cache?', 'Are you sure you want to clear the RNAlysis cache? ' 'This cannot be undone!', - defaultButton=QtWidgets.QMessageBox.No) - if reply == QtWidgets.QMessageBox.Yes: + defaultButton=QtWidgets.QMessageBox.StandardButton.No) + if reply == QtWidgets.QMessageBox.StandardButton.Yes: io.clear_gui_cache() io.clear_cache() @@ -3936,16 +3941,16 @@ def check_for_updates(self, confirm_updated: bool = True): # pragma: no cover reply = QtWidgets.QMessageBox.question(self, 'A new version is available', 'A new version of RNAlysis is available! ' 'Do you wish to download it?') - if reply == QtWidgets.QMessageBox.Yes: + if reply == QtWidgets.QMessageBox.StandardButton.Yes: url = QtCore.QUrl('https://github.com/GuyTeichman/RNAlysis/releases/latest') if not QtGui.QDesktopServices.openUrl(url): - QtGui.QMessageBox.warning(self, 'Connection failed', 'Could not download new version') + QtWidgets.QMessageBox.warning(self, 'Connection failed', 'Could not download new version') return reply = QtWidgets.QMessageBox.question(self, 'A new version is available', 'A new version of RNAlysis is available! ' 'Do you wish to update?') - if reply == QtWidgets.QMessageBox.Yes: + if reply == QtWidgets.QMessageBox.StandardButton.Yes: io.update_rnalysis() QtCore.QCoreApplication.quit() self.deleteLater() @@ -4187,7 +4192,7 @@ def _populate_pipelines(self, menu: QtWidgets.QMenu, func: Callable, pipeline_ar # Dynamically create the actions actions = [] for name, (pipeline, pipeline_id) in self.pipelines.items(): - action = QtWidgets.QAction(name, self) + action = QtGui.QAction(name, self) args = [] if pipeline_arg: args.append(pipeline) @@ -4225,11 +4230,11 @@ def _apply_table_pipeline(self, pipeline: filtering.Pipeline, pipeline_name: str apply_msg = f"Do you want to apply Pipeline '{pipeline_name}' inplace?" reply = QtWidgets.QMessageBox.question(self, f"Apply Pipeline '{pipeline_name}'", apply_msg, - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | - QtWidgets.QMessageBox.Cancel) - if reply == QtWidgets.QMessageBox.Cancel: + QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No | + QtWidgets.QMessageBox.StandardButton.Cancel) + if reply == QtWidgets.QMessageBox.StandardButton.Cancel: return - inplace = reply == QtWidgets.QMessageBox.Yes + inplace = reply == QtWidgets.QMessageBox.StandardButton.Yes available_objs = self.get_available_objects() filtered_available_objs = {} @@ -4254,11 +4259,11 @@ def clear_session(self, confirm_action: bool = True) -> bool: response = QtWidgets.QMessageBox.question(self, 'Clear session?', 'Are you sure you want to clear your session? ' 'All unsaved changes will be lost!', - defaultButton=QtWidgets.QMessageBox.No) + defaultButton=QtWidgets.QMessageBox.StandardButton.No) else: - response = QtWidgets.QMessageBox.Yes + response = QtWidgets.QMessageBox.StandardButton.Yes - if response == QtWidgets.QMessageBox.Yes: + if response == QtWidgets.QMessageBox.StandardButton.Yes: self.close_figs_action.trigger() self.close_external_windows() while self.tabs.count() > 1: @@ -4270,13 +4275,12 @@ def clear_session(self, confirm_action: bool = True) -> bool: self._change_undo_stack(0) self._reset_reporting() - return response == QtWidgets.QMessageBox.Yes + return response == QtWidgets.QMessageBox.StandardButton.Yes def load_session(self): session_filename, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Load session", - str(Path.home()), - "RNAlysis session files (*.rnal);;" - "All Files (*)") + filter="RNAlysis session files (*.rnal);;" + "All Files (*)") if session_filename: self._load_session_from(session_filename) @@ -4290,7 +4294,7 @@ def _load_session_from(self, session_filename: Union[str, Path]): load_report = QtWidgets.QMessageBox.question(self, 'Resume previous session report?', 'Do you want to resume the previous session report?\n' 'This will clear the current session and replace it. ', - defaultButton=QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes + defaultButton=QtWidgets.QMessageBox.StandardButton.Yes) == QtWidgets.QMessageBox.StandardButton.Yes if load_report: self.clear_session(confirm_action=False) self._toggle_reporting(True) @@ -4387,9 +4391,10 @@ def closeEvent(self, event): # pragma: no cover "All unsaved progress will be lost" reply = QtWidgets.QMessageBox.question(self, 'Close program', - quit_msg, QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.Yes) + quit_msg, QtWidgets.QMessageBox.StandardButton.No, + QtWidgets.QMessageBox.StandardButton.Yes) - if reply == QtWidgets.QMessageBox.Yes: + if reply == QtWidgets.QMessageBox.StandardButton.Yes: plt.close('all') # quit job and STDOUT listener threads try: @@ -4675,25 +4680,34 @@ async def run(): # pragma: no cover app.setDesktopFileName('RNAlysis') icon_pth = str(Path(__file__).parent.parent.joinpath('favicon.ico').absolute()) app.setWindowIcon(QtGui.QIcon(icon_pth)) - matplotlib.use('Qt5Agg') + matplotlib.use('QtAgg') if show_app: splash = gui_windows.splash_screen() app.processEvents() base_message = f"RNAlysis version {__version__}:\t" - splash.showMessage(base_message + 'loading dependencies', QtCore.Qt.AlignBottom | QtCore.Qt.AlignHCenter) + splash.showMessage(base_message + 'loading dependencies', + QtCore.Qt.AlignmentFlag.AlignBottom | QtCore.Qt.AlignmentFlag.AlignHCenter) gui_widgets.init_color_map_pixmap_cache() if io.check_changed_version(): video_files = gui_quickstart.QuickStartWizard.VIDEO_FILES splash.showMessage(base_message + 'validating tutorial videos', - QtCore.Qt.AlignBottom | QtCore.Qt.AlignHCenter) + QtCore.Qt.AlignmentFlag.AlignBottom | QtCore.Qt.AlignmentFlag.AlignHCenter) async for i in io.get_gui_videos(video_files): splash.showMessage(base_message + f'getting tutorial videos {i + 1}/{len(video_files)}', - QtCore.Qt.AlignBottom | QtCore.Qt.AlignHCenter) + QtCore.Qt.AlignmentFlag.AlignBottom | QtCore.Qt.AlignmentFlag.AlignHCenter) + + splash.showMessage(base_message + 'loading application', + QtCore.Qt.AlignmentFlag.AlignBottom | QtCore.Qt.AlignmentFlag.AlignHCenter) + + # set taskbar icon on Windows + if platform.system() == 'Windows': + import ctypes + myappid = u'RNAlysis.{version}'.format(version=__version__) # arbitrary string + ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid) - splash.showMessage(base_message + 'loading application', QtCore.Qt.AlignBottom | QtCore.Qt.AlignHCenter) window = MainWindow() sys.excepthook = window.excepthook builtins.input = window.input diff --git a/rnalysis/gui/gui_graphics.py b/rnalysis/gui/gui_graphics.py index ce0893216..2d9aa5245 100644 --- a/rnalysis/gui/gui_graphics.py +++ b/rnalysis/gui/gui_graphics.py @@ -7,7 +7,7 @@ import matplotlib import matplotlib_venn import upsetplot -from PyQt5 import QtCore, QtGui +from PyQt6 import QtCore, QtGui from matplotlib import pyplot as plt from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg, NavigationToolbar2QT @@ -473,6 +473,6 @@ def get_icon(name: str): return icon elif name == 'blank': pixmap = QtGui.QPixmap(32, 32) - pixmap.fill(QtCore.Qt.transparent) + pixmap.fill(QtCore.Qt.GlobalColor.transparent) return QtGui.QIcon(pixmap) return None diff --git a/rnalysis/gui/gui_quickstart.py b/rnalysis/gui/gui_quickstart.py index 46683855e..01590f587 100644 --- a/rnalysis/gui/gui_quickstart.py +++ b/rnalysis/gui/gui_quickstart.py @@ -1,6 +1,6 @@ from pathlib import Path -from PyQt5 import QtCore, QtWidgets, QtGui +from PyQt6 import QtCore, QtWidgets, QtGui from rnalysis.utils import settings, io @@ -134,9 +134,9 @@ def __init__(self, parent=None): self.currentIdChanged.connect(self.play_tutorial) - self.setWizardStyle(QtWidgets.QWizard.ModernStyle) + self.setWizardStyle(QtWidgets.QWizard.WizardStyle.ModernStyle) self.setWindowTitle('Welcome to RNAlysis!') - self.setPixmap(self.LogoPixmap, + self.setPixmap(self.WizardPixmap.LogoPixmap, QtGui.QPixmap(Path.cwd().parent.parent.joinpath('docs/source/favicon.ico').as_posix())) self.setField('dont_show_again', not settings.get_show_tutorial_settings()) @@ -223,22 +223,22 @@ def __init__(self, video_path: Path, parent=None): self.layout = QtWidgets.QGridLayout(self) self.label = QtWidgets.QLabel(self) self.video = QtGui.QMovie(full_video_path) - self.video.setCacheMode(self.video.CacheAll) + self.video.setCacheMode(self.video.CacheMode.CacheAll) self.label.setMovie(self.video) self.play_button = QtWidgets.QToolButton() self.play_button.clicked.connect(self.change_play_state) self.stop_button = QtWidgets.QToolButton() - self.stop_button.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_MediaStop)) + self.stop_button.setIcon(self.style().standardIcon(QtWidgets.QStyle.StandardPixmap.SP_MediaStop)) self.stop_button.clicked.connect(self.stop) self.speed_button = QtWidgets.QToolButton() - self.speed_button.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_MediaSeekForward)) + self.speed_button.setIcon(self.style().standardIcon(QtWidgets.QStyle.StandardPixmap.SP_MediaSeekForward)) self.speed_button.setCheckable(True) self.speed_button.clicked.connect(self.change_speed) - self.position_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal) + self.position_slider = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal) self.position_slider.setRange(0, self.video.frameCount()) self.position_slider.valueChanged.connect(self.set_frame) self.position_slider.setTracking(False) @@ -256,7 +256,7 @@ def __init__(self, video_path: Path, parent=None): pixmap = QtGui.QPixmap(full_video_path) size = pixmap.size() - size.scale(750, 750, QtCore.Qt.KeepAspectRatio) + size.scale(750, 750, QtCore.Qt.AspectRatioMode.KeepAspectRatio) self.video.setScaledSize(size) self.layout.addWidget(self.label, 0, 0, 4, 8) @@ -279,19 +279,19 @@ def change_play_state(self): def update_play_button(self): if self.paused: - self.play_button.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_MediaPlay)) + self.play_button.setIcon(self.style().standardIcon(QtWidgets.QStyle.StandardPixmap.SP_MediaPlay)) else: - self.play_button.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_MediaPause)) + self.play_button.setIcon(self.style().standardIcon(QtWidgets.QStyle.StandardPixmap.SP_MediaPause)) QtWidgets.QApplication.processEvents() def mousePressEvent(self, event): - if event.button() == QtCore.Qt.LeftButton: + if event.button() == QtCore.Qt.MouseButton.LeftButton: self.press_pos = event.pos() def mouseReleaseEvent(self, event): # ensure that the left button was pressed *and* released within the # geometry of the widget; if so, emit the signal; - if self.press_pos is not None and event.button() == QtCore.Qt.LeftButton and event.pos() in self.rect(): + if self.press_pos is not None and event.button() == QtCore.Qt.MouseButton.LeftButton and event.pos() in self.rect(): self.clicked.emit() self.press_pos = None diff --git a/rnalysis/gui/gui_style.py b/rnalysis/gui/gui_style.py index a5a43eaf7..3696304ba 100644 --- a/rnalysis/gui/gui_style.py +++ b/rnalysis/gui/gui_style.py @@ -43,5 +43,5 @@ def get_stylesheet(): QSplitter::handle:hover { background-color: #788D9C; } """ else: - other_stylesheet = qdarkstyle.load_stylesheet(qt_api='pyqt5', palette=palette) + other_stylesheet = qdarkstyle.load_stylesheet(qt_api='pyqt6', palette=palette) return param_stylesheet + '\n' + other_stylesheet diff --git a/rnalysis/gui/gui_widgets.py b/rnalysis/gui/gui_widgets.py index 6a148b9e6..0cea4c6b2 100644 --- a/rnalysis/gui/gui_widgets.py +++ b/rnalysis/gui/gui_widgets.py @@ -14,7 +14,7 @@ import numpy as np import polars as pl import polars.selectors as cs -from PyQt5 import QtCore, QtWidgets, QtGui +from PyQt6 import QtCore, QtWidgets, QtGui from joblib import Parallel, parallel_backend from tqdm.auto import tqdm from typing_extensions import get_origin, get_args @@ -93,13 +93,13 @@ def value(self): return picked_cols def _update_window_size(self): - self.dialog_table.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) - # self.dialog_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.dialog_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.dialog_table.setSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) + # self.dialog_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + self.dialog_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.dialog_table.resizeRowsToContents() self.dialog_table.resizeColumnsToContents() - self.dialog_table.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) - self.dialog_table.horizontalHeader().setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch) + self.dialog_table.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeMode.ResizeToContents) + self.dialog_table.horizontalHeader().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeMode.Stretch) screen_height = QtWidgets.QApplication.primaryScreen().size().height() @@ -303,7 +303,7 @@ def __init__(self, message: str = "No prompt available", parent=None): super().__init__(parent) self.message = message self.layout = QtWidgets.QVBoxLayout(self) - self.button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok) + self.button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.StandardButton.Ok) self.path = PathLineEdit(parent=self) self.init_ui() @@ -480,7 +480,7 @@ def create_colormap_pixmap(map_name: str): fig.canvas.draw() data = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8) data = data.reshape(fig.canvas.get_width_height()[::-1] + (3,)) - image = QtGui.QImage(data, data.shape[1], data.shape[0], QtGui.QImage.Format_RGB888) + image = QtGui.QImage(data, data.shape[1], data.shape[0], QtGui.QImage.Format.Format_RGB888) pixmap = QtGui.QPixmap.fromImage(image) plt.close(fig) @@ -530,7 +530,7 @@ def init_ui(self): def copy_to_clipboard(self): cb = QtWidgets.QApplication.clipboard() - cb.clear(mode=cb.Clipboard) + cb.clear(mode=QtGui.QClipboard.Mode.Clipboard) cb.setText(self.text_edit.toPlainText()) self.copied_label.setText('Copied to clipboard') @@ -570,7 +570,7 @@ def paintEvent(self, event): self.setMinimumHeight((radius + self.BORDER) * 2) painter = QtGui.QPainter(self) - painter.setRenderHint(QtGui.QPainter.Antialiasing) + painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing) painter.translate(center) painter.setBrush(QtGui.QColor("#cccccc")) @@ -584,7 +584,7 @@ def paintEvent(self, event): if not self.isChecked(): sw_rect.moveLeft(-width) painter.drawRoundedRect(sw_rect, radius, radius) - painter.drawText(sw_rect, QtCore.Qt.AlignCenter, label) + painter.drawText(sw_rect, QtCore.Qt.AlignmentFlag.AlignCenter, label) class ToggleSwitch(QtWidgets.QWidget): @@ -664,7 +664,7 @@ class HelpButton(QtWidgets.QToolButton): def __init__(self, parent=None): super().__init__(parent) - self.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_MessageBoxQuestion)) + self.setIcon(self.style().standardIcon(QtWidgets.QStyle.StandardPixmap.SP_MessageBoxQuestion)) self.param_name = '' self.desc = '' @@ -679,7 +679,7 @@ def set_param_help(self, param_name: str, desc: str): def mouseReleaseEvent(self, event: QtGui.QMouseEvent): super().mouseReleaseEvent(event) - help_event = QtGui.QHelpEvent(QtCore.QEvent.Type.ToolTip, event.pos(), event.globalPos()) + help_event = QtGui.QHelpEvent(QtCore.QEvent.Type.ToolTip, event.pos(), event.globalPosition().toPoint()) self.event(help_event) @@ -752,7 +752,7 @@ def __init__(self, items: Sequence, icons: Sequence = None, parent=None): self.items = [] self.list_items = [] self.list = QtWidgets.QListWidget(self) - self.list.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) + self.list.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.MultiSelection) self.layout.addWidget(self.list, 1, 1, 4, 1) self.select_all_button = QtWidgets.QPushButton('Select all', self) @@ -831,8 +831,8 @@ def delete_selected(self): def delete_all(self): accepted = QtWidgets.QMessageBox.question(self, f"{self.delete_text.capitalize()} all items?", f"Are you sure you want to {self.delete_text} all items?", - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - if accepted == QtWidgets.QMessageBox.Yes: + QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No) + if accepted == QtWidgets.QMessageBox.StandardButton.Yes: for n_item in reversed(range(len(self.items))): self.itemDeleted.emit(n_item) self.delete_all_quietly() @@ -1055,7 +1055,7 @@ class MinMaxDialog(QtWidgets.QDialog): def __init__(self, parent=None): super().__init__(parent) - self.setWindowFlag(QtCore.Qt.WindowMinMaxButtonsHint) + self.setWindowFlag(QtCore.Qt.WindowType.WindowMinMaxButtonsHint) class TrueFalseBoth(QtWidgets.QWidget): @@ -1796,7 +1796,7 @@ def __init__(self, parent=None): @QtCore.pyqtSlot(str) def append_text(self, text: str): - self.moveCursor(QtGui.QTextCursor.End) + self.moveCursor(QtGui.QTextCursor.MoveOperation.End) if text == '\n': return text = text.replace("<", "<").replace(">", ">") @@ -1805,8 +1805,8 @@ def append_text(self, text: str): self.carriage = False diff = self.document().characterCount() - self.prev_coord cursor = self.textCursor() - cursor.movePosition(QtGui.QTextCursor.PreviousCharacter, QtGui.QTextCursor.MoveAnchor, n=diff) - cursor.movePosition(QtGui.QTextCursor.End, QtGui.QTextCursor.KeepAnchor) + cursor.movePosition(QtGui.QTextCursor.MoveOperation.PreviousCharacter, QtGui.QTextCursor.MoveMode.MoveAnchor, n=diff) + cursor.movePosition(QtGui.QTextCursor.MoveOperation.End, QtGui.QTextCursor.MoveMode.KeepAnchor) cursor.removeSelectedText() if text.endswith('\r'): @@ -2180,7 +2180,7 @@ def __init__(self, *args, **kwargs): def contextMenu(self, value: str): self.context_menu = QtWidgets.QMenu(self) - copy_action = QtWidgets.QAction(f'Copy "{value}"') + copy_action = QtGui.QAction(f'Copy "{value}"') copy_action.triggered.connect(functools.partial(QtWidgets.QApplication.clipboard().setText, value)) self.context_menu.addAction(copy_action) @@ -2189,7 +2189,7 @@ def contextMenu(self, value: str): self.db_actions = [] for name in settings.get_databases_settings(): - action = QtWidgets.QAction(f'Search "{value}" on {name}') + action = QtGui.QAction(f'Search "{value}" on {name}') self.db_actions.append(action) open_url_partial = functools.partial(QtGui.QDesktopServices.openUrl, QtCore.QUrl(f'{databases[name]}{value}')) @@ -2225,7 +2225,7 @@ def __init__(self, *args, **kwargs): def contextMenu(self, value: str): self.context_menu = QtWidgets.QMenu(self) - copy_action = QtWidgets.QAction(f'Copy "{value}"') + copy_action = QtGui.QAction(f'Copy "{value}"') copy_action.triggered.connect(functools.partial(QtWidgets.QApplication.clipboard().setText, value)) self.context_menu.addAction(copy_action) @@ -2234,7 +2234,7 @@ def contextMenu(self, value: str): self.db_actions = [] for name in settings.get_databases_settings(): - action = QtWidgets.QAction(f'Search "{value}" on {name}') + action = QtGui.QAction(f'Search "{value}" on {name}') self.db_actions.append(action) open_url_partial = functools.partial(QtGui.QDesktopServices.openUrl, QtCore.QUrl(f'{databases[name]}{value}')) @@ -2268,7 +2268,7 @@ def __init__(self, *args, **kwargs): def contextMenu(self, value: str): self.context_menu = QtWidgets.QMenu(self) - copy_action = QtWidgets.QAction(f'Copy "{value}"') + copy_action = QtGui.QAction(f'Copy "{value}"') copy_action.triggered.connect(functools.partial(QtWidgets.QApplication.clipboard().setText, value)) self.context_menu.addAction(copy_action) diff --git a/rnalysis/gui/gui_windows.py b/rnalysis/gui/gui_windows.py index 0f2af4fdb..1a9539225 100644 --- a/rnalysis/gui/gui_windows.py +++ b/rnalysis/gui/gui_windows.py @@ -1,223 +1,26 @@ import functools -import itertools import json import time import traceback -import warnings from pathlib import Path -from queue import Queue from typing import Callable, Union, Tuple import polars as pl import yaml -from PyQt5 import QtCore, QtWidgets, QtGui +from PyQt6 import QtCore, QtWidgets, QtGui from rnalysis import __version__ from rnalysis.gui import gui_style, gui_widgets from rnalysis.utils import settings, io, generic, parsing -class CheckableFileSystemModel(QtWidgets.QFileSystemModel): - checkStateChanged = QtCore.pyqtSignal(str, bool) - finishedDataChange = QtCore.pyqtSignal() - - def __init__(self): - super().__init__() - self.checkStates = {} - self.rowsInserted.connect(self.checkAdded) - self.rowsRemoved.connect(self.checkParent) - self.rowsAboutToBeRemoved.connect(self.checkRemoved) - - def checkState(self, index): - return self.checkStates.get(self.filePath(index), QtCore.Qt.Unchecked) - - def setCheckState(self, index, state, emitStateChange=True): - path = self.filePath(index) - if self.checkStates.get(path) == state: - return - self.checkStates[path] = state - if emitStateChange: - self.checkStateChanged.emit(path, bool(state)) - - def checkAdded(self, parent, first, last): - # if a file/directory is added, ensure it follows the parent state as long - # as the parent is already tracked; note that this happens also when - # expanding a directory that has not been previously loaded - if not parent.isValid(): - return - if self.filePath(parent) in self.checkStates: - state = self.checkState(parent) - for row in range(first, last + 1): - index = self.index(row, 0, parent) - path = self.filePath(index) - if path not in self.checkStates: - self.checkStates[path] = state - self.checkParent(parent) - - def checkRemoved(self, parent, first, last): - # remove items from the internal dictionary when a file is deleted; - # note that this *has* to happen *before* the model actually updates, - # that's the reason this function is connected to rowsAboutToBeRemoved - for row in range(first, last + 1): - path = self.filePath(self.index(row, 0, parent)) - if path in self.checkStates: - self.checkStates.pop(path) - - def checkParent(self, parent): - # verify the state of the parent according to the children states - if not parent.isValid(): - self.finishedDataChange.emit() - return - childStates = [self.checkState(self.index(r, 0, parent)) for r in range(self.rowCount(parent))] - newState = QtCore.Qt.Checked if all(childStates) else QtCore.Qt.Unchecked - oldState = self.checkState(parent) - if newState != oldState: - self.setCheckState(parent, newState) - self.dataChanged.emit(parent, parent) - self.checkParent(parent.parent()) - - def flags(self, index): - return super().flags(index) | QtCore.Qt.ItemIsUserCheckable - - def data(self, index, role=QtCore.Qt.DisplayRole): - if role == QtCore.Qt.CheckStateRole and index.column() == 0: - return self.checkState(index) - return super().data(index, role) - - def setData(self, index, value, role, checkParent=True, emitStateChange=True): - if role == QtCore.Qt.CheckStateRole and index.column() == 0: - self.setCheckState(index, value, emitStateChange) - for row in range(self.rowCount(index)): - # set the data for the children, but do not emit the state change, - # and don't check the parent state (to avoid recursion) - self.setData(index.child(row, 0), value, QtCore.Qt.CheckStateRole, - checkParent=False, emitStateChange=False) - self.dataChanged.emit(index, index) - if checkParent: - self.checkParent(index.parent()) - return True - - return super().setData(index, value, role) - - -class FilterProxy(QtCore.QSortFilterProxyModel): - ''' - Based on StackOverflow answer by user ekhumoro: - https://stackoverflow.com/questions/72587813/how-to-filter-by-no-extension-in-qfilesystemmodel - ''' - - def __init__(self, disables=False, parent=None): - super().__init__(parent) - self._disables = bool(disables) - - def filterAcceptsRow(self, row, parent): - index = self.sourceModel().index(row, 0, parent) - if not self._disables: - return self.matchIndex(index) - return index.isValid() - - def matchIndex(self, index): - return (super().filterAcceptsRow(index.row(), index.parent())) - - def flags(self, index): - flags = super().flags(index) - if (self._disables and - not self.matchIndex(self.mapToSource(index))): - flags &= ~QtCore.Qt.ItemIsEnabled - return flags - - -class MultiFileSelectionDialog(gui_widgets.MinMaxDialog): - ''' - Based on a Stack Overflow answer by user 'musicamante': - https://stackoverflow.com/questions/63309406/qfilesystemmodel-with-checkboxes - ''' - - def __init__(self): - super().__init__() - self.layout = QtWidgets.QGridLayout(self) - self.tree_mycomputer = QtWidgets.QTreeView() - self.tree_home = QtWidgets.QTreeView() - self.open_button = QtWidgets.QPushButton('Open') - self.cancel_button = QtWidgets.QPushButton('Cancel') - self.logger = QtWidgets.QPlainTextEdit() - self.init_models() - self.init_ui() - - def init_models(self): - model_mycomputer = CheckableFileSystemModel() - model_mycomputer.setRootPath('') - model_home = CheckableFileSystemModel() - model_home.setRootPath('.') - proxy = FilterProxy(False, self) - proxy.setFilterRegularExpression(r'^(?![.])(?!.*[-_.]$).+') - proxy.setSourceModel(model_home) - self.tree_mycomputer.setModel(model_mycomputer) - self.tree_mycomputer.setRootIndex(model_mycomputer.index(model_mycomputer.myComputer())) - self.tree_home.setModel(proxy) - self.tree_home.setRootIndex(proxy.mapFromSource( - model_home.index(QtCore.QStandardPaths.standardLocations(QtCore.QStandardPaths.HomeLocation)[0]))) - model_mycomputer.finishedDataChange.connect(self.update_log) - model_home.finishedDataChange.connect(self.update_log) - - def init_ui(self): - self.setWindowTitle('Choose files:') - self.resize(1000, 750) - - self.tree_mycomputer.setSortingEnabled(True) - self.tree_home.setSortingEnabled(True) - self.tree_mycomputer.header().setSectionResizeMode(QtWidgets.QHeaderView.Stretch) - self.tree_home.header().setSectionResizeMode(QtWidgets.QHeaderView.Stretch) - - self.layout.addWidget(self.tree_mycomputer, 0, 0, 4, 8) - self.layout.addWidget(self.tree_home, 4, 0, 4, 8) - self.layout.setRowStretch(3, 2) - self.layout.setRowStretch(7, 2) - - self.layout.addWidget(self.open_button, 8, 7) - self.layout.addWidget(self.cancel_button, 9, 7) - - self.layout.addWidget(self.logger, 8, 0, 2, 7) - self.logger.setReadOnly(True) - - self.open_button.clicked.connect(self.accept) - self.cancel_button.clicked.connect(self.reject) - - self.update_log() - - def update_log(self): - self.logger.setPlainText("\n".join(self.result())) - self.logger.verticalScrollBar().setValue( - self.logger.verticalScrollBar().maximum()) - - def result(self): - files_to_open = [] - queue = Queue() - for pth, state in itertools.chain(self.tree_mycomputer.model().checkStates.items(), - self.tree_home.model().sourceModel().checkStates.items()): - if state: - queue.put(pth) - - while not queue.empty(): - this_path = Path(queue.get()) - if this_path.is_file(): - files_to_open.append(str(this_path)) - else: - try: - for item in this_path.iterdir(): - queue.put(item) - except PermissionError: - warnings.warn(f'Cannot access items under {this_path} - permission denied. ') - return files_to_open - - class DataFrameModel(QtCore.QAbstractTableModel): """ Based upon: https://stackoverflow.com/a/44605011 """ - DtypeRole = QtCore.Qt.UserRole + 1000 - ValueRole = QtCore.Qt.UserRole + 1001 + DtypeRole = QtCore.Qt.ItemDataRole.UserRole + 1000 + ValueRole = QtCore.Qt.ItemDataRole.UserRole + 1001 def __init__(self, df=pl.DataFrame(), parent=None): super().__init__(parent) @@ -236,9 +39,10 @@ def dataFrame(self): dataFrame = QtCore.pyqtProperty(pl.DataFrame, fget=dataFrame, fset=setDataFrame) @QtCore.pyqtSlot(int, QtCore.Qt.Orientation, result=str) - def headerData(self, section: int, orientation: QtCore.Qt.Orientation, role: int = QtCore.Qt.DisplayRole): - if role == QtCore.Qt.DisplayRole: - if orientation == QtCore.Qt.Horizontal: + def headerData(self, section: int, orientation: QtCore.Qt.Orientation, + role: int = QtCore.Qt.ItemDataRole.DisplayRole): + if role == QtCore.Qt.ItemDataRole.DisplayRole: + if orientation == QtCore.Qt.Orientation.Horizontal: return self._dataframe.columns[section + 1] else: return str(self._dataframe.row(section)[0]) @@ -254,7 +58,7 @@ def columnCount(self, parent=QtCore.QModelIndex()): return 0 return max(0, self._dataframe.width - 1) - def data(self, index, role=QtCore.Qt.DisplayRole): + def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole): if not index.isValid() or not (0 <= index.row() < self.rowCount() and 0 <= index.column() < self.columnCount()): return QtCore.QVariant() @@ -267,7 +71,7 @@ def data(self, index, role=QtCore.Qt.DisplayRole): val = self._dataframe[row, col] except IndexError: print(row, col, self._dataframe.shape) - if role == QtCore.Qt.DisplayRole: + if role == QtCore.Qt.ItemDataRole.DisplayRole: return str(val) elif role == DataFrameModel.ValueRole: return val @@ -277,7 +81,7 @@ def data(self, index, role=QtCore.Qt.DisplayRole): def roleNames(self): roles = { - QtCore.Qt.DisplayRole: b'display', + QtCore.Qt.ItemDataRole.DisplayRole: b'display', DataFrameModel.DtypeRole: b'dtype', DataFrameModel.ValueRole: b'value' } @@ -409,7 +213,7 @@ def __init__(self, exc_type, exc_value, exc_tb, parent=None): def init_ui(self): self.setWindowTitle("Error") - self.setWindowIcon(self.style().standardIcon(QtWidgets.QStyle.SP_MessageBoxCritical)) + self.setWindowIcon(self.style().standardIcon(QtWidgets.QStyle.StandardPixmap.SP_MessageBoxCritical)) self.widgets['error_label'] = QtWidgets.QLabel('RNAlysis has encountered the following error:') self.layout.addWidget(self.widgets['error_label']) @@ -442,8 +246,8 @@ def init_ui(self): def copy_to_clipboard(self): cb = QtWidgets.QApplication.clipboard() - cb.clear(mode=cb.Clipboard) - cb.setText("".join(traceback.format_exception(*self.exception)), mode=cb.Clipboard) + cb.clear(mode=QtGui.QClipboard.Mode.Clipboard) + cb.setText("".join(traceback.format_exception(*self.exception)), mode=QtGui.QClipboard.Mode.Clipboard) self.widgets['copied_label'].setText('Copied to clipboard') @@ -464,12 +268,12 @@ def __init__(self, parent=None): self.text.setTextFormat(QtCore.Qt.TextFormat.MarkdownText) self.text.setText(text) self.text.setWordWrap(True) - self.scroll_layout.addWidget( self.text) + self.scroll_layout.addWidget(self.text) self.layout().addWidget(self.scroll, 0, 0, 1, self.layout().columnCount()) self.setWindowTitle(f"What's new in version {__version__}") self.setStyleSheet("QScrollArea{min-width:900 px; min-height: 600px}" "QScrollBar:vertical {width: 40;}") - self.setStandardButtons(QtWidgets.QMessageBox.Ok) + self.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Ok) self.buttonClicked.connect(self.close) @@ -490,7 +294,7 @@ def __init__(self, parent=None):

""" self.setText(text) self.setWindowTitle("About RNAlysis") - self.setStandardButtons(QtWidgets.QMessageBox.Ok) + self.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Ok) self.buttonClicked.connect(self.close) @@ -516,8 +320,8 @@ def __init__(self, parent=None): self.tables_widgets = {} self.button_box = QtWidgets.QDialogButtonBox( - QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel | - QtWidgets.QDialogButtonBox.Apply | QtWidgets.QDialogButtonBox.RestoreDefaults) + QtWidgets.QDialogButtonBox.StandardButton.Ok | QtWidgets.QDialogButtonBox.StandardButton.Cancel | + QtWidgets.QDialogButtonBox.StandardButton.Apply | QtWidgets.QDialogButtonBox.StandardButton.RestoreDefaults) self.layout.addWidget(self.appearance_group) self.layout.addWidget(self.tables_group) @@ -552,7 +356,7 @@ def set_choices(self): for i in range(self.appearance_widgets['databases'].count()): item = self.appearance_widgets['databases'].item(i) if item.text() in current_dbs: - item.setCheckState(QtCore.Qt.Checked) + item.setCheckState(QtCore.Qt.CheckState.Checked) attr_ref_path = settings.get_attr_ref_path('predefined') if settings.is_setting_in_file( settings.__attr_file_key__) else 'No file chosen' @@ -567,10 +371,11 @@ def init_appearance_ui(self): self.appearance_widgets['app_theme'].addItems(self.THEMES.keys()) self.appearance_widgets['app_font'] = QtWidgets.QFontComboBox(self.appearance_group) - self.appearance_widgets['app_font'].setFontFilters(QtWidgets.QFontComboBox.ScalableFonts) + self.appearance_widgets['app_font'].setFontFilters(QtWidgets.QFontComboBox.FontFilter.ScalableFonts) self.appearance_widgets['app_font'].setEditable(True) - self.appearance_widgets['app_font'].completer().setCompletionMode(QtWidgets.QCompleter.PopupCompletion) - self.appearance_widgets['app_font'].setInsertPolicy(QtWidgets.QComboBox.NoInsert) + self.appearance_widgets['app_font'].completer().setCompletionMode( + QtWidgets.QCompleter.CompletionMode.PopupCompletion) + self.appearance_widgets['app_font'].setInsertPolicy(QtWidgets.QComboBox.InsertPolicy.NoInsert) self.appearance_widgets['app_font_size'] = QtWidgets.QComboBox(self.appearance_group) self.appearance_widgets['app_font_size'].addItems(self.FONT_SIZES) @@ -579,13 +384,13 @@ def init_appearance_ui(self): with open(self.LOOKUP_DATABASES_PATH) as f: for key in json.load(f).keys(): item = QtWidgets.QListWidgetItem(key) - item.setCheckState(QtCore.Qt.Unchecked) + item.setCheckState(QtCore.Qt.CheckState.Unchecked) self.appearance_widgets['databases'].addItem(item) self.appearance_widgets['show_tutorial'] = QtWidgets.QCheckBox("Show tutorial page on startup") self.appearance_widgets['report_gen'] = QtWidgets.QComboBox() self.appearance_widgets['report_gen'].addItems(self.REPORT_GEN_OPTIONS.keys()) - self.appearance_widgets['report_gen'].setInsertPolicy(QtWidgets.QComboBox.NoInsert) + self.appearance_widgets['report_gen'].setInsertPolicy(QtWidgets.QComboBox.InsertPolicy.NoInsert) for widget_name in ['app_theme', 'app_font', 'app_font_size', 'report_gen']: self.appearance_widgets[widget_name].currentIndexChanged.connect(self._trigger_settings_changed) @@ -659,9 +464,9 @@ def init_buttons(self): def handle_button_click(self, button): role = self.button_box.buttonRole(button) - if role == QtWidgets.QDialogButtonBox.ApplyRole: + if role == QtWidgets.QDialogButtonBox.ButtonRole.ApplyRole: self.save_settings() - elif role == QtWidgets.QDialogButtonBox.ResetRole: + elif role == QtWidgets.QDialogButtonBox.ButtonRole.ResetRole: self.reset_settings() def closeEvent(self, event): @@ -670,8 +475,9 @@ def closeEvent(self, event): quit_msg = "Are you sure you want to close settings without saving?" reply = QtWidgets.QMessageBox.question(self, 'Close settings without saving?', - quit_msg, QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.Yes) - to_exit = reply == QtWidgets.QMessageBox.Yes + quit_msg, QtWidgets.QMessageBox.StandardButton.No, + QtWidgets.QMessageBox.StandardButton.Yes) + to_exit = reply == QtWidgets.QMessageBox.StandardButton.Yes if to_exit: event.accept() @@ -681,49 +487,49 @@ def closeEvent(self, event): class HowToCiteWindow(gui_widgets.MinMaxDialog): CITATION_RNALYSIS = """ - Teichman, G., Cohen, D., Ganon, O., Dunsky, N., Shani, S., Gingold, H., and Rechavi, O. (2022). - RNAlysis: analyze your RNA sequencing data without writing a single line of code. BioRxiv 2022.11.25.517851. -
- doi.org/10.1101/2022.11.25.517851 - """ + Teichman, G., Cohen, D., Ganon, O., Dunsky, N., Shani, S., Gingold, H., and Rechavi, O. (2022). + RNAlysis: analyze your RNA sequencing data without writing a single line of code. BioRxiv 2022.11.25.517851. +
+ doi.org/10.1101/2022.11.25.517851 + """ CITATION_CUTADAPT = """ - Martin, M. (2011). Cutadapt removes adapter sequences from high-throughput sequencing reads. - EMBnet.journal, 17(1), pp. 10-12. -
- doi.org/10.14806/ej.17.1.200 - """ + Martin, M. (2011). Cutadapt removes adapter sequences from high-throughput sequencing reads. + EMBnet.journal, 17(1), pp. 10-12. +
+ doi.org/10.14806/ej.17.1.200 + """ CITATION_KALLISTO = """ - Bray, N., Pimentel, H., Melsted, P. et al. - Near-optimal probabilistic RNA-seq quantification. - Nat Biotechnol 34, 525–527 (2016). -
- doi.org/10.1038/nbt.3519 - """ + Bray, N., Pimentel, H., Melsted, P. et al. + Near-optimal probabilistic RNA-seq quantification. + Nat Biotechnol 34, 525–527 (2016). +
+ doi.org/10.1038/nbt.3519 + """ CITATION_DESEQ2 = """ - Love MI, Huber W, Anders S (2014). - “Moderated estimation of fold change and dispersion for RNA-seq data with DESeq2.” - Genome Biology, 15, 550. -
- doi.org/10.1186/s13059-014-0550-8 - """ + Love MI, Huber W, Anders S (2014). + “Moderated estimation of fold change and dispersion for RNA-seq data with DESeq2.” + Genome Biology, 15, 550. +
+ doi.org/10.1186/s13059-014-0550-8 + """ CITATION_HDBSCAN = """ - L. McInnes, J. Healy, S. Astels, hdbscan: - Hierarchical density based clustering In: - Journal of Open Source Software, The Open Journal, volume 2, number 11. 2017 -
- doi.org/10.1371/journal.pcbi.0030039""" + L. McInnes, J. Healy, S. Astels, hdbscan: + Hierarchical density based clustering In: + Journal of Open Source Software, The Open Journal, volume 2, number 11. 2017 +
+ doi.org/10.1371/journal.pcbi.0030039""" CITATION_XLMHG = """ -

- Eden, E., Lipson, D., Yogev, S., and Yakhini, Z. (2007). - Discovering Motifs in Ranked Lists of DNA Sequences. PLOS Comput. Biol. 3, e39. -
- doi.org/10.1371/journal.pcbi.0030039 -

-

- Wagner, F. (2017). The XL-mHG test for gene set enrichment. ArXiv. -
- doi.org/10.48550/arXiv.1507.07905 -

""" +

+ Eden, E., Lipson, D., Yogev, S., and Yakhini, Z. (2007). + Discovering Motifs in Ranked Lists of DNA Sequences. PLOS Comput. Biol. 3, e39. +
+ doi.org/10.1371/journal.pcbi.0030039 +

+

+ Wagner, F. (2017). The XL-mHG test for gene set enrichment. ArXiv. +
+ doi.org/10.48550/arXiv.1507.07905 +

""" CITATION_FILE_PATH = Path(__file__).parent.parent.joinpath('data_files/tool_citations.json') def __init__(self, parent=None): @@ -753,10 +559,10 @@ def __init__(self, parent=None): self.init_ui() def init_ui(self): - self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.scroll.setWidgetResizable(True) self.scroll.setWidget(self.scroll_widget) - self.layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) + self.layout.setSizeConstraint(QtWidgets.QLayout.SizeConstraint.SetMinAndMaxSize) self.main_layout.addWidget(self.scroll) @@ -777,7 +583,8 @@ def splash_screen(): splash_font = QtGui.QFont('Calibri', 16) splash = QtWidgets.QSplashScreen(splash_pixmap) splash.setFont(splash_font) - splash.showMessage(f"RNAlysis version {__version__}", QtCore.Qt.AlignBottom | QtCore.Qt.AlignHCenter) + splash.showMessage(f"RNAlysis version {__version__}", + QtCore.Qt.AlignmentFlag.AlignBottom | QtCore.Qt.AlignmentFlag.AlignHCenter) splash.show() return splash @@ -834,10 +641,10 @@ def __init__(self, func_name: str, func: Callable, help_link: Union[None, str], self.close_button = QtWidgets.QPushButton('Close') def init_ui(self): - self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.scroll.setWidgetResizable(True) self.scroll.setWidget(self.scroll_widget) - self.scroll_layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) + self.scroll_layout.setSizeConstraint(QtWidgets.QLayout.SizeConstraint.SetMinAndMaxSize) self.main_layout.addWidget(self.scroll) @@ -1131,7 +938,8 @@ def __init__(self, available_objects: dict, parent=None): self.available_objects = available_objects self.layout = QtWidgets.QVBoxLayout(self) self.label = QtWidgets.QLabel('Choose the tables you wish to apply your Pipeline to', self) - self.button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) + self.button_box = QtWidgets.QDialogButtonBox( + QtWidgets.QDialogButtonBox.StandardButton.Ok | QtWidgets.QDialogButtonBox.StandardButton.Cancel) self.list = gui_widgets.MultipleChoiceList(self.available_objects, [val[1] for val in self.available_objects.values()], self) @@ -1144,7 +952,7 @@ def init_ui(self): self.button_box.rejected.connect(self.reject) self.layout.addWidget(self.label) self.layout.addWidget(self.list) - self.layout.addWidget(self.button_box, QtCore.Qt.AlignCenter) + self.layout.addWidget(self.button_box, QtCore.Qt.AlignmentFlag.AlignCenter) def result(self): return [item.text() for item in self.list.get_sorted_selection()] @@ -1157,8 +965,8 @@ def __init__(self, parent=None): self.setText("Do you want to enable report generation for this session?\n" "(this will slow down the program slightly)") - self.yes_button = self.addButton(QtWidgets.QPushButton("Yes"), QtWidgets.QMessageBox.YesRole) - self.no_button = self.addButton(QtWidgets.QPushButton("No"), QtWidgets.QMessageBox.NoRole) + self.yes_button = self.addButton(QtWidgets.QPushButton("Yes"), QtWidgets.QMessageBox.ButtonRole.YesRole) + self.no_button = self.addButton(QtWidgets.QPushButton("No"), QtWidgets.QMessageBox.ButtonRole.NoRole) self.checkbox = QtWidgets.QCheckBox("Don't ask me again") self.setCheckBox(self.checkbox) diff --git a/tests/test_gui.py b/tests/test_gui.py index cdb0ab18a..15df0631c 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -11,8 +11,8 @@ matplotlib.use('Agg') from rnalysis.gui.gui import * -LEFT_CLICK = QtCore.Qt.LeftButton -RIGHT_CLICK = QtCore.Qt.RightButton +LEFT_CLICK = QtCore.Qt.MouseButton.LeftButton +RIGHT_CLICK = QtCore.Qt.MouseButton.RightButton def _pytestqt_graceful_shutdown(): @@ -27,7 +27,7 @@ def _pytestqt_graceful_shutdown(): @pytest.fixture(autouse=True) def mainwindow_setup(monkeypatch): - monkeypatch.setattr(QtWidgets.QMessageBox, 'question', lambda *args, **kwargs: QtWidgets.QMessageBox.Yes) + monkeypatch.setattr(QtWidgets.QMessageBox, 'question', lambda *args, **kwargs: QtWidgets.QMessageBox.StandardButton.Yes) monkeypatch.setattr(gui_widgets.ThreadStdOutStreamTextQueueReceiver, 'run', lambda self: None) monkeypatch.setattr(gui_quickstart.QuickStartWizard, '__init__', lambda *args, **kwargs: None) @@ -35,21 +35,21 @@ def mainwindow_setup(monkeypatch): @pytest.fixture def blank_icon(): pixmap = QtGui.QPixmap(32, 32) - pixmap.fill(QtCore.Qt.transparent) + pixmap.fill(QtCore.Qt.GlobalColor.transparent) return QtGui.QIcon(pixmap) @pytest.fixture def red_icon(): pixmap = QtGui.QPixmap(32, 32) - pixmap.fill(QtCore.Qt.red) + pixmap.fill(QtCore.Qt.GlobalColor.red) return QtGui.QIcon(pixmap) @pytest.fixture def green_icon(): pixmap = QtGui.QPixmap(32, 32) - pixmap.fill(QtCore.Qt.green) + pixmap.fill(QtCore.Qt.GlobalColor.green) return QtGui.QIcon(pixmap) @@ -72,11 +72,11 @@ def available_objects(qtbot, red_icon, green_icon): qtbot, first = widget_setup(qtbot, SetTabPage, 'first tab', {'WBGene00000002', 'WBGene00000006', 'WBGene00000015', 'WBGene00000017'}) - qtbot, second = widget_setup(qtbot, FilterTabPage, undo_stack=QtWidgets.QUndoStack()) + qtbot, second = widget_setup(qtbot, FilterTabPage, undo_stack=QtGui.QUndoStack()) second.start_from_filter_obj(filtering.DESeqFilter('tests/test_files/test_deseq.csv'), 1) second.rename('second tab') - qtbot, third = widget_setup(qtbot, FilterTabPage, undo_stack=QtWidgets.QUndoStack()) + qtbot, third = widget_setup(qtbot, FilterTabPage, undo_stack=QtGui.QUndoStack()) third.start_from_filter_obj(filtering.CountFilter('tests/test_files/counted.tsv'), 2) third.rename('third tab') @@ -89,19 +89,19 @@ def four_available_objects_and_empty(qtbot, red_icon, green_icon, blank_icon): qtbot, first = widget_setup(qtbot, SetTabPage, 'first tab', {'WBGene00008447', 'WBGene00044258', 'WBGene00045410', 'WBGene00010100'}) - qtbot, second = widget_setup(qtbot, FilterTabPage, undo_stack=QtWidgets.QUndoStack()) + qtbot, second = widget_setup(qtbot, FilterTabPage, undo_stack=QtGui.QUndoStack()) second.start_from_filter_obj(filtering.DESeqFilter('tests/test_files/test_deseq_set_ops_1.csv'), 2) second.rename('second tab') - qtbot, third = widget_setup(qtbot, FilterTabPage, undo_stack=QtWidgets.QUndoStack()) + qtbot, third = widget_setup(qtbot, FilterTabPage, undo_stack=QtGui.QUndoStack()) third.start_from_filter_obj(filtering.DESeqFilter('tests/test_files/test_deseq_set_ops_2.csv'), 3) third.rename('third tab') - qtbot, fourth = widget_setup(qtbot, FilterTabPage, undo_stack=QtWidgets.QUndoStack()) + qtbot, fourth = widget_setup(qtbot, FilterTabPage, undo_stack=QtGui.QUndoStack()) fourth.start_from_filter_obj(filtering.CountFilter('tests/test_files/counted.tsv'), 4) fourth.rename('fourth tab') - qtbot, empty = widget_setup(qtbot, FilterTabPage, undo_stack=QtWidgets.QUndoStack()) + qtbot, empty = widget_setup(qtbot, FilterTabPage, undo_stack=QtGui.QUndoStack()) yield {'first tab': (first, red_icon), 'second tab': (second, red_icon), 'third tab': (third, red_icon), 'fourth tab': (fourth, green_icon), 'empty tab': (empty, blank_icon)} @@ -160,7 +160,7 @@ def filtertabpage(qtbot): @pytest.fixture def filtertabpage_with_undo_stack(qtbot): - stack = QtWidgets.QUndoStack() + stack = QtGui.QUndoStack() qtbot, window = widget_setup(qtbot, FilterTabPage, undo_stack=stack) window.start_from_filter_obj(filtering.DESeqFilter('tests/test_files/test_deseq_sig.csv'), 1) yield window, stack @@ -169,7 +169,7 @@ def filtertabpage_with_undo_stack(qtbot): @pytest.fixture def countfiltertabpage_with_undo_stack(qtbot): - stack = QtWidgets.QUndoStack() + stack = QtGui.QUndoStack() qtbot, window = widget_setup(qtbot, FilterTabPage, undo_stack=stack) window.start_from_filter_obj(filtering.CountFilter('tests/test_files/counted.csv'), 1) yield window, stack @@ -178,7 +178,7 @@ def countfiltertabpage_with_undo_stack(qtbot): @pytest.fixture def settabpage_with_undo_stack(qtbot): - stack = QtWidgets.QUndoStack() + stack = QtGui.QUndoStack() qtbot, window = widget_setup(qtbot, SetTabPage, 'my set name', {'a', 'b', 'c', 'd'}, undo_stack=stack) yield window, stack _pytestqt_graceful_shutdown() @@ -627,7 +627,7 @@ def test_ClicomWindow_add_setup(qtbot, clicom_window): def test_ClicomWindow_remove_setup(qtbot, monkeypatch, clicom_window): - monkeypatch.setattr(QtWidgets.QMessageBox, 'question', lambda *args: QtWidgets.QMessageBox.Yes) + monkeypatch.setattr(QtWidgets.QMessageBox, 'question', lambda *args: QtWidgets.QMessageBox.StandardButton.Yes) qtbot.keyClicks(clicom_window.stack.func_combo, filtering.CountFilter.split_kmeans.readable_name) clicom_window.stack.parameter_widgets['n_clusters'].other.setValue(3) qtbot.mouseClick(clicom_window.setups_widgets['add_button'], LEFT_CLICK) @@ -1631,7 +1631,7 @@ def mock_show_multikeep(self): self.select_all.setChecked(True) self.change_all() self.accept() - self.button_box.button(QtWidgets.QDialogButtonBox.Ok).click() + self.button_box.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).click() monkeypatch.setattr(MultiKeepWindow, 'exec', mock_show_multikeep) @@ -1943,7 +1943,7 @@ def test_CreatePipelineWindow_from_pipeline(qtbot): ('Sequence files (paired-end)', fastq.PairedEndPipeline, False) ]) def test_CreatePipelineWindow_create_pipeline(qtbot, monkeypatch, pipeline_type, exp_pipeline, exp_filter_type): - monkeypatch.setattr(QtWidgets.QMessageBox, "question", lambda *args: QtWidgets.QMessageBox.Yes) + monkeypatch.setattr(QtWidgets.QMessageBox, "question", lambda *args: QtWidgets.QMessageBox.StandardButton.Yes) pipeline_name = 'my pipeline name' qtbot, window = widget_setup(qtbot, CreatePipelineWindow) window.basic_widgets['pipeline_name'].clear() @@ -1962,7 +1962,7 @@ def test_CreatePipelineWindow_add_function(qtbot, monkeypatch): pipeline_truth = filtering.Pipeline('DESeqFilter') pipeline_truth.add_function('split_fold_change_direction') - monkeypatch.setattr(QtWidgets.QMessageBox, "question", lambda *args: QtWidgets.QMessageBox.Yes) + monkeypatch.setattr(QtWidgets.QMessageBox, "question", lambda *args: QtWidgets.QMessageBox.StandardButton.Yes) qtbot, window = widget_setup(qtbot, CreatePipelineWindow) window.basic_widgets['pipeline_name'].clear() @@ -1980,7 +1980,7 @@ def test_CreatePipelineWindow_add_function(qtbot, monkeypatch): def test_CreatePipelineWindow_remove_function(qtbot, monkeypatch): warned = [] - monkeypatch.setattr(QtWidgets.QMessageBox, "question", lambda *args: QtWidgets.QMessageBox.Yes) + monkeypatch.setattr(QtWidgets.QMessageBox, "question", lambda *args: QtWidgets.QMessageBox.StandardButton.Yes) monkeypatch.setattr(QtWidgets.QMessageBox, 'exec', lambda *args, **kwargs: warned.append(True)) qtbot, window = widget_setup(qtbot, CreatePipelineWindow) @@ -2009,7 +2009,7 @@ def test_CreatePipelineWindow_add_function_with_args(qtbot, monkeypatch): pipeline_truth = filtering.Pipeline('DESeqFilter') pipeline_truth.add_function('filter_significant', alpha=0.01, opposite=True) - monkeypatch.setattr(QtWidgets.QMessageBox, "question", lambda *args: QtWidgets.QMessageBox.Yes) + monkeypatch.setattr(QtWidgets.QMessageBox, "question", lambda *args: QtWidgets.QMessageBox.StandardButton.Yes) qtbot, window = widget_setup(qtbot, CreatePipelineWindow) window.basic_widgets['pipeline_name'].clear() @@ -2039,7 +2039,7 @@ def test_CreatePipelineWindow_save_pipeline(qtbot, monkeypatch): pipeline_truth.add_function('describe', percentiles=[0.01, 0.25, 0.5, 0.75, 0.99]) pipeline_name = 'my pipeline name' - monkeypatch.setattr(QtWidgets.QMessageBox, "question", lambda *args: QtWidgets.QMessageBox.Yes) + monkeypatch.setattr(QtWidgets.QMessageBox, "question", lambda *args: QtWidgets.QMessageBox.StandardButton.Yes) qtbot, window = widget_setup(qtbot, CreatePipelineWindow) window.basic_widgets['pipeline_name'].clear() @@ -2062,7 +2062,7 @@ def test_CreatePipelineWindow_export_pipeline(qtbot, monkeypatch): pipeline_truth.add_function('describe', percentiles=[0.01, 0.25, 0.5, 0.75, 0.99]) pipeline_name = 'my pipeline name' - monkeypatch.setattr(QtWidgets.QMessageBox, "question", lambda *args: QtWidgets.QMessageBox.Yes) + monkeypatch.setattr(QtWidgets.QMessageBox, "question", lambda *args: QtWidgets.QMessageBox.StandardButton.Yes) qtbot, window = widget_setup(qtbot, CreatePipelineWindow) window.basic_widgets['pipeline_name'].clear() @@ -2212,7 +2212,7 @@ def __init__(self, parent=None): def test_ReactiveTabWidget_remove_tab(qtbot, tab_widget): qtbot, widget = widget_setup(qtbot, MockTab) - tab_widget.setCornerWidget(widget, QtCore.Qt.TopRightCorner) + tab_widget.setCornerWidget(widget, QtCore.Qt.Corner.TopRightCorner) for i in range(3): qtbot, widget = widget_setup(qtbot, MockTab) tab_widget.addTab(widget, 'name') @@ -2405,8 +2405,8 @@ def mock_get_dir(*args, **kwargs): def mock_question(*args, **kwargs): if args[1] == 'Close program': - return QtWidgets.QMessageBox.Yes - return QtWidgets.QMessageBox.Yes if normalize else QtWidgets.QMessageBox.No + return QtWidgets.QMessageBox.StandardButton.Yes + return QtWidgets.QMessageBox.StandardButton.Yes if normalize else QtWidgets.QMessageBox.StandardButton.No monkeypatch.setattr(QtWidgets.QFileDialog, 'getExistingDirectory', mock_get_dir) monkeypatch.setattr(QtWidgets.QMessageBox, 'question', mock_question) diff --git a/tests/test_gui_graphics.py b/tests/test_gui_graphics.py index 2228195d8..e0717f907 100644 --- a/tests/test_gui_graphics.py +++ b/tests/test_gui_graphics.py @@ -3,8 +3,8 @@ from rnalysis.gui.gui_graphics import * -LEFT_CLICK = QtCore.Qt.LeftButton -RIGHT_CLICK = QtCore.Qt.RightButton +LEFT_CLICK = QtCore.Qt.MouseButton.LeftButton +RIGHT_CLICK = QtCore.Qt.MouseButton.RightButton class MockEvent: @@ -47,7 +47,7 @@ def test_get_icon_invalid(qtbot): def test_get_icon_blank(qtbot): pixmap = QtGui.QPixmap(32, 32) - pixmap.fill(QtCore.Qt.transparent) + pixmap.fill(QtCore.Qt.GlobalColor.transparent) truth = pixmap.toImage() icon = get_icon('blank') diff --git a/tests/test_gui_quickstart.py b/tests/test_gui_quickstart.py index b89ce268c..2a9a4538b 100644 --- a/tests/test_gui_quickstart.py +++ b/tests/test_gui_quickstart.py @@ -2,8 +2,8 @@ from rnalysis.gui.gui_quickstart import * -LEFT_CLICK = QtCore.Qt.LeftButton -RIGHT_CLICK = QtCore.Qt.RightButton +LEFT_CLICK = QtCore.Qt.MouseButton.LeftButton +RIGHT_CLICK = QtCore.Qt.MouseButton.RightButton def widget_setup(qtbot, widget_class, *args, **kwargs): @@ -23,69 +23,69 @@ def test_TutorialMovie_set_frame(qtbot): window.video.start() window.pause() window.set_frame(17) - assert window.video.state() == QtGui.QMovie.Paused + assert window.video.state() == QtGui.QMovie.MovieState.Paused assert window.video.currentFrameNumber() == 17 window.set_frame(19) - assert window.video.state() == QtGui.QMovie.Paused + assert window.video.state() == QtGui.QMovie.MovieState.Paused assert window.video.currentFrameNumber() == 19 window.set_frame(19) - assert window.video.state() == QtGui.QMovie.Paused + assert window.video.state() == QtGui.QMovie.MovieState.Paused assert window.video.currentFrameNumber() == 19 window.set_frame(1) - assert window.video.state() == QtGui.QMovie.Paused + assert window.video.state() == QtGui.QMovie.MovieState.Paused assert window.video.currentFrameNumber() == 1 def test_TutorialMovie_start(qtbot): qtbot, window = widget_setup(qtbot, QuickStartMovie, Path('tests/test_files/test_video.webp')) - assert window.video.state() == QtGui.QMovie.NotRunning + assert window.video.state() == QtGui.QMovie.MovieState.NotRunning window.start() - assert window.video.state() == QtGui.QMovie.Running + assert window.video.state() == QtGui.QMovie.MovieState.Running def test_TutorialMovie_restart(qtbot): qtbot, window = widget_setup(qtbot, QuickStartMovie, Path('tests/test_files/test_video.webp')) window.restart() assert window.video.currentFrameNumber() in {0, 1} - assert window.video.state() == QtGui.QMovie.Running + assert window.video.state() == QtGui.QMovie.MovieState.Running def test_TutorialMovie_stop(qtbot): qtbot, window = widget_setup(qtbot, QuickStartMovie, Path('tests/test_files/test_video.webp')) window.video.start() window.stop() - assert window.video.state() == QtGui.QMovie.NotRunning + assert window.video.state() == QtGui.QMovie.MovieState.NotRunning def test_TutorialMovie_pause(qtbot): qtbot, window = widget_setup(qtbot, QuickStartMovie, Path('tests/test_files/test_video.webp')) window.video.start() window.pause() - assert window.video.state() == QtGui.QMovie.Paused + assert window.video.state() == QtGui.QMovie.MovieState.Paused def test_TutorialMovie_stop_button(qtbot): qtbot, window = widget_setup(qtbot, QuickStartMovie, Path('tests/test_files/test_video.webp')) qtbot.mouseClick(window.stop_button, LEFT_CLICK) - assert window.video.state() == QtGui.QMovie.NotRunning + assert window.video.state() == QtGui.QMovie.MovieState.NotRunning def test_TutorialMovie_pause_button(qtbot): qtbot, window = widget_setup(qtbot, QuickStartMovie, Path('tests/test_files/test_video.webp')) window.video.start() qtbot.mouseClick(window.play_button, LEFT_CLICK) - assert window.video.state() == QtGui.QMovie.Paused + assert window.video.state() == QtGui.QMovie.MovieState.Paused qtbot.mouseClick(window.play_button, LEFT_CLICK) - assert window.video.state() == QtGui.QMovie.Running + assert window.video.state() == QtGui.QMovie.MovieState.Running def test_TutorialMovie_pause_click_video(qtbot): qtbot, window = widget_setup(qtbot, QuickStartMovie, Path('tests/test_files/test_video.webp')) window.video.start() qtbot.mouseClick(window, LEFT_CLICK) - assert window.video.state() == QtGui.QMovie.Paused + assert window.video.state() == QtGui.QMovie.MovieState.Paused qtbot.mouseClick(window, LEFT_CLICK) - assert window.video.state() == QtGui.QMovie.Running + assert window.video.state() == QtGui.QMovie.MovieState.Running def test_TutorialMovie_speed_button(qtbot): @@ -101,16 +101,16 @@ def test_TutorialMovie_speed_button(qtbot): def test_WelcomeWizard_init(qtbot): qtbot, window = widget_setup(qtbot, QuickStartWizard) for i in range(len(window.TITLES) + 1): - qtbot.mouseClick(window.button(QtWidgets.QWizard.NextButton), LEFT_CLICK) - qtbot.mouseClick(window.button(QtWidgets.QWizard.FinishButton), LEFT_CLICK) + qtbot.mouseClick(window.button(QtWidgets.QWizard.WizardButton.NextButton), LEFT_CLICK) + qtbot.mouseClick(window.button(QtWidgets.QWizard.WizardButton.FinishButton), LEFT_CLICK) @pytest.mark.parametrize('n_next', [0, 1, len(QuickStartWizard.TITLES)]) def test_WelcomeWizard_cancel(qtbot, n_next): qtbot, window = widget_setup(qtbot, QuickStartWizard) for i in range(n_next): - qtbot.mouseClick(window.button(QtWidgets.QWizard.NextButton), LEFT_CLICK) - qtbot.mouseClick(window.button(QtWidgets.QWizard.CancelButton), LEFT_CLICK) + qtbot.mouseClick(window.button(QtWidgets.QWizard.WizardButton.NextButton), LEFT_CLICK) + qtbot.mouseClick(window.button(QtWidgets.QWizard.WizardButton.CancelButton), LEFT_CLICK) def test_WelcomeWizard_do_not_show_again_finish(qtbot, monkeypatch): @@ -125,7 +125,7 @@ def save_func(show_tutorial): qtbot, window = widget_setup(qtbot, QuickStartWizard) assert not window.currentPage().dont_show_again.isChecked() window.currentPage().dont_show_again.setChecked(True) - qtbot.mouseClick(window.button(QtWidgets.QWizard.FinishButton), LEFT_CLICK) + qtbot.mouseClick(window.button(QtWidgets.QWizard.WizardButton.FinishButton), LEFT_CLICK) assert saved[0] @@ -143,5 +143,5 @@ def save_func(show_tutorial): qtbot, window = widget_setup(qtbot, QuickStartWizard) assert window.currentPage().dont_show_again.isChecked() != show window.currentPage().dont_show_again.setChecked(show) - qtbot.mouseClick(window.button(QtWidgets.QWizard.CancelButton), LEFT_CLICK) + qtbot.mouseClick(window.button(QtWidgets.QWizard.WizardButton.CancelButton), LEFT_CLICK) assert saved[0] diff --git a/tests/test_gui_widgets.py b/tests/test_gui_widgets.py index e917b03fc..8793b9e6d 100644 --- a/tests/test_gui_widgets.py +++ b/tests/test_gui_widgets.py @@ -7,8 +7,8 @@ from rnalysis.gui.gui_widgets import * from rnalysis.utils import io -LEFT_CLICK = QtCore.Qt.LeftButton -RIGHT_CLICK = QtCore.Qt.RightButton +LEFT_CLICK = QtCore.Qt.MouseButton.LeftButton +RIGHT_CLICK = QtCore.Qt.MouseButton.RightButton def widget_setup(qtbot, widget_class, *args, **kwargs): @@ -443,11 +443,11 @@ def selection_changed_slot(*args, **kwargs): def test_MultipleChoiceList_icons(qtbot): pixmap = QtGui.QPixmap(32, 32) - pixmap.fill(QtCore.Qt.transparent) + pixmap.fill(QtCore.Qt.GlobalColor.transparent) icon = QtGui.QIcon(pixmap) pixmap2 = QtGui.QPixmap(34, 34) - pixmap2.fill(QtCore.Qt.white) + pixmap2.fill(QtCore.Qt.GlobalColor.white) icon2 = QtGui.QIcon(pixmap2) items = ['item1', 'item2', 'item3'] icons = [icon, icon2, icon2] @@ -622,7 +622,7 @@ def test_ToggleSwitchCore(qtbot): def test_MultiChoiceListWithDelete_delete_all(qtbot, monkeypatch): - monkeypatch.setattr(QtWidgets.QMessageBox, "question", lambda *args: QtWidgets.QMessageBox.Yes) + monkeypatch.setattr(QtWidgets.QMessageBox, "question", lambda *args: QtWidgets.QMessageBox.StandardButton.Yes) items = ['item1', 'item2', 'item3'] qtbot, widget = widget_setup(qtbot, MultiChoiceListWithDelete, items) diff --git a/tests/test_gui_windows.py b/tests/test_gui_windows.py index 25f770718..07f9b4718 100644 --- a/tests/test_gui_windows.py +++ b/tests/test_gui_windows.py @@ -5,8 +5,8 @@ from rnalysis.gui.gui_windows import * -LEFT_CLICK = QtCore.Qt.LeftButton -RIGHT_CLICK = QtCore.Qt.RightButton +LEFT_CLICK = QtCore.Qt.MouseButton.LeftButton +RIGHT_CLICK = QtCore.Qt.MouseButton.RightButton @pytest.fixture @@ -205,8 +205,8 @@ def test_SettingsWindow_get_defaults(qtbot, use_temp_settings_file): for i in range(dialog.appearance_widgets['databases'].count()): item = dialog.appearance_widgets['databases'].item(i) - assert bool(item.checkState()) == (item.text() in dbs_truth) - if item.checkState(): + assert (item.checkState() == QtCore.Qt.CheckState.Checked) == (item.text() in dbs_truth) + if item.checkState() == QtCore.Qt.CheckState.Checked: dbs_matched.append(item.text()) assert sorted(dbs_matched) == sorted(dbs_truth) @@ -227,7 +227,7 @@ def mock_reset(): monkeypatch.setattr(settings, 'reset_settings', mock_reset) qtbot, dialog = widget_setup(qtbot, SettingsWindow) - qtbot.mouseClick(dialog.button_box.button(QtWidgets.QDialogButtonBox.RestoreDefaults), LEFT_CLICK) + qtbot.mouseClick(dialog.button_box.button(QtWidgets.QDialogButtonBox.StandardButton.RestoreDefaults), LEFT_CLICK) assert len(reset_done) == 1 assert reset_done[0] @@ -280,7 +280,7 @@ def mock_save_tables(attr, biotype): qtbot.keyClicks(dialog.tables_widgets['attr_ref_path'].file_path, attr_truth) qtbot.keyClicks(dialog.tables_widgets['biotype_ref_path'].file_path, biotype_truth) - qtbot.mouseClick(dialog.button_box.button(QtWidgets.QDialogButtonBox.Apply), LEFT_CLICK) + qtbot.mouseClick(dialog.button_box.button(QtWidgets.QDialogButtonBox.StandardButton.Apply), LEFT_CLICK) assert settings_saved[0] assert settings_saved[1] @@ -295,68 +295,11 @@ def mock_save(): monkeypatch.setattr(settings, 'set_table_settings', mock_save) qtbot, dialog = widget_setup(qtbot, SettingsWindow) dialog.appearance_widgets['app_font'].setCurrentText("David") - qtbot.mouseClick(dialog.button_box.button(QtWidgets.QDialogButtonBox.Cancel), LEFT_CLICK) + qtbot.mouseClick(dialog.button_box.button(QtWidgets.QDialogButtonBox.StandardButton.Cancel), LEFT_CLICK) assert len(save_done) == 0 -def test_MultiFileSelectionDialog_init(qtbot, use_temp_settings_file): - _, _ = widget_setup(qtbot, MultiFileSelectionDialog) - - -@pytest.mark.parametrize('pth', ['tests/test_files/test_deseq.csv', 'tests/test_files/test_fastqs']) -def test_MultiFileSelectionDialog_selection_log(qtbot, use_temp_settings_file, pth): - qtbot, dialog = widget_setup(qtbot, MultiFileSelectionDialog) - model = dialog.tree_mycomputer.model() - - for parent in reversed(list(Path(pth).absolute().parents)): - dialog.tree_mycomputer.expand(model.index(str(parent))) - - index = model.index(str(Path(pth).absolute())) - model.setCheckState(index, True) - - dialog.update_log() - txt = dialog.logger.toPlainText() - assert str(Path(pth).name) in txt - - if Path(pth).is_dir(): - for item in Path(pth).rglob('*'): - assert str(item.absolute()) in txt - - -@pytest.mark.parametrize('pth', ['tests/test_files/test_deseq.csv', 'tests/test_files/counted.tsv']) -def test_MultiFileSelectionDialog_selection_files(qtbot, use_temp_settings_file, pth): - qtbot, dialog = widget_setup(qtbot, MultiFileSelectionDialog) - model = dialog.tree_mycomputer.model() - - for parent in reversed(list(Path(pth).absolute().parents)): - dialog.tree_mycomputer.expand(model.index(str(parent))) - - index = model.index(str(Path(pth).absolute())) - model.setCheckState(index, True) - assert dialog.result() == [str(Path(pth).absolute())] - - -@pytest.mark.parametrize('pth', ['tests/test_files/test_count_from_folder', 'tests/test_files']) -def test_MultiFileSelectionDialog_selection_folders(qtbot, use_temp_settings_file, pth): - qtbot, dialog = widget_setup(qtbot, MultiFileSelectionDialog) - model = dialog.tree_mycomputer.model() - - for parent in reversed(list(Path(pth).absolute().parents)): - dialog.tree_mycomputer.expand(model.index(str(parent))) - - index = model.index(str(Path(pth).absolute())) - model.setCheckState(index, True) - assert sorted(dialog.result()) == sorted( - [str(Path(child).absolute()) for child in Path(pth).rglob('*') if child.is_file()]) - - -def test_MultiFileSelectionDialog_no_selection(qtbot, use_temp_settings_file): - qtbot, dialog = widget_setup(qtbot, MultiFileSelectionDialog) - - assert len(dialog.result()) == 0 - - def test_splash_screen(qtbot): splash = splash_screen() splash.show()