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.
@@ -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 = "UNKNOWN", # noqa: A002
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"{display_name} <small>({package_name})</small>"
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("Uninstall"))
268
- self.action_button.setObjectName("remove_button")
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("Install"))
275
- self.action_button.setObjectName("install_button")
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"Not supported {action_name}")
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("enable/disable"))
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("small_italic_text")
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, -2, 0, -2)
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("install_button")
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(70)
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("Cancel", self)
437
- self.cancel_btn.setObjectName("remove_button")
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(70)
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
- "Installation Info", collapsedIcon=coll_icon, expandedIcon=exp_icon
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("install_info_button")
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("info_widget")
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("warning_icon")
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("install_choice_widget")
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: Literal["PyPI", "Conda"]):
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.PIP
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"{pkg_name} {project_info.display_name} {project_info.metadata.summary}"
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: str | None = None,
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"{self._SORT_ORDER_PREFIX}{item.text()}")
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"=={item.widget.version_choice_dropdown.currentText()}"
854
+ f'=={item.widget.version_choice_dropdown.currentText()}'
848
855
  )
849
- widget.set_busy(self._trans("installing..."), action_name)
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("current_job_id", job_id)
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"=={item.latest_version}"
868
+ pkg_name += f'=={item.latest_version}'
864
869
 
865
- widget.set_busy(self._trans("updating..."), action_name)
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("current_job_id", job_id)
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("uninstalling..."), action_name)
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("current_job_id", job_id)
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("cancelling..."), action_name)
894
+ widget.set_busy(self._trans('cancelling...'), action_name)
894
895
  try:
895
- job_id = widget.property("current_job_id")
896
+ job_id = widget.property('current_job_id')
896
897
  self.installer.cancel(job_id)
897
898
  finally:
898
- widget.setProperty("current_job_id", None)
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("update (v{latest})", latest=latest)
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
- "Plugin not yet available for installation within the bundle application"
960
+ 'Plugin not yet available for installation within the bundle application'
960
961
  )
961
962
  )
962
- widget.setObjectName("unavailable")
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(self, process_finished_data: ProcessFinishedData):
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, distname, enabled, norm_name, plugin_api_version=1
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="1.0",
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=meta.get('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("Installed Plugins"))
1380
+ self.installed_label = QLabel(self._trans('Installed Plugins'))
1369
1381
  self.packages_search = QLineEdit()
1370
1382
  self.packages_search.setPlaceholderText(
1371
- self._trans("Type here to start searching for plugins...")
1383
+ self._trans('Type here to start searching for plugins...')
1372
1384
  )
1373
1385
  self.packages_search.setToolTip(
1374
1386
  self._trans(
1375
- "The search text will filter currently installed plugins "
1376
- "while also being used to search for plugins on the {package_name} hub",
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("import_button")
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("export_button")
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("refresh_button")
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("Available Plugins"))
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
- lay.addWidget(self.available_list)
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("plugin_manager_process_status")
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("loading ..."), self)
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("error_label")
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("success_label")
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("Install"))
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('pip'), self)
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("Show Status"), self)
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("cancel all actions"), self
1509
+ self._trans('cancel all actions'), self
1491
1510
  )
1492
- self.cancel_all_btn.setObjectName("remove_button")
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("Close"), self)
1515
+ self.close_btn = QPushButton(self._trans('Close'), self)
1497
1516
  self.close_btn.clicked.connect(self.accept)
1498
- self.close_btn.setObjectName("close_button")
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.addSpacing(20)
1525
+ buttonBox.addWidget(self.show_status_btn)
1509
1526
  buttonBox.addWidget(self.cancel_all_btn)
1510
- buttonBox.addSpacing(20)
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(1, 2)
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.PIP)
1546
+ else str(InstallerTools.PYPI)
1530
1547
  )
1531
1548
  self.direct_entry_edit.setPlaceholderText(
1532
1549
  self._trans(
1533
- "install with '{tool}' by name/url, or drop file...", tool=tool
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
- "Installed Plugins ({amount})",
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
- "Installed Plugins ({count}/{amount})",
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
- "{amount} plugins available on the napari hub",
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
- "Found {found} out of {amount} plugins on the napari hub. Displaying the first {max_count}...",
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
- "Found {found} out of {amount} plugins on the napari hub",
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.PIP
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(self, data: tuple[PackageMetadataProtocol, bool, dict]):
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"{i[0].name} {i[-1].get('display_name', '')} {i[0].summary}".lower()
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="Text files (*.txt)")
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="Text files (*.txt)")
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
- hasattr(self, '_installed_on_show')
1753
- and self._installed_on_show != self.already_installed
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.available_list.hideAll()
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("Hide Status"))
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("Show Status"))
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"{name}=={version}\n")
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
- print(plugins)
1883
+ log.info(plugins)
1864
1884
 
1865
1885
  plugins = [p for p in plugins if p]
1866
1886
  self._install_packages(plugins)