napari-plugin-manager 0.1.5rc1__py3-none-any.whl → 0.1.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- napari_plugin_manager/_tests/conftest.py +10 -10
- napari_plugin_manager/_tests/test_installer_process.py +123 -82
- napari_plugin_manager/_tests/test_npe2api.py +12 -10
- napari_plugin_manager/_tests/test_qt_plugin_dialog.py +91 -93
- napari_plugin_manager/_tests/test_utils.py +39 -8
- napari_plugin_manager/_version.py +16 -3
- napari_plugin_manager/base_qt_package_installer.py +141 -65
- napari_plugin_manager/base_qt_plugin_dialog.py +180 -160
- napari_plugin_manager/npe2api.py +11 -6
- napari_plugin_manager/qt_package_installer.py +44 -16
- napari_plugin_manager/qt_plugin_dialog.py +40 -30
- napari_plugin_manager/qt_warning_dialog.py +5 -3
- napari_plugin_manager/qt_widgets.py +3 -3
- napari_plugin_manager/styles.qss +30 -13
- napari_plugin_manager/utils.py +57 -2
- {napari_plugin_manager-0.1.5rc1.dist-info → napari_plugin_manager-0.1.7.dist-info}/METADATA +9 -35
- napari_plugin_manager-0.1.7.dist-info/RECORD +23 -0
- {napari_plugin_manager-0.1.5rc1.dist-info → napari_plugin_manager-0.1.7.dist-info}/WHEEL +1 -1
- napari_plugin_manager-0.1.5rc1.dist-info/RECORD +0 -23
- {napari_plugin_manager-0.1.5rc1.dist-info → napari_plugin_manager-0.1.7.dist-info}/licenses/LICENSE +0 -0
- {napari_plugin_manager-0.1.5rc1.dist-info → napari_plugin_manager-0.1.7.dist-info}/top_level.txt +0 -0
|
@@ -2,8 +2,9 @@ import contextlib
|
|
|
2
2
|
import importlib.metadata
|
|
3
3
|
import os
|
|
4
4
|
import webbrowser
|
|
5
|
-
from collections.abc import Sequence
|
|
5
|
+
from collections.abc import Iterable, Sequence
|
|
6
6
|
from functools import partial
|
|
7
|
+
from logging import getLogger
|
|
7
8
|
from typing import (
|
|
8
9
|
Any,
|
|
9
10
|
Literal,
|
|
@@ -17,7 +18,11 @@ from qtpy.QtCore import QSize, Qt, QTimer, Signal, Slot
|
|
|
17
18
|
from qtpy.QtGui import (
|
|
18
19
|
QAction,
|
|
19
20
|
QActionGroup,
|
|
21
|
+
QCloseEvent,
|
|
22
|
+
QDropEvent,
|
|
23
|
+
QEnterEvent,
|
|
20
24
|
QFont,
|
|
25
|
+
QHideEvent,
|
|
21
26
|
QIcon,
|
|
22
27
|
QKeySequence,
|
|
23
28
|
QMovie,
|
|
@@ -38,6 +43,7 @@ from qtpy.QtWidgets import (
|
|
|
38
43
|
QPushButton,
|
|
39
44
|
QSizePolicy,
|
|
40
45
|
QSplitter,
|
|
46
|
+
QStackedWidget,
|
|
41
47
|
QTextEdit,
|
|
42
48
|
QToolButton,
|
|
43
49
|
QVBoxLayout,
|
|
@@ -53,10 +59,12 @@ from napari_plugin_manager.base_qt_package_installer import (
|
|
|
53
59
|
)
|
|
54
60
|
from napari_plugin_manager.qt_warning_dialog import RestartWarningDialog
|
|
55
61
|
from napari_plugin_manager.qt_widgets import ClickableLabel
|
|
56
|
-
from napari_plugin_manager.utils import is_conda_package
|
|
62
|
+
from napari_plugin_manager.utils import get_homepage_url, is_conda_package
|
|
57
63
|
|
|
58
64
|
CONDA = 'Conda'
|
|
59
65
|
PYPI = 'PyPI'
|
|
66
|
+
PACKAGE_SOURCES = Literal['PyPI', 'Conda']
|
|
67
|
+
log = getLogger(__name__)
|
|
60
68
|
|
|
61
69
|
|
|
62
70
|
class PackageMetadataProtocol(Protocol):
|
|
@@ -142,13 +150,13 @@ class BasePluginListItem(QFrame):
|
|
|
142
150
|
url: str = '',
|
|
143
151
|
summary: str = '',
|
|
144
152
|
author: str = '',
|
|
145
|
-
license: str =
|
|
153
|
+
license: str = 'UNKNOWN', # noqa: A002
|
|
146
154
|
*,
|
|
147
155
|
plugin_name: str | None = None,
|
|
148
156
|
parent: QWidget = None,
|
|
149
157
|
enabled: bool = True,
|
|
150
158
|
installed: bool = False,
|
|
151
|
-
plugin_api_version=1,
|
|
159
|
+
plugin_api_version: int | None = 1,
|
|
152
160
|
versions_conda: list[str] | None = None,
|
|
153
161
|
versions_pypi: list[str] | None = None,
|
|
154
162
|
prefix=None,
|
|
@@ -160,14 +168,14 @@ class BasePluginListItem(QFrame):
|
|
|
160
168
|
self.name = package_name
|
|
161
169
|
self.plugin_api_version = plugin_api_version
|
|
162
170
|
self._version = version
|
|
163
|
-
self._versions_conda = versions_conda
|
|
164
|
-
self._versions_pypi = versions_pypi
|
|
171
|
+
self._versions_conda = versions_conda or []
|
|
172
|
+
self._versions_pypi = versions_pypi or []
|
|
165
173
|
self.setup_ui(enabled)
|
|
166
174
|
|
|
167
175
|
if package_name == display_name:
|
|
168
176
|
name = package_name
|
|
169
177
|
else:
|
|
170
|
-
name = f
|
|
178
|
+
name = f'{display_name} <small>({package_name})</small>'
|
|
171
179
|
|
|
172
180
|
self.plugin_name.setText(name)
|
|
173
181
|
|
|
@@ -255,24 +263,24 @@ class BasePluginListItem(QFrame):
|
|
|
255
263
|
"""
|
|
256
264
|
raise NotImplementedError
|
|
257
265
|
|
|
258
|
-
def _is_main_app_conda_package(self):
|
|
266
|
+
def _is_main_app_conda_package(self) -> bool:
|
|
259
267
|
return is_conda_package(self.BASE_PACKAGE_NAME)
|
|
260
268
|
|
|
261
|
-
def _set_installed(self, installed: bool, package_name):
|
|
269
|
+
def _set_installed(self, installed: bool, package_name) -> None:
|
|
262
270
|
if installed:
|
|
263
271
|
if is_conda_package(package_name):
|
|
264
272
|
self.source.setText(CONDA)
|
|
265
273
|
|
|
266
274
|
self.enabled_checkbox.show()
|
|
267
|
-
self.action_button.setText(self._trans(
|
|
268
|
-
self.action_button.setObjectName(
|
|
275
|
+
self.action_button.setText(self._trans('Uninstall'))
|
|
276
|
+
self.action_button.setObjectName('remove_button')
|
|
269
277
|
self.info_choice_wdg.hide()
|
|
270
278
|
self.install_info_button.addWidget(self.info_widget)
|
|
271
279
|
self.info_widget.show()
|
|
272
280
|
else:
|
|
273
281
|
self.enabled_checkbox.hide()
|
|
274
|
-
self.action_button.setText(self._trans(
|
|
275
|
-
self.action_button.setObjectName(
|
|
282
|
+
self.action_button.setText(self._trans('Install'))
|
|
283
|
+
self.action_button.setObjectName('install_button')
|
|
276
284
|
self.info_widget.hide()
|
|
277
285
|
self.install_info_button.addWidget(self.info_choice_wdg)
|
|
278
286
|
self.info_choice_wdg.show()
|
|
@@ -292,7 +300,7 @@ class BasePluginListItem(QFrame):
|
|
|
292
300
|
"""
|
|
293
301
|
raise NotImplementedError
|
|
294
302
|
|
|
295
|
-
def set_status(self, icon=None, text=''):
|
|
303
|
+
def set_status(self, icon=None, text='') -> None:
|
|
296
304
|
"""Set the status icon and text next to the package name."""
|
|
297
305
|
if icon:
|
|
298
306
|
self.status_icon.setPixmap(icon)
|
|
@@ -309,7 +317,7 @@ class BasePluginListItem(QFrame):
|
|
|
309
317
|
action_name: (
|
|
310
318
|
Literal['install', 'uninstall', 'cancel', 'upgrade'] | None
|
|
311
319
|
) = None,
|
|
312
|
-
):
|
|
320
|
+
) -> None:
|
|
313
321
|
"""Updates status text and what buttons are visible when any button is pushed.
|
|
314
322
|
|
|
315
323
|
Parameters
|
|
@@ -332,18 +340,18 @@ class BasePluginListItem(QFrame):
|
|
|
332
340
|
self.action_button.setDisabled(False)
|
|
333
341
|
self.cancel_btn.setVisible(False)
|
|
334
342
|
else: # pragma: no cover
|
|
335
|
-
raise ValueError(f
|
|
343
|
+
raise ValueError(f'Not supported {action_name}')
|
|
336
344
|
|
|
337
|
-
def is_busy(self):
|
|
345
|
+
def is_busy(self) -> bool:
|
|
338
346
|
return bool(self.item_status.text())
|
|
339
347
|
|
|
340
|
-
def setup_ui(self, enabled=True):
|
|
348
|
+
def setup_ui(self, enabled: bool = True) -> None:
|
|
341
349
|
"""Define the layout of the PluginListItem"""
|
|
342
350
|
# Enabled checkbox
|
|
343
351
|
self.enabled_checkbox = QCheckBox(self)
|
|
344
352
|
self.enabled_checkbox.setChecked(enabled)
|
|
345
|
-
self.enabled_checkbox.setToolTip(self._trans(
|
|
346
|
-
self.enabled_checkbox.setText(
|
|
353
|
+
self.enabled_checkbox.setToolTip(self._trans('enable/disable'))
|
|
354
|
+
self.enabled_checkbox.setText('')
|
|
347
355
|
self.enabled_checkbox.stateChanged.connect(self._on_enabled_checkbox)
|
|
348
356
|
|
|
349
357
|
sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
|
@@ -391,7 +399,7 @@ class BasePluginListItem(QFrame):
|
|
|
391
399
|
|
|
392
400
|
# Item status
|
|
393
401
|
self.item_status = QLabel(self)
|
|
394
|
-
self.item_status.setObjectName(
|
|
402
|
+
self.item_status.setObjectName('small_italic_text')
|
|
395
403
|
self.item_status.setSizePolicy(sizePolicy)
|
|
396
404
|
|
|
397
405
|
# Summary
|
|
@@ -407,7 +415,7 @@ class BasePluginListItem(QFrame):
|
|
|
407
415
|
sizePolicy.setHorizontalStretch(1)
|
|
408
416
|
sizePolicy.setVerticalStretch(0)
|
|
409
417
|
self.summary.setSizePolicy(sizePolicy)
|
|
410
|
-
self.summary.setContentsMargins(0,
|
|
418
|
+
self.summary.setContentsMargins(0, 0, 0, 0)
|
|
411
419
|
|
|
412
420
|
# Package author
|
|
413
421
|
self.package_author = QElidingLabel(self)
|
|
@@ -417,7 +425,7 @@ class BasePluginListItem(QFrame):
|
|
|
417
425
|
|
|
418
426
|
# Update button
|
|
419
427
|
self.update_btn = QPushButton('Update', self)
|
|
420
|
-
self.update_btn.setObjectName(
|
|
428
|
+
self.update_btn.setObjectName('install_button')
|
|
421
429
|
self.update_btn.setVisible(False)
|
|
422
430
|
self.update_btn.clicked.connect(self._update_requested)
|
|
423
431
|
sizePolicy.setRetainSizeWhenHidden(True)
|
|
@@ -427,16 +435,16 @@ class BasePluginListItem(QFrame):
|
|
|
427
435
|
|
|
428
436
|
# Action Button
|
|
429
437
|
self.action_button = QPushButton(self)
|
|
430
|
-
self.action_button.setFixedWidth(
|
|
438
|
+
self.action_button.setFixedWidth(80)
|
|
431
439
|
sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
|
|
432
440
|
self.action_button.setSizePolicy(sizePolicy1)
|
|
433
441
|
self.action_button.clicked.connect(self._action_requested)
|
|
434
442
|
|
|
435
443
|
# Cancel
|
|
436
|
-
self.cancel_btn = QPushButton(
|
|
437
|
-
self.cancel_btn.setObjectName(
|
|
444
|
+
self.cancel_btn = QPushButton('Cancel', self)
|
|
445
|
+
self.cancel_btn.setObjectName('remove_button')
|
|
438
446
|
self.cancel_btn.setSizePolicy(sizePolicy)
|
|
439
|
-
self.cancel_btn.setFixedWidth(
|
|
447
|
+
self.cancel_btn.setFixedWidth(80)
|
|
440
448
|
self.cancel_btn.clicked.connect(self._cancel_requested)
|
|
441
449
|
|
|
442
450
|
# Collapsible button
|
|
@@ -444,12 +452,12 @@ class BasePluginListItem(QFrame):
|
|
|
444
452
|
exp_icon = self._expanded_icon()
|
|
445
453
|
|
|
446
454
|
self.install_info_button = QCollapsible(
|
|
447
|
-
|
|
455
|
+
'Installation Info', collapsedIcon=coll_icon, expandedIcon=exp_icon
|
|
448
456
|
)
|
|
449
457
|
self.install_info_button.setLayoutDirection(
|
|
450
458
|
Qt.RightToLeft
|
|
451
459
|
) # Make icon appear on the right
|
|
452
|
-
self.install_info_button.setObjectName(
|
|
460
|
+
self.install_info_button.setObjectName('install_info_button')
|
|
453
461
|
self.install_info_button.setFixedWidth(180)
|
|
454
462
|
self.install_info_button.content().layout().setContentsMargins(
|
|
455
463
|
0, 0, 0, 0
|
|
@@ -485,7 +493,7 @@ class BasePluginListItem(QFrame):
|
|
|
485
493
|
# Information widget for installed packages
|
|
486
494
|
self.info_widget = QWidget(self)
|
|
487
495
|
self.info_widget.setLayoutDirection(Qt.LeftToRight)
|
|
488
|
-
self.info_widget.setObjectName(
|
|
496
|
+
self.info_widget.setObjectName('info_widget')
|
|
489
497
|
self.info_widget.setFixedWidth(180)
|
|
490
498
|
|
|
491
499
|
self.source_text = QLabel('Source:')
|
|
@@ -505,7 +513,7 @@ class BasePluginListItem(QFrame):
|
|
|
505
513
|
|
|
506
514
|
# Error indicator
|
|
507
515
|
self.error_indicator = QPushButton()
|
|
508
|
-
self.error_indicator.setObjectName(
|
|
516
|
+
self.error_indicator.setObjectName('warning_icon')
|
|
509
517
|
self.error_indicator.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
510
518
|
self.error_indicator.hide()
|
|
511
519
|
|
|
@@ -568,10 +576,10 @@ class BasePluginListItem(QFrame):
|
|
|
568
576
|
|
|
569
577
|
self.info_choice_wdg.setLayout(info_layout)
|
|
570
578
|
self.info_choice_wdg.setLayoutDirection(Qt.LeftToRight)
|
|
571
|
-
self.info_choice_wdg.setObjectName(
|
|
579
|
+
self.info_choice_wdg.setObjectName('install_choice_widget')
|
|
572
580
|
self.info_choice_wdg.hide()
|
|
573
581
|
|
|
574
|
-
def _populate_version_dropdown(self, source:
|
|
582
|
+
def _populate_version_dropdown(self, source: PACKAGE_SOURCES) -> None:
|
|
575
583
|
"""Display the versions available after selecting a source: pypi or conda."""
|
|
576
584
|
if source == PYPI:
|
|
577
585
|
versions = self._versions_pypi
|
|
@@ -612,14 +620,14 @@ class BasePluginListItem(QFrame):
|
|
|
612
620
|
"""
|
|
613
621
|
raise NotImplementedError
|
|
614
622
|
|
|
615
|
-
def _cancel_requested(self):
|
|
623
|
+
def _cancel_requested(self) -> None:
|
|
616
624
|
version = self.version_choice_dropdown.currentText()
|
|
617
625
|
tool = self.get_installer_tool()
|
|
618
626
|
self.actionRequested.emit(
|
|
619
627
|
self.item, self.name, InstallerActions.CANCEL, version, tool
|
|
620
628
|
)
|
|
621
629
|
|
|
622
|
-
def _action_requested(self):
|
|
630
|
+
def _action_requested(self) -> None:
|
|
623
631
|
version = self.version_choice_dropdown.currentText()
|
|
624
632
|
tool = self.get_installer_tool()
|
|
625
633
|
action = (
|
|
@@ -632,19 +640,19 @@ class BasePluginListItem(QFrame):
|
|
|
632
640
|
self.item, self.name, action, version, tool
|
|
633
641
|
)
|
|
634
642
|
|
|
635
|
-
def _update_requested(self):
|
|
643
|
+
def _update_requested(self) -> None:
|
|
636
644
|
version = self.version_choice_dropdown.currentText()
|
|
637
645
|
tool = self.get_installer_tool()
|
|
638
646
|
self.actionRequested.emit(
|
|
639
647
|
self.item, self.name, InstallerActions.UPGRADE, version, tool
|
|
640
648
|
)
|
|
641
649
|
|
|
642
|
-
def show_warning(self, message: str =
|
|
650
|
+
def show_warning(self, message: str = '') -> None:
|
|
643
651
|
"""Show warning icon and tooltip."""
|
|
644
652
|
self.warning_tooltip.setVisible(bool(message))
|
|
645
653
|
self.warning_tooltip.setToolTip(message)
|
|
646
654
|
|
|
647
|
-
def get_installer_source(self):
|
|
655
|
+
def get_installer_source(self) -> Literal['Conda', 'PyPI']:
|
|
648
656
|
return (
|
|
649
657
|
CONDA
|
|
650
658
|
if self.source_choice_dropdown.currentText() == CONDA
|
|
@@ -652,12 +660,12 @@ class BasePluginListItem(QFrame):
|
|
|
652
660
|
else PYPI
|
|
653
661
|
)
|
|
654
662
|
|
|
655
|
-
def get_installer_tool(self):
|
|
663
|
+
def get_installer_tool(self) -> InstallerTools:
|
|
656
664
|
return (
|
|
657
665
|
InstallerTools.CONDA
|
|
658
666
|
if self.source_choice_dropdown.currentText() == CONDA
|
|
659
667
|
or is_conda_package(self.name, prefix=self.prefix)
|
|
660
|
-
else InstallerTools.
|
|
668
|
+
else InstallerTools.PYPI
|
|
661
669
|
)
|
|
662
670
|
|
|
663
671
|
|
|
@@ -719,11 +727,11 @@ class BaseQPluginList(QListWidget):
|
|
|
719
727
|
def addItem(
|
|
720
728
|
self,
|
|
721
729
|
project_info: BaseProjectInfoVersions,
|
|
722
|
-
installed=False,
|
|
723
|
-
plugin_name=None,
|
|
724
|
-
enabled=True,
|
|
725
|
-
plugin_api_version=None,
|
|
726
|
-
):
|
|
730
|
+
installed: bool = False,
|
|
731
|
+
plugin_name: str | None = None,
|
|
732
|
+
enabled: bool = True,
|
|
733
|
+
plugin_api_version: int | None = None,
|
|
734
|
+
) -> None:
|
|
727
735
|
pkg_name = project_info.metadata.name
|
|
728
736
|
# don't add duplicates
|
|
729
737
|
if (
|
|
@@ -733,7 +741,7 @@ class BaseQPluginList(QListWidget):
|
|
|
733
741
|
return
|
|
734
742
|
|
|
735
743
|
# including summary here for sake of filtering below.
|
|
736
|
-
searchable_text = f
|
|
744
|
+
searchable_text = f'{pkg_name} {project_info.display_name} {project_info.metadata.summary}'
|
|
737
745
|
item = QListWidgetItem(searchable_text, self)
|
|
738
746
|
item.version = project_info.metadata.version
|
|
739
747
|
super().addItem(item)
|
|
@@ -774,7 +782,7 @@ class BaseQPluginList(QListWidget):
|
|
|
774
782
|
lambda: self._resize_pluginlistitem(item)
|
|
775
783
|
)
|
|
776
784
|
|
|
777
|
-
def removeItem(self, name):
|
|
785
|
+
def removeItem(self, name: str) -> None:
|
|
778
786
|
count = self.count()
|
|
779
787
|
for i in range(count):
|
|
780
788
|
item = self.item(i)
|
|
@@ -782,7 +790,7 @@ class BaseQPluginList(QListWidget):
|
|
|
782
790
|
self.takeItem(i)
|
|
783
791
|
break
|
|
784
792
|
|
|
785
|
-
def refreshItem(self, name, version=None):
|
|
793
|
+
def refreshItem(self, name: str, version: str | None = None) -> None:
|
|
786
794
|
count = self.count()
|
|
787
795
|
for i in range(count):
|
|
788
796
|
item = self.item(i)
|
|
@@ -797,7 +805,7 @@ class BaseQPluginList(QListWidget):
|
|
|
797
805
|
item.setText(item.text()[len(self._SORT_ORDER_PREFIX) :])
|
|
798
806
|
break
|
|
799
807
|
|
|
800
|
-
def _resize_pluginlistitem(self, item):
|
|
808
|
+
def _resize_pluginlistitem(self, item: QListWidgetItem):
|
|
801
809
|
"""Resize the plugin list item, especially after toggling QCollapsible."""
|
|
802
810
|
if item.widget.install_info_button.isExpanded():
|
|
803
811
|
item.widget.setFixedHeight(self._initial_height + 35)
|
|
@@ -830,39 +838,36 @@ class BaseQPluginList(QListWidget):
|
|
|
830
838
|
pkg_name: str,
|
|
831
839
|
action_name: InstallerActions,
|
|
832
840
|
version: str | None = None,
|
|
833
|
-
installer_choice:
|
|
834
|
-
):
|
|
841
|
+
installer_choice: InstallerTools | None = None,
|
|
842
|
+
) -> None:
|
|
835
843
|
"""Determine which action is called (install, uninstall, update, cancel).
|
|
836
844
|
Update buttons appropriately and run the action."""
|
|
837
845
|
widget = item.widget
|
|
838
846
|
tool = installer_choice or widget.get_installer_tool()
|
|
839
847
|
self._remove_list.append((pkg_name, item))
|
|
840
|
-
self._warn_dialog = None
|
|
841
848
|
if not item.text().startswith(self._SORT_ORDER_PREFIX):
|
|
842
|
-
item.setText(f
|
|
849
|
+
item.setText(f'{self._SORT_ORDER_PREFIX}{item.text()}')
|
|
843
850
|
|
|
844
851
|
if action_name == InstallerActions.INSTALL:
|
|
845
852
|
if version:
|
|
846
853
|
pkg_name += (
|
|
847
|
-
f
|
|
854
|
+
f'=={item.widget.version_choice_dropdown.currentText()}'
|
|
848
855
|
)
|
|
849
|
-
widget.set_busy(self._trans(
|
|
856
|
+
widget.set_busy(self._trans('installing...'), action_name)
|
|
850
857
|
|
|
851
858
|
job_id = self.installer.install(
|
|
852
859
|
tool=tool,
|
|
853
860
|
pkgs=[pkg_name],
|
|
854
861
|
# origins="TODO",
|
|
855
862
|
)
|
|
856
|
-
widget.setProperty(
|
|
857
|
-
if self._warn_dialog:
|
|
858
|
-
self._warn_dialog.exec_()
|
|
863
|
+
widget.setProperty('current_job_id', job_id)
|
|
859
864
|
self.scrollToTop()
|
|
860
865
|
|
|
861
866
|
if action_name == InstallerActions.UPGRADE:
|
|
862
867
|
if hasattr(item, 'latest_version'):
|
|
863
|
-
pkg_name += f
|
|
868
|
+
pkg_name += f'=={item.latest_version}'
|
|
864
869
|
|
|
865
|
-
widget.set_busy(self._trans(
|
|
870
|
+
widget.set_busy(self._trans('updating...'), action_name)
|
|
866
871
|
widget.update_btn.setDisabled(True)
|
|
867
872
|
widget.action_button.setDisabled(True)
|
|
868
873
|
|
|
@@ -871,13 +876,11 @@ class BaseQPluginList(QListWidget):
|
|
|
871
876
|
pkgs=[pkg_name],
|
|
872
877
|
# origins="TODO",
|
|
873
878
|
)
|
|
874
|
-
widget.setProperty(
|
|
875
|
-
if self._warn_dialog:
|
|
876
|
-
self._warn_dialog.exec_()
|
|
879
|
+
widget.setProperty('current_job_id', job_id)
|
|
877
880
|
self.scrollToTop()
|
|
878
881
|
|
|
879
882
|
elif action_name == InstallerActions.UNINSTALL:
|
|
880
|
-
widget.set_busy(self._trans(
|
|
883
|
+
widget.set_busy(self._trans('uninstalling...'), action_name)
|
|
881
884
|
widget.update_btn.setDisabled(True)
|
|
882
885
|
job_id = self.installer.uninstall(
|
|
883
886
|
tool=tool,
|
|
@@ -885,31 +888,29 @@ class BaseQPluginList(QListWidget):
|
|
|
885
888
|
# origins="TODO",
|
|
886
889
|
# upgrade=False,
|
|
887
890
|
)
|
|
888
|
-
widget.setProperty(
|
|
889
|
-
if self._warn_dialog:
|
|
890
|
-
self._warn_dialog.exec_()
|
|
891
|
+
widget.setProperty('current_job_id', job_id)
|
|
891
892
|
self.scrollToTop()
|
|
892
893
|
elif action_name == InstallerActions.CANCEL:
|
|
893
|
-
widget.set_busy(self._trans(
|
|
894
|
+
widget.set_busy(self._trans('cancelling...'), action_name)
|
|
894
895
|
try:
|
|
895
|
-
job_id = widget.property(
|
|
896
|
+
job_id = widget.property('current_job_id')
|
|
896
897
|
self.installer.cancel(job_id)
|
|
897
898
|
finally:
|
|
898
|
-
widget.setProperty(
|
|
899
|
+
widget.setProperty('current_job_id', None)
|
|
899
900
|
|
|
900
|
-
def set_data(self, data):
|
|
901
|
+
def set_data(self, data) -> None:
|
|
901
902
|
self._data = data
|
|
902
903
|
|
|
903
|
-
def is_running(self):
|
|
904
|
+
def is_running(self) -> bool:
|
|
904
905
|
return self.count() != len(self._data)
|
|
905
906
|
|
|
906
|
-
def packages(self):
|
|
907
|
+
def packages(self) -> list[str]:
|
|
907
908
|
return [self.item(idx).widget.name for idx in range(self.count())]
|
|
908
909
|
|
|
909
910
|
@Slot(PackageMetadataProtocol, bool)
|
|
910
911
|
def tag_outdated(
|
|
911
912
|
self, metadata: PackageMetadataProtocol, is_available: bool
|
|
912
|
-
):
|
|
913
|
+
) -> None:
|
|
913
914
|
"""Determines if an installed plugin is up to date with the latest version.
|
|
914
915
|
If it is not, the latest version will be displayed on the update button.
|
|
915
916
|
"""
|
|
@@ -940,10 +941,10 @@ class BaseQPluginList(QListWidget):
|
|
|
940
941
|
widg = self.itemWidget(item)
|
|
941
942
|
widg.update_btn.setVisible(True)
|
|
942
943
|
widg.update_btn.setText(
|
|
943
|
-
self._trans(
|
|
944
|
+
self._trans('update (v{latest})', latest=latest)
|
|
944
945
|
)
|
|
945
946
|
|
|
946
|
-
def tag_unavailable(self, metadata: PackageMetadataProtocol):
|
|
947
|
+
def tag_unavailable(self, metadata: PackageMetadataProtocol) -> None:
|
|
947
948
|
"""
|
|
948
949
|
Tag list items as unavailable for install with conda-forge.
|
|
949
950
|
|
|
@@ -956,16 +957,16 @@ class BaseQPluginList(QListWidget):
|
|
|
956
957
|
widget = self.itemWidget(item)
|
|
957
958
|
widget.show_warning(
|
|
958
959
|
self._trans(
|
|
959
|
-
|
|
960
|
+
'Plugin not yet available for installation within the bundle application'
|
|
960
961
|
)
|
|
961
962
|
)
|
|
962
|
-
widget.setObjectName(
|
|
963
|
+
widget.setObjectName('unavailable')
|
|
963
964
|
widget.style().unpolish(widget)
|
|
964
965
|
widget.style().polish(widget)
|
|
965
966
|
widget.action_button.setEnabled(False)
|
|
966
967
|
widget.warning_tooltip.setVisible(True)
|
|
967
968
|
|
|
968
|
-
def filter(self, text: str, starts_with_chars: int = 1):
|
|
969
|
+
def filter(self, text: str, starts_with_chars: int = 1) -> None:
|
|
969
970
|
"""Filter items to those containing `text`."""
|
|
970
971
|
if text:
|
|
971
972
|
# PySide has some issues, so we compare using id
|
|
@@ -997,7 +998,7 @@ class BaseQPluginList(QListWidget):
|
|
|
997
998
|
item = self.item(i)
|
|
998
999
|
item.setHidden(False)
|
|
999
1000
|
|
|
1000
|
-
def hideAll(self):
|
|
1001
|
+
def hideAll(self) -> None:
|
|
1001
1002
|
for i in range(self.count()):
|
|
1002
1003
|
item = self.item(i)
|
|
1003
1004
|
item.setHidden(not item.widget.is_busy())
|
|
@@ -1027,7 +1028,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1027
1028
|
|
|
1028
1029
|
finished = Signal()
|
|
1029
1030
|
|
|
1030
|
-
def __init__(self, parent=None, prefix=None) -> None:
|
|
1031
|
+
def __init__(self, parent: QDialog = None, prefix=None) -> None:
|
|
1031
1032
|
super().__init__(parent)
|
|
1032
1033
|
|
|
1033
1034
|
self._parent = parent
|
|
@@ -1040,6 +1041,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1040
1041
|
self._plugins_found = 0
|
|
1041
1042
|
self.already_installed = set()
|
|
1042
1043
|
self.available_set = set()
|
|
1044
|
+
self.modified_set = set()
|
|
1043
1045
|
self._prefix = prefix
|
|
1044
1046
|
self._first_open = True
|
|
1045
1047
|
self._plugin_queue = [] # Store plugin data to be added
|
|
@@ -1080,15 +1082,15 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1080
1082
|
|
|
1081
1083
|
# region - Private methods
|
|
1082
1084
|
# ------------------------------------------------------------------------
|
|
1083
|
-
def _enable_refresh_button(self):
|
|
1085
|
+
def _enable_refresh_button(self) -> None:
|
|
1084
1086
|
self.refresh_button.setEnabled(True)
|
|
1085
1087
|
|
|
1086
|
-
def _quit(self):
|
|
1088
|
+
def _quit(self) -> None:
|
|
1087
1089
|
self.close()
|
|
1088
1090
|
with contextlib.suppress(AttributeError):
|
|
1089
1091
|
self._parent.close(quit_app=True, confirm_need=True)
|
|
1090
1092
|
|
|
1091
|
-
def _setup_shortcuts(self):
|
|
1093
|
+
def _setup_shortcuts(self) -> None:
|
|
1092
1094
|
self._refresh_styles_action = QAction(
|
|
1093
1095
|
self._trans('Refresh Styles'), self
|
|
1094
1096
|
)
|
|
@@ -1128,7 +1130,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1128
1130
|
"""
|
|
1129
1131
|
raise NotImplementedError
|
|
1130
1132
|
|
|
1131
|
-
def _on_installer_start(self):
|
|
1133
|
+
def _on_installer_start(self) -> None:
|
|
1132
1134
|
"""Updates dialog buttons and status when installing a plugin."""
|
|
1133
1135
|
self.cancel_all_btn.setVisible(True)
|
|
1134
1136
|
self.working_indicator.show()
|
|
@@ -1136,7 +1138,9 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1136
1138
|
self.process_error_indicator.hide()
|
|
1137
1139
|
self.refresh_button.setDisabled(True)
|
|
1138
1140
|
|
|
1139
|
-
def _on_process_finished(
|
|
1141
|
+
def _on_process_finished(
|
|
1142
|
+
self, process_finished_data: ProcessFinishedData
|
|
1143
|
+
) -> None:
|
|
1140
1144
|
action = process_finished_data['action']
|
|
1141
1145
|
exit_code = process_finished_data['exit_code']
|
|
1142
1146
|
pkg_names = [
|
|
@@ -1150,6 +1154,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1150
1154
|
|
|
1151
1155
|
self.available_list.removeItem(pkg_name)
|
|
1152
1156
|
self._add_installed(pkg_name)
|
|
1157
|
+
self.modified_set.add(pkg_name)
|
|
1153
1158
|
self._tag_outdated_plugins()
|
|
1154
1159
|
else:
|
|
1155
1160
|
for pkg_name in pkg_names:
|
|
@@ -1162,6 +1167,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1162
1167
|
|
|
1163
1168
|
self.installed_list.removeItem(pkg_name)
|
|
1164
1169
|
self._add_to_available(pkg_name)
|
|
1170
|
+
self.modified_set.add(pkg_name)
|
|
1165
1171
|
else:
|
|
1166
1172
|
for pkg_name in pkg_names:
|
|
1167
1173
|
self.installed_list.refreshItem(pkg_name)
|
|
@@ -1175,6 +1181,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1175
1181
|
self.installed_list.refreshItem(
|
|
1176
1182
|
pkg_name, version=pkg_version
|
|
1177
1183
|
)
|
|
1184
|
+
self.modified_set.add(pkg_name)
|
|
1178
1185
|
else:
|
|
1179
1186
|
self.installed_list.refreshItem(pkg)
|
|
1180
1187
|
self._tag_outdated_plugins()
|
|
@@ -1190,7 +1197,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1190
1197
|
else:
|
|
1191
1198
|
self.process_success_indicator.show()
|
|
1192
1199
|
|
|
1193
|
-
def _on_installer_all_finished(self, exit_codes):
|
|
1200
|
+
def _on_installer_all_finished(self, exit_codes: Iterable[int]) -> None:
|
|
1194
1201
|
self.working_indicator.hide()
|
|
1195
1202
|
self.cancel_all_btn.setVisible(False)
|
|
1196
1203
|
self.close_btn.setDisabled(False)
|
|
@@ -1211,8 +1218,12 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1211
1218
|
self.search()
|
|
1212
1219
|
|
|
1213
1220
|
def _add_to_installed(
|
|
1214
|
-
self,
|
|
1215
|
-
|
|
1221
|
+
self,
|
|
1222
|
+
distname: str,
|
|
1223
|
+
enabled: bool,
|
|
1224
|
+
norm_name: str,
|
|
1225
|
+
plugin_api_version: int = 1,
|
|
1226
|
+
) -> None:
|
|
1216
1227
|
if distname:
|
|
1217
1228
|
try:
|
|
1218
1229
|
meta = importlib.metadata.metadata(distname)
|
|
@@ -1225,18 +1236,19 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1225
1236
|
self.already_installed.add(norm_name)
|
|
1226
1237
|
else:
|
|
1227
1238
|
meta = {}
|
|
1228
|
-
|
|
1239
|
+
meta_dict = meta if isinstance(meta, dict) else meta.json
|
|
1240
|
+
home_page = get_homepage_url(meta_dict)
|
|
1229
1241
|
self.installed_list.addItem(
|
|
1230
1242
|
self.PROJECT_INFO_VERSION_CLASS(
|
|
1231
1243
|
display_name=norm_name,
|
|
1232
1244
|
pypi_versions=[],
|
|
1233
1245
|
conda_versions=[],
|
|
1234
1246
|
metadata=self.PACKAGE_METADATA_CLASS(
|
|
1235
|
-
metadata_version=
|
|
1247
|
+
metadata_version='1.0',
|
|
1236
1248
|
name=norm_name,
|
|
1237
1249
|
version=meta.get('version', ''),
|
|
1238
1250
|
summary=meta.get('summary', ''),
|
|
1239
|
-
home_page=
|
|
1251
|
+
home_page=home_page,
|
|
1240
1252
|
author=meta.get('author', ''),
|
|
1241
1253
|
license=meta.get('license', ''),
|
|
1242
1254
|
),
|
|
@@ -1246,7 +1258,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1246
1258
|
plugin_api_version=plugin_api_version,
|
|
1247
1259
|
)
|
|
1248
1260
|
|
|
1249
|
-
def _add_to_available(self, pkg_name):
|
|
1261
|
+
def _add_to_available(self, pkg_name: str) -> None:
|
|
1250
1262
|
self._add_items_timer.stop()
|
|
1251
1263
|
if self._plugin_queue is not None:
|
|
1252
1264
|
self._plugin_queue.insert(0, self._plugin_data_map[pkg_name])
|
|
@@ -1348,10 +1360,10 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1348
1360
|
"""
|
|
1349
1361
|
raise NotImplementedError
|
|
1350
1362
|
|
|
1351
|
-
def _is_main_app_conda_package(self):
|
|
1363
|
+
def _is_main_app_conda_package(self) -> bool:
|
|
1352
1364
|
return is_conda_package(self.BASE_PACKAGE_NAME)
|
|
1353
1365
|
|
|
1354
|
-
def _setup_ui(self):
|
|
1366
|
+
def _setup_ui(self) -> None:
|
|
1355
1367
|
"""Defines the layout for the PluginDialog."""
|
|
1356
1368
|
self.resize(900, 600)
|
|
1357
1369
|
vlay_1 = QVBoxLayout(self)
|
|
@@ -1365,15 +1377,15 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1365
1377
|
installed = QWidget(self.v_splitter)
|
|
1366
1378
|
lay = QVBoxLayout(installed)
|
|
1367
1379
|
lay.setContentsMargins(0, 2, 0, 2)
|
|
1368
|
-
self.installed_label = QLabel(self._trans(
|
|
1380
|
+
self.installed_label = QLabel(self._trans('Installed Plugins'))
|
|
1369
1381
|
self.packages_search = QLineEdit()
|
|
1370
1382
|
self.packages_search.setPlaceholderText(
|
|
1371
|
-
self._trans(
|
|
1383
|
+
self._trans('Type here to start searching for plugins...')
|
|
1372
1384
|
)
|
|
1373
1385
|
self.packages_search.setToolTip(
|
|
1374
1386
|
self._trans(
|
|
1375
|
-
|
|
1376
|
-
|
|
1387
|
+
'The search text will filter currently installed plugins '
|
|
1388
|
+
'while also being used to search for plugins on the {package_name} hub',
|
|
1377
1389
|
package_name=self.BASE_PACKAGE_NAME,
|
|
1378
1390
|
)
|
|
1379
1391
|
)
|
|
@@ -1382,19 +1394,19 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1382
1394
|
self.packages_search.textChanged.connect(self.search)
|
|
1383
1395
|
|
|
1384
1396
|
self.import_button = QPushButton(self._trans('Import'), self)
|
|
1385
|
-
self.import_button.setObjectName(
|
|
1397
|
+
self.import_button.setObjectName('import_button')
|
|
1386
1398
|
self.import_button.setToolTip(self._trans('Import plugins from file'))
|
|
1387
1399
|
self.import_button.clicked.connect(self._import_plugins)
|
|
1388
1400
|
|
|
1389
1401
|
self.export_button = QPushButton(self._trans('Export'), self)
|
|
1390
|
-
self.export_button.setObjectName(
|
|
1402
|
+
self.export_button.setObjectName('export_button')
|
|
1391
1403
|
self.export_button.setToolTip(
|
|
1392
1404
|
self._trans('Export installed plugins list')
|
|
1393
1405
|
)
|
|
1394
1406
|
self.export_button.clicked.connect(self._export_plugins)
|
|
1395
1407
|
|
|
1396
1408
|
self.refresh_button = QPushButton(self._trans('Refresh'), self)
|
|
1397
|
-
self.refresh_button.setObjectName(
|
|
1409
|
+
self.refresh_button.setObjectName('refresh_button')
|
|
1398
1410
|
self.refresh_button.setToolTip(
|
|
1399
1411
|
self._trans(
|
|
1400
1412
|
'This will clear and refresh the available and installed plugins lists.'
|
|
@@ -1421,31 +1433,39 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1421
1433
|
uninstalled = QWidget(self.v_splitter)
|
|
1422
1434
|
lay = QVBoxLayout(uninstalled)
|
|
1423
1435
|
lay.setContentsMargins(0, 2, 0, 2)
|
|
1424
|
-
self.avail_label = QLabel(self._trans(
|
|
1436
|
+
self.avail_label = QLabel(self._trans('Available Plugins'))
|
|
1425
1437
|
mid_layout = QHBoxLayout()
|
|
1426
1438
|
mid_layout.addWidget(self.avail_label)
|
|
1427
1439
|
mid_layout.addStretch()
|
|
1428
1440
|
lay.addLayout(mid_layout)
|
|
1441
|
+
self.available_widget = QStackedWidget()
|
|
1429
1442
|
self.available_list = self.PLUGIN_LIST_CLASS(
|
|
1430
1443
|
uninstalled, self.installer, self.BASE_PACKAGE_NAME
|
|
1431
1444
|
)
|
|
1432
|
-
|
|
1445
|
+
self.available_message = QLabel(
|
|
1446
|
+
self._trans('Use the search box above to find plugins.')
|
|
1447
|
+
)
|
|
1448
|
+
self.available_message.setObjectName('available_message')
|
|
1449
|
+
self.available_message.setAlignment(Qt.AlignCenter)
|
|
1450
|
+
self.available_widget.addWidget(self.available_list)
|
|
1451
|
+
self.available_widget.addWidget(self.available_message)
|
|
1452
|
+
lay.addWidget(self.available_widget)
|
|
1433
1453
|
|
|
1434
1454
|
self.stdout_text = QTextEdit(self.v_splitter)
|
|
1435
1455
|
self.stdout_text.setReadOnly(True)
|
|
1436
|
-
self.stdout_text.setObjectName(
|
|
1456
|
+
self.stdout_text.setObjectName('plugin_manager_process_status')
|
|
1437
1457
|
self.stdout_text.hide()
|
|
1438
1458
|
|
|
1439
1459
|
buttonBox = QHBoxLayout()
|
|
1440
|
-
self.working_indicator = QLabel(self._trans(
|
|
1460
|
+
self.working_indicator = QLabel(self._trans('loading ...'), self)
|
|
1441
1461
|
sp = self.working_indicator.sizePolicy()
|
|
1442
1462
|
sp.setRetainSizeWhenHidden(True)
|
|
1443
1463
|
self.working_indicator.setSizePolicy(sp)
|
|
1444
1464
|
self.process_error_indicator = QLabel(self)
|
|
1445
|
-
self.process_error_indicator.setObjectName(
|
|
1465
|
+
self.process_error_indicator.setObjectName('error_label')
|
|
1446
1466
|
self.process_error_indicator.hide()
|
|
1447
1467
|
self.process_success_indicator = QLabel(self)
|
|
1448
|
-
self.process_success_indicator.setObjectName(
|
|
1468
|
+
self.process_success_indicator.setObjectName('success_label')
|
|
1449
1469
|
self.process_success_indicator.hide()
|
|
1450
1470
|
mov = self._loading_gif()
|
|
1451
1471
|
self.working_indicator.setMovie(mov)
|
|
@@ -1459,13 +1479,13 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1459
1479
|
self.direct_entry_btn = QToolButton(self)
|
|
1460
1480
|
self.direct_entry_btn.setVisible(visibility_direct_entry)
|
|
1461
1481
|
self.direct_entry_btn.clicked.connect(self._install_packages)
|
|
1462
|
-
self.direct_entry_btn.setText(self._trans(
|
|
1482
|
+
self.direct_entry_btn.setText(self._trans('Install'))
|
|
1463
1483
|
|
|
1464
1484
|
self._action_conda = QAction(self._trans('Conda'), self)
|
|
1465
1485
|
self._action_conda.setCheckable(True)
|
|
1466
1486
|
self._action_conda.triggered.connect(self._update_direct_entry_text)
|
|
1467
1487
|
|
|
1468
|
-
self._action_pypi = QAction(self._trans('
|
|
1488
|
+
self._action_pypi = QAction(self._trans('PyPI'), self)
|
|
1469
1489
|
self._action_pypi.setCheckable(True)
|
|
1470
1490
|
self._action_pypi.triggered.connect(self._update_direct_entry_text)
|
|
1471
1491
|
|
|
@@ -1483,31 +1503,28 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1483
1503
|
self._action_conda.setChecked(True)
|
|
1484
1504
|
self.direct_entry_btn.setMenu(self._menu)
|
|
1485
1505
|
|
|
1486
|
-
self.show_status_btn = QPushButton(self._trans(
|
|
1487
|
-
self.show_status_btn.setFixedWidth(100)
|
|
1506
|
+
self.show_status_btn = QPushButton(self._trans('Show Status'), self)
|
|
1488
1507
|
|
|
1489
1508
|
self.cancel_all_btn = QPushButton(
|
|
1490
|
-
self._trans(
|
|
1509
|
+
self._trans('cancel all actions'), self
|
|
1491
1510
|
)
|
|
1492
|
-
self.cancel_all_btn.setObjectName(
|
|
1511
|
+
self.cancel_all_btn.setObjectName('remove_button')
|
|
1493
1512
|
self.cancel_all_btn.setVisible(False)
|
|
1494
1513
|
self.cancel_all_btn.clicked.connect(self.installer.cancel_all)
|
|
1495
1514
|
|
|
1496
|
-
self.close_btn = QPushButton(self._trans(
|
|
1515
|
+
self.close_btn = QPushButton(self._trans('Close'), self)
|
|
1497
1516
|
self.close_btn.clicked.connect(self.accept)
|
|
1498
|
-
self.close_btn.setObjectName(
|
|
1517
|
+
self.close_btn.setObjectName('close_button')
|
|
1499
1518
|
|
|
1500
|
-
buttonBox.addWidget(self.show_status_btn)
|
|
1501
|
-
buttonBox.addWidget(self.working_indicator)
|
|
1502
1519
|
buttonBox.addWidget(self.direct_entry_edit)
|
|
1503
1520
|
buttonBox.addWidget(self.direct_entry_btn)
|
|
1504
1521
|
if not visibility_direct_entry:
|
|
1505
1522
|
buttonBox.addStretch()
|
|
1506
1523
|
buttonBox.addWidget(self.process_success_indicator)
|
|
1507
1524
|
buttonBox.addWidget(self.process_error_indicator)
|
|
1508
|
-
buttonBox.
|
|
1525
|
+
buttonBox.addWidget(self.show_status_btn)
|
|
1509
1526
|
buttonBox.addWidget(self.cancel_all_btn)
|
|
1510
|
-
buttonBox.
|
|
1527
|
+
buttonBox.addWidget(self.working_indicator)
|
|
1511
1528
|
buttonBox.addWidget(self.close_btn)
|
|
1512
1529
|
buttonBox.setContentsMargins(0, 0, 4, 0)
|
|
1513
1530
|
vlay_1.addLayout(buttonBox)
|
|
@@ -1516,25 +1533,25 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1516
1533
|
self.show_status_btn.setChecked(False)
|
|
1517
1534
|
self.show_status_btn.toggled.connect(self.toggle_status)
|
|
1518
1535
|
|
|
1519
|
-
self.v_splitter.setStretchFactor(
|
|
1536
|
+
self.v_splitter.setStretchFactor(0, 2)
|
|
1520
1537
|
self.h_splitter.setStretchFactor(0, 2)
|
|
1521
1538
|
|
|
1522
1539
|
self.packages_search.setFocus()
|
|
1523
1540
|
self._update_direct_entry_text()
|
|
1524
1541
|
|
|
1525
|
-
def _update_direct_entry_text(self):
|
|
1542
|
+
def _update_direct_entry_text(self) -> None:
|
|
1526
1543
|
tool = (
|
|
1527
1544
|
str(InstallerTools.CONDA)
|
|
1528
1545
|
if self._action_conda.isChecked()
|
|
1529
|
-
else str(InstallerTools.
|
|
1546
|
+
else str(InstallerTools.PYPI)
|
|
1530
1547
|
)
|
|
1531
1548
|
self.direct_entry_edit.setPlaceholderText(
|
|
1532
1549
|
self._trans(
|
|
1533
|
-
"install
|
|
1550
|
+
"install from '{tool}' by name/url, or drop file...", tool=tool
|
|
1534
1551
|
)
|
|
1535
1552
|
)
|
|
1536
1553
|
|
|
1537
|
-
def _update_plugin_count(self):
|
|
1554
|
+
def _update_plugin_count(self) -> None:
|
|
1538
1555
|
"""Update count labels for both installed and available plugin lists.
|
|
1539
1556
|
Displays also amount of visible plugins out of total when filtering.
|
|
1540
1557
|
"""
|
|
@@ -1543,14 +1560,14 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1543
1560
|
if installed_count == installed_count_visible:
|
|
1544
1561
|
self.installed_label.setText(
|
|
1545
1562
|
self._trans(
|
|
1546
|
-
|
|
1563
|
+
'Installed Plugins ({amount})',
|
|
1547
1564
|
amount=installed_count,
|
|
1548
1565
|
)
|
|
1549
1566
|
)
|
|
1550
1567
|
else:
|
|
1551
1568
|
self.installed_label.setText(
|
|
1552
1569
|
self._trans(
|
|
1553
|
-
|
|
1570
|
+
'Installed Plugins ({count}/{amount})',
|
|
1554
1571
|
count=installed_count_visible,
|
|
1555
1572
|
amount=installed_count,
|
|
1556
1573
|
)
|
|
@@ -1562,14 +1579,14 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1562
1579
|
if self._plugins_found == 0:
|
|
1563
1580
|
self.avail_label.setText(
|
|
1564
1581
|
self._trans(
|
|
1565
|
-
|
|
1582
|
+
'{amount} plugins available on the napari hub',
|
|
1566
1583
|
amount=available_count,
|
|
1567
1584
|
)
|
|
1568
1585
|
)
|
|
1569
1586
|
elif self._plugins_found > self.MAX_PLUGIN_SEARCH_ITEMS:
|
|
1570
1587
|
self.avail_label.setText(
|
|
1571
1588
|
self._trans(
|
|
1572
|
-
|
|
1589
|
+
'Found {found} out of {amount} plugins on the napari hub. Displaying the first {max_count}...',
|
|
1573
1590
|
found=self._plugins_found,
|
|
1574
1591
|
amount=available_count,
|
|
1575
1592
|
max_count=self.MAX_PLUGIN_SEARCH_ITEMS,
|
|
@@ -1578,7 +1595,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1578
1595
|
else:
|
|
1579
1596
|
self.avail_label.setText(
|
|
1580
1597
|
self._trans(
|
|
1581
|
-
|
|
1598
|
+
'Found {found} out of {amount} plugins on the napari hub',
|
|
1582
1599
|
found=self._plugins_found,
|
|
1583
1600
|
amount=available_count,
|
|
1584
1601
|
)
|
|
@@ -1587,7 +1604,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1587
1604
|
def _install_packages(
|
|
1588
1605
|
self,
|
|
1589
1606
|
packages: Sequence[str] = (),
|
|
1590
|
-
):
|
|
1607
|
+
) -> None:
|
|
1591
1608
|
if not packages:
|
|
1592
1609
|
_packages = self.direct_entry_edit.text()
|
|
1593
1610
|
packages = (
|
|
@@ -1599,11 +1616,11 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1599
1616
|
tool = (
|
|
1600
1617
|
InstallerTools.CONDA
|
|
1601
1618
|
if self._action_conda.isChecked()
|
|
1602
|
-
else InstallerTools.
|
|
1619
|
+
else InstallerTools.PYPI
|
|
1603
1620
|
)
|
|
1604
1621
|
self.installer.install(tool, packages)
|
|
1605
1622
|
|
|
1606
|
-
def _tag_outdated_plugins(self):
|
|
1623
|
+
def _tag_outdated_plugins(self) -> None:
|
|
1607
1624
|
"""Tag installed plugins that might be outdated."""
|
|
1608
1625
|
for pkg_name in self.installed_list.packages():
|
|
1609
1626
|
_data = self._plugin_data_map.get(pkg_name)
|
|
@@ -1613,7 +1630,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1613
1630
|
metadata, is_available_in_conda
|
|
1614
1631
|
)
|
|
1615
1632
|
|
|
1616
|
-
def _add_items(self):
|
|
1633
|
+
def _add_items(self) -> None:
|
|
1617
1634
|
"""
|
|
1618
1635
|
Add items to the lists by `batch_size` using a timer to add a pause
|
|
1619
1636
|
and prevent freezing the UI.
|
|
@@ -1667,7 +1684,9 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1667
1684
|
|
|
1668
1685
|
self._update_plugin_count()
|
|
1669
1686
|
|
|
1670
|
-
def _handle_yield(
|
|
1687
|
+
def _handle_yield(
|
|
1688
|
+
self, data: tuple[PackageMetadataProtocol, bool, dict]
|
|
1689
|
+
) -> None:
|
|
1671
1690
|
"""Output from a worker process.
|
|
1672
1691
|
|
|
1673
1692
|
Includes information about the plugin, including available versions on conda and pypi.
|
|
@@ -1677,7 +1696,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1677
1696
|
"""
|
|
1678
1697
|
self._plugin_data.append(data)
|
|
1679
1698
|
self._filter_texts = [
|
|
1680
|
-
f
|
|
1699
|
+
f'{i[0].name} {i[-1].get("display_name", "")} {i[0].summary}'.lower()
|
|
1681
1700
|
for i in self._plugin_data
|
|
1682
1701
|
]
|
|
1683
1702
|
metadata, _, _ = data
|
|
@@ -1685,7 +1704,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1685
1704
|
self.available_list.set_data(self._plugin_data)
|
|
1686
1705
|
self._update_plugin_count()
|
|
1687
1706
|
|
|
1688
|
-
def _search_in_available(self, text):
|
|
1707
|
+
def _search_in_available(self, text: str) -> list[int]:
|
|
1689
1708
|
idxs = []
|
|
1690
1709
|
for idx, item in enumerate(self._filter_texts):
|
|
1691
1710
|
if text.lower().strip() in item:
|
|
@@ -1694,16 +1713,16 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1694
1713
|
|
|
1695
1714
|
return idxs
|
|
1696
1715
|
|
|
1697
|
-
def _refresh_and_clear_cache(self):
|
|
1716
|
+
def _refresh_and_clear_cache(self) -> None:
|
|
1698
1717
|
self.refresh(clear_cache=True)
|
|
1699
1718
|
|
|
1700
|
-
def _import_plugins(self):
|
|
1701
|
-
fpath, _ = getopenfilename(filters=
|
|
1719
|
+
def _import_plugins(self) -> None:
|
|
1720
|
+
fpath, _ = getopenfilename(filters='Text files (*.txt)')
|
|
1702
1721
|
if fpath:
|
|
1703
1722
|
self.import_plugins(fpath)
|
|
1704
1723
|
|
|
1705
|
-
def _export_plugins(self):
|
|
1706
|
-
fpath, _ = getsavefilename(filters=
|
|
1724
|
+
def _export_plugins(self) -> None:
|
|
1725
|
+
fpath, _ = getsavefilename(filters='Text files (*.txt)')
|
|
1707
1726
|
if fpath:
|
|
1708
1727
|
self.export_plugins(fpath)
|
|
1709
1728
|
|
|
@@ -1711,7 +1730,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1711
1730
|
|
|
1712
1731
|
# region - Qt overrides
|
|
1713
1732
|
# ------------------------------------------------------------------------
|
|
1714
|
-
def closeEvent(self, event):
|
|
1733
|
+
def closeEvent(self, event: QCloseEvent) -> None:
|
|
1715
1734
|
if self._parent is not None:
|
|
1716
1735
|
plugin_dialog = getattr(self._parent, '_plugin_dialog', self)
|
|
1717
1736
|
if self != plugin_dialog:
|
|
@@ -1722,10 +1741,10 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1722
1741
|
else:
|
|
1723
1742
|
super().closeEvent(event)
|
|
1724
1743
|
|
|
1725
|
-
def dragEnterEvent(self, event):
|
|
1744
|
+
def dragEnterEvent(self, event: QEnterEvent) -> None:
|
|
1726
1745
|
event.accept()
|
|
1727
1746
|
|
|
1728
|
-
def dropEvent(self, event):
|
|
1747
|
+
def dropEvent(self, event: QDropEvent) -> None:
|
|
1729
1748
|
md = event.mimeData()
|
|
1730
1749
|
if md.hasUrls():
|
|
1731
1750
|
files = [url.toLocalFile() for url in md.urls()]
|
|
@@ -1734,24 +1753,24 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1734
1753
|
|
|
1735
1754
|
return super().dropEvent(event)
|
|
1736
1755
|
|
|
1737
|
-
def exec_(self):
|
|
1756
|
+
def exec_(self) -> None:
|
|
1738
1757
|
plugin_dialog = getattr(self._parent, '_plugin_dialog', self)
|
|
1739
1758
|
if plugin_dialog != self:
|
|
1740
1759
|
self.close()
|
|
1741
1760
|
|
|
1742
1761
|
plugin_dialog.setModal(True)
|
|
1743
1762
|
plugin_dialog.show()
|
|
1744
|
-
plugin_dialog._installed_on_show = set(plugin_dialog.already_installed)
|
|
1745
1763
|
|
|
1746
1764
|
if self._first_open:
|
|
1747
1765
|
self._update_theme(None)
|
|
1748
1766
|
self._first_open = False
|
|
1749
1767
|
|
|
1750
|
-
def hideEvent(self, event):
|
|
1751
|
-
if (
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1768
|
+
def hideEvent(self, event: QHideEvent) -> None:
|
|
1769
|
+
if len(self.modified_set):
|
|
1770
|
+
# At least one plugin was installed, uninstalled or updated so
|
|
1771
|
+
# clear modified packages (to show warning only once) and
|
|
1772
|
+
# show restart warning
|
|
1773
|
+
self.modified_set = set()
|
|
1755
1774
|
RestartWarningDialog(self).exec_()
|
|
1756
1775
|
self.packages_search.clear()
|
|
1757
1776
|
self.toggle_status(False)
|
|
@@ -1770,11 +1789,12 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1770
1789
|
|
|
1771
1790
|
if len(text.strip()) == 0:
|
|
1772
1791
|
self.installed_list.filter('')
|
|
1773
|
-
self.
|
|
1792
|
+
self.available_widget.setCurrentWidget(self.available_message)
|
|
1774
1793
|
self._plugin_queue = None
|
|
1775
1794
|
self._add_items_timer.stop()
|
|
1776
1795
|
self._plugins_found = 0
|
|
1777
1796
|
else:
|
|
1797
|
+
self.available_widget.setCurrentWidget(self.available_list)
|
|
1778
1798
|
items = [
|
|
1779
1799
|
self._plugin_data[idx]
|
|
1780
1800
|
for idx in self._search_in_available(text)
|
|
@@ -1795,7 +1815,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1795
1815
|
|
|
1796
1816
|
self._update_plugin_count()
|
|
1797
1817
|
|
|
1798
|
-
def refresh(self, clear_cache: bool = False):
|
|
1818
|
+
def refresh(self, clear_cache: bool = False) -> None:
|
|
1799
1819
|
self.refresh_button.setDisabled(True)
|
|
1800
1820
|
|
|
1801
1821
|
if self.worker is not None:
|
|
@@ -1819,16 +1839,16 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1819
1839
|
|
|
1820
1840
|
self._refresh_timer.start()
|
|
1821
1841
|
|
|
1822
|
-
def toggle_status(self, show=None):
|
|
1842
|
+
def toggle_status(self, show: bool | None = None) -> None:
|
|
1823
1843
|
show = not self.stdout_text.isVisible() if show is None else show
|
|
1824
1844
|
if show:
|
|
1825
|
-
self.show_status_btn.setText(self._trans(
|
|
1845
|
+
self.show_status_btn.setText(self._trans('Hide Status'))
|
|
1826
1846
|
self.stdout_text.show()
|
|
1827
1847
|
else:
|
|
1828
|
-
self.show_status_btn.setText(self._trans(
|
|
1848
|
+
self.show_status_btn.setText(self._trans('Show Status'))
|
|
1829
1849
|
self.stdout_text.hide()
|
|
1830
1850
|
|
|
1831
|
-
def set_prefix(self, prefix):
|
|
1851
|
+
def set_prefix(self, prefix) -> None:
|
|
1832
1852
|
self._prefix = prefix
|
|
1833
1853
|
self.installer._prefix = prefix
|
|
1834
1854
|
for idx in range(self.available_list.count()):
|
|
@@ -1848,7 +1868,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1848
1868
|
if item:
|
|
1849
1869
|
name = item.widget.name
|
|
1850
1870
|
version = item.widget._version # Make public attr?
|
|
1851
|
-
plugins.append(f
|
|
1871
|
+
plugins.append(f'{name}=={version}\n')
|
|
1852
1872
|
|
|
1853
1873
|
with open(fpath, 'w') as f:
|
|
1854
1874
|
f.writelines(plugins)
|
|
@@ -1860,7 +1880,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1860
1880
|
with open(fpath) as f:
|
|
1861
1881
|
plugins = f.read().split('\n')
|
|
1862
1882
|
|
|
1863
|
-
|
|
1883
|
+
log.info(plugins)
|
|
1864
1884
|
|
|
1865
1885
|
plugins = [p for p in plugins if p]
|
|
1866
1886
|
self._install_packages(plugins)
|