Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an optional disclaimer message widget related with 3rd party nature of the plugins #95

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions napari_plugin_manager/_tests/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from unittest.mock import patch

from napari_plugin_manager import config


def test_config_file(tmp_path):
TMP_DEFAULT_CONFIG_PATH = tmp_path / ".napari-plugin-manager"
TMP_DEFAULT_CONFIG_FILE_PATH = (
TMP_DEFAULT_CONFIG_PATH / "napari-plugin-manager.ini"
)

assert not TMP_DEFAULT_CONFIG_PATH.exists()
assert not TMP_DEFAULT_CONFIG_FILE_PATH.exists()

with (
patch.object(config, "DEFAULT_CONFIG_PATH", TMP_DEFAULT_CONFIG_PATH),
patch.object(
config, "DEFAULT_CONFIG_FILE_PATH", TMP_DEFAULT_CONFIG_FILE_PATH
),
):
initial_config = config.get_configuration()
assert TMP_DEFAULT_CONFIG_PATH.exists()
assert TMP_DEFAULT_CONFIG_FILE_PATH.exists()
assert initial_config.getboolean("general", "show_disclaimer")
second_config = config.get_configuration()
assert TMP_DEFAULT_CONFIG_PATH.exists()
assert TMP_DEFAULT_CONFIG_FILE_PATH.exists()
assert not second_config.getboolean("general", "show_disclaimer")
7 changes: 7 additions & 0 deletions napari_plugin_manager/_tests/test_qt_plugin_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,3 +583,10 @@ def test_shortcut_quit(plugin_dialog, qtbot):
)
qtbot.wait(200)
assert not plugin_dialog.isVisible()


def test_disclaimer_widget(plugin_dialog, qtbot):
assert not plugin_dialog.disclaimer_widget.isVisible()
plugin_dialog._show_disclaimer = True
plugin_dialog.exec_()
assert plugin_dialog.disclaimer_widget.isVisible()
34 changes: 34 additions & 0 deletions napari_plugin_manager/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import configparser
from pathlib import Path

DEFAULT_CONFIG_PATH = Path.home() / ".napari-plugin-manager"
DEFAULT_CONFIG_FILE_PATH = DEFAULT_CONFIG_PATH / "napari-plugin-manager.ini"


def get_configuration():
"""
Get plugin manager configuration.

Currently only used to store need to show an initial disclaimer message:
* `['general']['show_disclaimer']` -> bool
"""
DEFAULT_CONFIG_PATH.mkdir(exist_ok=True)
config = configparser.ConfigParser()

if DEFAULT_CONFIG_FILE_PATH.exists():
config.read(DEFAULT_CONFIG_FILE_PATH)
# Since the config was stored ensure the disclamer config is now `False`
# an update save config for the next time
if config.getboolean("general", "show_disclaimer"):
config.set("general", "show_disclaimer", "False")
with open(DEFAULT_CONFIG_FILE_PATH, "w") as configfile:
config.write(configfile)
else:
# Set default config
config["general"] = {"show_disclaimer": True}

# Write the configuration to a file
with open(DEFAULT_CONFIG_FILE_PATH, "w") as configfile:
config.write(configfile)

return config
16 changes: 14 additions & 2 deletions napari_plugin_manager/qt_plugin_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
)
from superqt import QCollapsible, QElidingLabel

from napari_plugin_manager.config import get_configuration
from napari_plugin_manager.npe2api import (
cache_clear,
iter_napari_plugin_info,
Expand All @@ -63,7 +64,7 @@
InstallerTools,
ProcessFinishedData,
)
from napari_plugin_manager.qt_widgets import ClickableLabel
from napari_plugin_manager.qt_widgets import ClickableLabel, DisclaimerWidget
from napari_plugin_manager.utils import is_conda_package

