From 187b81159565b2748c133d0d0e636b600d95a2c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Pe=C3=B1a-Castellanos?= Date: Thu, 8 Aug 2024 22:02:12 -0500 Subject: [PATCH 1/6] Add toolbutton to select action from direct entry install --- napari_plugin_manager/qt_plugin_dialog.py | 60 ++++++++++++++++++++--- napari_plugin_manager/styles.qss | 23 +++++++++ 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/napari_plugin_manager/qt_plugin_dialog.py b/napari_plugin_manager/qt_plugin_dialog.py index 58faf0a0..0b8d2eaf 100644 --- a/napari_plugin_manager/qt_plugin_dialog.py +++ b/napari_plugin_manager/qt_plugin_dialog.py @@ -23,7 +23,14 @@ from napari.utils.notifications import show_info, show_warning from napari.utils.translations import trans from qtpy.QtCore import QPoint, QSize, Qt, QTimer, Signal, Slot -from qtpy.QtGui import QAction, QFont, QKeySequence, QMovie, QShortcut +from qtpy.QtGui import ( + QAction, + QActionGroup, + QFont, + QKeySequence, + QMovie, + QShortcut, +) from qtpy.QtWidgets import ( QCheckBox, QComboBox, @@ -35,10 +42,12 @@ QLineEdit, QListWidget, QListWidgetItem, + QMenu, QPushButton, QSizePolicy, QSplitter, QTextEdit, + QToolButton, QVBoxLayout, QWidget, ) @@ -1178,13 +1187,34 @@ def _setup_ui(self): visibility_direct_entry = not running_as_constructor_app() self.direct_entry_edit = QLineEdit(self) self.direct_entry_edit.installEventFilter(self) - self.direct_entry_edit.setPlaceholderText( - trans._('install by name/url, or drop file...') - ) + self.direct_entry_edit.returnPressed.connect(self._install_packages) self.direct_entry_edit.setVisible(visibility_direct_entry) - self.direct_entry_btn = QPushButton(trans._("Install"), self) + self.direct_entry_btn = QToolButton(self) self.direct_entry_btn.setVisible(visibility_direct_entry) self.direct_entry_btn.clicked.connect(self._install_packages) + self.direct_entry_btn.setText(trans._("Install")) + self.direct_entry_btn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) + + self._action_conda = QAction(trans._('Conda'), self) + self._action_conda.setCheckable(True) + self._action_conda.triggered.connect(self._update_direct_entry_text) + + self._action_pypi = QAction(trans._('pip'), self) + self._action_pypi.setCheckable(True) + self._action_pypi.triggered.connect(self._update_direct_entry_text) + + self._action_group = QActionGroup(self) + self._action_group.addAction(self._action_pypi) + self._action_group.addAction(self._action_conda) + self._action_group.setExclusive(True) + + self._menu = QMenu(self) + self._menu.addAction(self._action_conda) + self._menu.addAction(self._action_pypi) + + if IS_NAPARI_CONDA_INSTALLED: + self._action_conda.setChecked(True) + self.direct_entry_btn.setMenu(self._menu) self.show_status_btn = QPushButton(trans._("Show Status"), self) self.show_status_btn.setFixedWidth(100) @@ -1221,6 +1251,19 @@ def _setup_ui(self): self.h_splitter.setStretchFactor(0, 2) self.packages_filter.setFocus() + self._update_direct_entry_text() + + def _update_direct_entry_text(self): + tool = ( + str(InstallerTools.CONDA) + if self._action_conda.isChecked() + else str(InstallerTools.PIP) + ) + self.direct_entry_edit.setPlaceholderText( + trans._( + "install with '{tool}' by name/url, or drop file...", tool=tool + ) + ) def _update_plugin_count(self): """Update count labels for both installed and available plugin lists. @@ -1275,7 +1318,12 @@ def _install_packages( self.direct_entry_edit.clear() if packages: - self.installer.install(InstallerTools.PIP, packages) + tool = ( + InstallerTools.CONDA + if self._action_conda.isChecked() + else InstallerTools.PIP + ) + self.installer.install(tool, packages) def _tag_outdated_plugins(self): """Tag installed plugins that might be outdated.""" diff --git a/napari_plugin_manager/styles.qss b/napari_plugin_manager/styles.qss index 6ec934f9..8f67ad08 100644 --- a/napari_plugin_manager/styles.qss +++ b/napari_plugin_manager/styles.qss @@ -350,3 +350,26 @@ QtLayerList::indicator:checked { min-height: 36px; min-width: 36px; } + +QToolButton { + background-color: {{ darken(foreground, 20) }}; + color: {{ darken(text, 15) }}; + padding: 3 12px; + font: 14px; +} + +QToolButton:hover { + background-color: {{ lighten(foreground, 10) }} +} + +QToolButton:pressed { + background-color: {{ lighten(foreground, 20) }} +} + +QToolButton::menu-indicator { /* the arrow mark in the tool buttons */ + image: url("theme_{{ id }}:/down_arrow.svg"); + top: -2px; + left: -2px; + width: 10px; + height: 10px; +} From f6360a1678e7dcde602edc8a08ba8db4040565a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Pe=C3=B1a-Castellanos?= Date: Fri, 9 Aug 2024 14:12:07 -0500 Subject: [PATCH 2/6] Add tests --- .../_tests/test_qt_plugin_dialog.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/napari_plugin_manager/_tests/test_qt_plugin_dialog.py b/napari_plugin_manager/_tests/test_qt_plugin_dialog.py index b773281a..2a7b8bbf 100644 --- a/napari_plugin_manager/_tests/test_qt_plugin_dialog.py +++ b/napari_plugin_manager/_tests/test_qt_plugin_dialog.py @@ -547,6 +547,28 @@ def test_cancel_all(qtbot, tmp_virtualenv, plugin_dialog, request): assert plugin_dialog.installed_list.count() == 2 +@pytest.mark.skipif( + qtpy.API_NAME.lower().startswith('pyside'), reason='pyside specific bug' +) +def test_direct_entry_installs(qtbot, tmp_virtualenv, plugin_dialog, request): + if "[constructor]" in request.node.name: + pytest.skip( + reason="This test is only relevant for constructor-based installs" + ) + + plugin_dialog.set_prefix(str(tmp_virtualenv)) + with qtbot.waitSignal( + plugin_dialog.installer.processFinished, timeout=60_000 + ) as blocker: + plugin_dialog.direct_entry_edit.setText('requests') + plugin_dialog.direct_entry_btn.click() + + process_finished_data = blocker.args[0] + assert process_finished_data['action'] == InstallerActions.INSTALL + assert process_finished_data['pkgs'][0].startswith("requests") + qtbot.wait(5000) + + def test_shortcut_close(plugin_dialog, qtbot): qtbot.keyClicks( plugin_dialog, 'W', modifier=Qt.KeyboardModifier.ControlModifier From fcd8cf3ec0a235861c752383e5acb02ba2a2629b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Pe=C3=B1a-Castellanos?= Date: Mon, 12 Aug 2024 23:54:58 -0500 Subject: [PATCH 3/6] Try with popupmode --- napari_plugin_manager/qt_plugin_dialog.py | 1 + napari_plugin_manager/styles.qss | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/napari_plugin_manager/qt_plugin_dialog.py b/napari_plugin_manager/qt_plugin_dialog.py index 0b8d2eaf..b32d674a 100644 --- a/napari_plugin_manager/qt_plugin_dialog.py +++ b/napari_plugin_manager/qt_plugin_dialog.py @@ -1194,6 +1194,7 @@ def _setup_ui(self): self.direct_entry_btn.clicked.connect(self._install_packages) self.direct_entry_btn.setText(trans._("Install")) self.direct_entry_btn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) + self.direct_entry_btn.setPopupMode(QToolButton.MenuButtonPopup) self._action_conda = QAction(trans._('Conda'), self) self._action_conda.setCheckable(True) diff --git a/napari_plugin_manager/styles.qss b/napari_plugin_manager/styles.qss index 8f67ad08..3b7c49d3 100644 --- a/napari_plugin_manager/styles.qss +++ b/napari_plugin_manager/styles.qss @@ -366,10 +366,10 @@ QToolButton:pressed { background-color: {{ lighten(foreground, 20) }} } -QToolButton::menu-indicator { /* the arrow mark in the tool buttons */ +QToolButton::menu-arrow { image: url("theme_{{ id }}:/down_arrow.svg"); - top: -2px; - left: -2px; - width: 10px; - height: 10px; + top: 0px; + left: 0px; + width: 5px; + height: 5px; } From 6c276cafb6625b3fb370867c342858ef0f79b640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Pe=C3=B1a-Castellanos?= Date: Sun, 18 Aug 2024 20:44:54 -0500 Subject: [PATCH 4/6] Add refresh styles shortcut for debugging styles and move popup mode to if condition --- napari_plugin_manager/qt_plugin_dialog.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/napari_plugin_manager/qt_plugin_dialog.py b/napari_plugin_manager/qt_plugin_dialog.py index b32d674a..4877eb3a 100644 --- a/napari_plugin_manager/qt_plugin_dialog.py +++ b/napari_plugin_manager/qt_plugin_dialog.py @@ -923,6 +923,11 @@ def _quit(self): self._parent.close(quit_app=True, confirm_need=True) def _setup_shortcuts(self): + self._refresh_styles_action = QAction(trans._('Refresh Styles'), self) + self._refresh_styles_action.setShortcut('Ctrl+R') + self._refresh_styles_action.triggered.connect(self._update_theme) + self.addAction(self._refresh_styles_action) + self._quit_action = QAction(trans._('Exit'), self) self._quit_action.setShortcut('Ctrl+Q') self._quit_action.setMenuRole(QAction.QuitRole) @@ -1193,8 +1198,6 @@ def _setup_ui(self): self.direct_entry_btn.setVisible(visibility_direct_entry) self.direct_entry_btn.clicked.connect(self._install_packages) self.direct_entry_btn.setText(trans._("Install")) - self.direct_entry_btn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) - self.direct_entry_btn.setPopupMode(QToolButton.MenuButtonPopup) self._action_conda = QAction(trans._('Conda'), self) self._action_conda.setCheckable(True) @@ -1214,6 +1217,7 @@ def _setup_ui(self): self._menu.addAction(self._action_pypi) if IS_NAPARI_CONDA_INSTALLED: + self.direct_entry_btn.setPopupMode(QToolButton.MenuButtonPopup) self._action_conda.setChecked(True) self.direct_entry_btn.setMenu(self._menu) From 988626341032e5d8a5fdd2cf0584720eacc39d74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Pe=C3=B1a-Castellanos?= Date: Mon, 19 Aug 2024 12:36:37 -0500 Subject: [PATCH 5/6] Fix QSS --- napari_plugin_manager/styles.qss | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/napari_plugin_manager/styles.qss b/napari_plugin_manager/styles.qss index 3b7c49d3..6f6b2306 100644 --- a/napari_plugin_manager/styles.qss +++ b/napari_plugin_manager/styles.qss @@ -370,6 +370,14 @@ QToolButton::menu-arrow { image: url("theme_{{ id }}:/down_arrow.svg"); top: 0px; left: 0px; - width: 5px; - height: 5px; + width: 8px; + height: 8px; +} + +QToolButton[popupMode="1"]{ + padding-right: 20px; +} + +QToolButton::menu-button { + width: 16px; } From a0ee0f1ce73f3069042cf0a35713850d56110789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Pe=C3=B1a-Castellanos?= Date: Mon, 19 Aug 2024 13:21:41 -0500 Subject: [PATCH 6/6] Add flaky test for intermitent connectivity errors --- napari_plugin_manager/_tests/test_npe2api.py | 3 +++ pyproject.toml | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/napari_plugin_manager/_tests/test_npe2api.py b/napari_plugin_manager/_tests/test_npe2api.py index 206ee1b4..859e255b 100644 --- a/napari_plugin_manager/_tests/test_npe2api.py +++ b/napari_plugin_manager/_tests/test_npe2api.py @@ -1,3 +1,5 @@ +from flaky import flaky + from napari_plugin_manager.npe2api import ( _user_agent, cache_clear, @@ -11,6 +13,7 @@ def test_user_agent(): assert _user_agent() +@flaky(max_runs=3, min_passes=2) def test_plugin_summaries(): keys = [ "name", diff --git a/pyproject.toml b/pyproject.toml index d38b1d9c..fa612e9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,10 +63,11 @@ dev = [ ] testing = [ + "flaky", "pytest", - "virtualenv", "pytest-cov", "pytest-qt", + "virtualenv" ] docs = [