napari-plugin-manager 0.1.6__py3-none-any.whl → 0.1.8__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 -11
- napari_plugin_manager/_tests/test_qt_plugin_dialog.py +91 -93
- napari_plugin_manager/_tests/test_utils.py +7 -7
- napari_plugin_manager/_version.py +16 -3
- napari_plugin_manager/base_qt_package_installer.py +144 -68
- napari_plugin_manager/base_qt_plugin_dialog.py +176 -157
- napari_plugin_manager/npe2api.py +3 -3
- 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 +3 -3
- {napari_plugin_manager-0.1.6.dist-info → napari_plugin_manager-0.1.8.dist-info}/METADATA +8 -35
- napari_plugin_manager-0.1.8.dist-info/RECORD +23 -0
- {napari_plugin_manager-0.1.6.dist-info → napari_plugin_manager-0.1.8.dist-info}/WHEEL +1 -1
- napari_plugin_manager-0.1.6.dist-info/RECORD +0 -23
- {napari_plugin_manager-0.1.6.dist-info → napari_plugin_manager-0.1.8.dist-info}/licenses/LICENSE +0 -0
- {napari_plugin_manager-0.1.6.dist-info → napari_plugin_manager-0.1.8.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,
|
|
@@ -57,6 +63,8 @@ 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)
|
|
@@ -1233,7 +1244,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1233
1244
|
pypi_versions=[],
|
|
1234
1245
|
conda_versions=[],
|
|
1235
1246
|
metadata=self.PACKAGE_METADATA_CLASS(
|
|
1236
|
-
metadata_version=
|
|
1247
|
+
metadata_version='1.0',
|
|
1237
1248
|
name=norm_name,
|
|
1238
1249
|
version=meta.get('version', ''),
|
|
1239
1250
|
summary=meta.get('summary', ''),
|
|
@@ -1247,7 +1258,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1247
1258
|
plugin_api_version=plugin_api_version,
|
|
1248
1259
|
)
|
|
1249
1260
|
|
|
1250
|
-
def _add_to_available(self, pkg_name):
|
|
1261
|
+
def _add_to_available(self, pkg_name: str) -> None:
|
|
1251
1262
|
self._add_items_timer.stop()
|
|
1252
1263
|
if self._plugin_queue is not None:
|
|
1253
1264
|
self._plugin_queue.insert(0, self._plugin_data_map[pkg_name])
|
|
@@ -1349,10 +1360,10 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1349
1360
|
"""
|
|
1350
1361
|
raise NotImplementedError
|
|
1351
1362
|
|
|
1352
|
-
def _is_main_app_conda_package(self):
|
|
1363
|
+
def _is_main_app_conda_package(self) -> bool:
|
|
1353
1364
|
return is_conda_package(self.BASE_PACKAGE_NAME)
|
|
1354
1365
|
|
|
1355
|
-
def _setup_ui(self):
|
|
1366
|
+
def _setup_ui(self) -> None:
|
|
1356
1367
|
"""Defines the layout for the PluginDialog."""
|
|
1357
1368
|
self.resize(900, 600)
|
|
1358
1369
|
vlay_1 = QVBoxLayout(self)
|
|
@@ -1366,15 +1377,15 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1366
1377
|
installed = QWidget(self.v_splitter)
|
|
1367
1378
|
lay = QVBoxLayout(installed)
|
|
1368
1379
|
lay.setContentsMargins(0, 2, 0, 2)
|
|
1369
|
-
self.installed_label = QLabel(self._trans(
|
|
1380
|
+
self.installed_label = QLabel(self._trans('Installed Plugins'))
|
|
1370
1381
|
self.packages_search = QLineEdit()
|
|
1371
1382
|
self.packages_search.setPlaceholderText(
|
|
1372
|
-
self._trans(
|
|
1383
|
+
self._trans('Type here to start searching for plugins...')
|
|
1373
1384
|
)
|
|
1374
1385
|
self.packages_search.setToolTip(
|
|
1375
1386
|
self._trans(
|
|
1376
|
-
|
|
1377
|
-
|
|
1387
|
+
'The search text will filter currently installed plugins '
|
|
1388
|
+
'while also being used to search for plugins on the {package_name} hub',
|
|
1378
1389
|
package_name=self.BASE_PACKAGE_NAME,
|
|
1379
1390
|
)
|
|
1380
1391
|
)
|
|
@@ -1383,19 +1394,19 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1383
1394
|
self.packages_search.textChanged.connect(self.search)
|
|
1384
1395
|
|
|
1385
1396
|
self.import_button = QPushButton(self._trans('Import'), self)
|
|
1386
|
-
self.import_button.setObjectName(
|
|
1397
|
+
self.import_button.setObjectName('import_button')
|
|
1387
1398
|
self.import_button.setToolTip(self._trans('Import plugins from file'))
|
|
1388
1399
|
self.import_button.clicked.connect(self._import_plugins)
|
|
1389
1400
|
|
|
1390
1401
|
self.export_button = QPushButton(self._trans('Export'), self)
|
|
1391
|
-
self.export_button.setObjectName(
|
|
1402
|
+
self.export_button.setObjectName('export_button')
|
|
1392
1403
|
self.export_button.setToolTip(
|
|
1393
1404
|
self._trans('Export installed plugins list')
|
|
1394
1405
|
)
|
|
1395
1406
|
self.export_button.clicked.connect(self._export_plugins)
|
|
1396
1407
|
|
|
1397
1408
|
self.refresh_button = QPushButton(self._trans('Refresh'), self)
|
|
1398
|
-
self.refresh_button.setObjectName(
|
|
1409
|
+
self.refresh_button.setObjectName('refresh_button')
|
|
1399
1410
|
self.refresh_button.setToolTip(
|
|
1400
1411
|
self._trans(
|
|
1401
1412
|
'This will clear and refresh the available and installed plugins lists.'
|
|
@@ -1422,31 +1433,39 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1422
1433
|
uninstalled = QWidget(self.v_splitter)
|
|
1423
1434
|
lay = QVBoxLayout(uninstalled)
|
|
1424
1435
|
lay.setContentsMargins(0, 2, 0, 2)
|
|
1425
|
-
self.avail_label = QLabel(self._trans(
|
|
1436
|
+
self.avail_label = QLabel(self._trans('Available Plugins'))
|
|
1426
1437
|
mid_layout = QHBoxLayout()
|
|
1427
1438
|
mid_layout.addWidget(self.avail_label)
|
|
1428
1439
|
mid_layout.addStretch()
|
|
1429
1440
|
lay.addLayout(mid_layout)
|
|
1441
|
+
self.available_widget = QStackedWidget()
|
|
1430
1442
|
self.available_list = self.PLUGIN_LIST_CLASS(
|
|
1431
1443
|
uninstalled, self.installer, self.BASE_PACKAGE_NAME
|
|
1432
1444
|
)
|
|
1433
|
-
|
|
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)
|
|
1434
1453
|
|
|
1435
1454
|
self.stdout_text = QTextEdit(self.v_splitter)
|
|
1436
1455
|
self.stdout_text.setReadOnly(True)
|
|
1437
|
-
self.stdout_text.setObjectName(
|
|
1456
|
+
self.stdout_text.setObjectName('plugin_manager_process_status')
|
|
1438
1457
|
self.stdout_text.hide()
|
|
1439
1458
|
|
|
1440
1459
|
buttonBox = QHBoxLayout()
|
|
1441
|
-
self.working_indicator = QLabel(self._trans(
|
|
1460
|
+
self.working_indicator = QLabel(self._trans('loading ...'), self)
|
|
1442
1461
|
sp = self.working_indicator.sizePolicy()
|
|
1443
1462
|
sp.setRetainSizeWhenHidden(True)
|
|
1444
1463
|
self.working_indicator.setSizePolicy(sp)
|
|
1445
1464
|
self.process_error_indicator = QLabel(self)
|
|
1446
|
-
self.process_error_indicator.setObjectName(
|
|
1465
|
+
self.process_error_indicator.setObjectName('error_label')
|
|
1447
1466
|
self.process_error_indicator.hide()
|
|
1448
1467
|
self.process_success_indicator = QLabel(self)
|
|
1449
|
-
self.process_success_indicator.setObjectName(
|
|
1468
|
+
self.process_success_indicator.setObjectName('success_label')
|
|
1450
1469
|
self.process_success_indicator.hide()
|
|
1451
1470
|
mov = self._loading_gif()
|
|
1452
1471
|
self.working_indicator.setMovie(mov)
|
|
@@ -1460,13 +1479,13 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1460
1479
|
self.direct_entry_btn = QToolButton(self)
|
|
1461
1480
|
self.direct_entry_btn.setVisible(visibility_direct_entry)
|
|
1462
1481
|
self.direct_entry_btn.clicked.connect(self._install_packages)
|
|
1463
|
-
self.direct_entry_btn.setText(self._trans(
|
|
1482
|
+
self.direct_entry_btn.setText(self._trans('Install'))
|
|
1464
1483
|
|
|
1465
1484
|
self._action_conda = QAction(self._trans('Conda'), self)
|
|
1466
1485
|
self._action_conda.setCheckable(True)
|
|
1467
1486
|
self._action_conda.triggered.connect(self._update_direct_entry_text)
|
|
1468
1487
|
|
|
1469
|
-
self._action_pypi = QAction(self._trans('
|
|
1488
|
+
self._action_pypi = QAction(self._trans('PyPI'), self)
|
|
1470
1489
|
self._action_pypi.setCheckable(True)
|
|
1471
1490
|
self._action_pypi.triggered.connect(self._update_direct_entry_text)
|
|
1472
1491
|
|
|
@@ -1484,31 +1503,28 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1484
1503
|
self._action_conda.setChecked(True)
|
|
1485
1504
|
self.direct_entry_btn.setMenu(self._menu)
|
|
1486
1505
|
|
|
1487
|
-
self.show_status_btn = QPushButton(self._trans(
|
|
1488
|
-
self.show_status_btn.setFixedWidth(100)
|
|
1506
|
+
self.show_status_btn = QPushButton(self._trans('Show Status'), self)
|
|
1489
1507
|
|
|
1490
1508
|
self.cancel_all_btn = QPushButton(
|
|
1491
|
-
self._trans(
|
|
1509
|
+
self._trans('cancel all actions'), self
|
|
1492
1510
|
)
|
|
1493
|
-
self.cancel_all_btn.setObjectName(
|
|
1511
|
+
self.cancel_all_btn.setObjectName('remove_button')
|
|
1494
1512
|
self.cancel_all_btn.setVisible(False)
|
|
1495
1513
|
self.cancel_all_btn.clicked.connect(self.installer.cancel_all)
|
|
1496
1514
|
|
|
1497
|
-
self.close_btn = QPushButton(self._trans(
|
|
1515
|
+
self.close_btn = QPushButton(self._trans('Close'), self)
|
|
1498
1516
|
self.close_btn.clicked.connect(self.accept)
|
|
1499
|
-
self.close_btn.setObjectName(
|
|
1517
|
+
self.close_btn.setObjectName('close_button')
|
|
1500
1518
|
|
|
1501
|
-
buttonBox.addWidget(self.show_status_btn)
|
|
1502
|
-
buttonBox.addWidget(self.working_indicator)
|
|
1503
1519
|
buttonBox.addWidget(self.direct_entry_edit)
|
|
1504
1520
|
buttonBox.addWidget(self.direct_entry_btn)
|
|
1505
1521
|
if not visibility_direct_entry:
|
|
1506
1522
|
buttonBox.addStretch()
|
|
1507
1523
|
buttonBox.addWidget(self.process_success_indicator)
|
|
1508
1524
|
buttonBox.addWidget(self.process_error_indicator)
|
|
1509
|
-
buttonBox.
|
|
1525
|
+
buttonBox.addWidget(self.show_status_btn)
|
|
1510
1526
|
buttonBox.addWidget(self.cancel_all_btn)
|
|
1511
|
-
buttonBox.
|
|
1527
|
+
buttonBox.addWidget(self.working_indicator)
|
|
1512
1528
|
buttonBox.addWidget(self.close_btn)
|
|
1513
1529
|
buttonBox.setContentsMargins(0, 0, 4, 0)
|
|
1514
1530
|
vlay_1.addLayout(buttonBox)
|
|
@@ -1517,25 +1533,25 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1517
1533
|
self.show_status_btn.setChecked(False)
|
|
1518
1534
|
self.show_status_btn.toggled.connect(self.toggle_status)
|
|
1519
1535
|
|
|
1520
|
-
self.v_splitter.setStretchFactor(
|
|
1536
|
+
self.v_splitter.setStretchFactor(0, 2)
|
|
1521
1537
|
self.h_splitter.setStretchFactor(0, 2)
|
|
1522
1538
|
|
|
1523
1539
|
self.packages_search.setFocus()
|
|
1524
1540
|
self._update_direct_entry_text()
|
|
1525
1541
|
|
|
1526
|
-
def _update_direct_entry_text(self):
|
|
1542
|
+
def _update_direct_entry_text(self) -> None:
|
|
1527
1543
|
tool = (
|
|
1528
1544
|
str(InstallerTools.CONDA)
|
|
1529
1545
|
if self._action_conda.isChecked()
|
|
1530
|
-
else str(InstallerTools.
|
|
1546
|
+
else str(InstallerTools.PYPI)
|
|
1531
1547
|
)
|
|
1532
1548
|
self.direct_entry_edit.setPlaceholderText(
|
|
1533
1549
|
self._trans(
|
|
1534
|
-
"install
|
|
1550
|
+
"install from '{tool}' by name/url, or drop file...", tool=tool
|
|
1535
1551
|
)
|
|
1536
1552
|
)
|
|
1537
1553
|
|
|
1538
|
-
def _update_plugin_count(self):
|
|
1554
|
+
def _update_plugin_count(self) -> None:
|
|
1539
1555
|
"""Update count labels for both installed and available plugin lists.
|
|
1540
1556
|
Displays also amount of visible plugins out of total when filtering.
|
|
1541
1557
|
"""
|
|
@@ -1544,14 +1560,14 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1544
1560
|
if installed_count == installed_count_visible:
|
|
1545
1561
|
self.installed_label.setText(
|
|
1546
1562
|
self._trans(
|
|
1547
|
-
|
|
1563
|
+
'Installed Plugins ({amount})',
|
|
1548
1564
|
amount=installed_count,
|
|
1549
1565
|
)
|
|
1550
1566
|
)
|
|
1551
1567
|
else:
|
|
1552
1568
|
self.installed_label.setText(
|
|
1553
1569
|
self._trans(
|
|
1554
|
-
|
|
1570
|
+
'Installed Plugins ({count}/{amount})',
|
|
1555
1571
|
count=installed_count_visible,
|
|
1556
1572
|
amount=installed_count,
|
|
1557
1573
|
)
|
|
@@ -1563,14 +1579,14 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1563
1579
|
if self._plugins_found == 0:
|
|
1564
1580
|
self.avail_label.setText(
|
|
1565
1581
|
self._trans(
|
|
1566
|
-
|
|
1582
|
+
'{amount} plugins available on the napari hub',
|
|
1567
1583
|
amount=available_count,
|
|
1568
1584
|
)
|
|
1569
1585
|
)
|
|
1570
1586
|
elif self._plugins_found > self.MAX_PLUGIN_SEARCH_ITEMS:
|
|
1571
1587
|
self.avail_label.setText(
|
|
1572
1588
|
self._trans(
|
|
1573
|
-
|
|
1589
|
+
'Found {found} out of {amount} plugins on the napari hub. Displaying the first {max_count}...',
|
|
1574
1590
|
found=self._plugins_found,
|
|
1575
1591
|
amount=available_count,
|
|
1576
1592
|
max_count=self.MAX_PLUGIN_SEARCH_ITEMS,
|
|
@@ -1579,7 +1595,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1579
1595
|
else:
|
|
1580
1596
|
self.avail_label.setText(
|
|
1581
1597
|
self._trans(
|
|
1582
|
-
|
|
1598
|
+
'Found {found} out of {amount} plugins on the napari hub',
|
|
1583
1599
|
found=self._plugins_found,
|
|
1584
1600
|
amount=available_count,
|
|
1585
1601
|
)
|
|
@@ -1588,7 +1604,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1588
1604
|
def _install_packages(
|
|
1589
1605
|
self,
|
|
1590
1606
|
packages: Sequence[str] = (),
|
|
1591
|
-
):
|
|
1607
|
+
) -> None:
|
|
1592
1608
|
if not packages:
|
|
1593
1609
|
_packages = self.direct_entry_edit.text()
|
|
1594
1610
|
packages = (
|
|
@@ -1600,11 +1616,11 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1600
1616
|
tool = (
|
|
1601
1617
|
InstallerTools.CONDA
|
|
1602
1618
|
if self._action_conda.isChecked()
|
|
1603
|
-
else InstallerTools.
|
|
1619
|
+
else InstallerTools.PYPI
|
|
1604
1620
|
)
|
|
1605
1621
|
self.installer.install(tool, packages)
|
|
1606
1622
|
|
|
1607
|
-
def _tag_outdated_plugins(self):
|
|
1623
|
+
def _tag_outdated_plugins(self) -> None:
|
|
1608
1624
|
"""Tag installed plugins that might be outdated."""
|
|
1609
1625
|
for pkg_name in self.installed_list.packages():
|
|
1610
1626
|
_data = self._plugin_data_map.get(pkg_name)
|
|
@@ -1614,7 +1630,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1614
1630
|
metadata, is_available_in_conda
|
|
1615
1631
|
)
|
|
1616
1632
|
|
|
1617
|
-
def _add_items(self):
|
|
1633
|
+
def _add_items(self) -> None:
|
|
1618
1634
|
"""
|
|
1619
1635
|
Add items to the lists by `batch_size` using a timer to add a pause
|
|
1620
1636
|
and prevent freezing the UI.
|
|
@@ -1668,7 +1684,9 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1668
1684
|
|
|
1669
1685
|
self._update_plugin_count()
|
|
1670
1686
|
|
|
1671
|
-
def _handle_yield(
|
|
1687
|
+
def _handle_yield(
|
|
1688
|
+
self, data: tuple[PackageMetadataProtocol, bool, dict]
|
|
1689
|
+
) -> None:
|
|
1672
1690
|
"""Output from a worker process.
|
|
1673
1691
|
|
|
1674
1692
|
Includes information about the plugin, including available versions on conda and pypi.
|
|
@@ -1678,7 +1696,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1678
1696
|
"""
|
|
1679
1697
|
self._plugin_data.append(data)
|
|
1680
1698
|
self._filter_texts = [
|
|
1681
|
-
f
|
|
1699
|
+
f'{i[0].name} {i[-1].get("display_name", "")} {i[0].summary}'.lower()
|
|
1682
1700
|
for i in self._plugin_data
|
|
1683
1701
|
]
|
|
1684
1702
|
metadata, _, _ = data
|
|
@@ -1686,7 +1704,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1686
1704
|
self.available_list.set_data(self._plugin_data)
|
|
1687
1705
|
self._update_plugin_count()
|
|
1688
1706
|
|
|
1689
|
-
def _search_in_available(self, text):
|
|
1707
|
+
def _search_in_available(self, text: str) -> list[int]:
|
|
1690
1708
|
idxs = []
|
|
1691
1709
|
for idx, item in enumerate(self._filter_texts):
|
|
1692
1710
|
if text.lower().strip() in item:
|
|
@@ -1695,16 +1713,16 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1695
1713
|
|
|
1696
1714
|
return idxs
|
|
1697
1715
|
|
|
1698
|
-
def _refresh_and_clear_cache(self):
|
|
1716
|
+
def _refresh_and_clear_cache(self) -> None:
|
|
1699
1717
|
self.refresh(clear_cache=True)
|
|
1700
1718
|
|
|
1701
|
-
def _import_plugins(self):
|
|
1702
|
-
fpath, _ = getopenfilename(filters=
|
|
1719
|
+
def _import_plugins(self) -> None:
|
|
1720
|
+
fpath, _ = getopenfilename(filters='Text files (*.txt)')
|
|
1703
1721
|
if fpath:
|
|
1704
1722
|
self.import_plugins(fpath)
|
|
1705
1723
|
|
|
1706
|
-
def _export_plugins(self):
|
|
1707
|
-
fpath, _ = getsavefilename(filters=
|
|
1724
|
+
def _export_plugins(self) -> None:
|
|
1725
|
+
fpath, _ = getsavefilename(filters='Text files (*.txt)')
|
|
1708
1726
|
if fpath:
|
|
1709
1727
|
self.export_plugins(fpath)
|
|
1710
1728
|
|
|
@@ -1712,7 +1730,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1712
1730
|
|
|
1713
1731
|
# region - Qt overrides
|
|
1714
1732
|
# ------------------------------------------------------------------------
|
|
1715
|
-
def closeEvent(self, event):
|
|
1733
|
+
def closeEvent(self, event: QCloseEvent) -> None:
|
|
1716
1734
|
if self._parent is not None:
|
|
1717
1735
|
plugin_dialog = getattr(self._parent, '_plugin_dialog', self)
|
|
1718
1736
|
if self != plugin_dialog:
|
|
@@ -1723,10 +1741,10 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1723
1741
|
else:
|
|
1724
1742
|
super().closeEvent(event)
|
|
1725
1743
|
|
|
1726
|
-
def dragEnterEvent(self, event):
|
|
1744
|
+
def dragEnterEvent(self, event: QEnterEvent) -> None:
|
|
1727
1745
|
event.accept()
|
|
1728
1746
|
|
|
1729
|
-
def dropEvent(self, event):
|
|
1747
|
+
def dropEvent(self, event: QDropEvent) -> None:
|
|
1730
1748
|
md = event.mimeData()
|
|
1731
1749
|
if md.hasUrls():
|
|
1732
1750
|
files = [url.toLocalFile() for url in md.urls()]
|
|
@@ -1735,24 +1753,24 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1735
1753
|
|
|
1736
1754
|
return super().dropEvent(event)
|
|
1737
1755
|
|
|
1738
|
-
def exec_(self):
|
|
1756
|
+
def exec_(self) -> None:
|
|
1739
1757
|
plugin_dialog = getattr(self._parent, '_plugin_dialog', self)
|
|
1740
1758
|
if plugin_dialog != self:
|
|
1741
1759
|
self.close()
|
|
1742
1760
|
|
|
1743
1761
|
plugin_dialog.setModal(True)
|
|
1744
1762
|
plugin_dialog.show()
|
|
1745
|
-
plugin_dialog._installed_on_show = set(plugin_dialog.already_installed)
|
|
1746
1763
|
|
|
1747
1764
|
if self._first_open:
|
|
1748
1765
|
self._update_theme(None)
|
|
1749
1766
|
self._first_open = False
|
|
1750
1767
|
|
|
1751
|
-
def hideEvent(self, event):
|
|
1752
|
-
if (
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
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()
|
|
1756
1774
|
RestartWarningDialog(self).exec_()
|
|
1757
1775
|
self.packages_search.clear()
|
|
1758
1776
|
self.toggle_status(False)
|
|
@@ -1771,11 +1789,12 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1771
1789
|
|
|
1772
1790
|
if len(text.strip()) == 0:
|
|
1773
1791
|
self.installed_list.filter('')
|
|
1774
|
-
self.
|
|
1792
|
+
self.available_widget.setCurrentWidget(self.available_message)
|
|
1775
1793
|
self._plugin_queue = None
|
|
1776
1794
|
self._add_items_timer.stop()
|
|
1777
1795
|
self._plugins_found = 0
|
|
1778
1796
|
else:
|
|
1797
|
+
self.available_widget.setCurrentWidget(self.available_list)
|
|
1779
1798
|
items = [
|
|
1780
1799
|
self._plugin_data[idx]
|
|
1781
1800
|
for idx in self._search_in_available(text)
|
|
@@ -1796,7 +1815,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1796
1815
|
|
|
1797
1816
|
self._update_plugin_count()
|
|
1798
1817
|
|
|
1799
|
-
def refresh(self, clear_cache: bool = False):
|
|
1818
|
+
def refresh(self, clear_cache: bool = False) -> None:
|
|
1800
1819
|
self.refresh_button.setDisabled(True)
|
|
1801
1820
|
|
|
1802
1821
|
if self.worker is not None:
|
|
@@ -1820,16 +1839,16 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1820
1839
|
|
|
1821
1840
|
self._refresh_timer.start()
|
|
1822
1841
|
|
|
1823
|
-
def toggle_status(self, show=None):
|
|
1842
|
+
def toggle_status(self, show: bool | None = None) -> None:
|
|
1824
1843
|
show = not self.stdout_text.isVisible() if show is None else show
|
|
1825
1844
|
if show:
|
|
1826
|
-
self.show_status_btn.setText(self._trans(
|
|
1845
|
+
self.show_status_btn.setText(self._trans('Hide Status'))
|
|
1827
1846
|
self.stdout_text.show()
|
|
1828
1847
|
else:
|
|
1829
|
-
self.show_status_btn.setText(self._trans(
|
|
1848
|
+
self.show_status_btn.setText(self._trans('Show Status'))
|
|
1830
1849
|
self.stdout_text.hide()
|
|
1831
1850
|
|
|
1832
|
-
def set_prefix(self, prefix):
|
|
1851
|
+
def set_prefix(self, prefix) -> None:
|
|
1833
1852
|
self._prefix = prefix
|
|
1834
1853
|
self.installer._prefix = prefix
|
|
1835
1854
|
for idx in range(self.available_list.count()):
|
|
@@ -1849,7 +1868,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1849
1868
|
if item:
|
|
1850
1869
|
name = item.widget.name
|
|
1851
1870
|
version = item.widget._version # Make public attr?
|
|
1852
|
-
plugins.append(f
|
|
1871
|
+
plugins.append(f'{name}=={version}\n')
|
|
1853
1872
|
|
|
1854
1873
|
with open(fpath, 'w') as f:
|
|
1855
1874
|
f.writelines(plugins)
|
|
@@ -1861,7 +1880,7 @@ class BaseQtPluginDialog(QDialog):
|
|
|
1861
1880
|
with open(fpath) as f:
|
|
1862
1881
|
plugins = f.read().split('\n')
|
|
1863
1882
|
|
|
1864
|
-
|
|
1883
|
+
log.info(plugins)
|
|
1865
1884
|
|
|
1866
1885
|
plugins = [p for p in plugins if p]
|
|
1867
1886
|
self._install_packages(plugins)
|