# Scaling factor for each list widget item when expanding.
Expand Down Expand Up @@ -871,6 +872,9 @@ def __init__(self, parent=None, prefix=None) -> None:
self.available_set = set()
self._prefix = prefix
self._first_open = True
self._show_disclaimer = get_configuration().getboolean(
'general', 'show_disclaimer'
)
self._plugin_queue = [] # Store plugin data to be added
self._plugin_data = [] # Store all plugin data
self._filter_texts = []
Expand Down Expand Up @@ -1152,7 +1156,6 @@ def _setup_ui(self):
horizontal_mid_layout.addStretch()
horizontal_mid_layout.addWidget(self.refresh_button)
mid_layout.addLayout(horizontal_mid_layout)
# mid_layout.addWidget(self.packages_filter)
mid_layout.addWidget(self.installed_label)
lay.addLayout(mid_layout)

Expand All @@ -1167,6 +1170,12 @@ def _setup_ui(self):
mid_layout.addWidget(self.avail_label)
mid_layout.addStretch()
lay.addLayout(mid_layout)
self.disclaimer_widget = DisclaimerWidget(
trans._(
"DISCLAIMER: Available plugin packages are user produced content. Any use of the provided files is at your own risk."
)
)
lay.addWidget(self.disclaimer_widget)
self.available_list = QPluginList(uninstalled, self.installer)
lay.addWidget(self.available_list)

Expand Down Expand Up @@ -1255,6 +1264,8 @@ def _setup_ui(self):
self.show_status_btn.setChecked(False)
self.show_status_btn.toggled.connect(self.toggle_status)

self.disclaimer_widget.setVisible(self._show_disclaimer)

self.v_splitter.setStretchFactor(1, 2)
self.h_splitter.setStretchFactor(0, 2)

Expand Down Expand Up @@ -1456,6 +1467,7 @@ def exec_(self):
if plugin_dialog != self:
self.close()

plugin_dialog.disclaimer_widget.setVisible(self._show_disclaimer)
plugin_dialog.setModal(True)
plugin_dialog.show()

Expand Down
45 changes: 42 additions & 3 deletions napari_plugin_manager/qt_widgets.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
from qtpy.QtCore import Signal
from qtpy.QtGui import QMouseEvent
from qtpy.QtWidgets import QLabel
from qtpy.QtCore import Qt, Signal
from qtpy.QtGui import QMouseEvent, QPainter
from qtpy.QtWidgets import (
QHBoxLayout,
QLabel,
QPushButton,
QStyle,
QStyleOption,
QWidget,
)


class ClickableLabel(QLabel):
Expand All @@ -12,3 +19,35 @@ def __init__(self, parent=None):
def mouseReleaseEvent(self, event: QMouseEvent):
super().mouseReleaseEvent(event)
self.clicked.emit()


class DisclaimerWidget(QWidget):
def __init__(self, text, parent=None):
super().__init__(parent=parent)

# Setup widgets
disclaimer_label = QLabel(text)
disclaimer_label.setAlignment(Qt.AlignmentFlag.AlignCenter)

disclaimer_button = QPushButton("x")
disclaimer_button.setFixedSize(20, 20)
disclaimer_button.clicked.connect(self.hide)

# Setup layout
disclaimer_layout = QHBoxLayout()
disclaimer_layout.addWidget(disclaimer_label)
disclaimer_layout.addWidget(disclaimer_button)
self.setLayout(disclaimer_layout)

def paintEvent(self, paint_event):
"""
Override so `QWidget` subclass can be affect by the stylesheet.

For details you can check: https://doc.qt.io/qt-5/stylesheet-reference.html#list-of-stylable-widgets
"""
style_option = QStyleOption()
style_option.initFrom(self)
painter = QPainter(self)
self.style().drawPrimitive(
QStyle.PE_Widget, style_option, painter, self
)
10 changes: 10 additions & 0 deletions napari_plugin_manager/styles.qss
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,16 @@ QPushButton#refresh_button:disabled {
font-style: italic;
}

DisclaimerWidget {
color: {{ opacity(text, 150) }};
font-size: {{ font_size }};
font-weight: bold;
background-color: {{ foreground }};
border: 1px solid {{ foreground }};
padding: 5px;
border-radius: 3px;
}

#plugin_manager_process_status{
background: {{ background }};
color: {{ opacity(text, 200) }};
Expand Down