src/eric7/PipInterface/PipPackagesWidget.py

branch
eric7
changeset 11093
e8932a99beb4
parent 11091
3a1ba42ac50a
child 11113
2e03383143e3
equal deleted inserted replaced
11092:e2aaf07ef8fd 11093:e8932a99beb4
7 Module implementing the pip packages management widget. 7 Module implementing the pip packages management widget.
8 """ 8 """
9 9
10 import contextlib 10 import contextlib
11 import enum 11 import enum
12 import html.parser
13 import os 12 import os
14 import textwrap
15 13
16 from packaging.specifiers import InvalidSpecifier, SpecifierSet 14 from packaging.specifiers import InvalidSpecifier, SpecifierSet
17 from PyQt6.QtCore import Qt, QUrl, QUrlQuery, pyqtSlot 15 from PyQt6.QtCore import Qt, QUrl, QUrlQuery, pyqtSlot
18 from PyQt6.QtGui import QIcon 16 from PyQt6.QtGui import QDesktopServices, QIcon
19 from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest
20 from PyQt6.QtWidgets import ( 17 from PyQt6.QtWidgets import (
21 QAbstractItemView,
22 QDialog, 18 QDialog,
23 QHeaderView, 19 QHeaderView,
24 QMenu, 20 QMenu,
25 QToolButton, 21 QToolButton,
26 QTreeWidgetItem, 22 QTreeWidgetItem,
35 31
36 from .PipVulnerabilityChecker import Package, VulnerabilityCheckError 32 from .PipVulnerabilityChecker import Package, VulnerabilityCheckError
37 from .Ui_PipPackagesWidget import Ui_PipPackagesWidget 33 from .Ui_PipPackagesWidget import Ui_PipPackagesWidget
38 34
39 35
40 class PypiSearchResultsParser(html.parser.HTMLParser):
41 """
42 Class implementing the parser for the PyPI search result page.
43 """
44
45 ClassPrefix = "package-snippet__"
46
47 def __init__(self, data):
48 """
49 Constructor
50
51 @param data data to be parsed
52 @type str
53 """
54 super().__init__()
55 self.__results = []
56 self.__activeClass = None
57 self.feed(data)
58
59 def __getClass(self, attrs):
60 """
61 Private method to extract the class attribute out of the list of
62 attributes.
63
64 @param attrs list of tag attributes as (name, value) tuples
65 @type list of tuple of (str, str)
66 @return value of the 'class' attribute or None
67 @rtype str
68 """
69 for name, value in attrs:
70 if name == "class":
71 return value
72
73 return None
74
75 def __getDate(self, attrs):
76 """
77 Private method to extract the datetime attribute out of the list of
78 attributes and process it.
79
80 @param attrs list of tag attributes as (name, value) tuples
81 @type list of tuple of (str, str)
82 @return value of the 'class' attribute or None
83 @rtype str
84 """
85 for name, value in attrs:
86 if name == "datetime":
87 return value.split("T")[0]
88
89 return None
90
91 def handle_starttag(self, tag, attrs):
92 """
93 Public method to process the start tag.
94
95 @param tag tag name (all lowercase)
96 @type str
97 @param attrs list of tag attributes as (name, value) tuples
98 @type list of tuple of (str, str)
99 """
100 if tag == "a" and self.__getClass(attrs) == "package-snippet":
101 self.__results.append({})
102
103 if tag in ("span", "p"):
104 tagClass = self.__getClass(attrs)
105 if tagClass in (
106 "package-snippet__name",
107 "package-snippet__description",
108 "package-snippet__version",
109 "package-snippet__released",
110 "package-snippet__created",
111 ):
112 self.__activeClass = tagClass
113 else:
114 self.__activeClass = None
115 elif tag == "time":
116 attributeName = self.__activeClass.replace(self.ClassPrefix, "")
117 self.__results[-1][attributeName] = self.__getDate(attrs)
118 self.__activeClass = None
119 else:
120 self.__activeClass = None
121
122 def handle_data(self, data):
123 """
124 Public method process arbitrary data.
125
126 @param data data to be processed
127 @type str
128 """
129 if self.__activeClass is not None:
130 attributeName = self.__activeClass.replace(self.ClassPrefix, "")
131 self.__results[-1][attributeName] = data
132
133 def handle_endtag(self, _tag):
134 """
135 Public method to process the end tag.
136
137 @param _tag tag name (all lowercase) (unused)
138 @type str
139 """
140 self.__activeClass = None
141
142 def getResults(self):
143 """
144 Public method to get the extracted search results.
145
146 @return extracted result data
147 @rtype list of dict
148 """
149 return self.__results
150
151
152 class PipPackageInformationMode(enum.Enum): 36 class PipPackageInformationMode(enum.Enum):
153 """ 37 """
154 Class defining the show information process modes. 38 Class defining the show information process modes.
155 """ 39 """
156 40
201 self.pipMenuButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly) 85 self.pipMenuButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
202 self.pipMenuButton.setFocusPolicy(Qt.FocusPolicy.NoFocus) 86 self.pipMenuButton.setFocusPolicy(Qt.FocusPolicy.NoFocus)
203 self.pipMenuButton.setShowMenuInside(True) 87 self.pipMenuButton.setShowMenuInside(True)
204 88
205 self.refreshButton.setIcon(EricPixmapCache.getIcon("reload")) 89 self.refreshButton.setIcon(EricPixmapCache.getIcon("reload"))
90 self.installButton.setIcon(EricPixmapCache.getIcon("plus"))
206 self.upgradeButton.setIcon(EricPixmapCache.getIcon("1uparrow")) 91 self.upgradeButton.setIcon(EricPixmapCache.getIcon("1uparrow"))
207 self.upgradeAllButton.setIcon(EricPixmapCache.getIcon("2uparrow")) 92 self.upgradeAllButton.setIcon(EricPixmapCache.getIcon("2uparrow"))
208 self.uninstallButton.setIcon(EricPixmapCache.getIcon("minus")) 93 self.uninstallButton.setIcon(EricPixmapCache.getIcon("minus"))
209 self.showPackageDetailsButton.setIcon(EricPixmapCache.getIcon("info")) 94 self.showPackageDetailsButton.setIcon(EricPixmapCache.getIcon("info"))
210 self.searchToggleButton_1.setIcon(EricPixmapCache.getIcon("find")) 95 self.searchButton.setIcon(EricPixmapCache.getIcon("find"))
211 self.searchToggleButton_2.setIcon(EricPixmapCache.getIcon("find"))
212 self.searchButton.setIcon(EricPixmapCache.getIcon("findNext"))
213 self.searchMoreButton.setIcon(EricPixmapCache.getIcon("plus"))
214 self.installButton.setIcon(EricPixmapCache.getIcon("plus"))
215 self.installUserSiteButton.setIcon(EricPixmapCache.getIcon("addUser"))
216 self.showDetailsButton.setIcon(EricPixmapCache.getIcon("info"))
217 self.cleanupButton.setIcon(EricPixmapCache.getIcon("clear")) 96 self.cleanupButton.setIcon(EricPixmapCache.getIcon("clear"))
218 97
219 self.refreshDependenciesButton.setIcon(EricPixmapCache.getIcon("reload")) 98 self.refreshDependenciesButton.setIcon(EricPixmapCache.getIcon("reload"))
220 self.showDepPackageDetailsButton.setIcon(EricPixmapCache.getIcon("info")) 99 self.showDepPackageDetailsButton.setIcon(EricPixmapCache.getIcon("info"))
221 self.dependencyRepairButton.setIcon(EricPixmapCache.getIcon("repair")) 100 self.dependencyRepairButton.setIcon(EricPixmapCache.getIcon("repair"))
261 project.projectOpened.connect(self.__projectOpened) 140 project.projectOpened.connect(self.__projectOpened)
262 project.projectClosed.connect(self.__projectClosed) 141 project.projectClosed.connect(self.__projectClosed)
263 142
264 self.__packageDetailsDialog = None 143 self.__packageDetailsDialog = None
265 144
145 self.installButton.clicked.connect(self.__installPackages)
146
266 self.__initPipMenu() 147 self.__initPipMenu()
267 self.__populateEnvironments() 148 self.__populateEnvironments()
268 self.__updateActionButtons() 149 self.__updateActionButtons()
269 self.__updateDepActionButtons() 150 self.__updateDepActionButtons()
270 151
271 self.statusLabel.hide() 152 self.statusLabel.hide()
272 self.searchWidget.hide()
273 self.__lastSearchPage = 0 153 self.__lastSearchPage = 0
274 154
275 self.__queryName = [] 155 self.__queryName = []
276 self.__querySummary = [] 156 self.__querySummary = []
277 157
424 def __updateActionButtons(self): 304 def __updateActionButtons(self):
425 """ 305 """
426 Private method to set the state of the action buttons. 306 Private method to set the state of the action buttons.
427 """ 307 """
428 if self.__isPipAvailable(): 308 if self.__isPipAvailable():
309 self.installButton.setEnabled(True)
429 self.upgradeButton.setEnabled(bool(self.__selectedUpdateableItems())) 310 self.upgradeButton.setEnabled(bool(self.__selectedUpdateableItems()))
430 self.uninstallButton.setEnabled(bool(self.packagesList.selectedItems())) 311 self.uninstallButton.setEnabled(bool(self.packagesList.selectedItems()))
431 self.upgradeAllButton.setEnabled(bool(self.__allUpdateableItems())) 312 self.upgradeAllButton.setEnabled(bool(self.__allUpdateableItems()))
432 self.showPackageDetailsButton.setEnabled( 313 self.showPackageDetailsButton.setEnabled(
433 len(self.packagesList.selectedItems()) == 1 314 len(self.packagesList.selectedItems()) == 1
434 ) 315 )
435 self.cleanupButton.setEnabled(True) 316 self.cleanupButton.setEnabled(True)
436 else: 317 else:
318 self.installButton.setEnabled(False)
437 self.upgradeButton.setEnabled(False) 319 self.upgradeButton.setEnabled(False)
438 self.uninstallButton.setEnabled(False) 320 self.uninstallButton.setEnabled(False)
439 self.upgradeAllButton.setEnabled(False) 321 self.upgradeAllButton.setEnabled(False)
440 self.showPackageDetailsButton.setEnabled(False) 322 self.showPackageDetailsButton.setEnabled(False)
441 self.cleanupButton.setEnabled(False) 323 self.cleanupButton.setEnabled(False)
489 callback=self.__updateOutdatedInfo, 371 callback=self.__updateOutdatedInfo,
490 ) 372 )
491 373
492 else: 374 else:
493 self.__updateActionButtons() 375 self.__updateActionButtons()
494 self.__updateSearchActionButtons()
495 self.__updateSearchButton()
496 self.__updateSearchMoreButton(False)
497 376
498 def __updateOutdatedInfo(self, outdatedPackages): 377 def __updateOutdatedInfo(self, outdatedPackages):
499 """ 378 """
500 Private method to process the list of outdated packages. 379 Private method to process the list of outdated packages.
501 380
513 self.packagesList.resizeColumnToContents( 392 self.packagesList.resizeColumnToContents(
514 PipPackagesWidget.AvailableVersionColumn 393 PipPackagesWidget.AvailableVersionColumn
515 ) 394 )
516 395
517 self.__updateActionButtons() 396 self.__updateActionButtons()
518 self.__updateSearchActionButtons()
519 self.__updateSearchButton()
520 self.__updateSearchMoreButton(False)
521 397
522 self.statusLabel.hide() 398 self.statusLabel.hide()
523 399
524 @pyqtSlot(str) 400 @pyqtSlot(str)
525 def on_environmentsComboBox_currentTextChanged(self, name): 401 def on_environmentsComboBox_currentTextChanged(self, name):
532 if name != self.__selectedEnvironment: 408 if name != self.__selectedEnvironment:
533 if name: 409 if name:
534 self.environmentPathLabel.setPath( 410 self.environmentPathLabel.setPath(
535 self.__pip.getVirtualenvInterpreter(name) 411 self.__pip.getVirtualenvInterpreter(name)
536 ) 412 )
537 self.searchNameEdit.setEnabled(True)
538 else: 413 else:
539 self.environmentPathLabel.setPath("") 414 self.environmentPathLabel.setPath("")
540 self.searchNameEdit.clear()
541 self.searchNameEdit.setEnabled(False)
542 self.searchResultList.clear()
543 if self.__packageDetailsDialog is not None: 415 if self.__packageDetailsDialog is not None:
544 self.__packageDetailsDialog.close() 416 self.__packageDetailsDialog.close()
545 417
546 if self.viewToggleButton.isChecked(): 418 if self.viewToggleButton.isChecked():
547 self.__refreshDependencyTree() 419 self.__refreshDependencyTree()
548 else: 420 else:
549 self.__refreshPackagesList() 421 self.__refreshPackagesList()
550 self.__selectedEnvironment = name 422 self.__selectedEnvironment = name
551 423
552 ##self.cleanupButton.setEnabled(bool(name))
553 self.__updateActionButtons() 424 self.__updateActionButtons()
554 425
555 @pyqtSlot() 426 @pyqtSlot()
556 def on_localCheckBox_clicked(self): 427 def on_localCheckBox_clicked(self):
557 """ 428 """
862 EricMessageBox.warning( 733 EricMessageBox.warning(
863 self, 734 self,
864 self.tr("Cleanup Environment"), 735 self.tr("Cleanup Environment"),
865 self.tr( 736 self.tr(
866 "Some leftover package directories could not been removed." 737 "Some leftover package directories could not been removed."
867 " Delete them manually."), 738 " Delete them manually."
868 )
869
870 #######################################################################
871 ## Search widget related methods below
872 #######################################################################
873
874 def __updateSearchActionButtons(self):
875 """
876 Private method to update the action button states of the search widget.
877 """
878 installEnable = (
879 len(self.searchResultList.selectedItems()) > 0
880 and self.environmentsComboBox.currentIndex() > 0
881 and self.__isPipAvailable()
882 )
883 self.installButton.setEnabled(installEnable)
884 self.installUserSiteButton.setEnabled(installEnable)
885
886 self.showDetailsButton.setEnabled(
887 len(self.searchResultList.selectedItems()) == 1 and self.__isPipAvailable()
888 )
889
890 def __updateSearchButton(self):
891 """
892 Private method to update the state of the search button.
893 """
894 self.searchButton.setEnabled(
895 bool(self.searchNameEdit.text()) and self.__isPipAvailable()
896 )
897
898 def __updateSearchMoreButton(self, enable):
899 """
900 Private method to update the state of the search more button.
901
902 @param enable flag indicating the desired enable state
903 @type bool
904 """
905 self.searchMoreButton.setEnabled(
906 enable and bool(self.searchNameEdit.text()) and self.__isPipAvailable()
907 )
908
909 @pyqtSlot(bool)
910 def on_searchToggleButton_1_toggled(self, checked):
911 """
912 Private slot to toggle the search widget.
913
914 @param checked state of the search widget button
915 @type bool
916 """
917 self.searchWidget.setVisible(checked)
918 self.searchToggleButton_2.setChecked(checked)
919
920 if checked:
921 self.searchNameEdit.setFocus(Qt.FocusReason.OtherFocusReason)
922 self.searchNameEdit.selectAll()
923
924 self.__updateSearchActionButtons()
925 self.__updateSearchButton()
926 self.__updateSearchMoreButton(False)
927
928 @pyqtSlot(bool)
929 def on_searchToggleButton_2_toggled(self, checked):
930 """
931 Private slot to toggle the search widget.
932
933 @param checked state of the search widget button
934 @type bool
935 """
936 self.searchToggleButton_1.setChecked(checked)
937
938 @pyqtSlot(str)
939 def on_searchNameEdit_textChanged(self, _txt):
940 """
941 Private slot handling a change of the search term.
942
943 @param _txt search term (unused)
944 @type str
945 """
946 self.__updateSearchButton()
947
948 @pyqtSlot()
949 def on_searchNameEdit_returnPressed(self):
950 """
951 Private slot initiating a search via a press of the Return key.
952 """
953 if bool(self.searchNameEdit.text()) and self.__isPipAvailable():
954 self.__searchFirst()
955
956 @pyqtSlot()
957 def on_searchButton_clicked(self):
958 """
959 Private slot handling a press of the search button.
960 """
961 self.__searchFirst()
962
963 @pyqtSlot()
964 def on_searchMoreButton_clicked(self):
965 """
966 Private slot handling a press of the search more button.
967 """
968 self.__search(self.__lastSearchPage + 1)
969
970 @pyqtSlot()
971 def on_searchResultList_itemSelectionChanged(self):
972 """
973 Private slot handling changes of the search result selection.
974 """
975 self.__updateSearchActionButtons()
976
977 def __searchFirst(self):
978 """
979 Private method to perform the search for packages.
980 """
981 self.searchResultList.clear()
982 self.searchInfoLabel.clear()
983
984 self.__updateSearchMoreButton(False)
985
986 self.__search()
987
988 def __search(self, page=1):
989 """
990 Private method to perform the search by calling the PyPI search URL.
991
992 @param page search page to retrieve (defaults to 1)
993 @type int (optional)
994 """
995 self.__lastSearchPage = page
996
997 self.searchButton.setEnabled(False)
998
999 searchTerm = self.searchNameEdit.text().strip()
1000 searchTerm = bytes(QUrl.toPercentEncoding(searchTerm)).decode()
1001 urlQuery = QUrlQuery()
1002 urlQuery.addQueryItem("q", searchTerm)
1003 urlQuery.addQueryItem("page", str(page))
1004 url = QUrl(self.__pip.getIndexUrlSearch())
1005 url.setQuery(urlQuery)
1006
1007 request = QNetworkRequest(QUrl(url))
1008 request.setAttribute(
1009 QNetworkRequest.Attribute.CacheLoadControlAttribute,
1010 QNetworkRequest.CacheLoadControl.AlwaysNetwork,
1011 )
1012 reply = self.__pip.getNetworkAccessManager().get(request)
1013 reply.finished.connect(lambda: self.__searchResponse(reply))
1014 self.__replies.append(reply)
1015
1016 def __searchResponse(self, reply):
1017 """
1018 Private method to extract the search result data from the response.
1019
1020 @param reply reference to the reply object containing the data
1021 @type QNetworkReply
1022 """
1023 if reply in self.__replies:
1024 self.__replies.remove(reply)
1025
1026 urlQuery = QUrlQuery(reply.url())
1027 searchTerm = urlQuery.queryItemValue("q")
1028
1029 if reply.error() != QNetworkReply.NetworkError.NoError:
1030 EricMessageBox.warning(
1031 None,
1032 self.tr("Search PyPI"),
1033 self.tr(
1034 "<p>Received an error while searching for <b>{0}</b>.</p>"
1035 "<p>Error: {1}</p>"
1036 ).format(searchTerm, reply.errorString()),
1037 )
1038 reply.deleteLater()
1039 return
1040
1041 data = bytes(reply.readAll()).decode()
1042 reply.deleteLater()
1043
1044 results = PypiSearchResultsParser(data).getResults()
1045 if results:
1046 # PyPI returns max. 20 entries per page
1047 if len(results) < 20:
1048 msg = self.tr(
1049 "%n package(s) found.",
1050 "",
1051 (self.__lastSearchPage - 1) * 20 + len(results),
1052 )
1053 self.__updateSearchMoreButton(False)
1054 else:
1055 msg = self.tr("Showing first {0} packages found.").format(
1056 self.__lastSearchPage * 20
1057 )
1058 self.__updateSearchMoreButton(True)
1059 self.searchInfoLabel.setText(msg)
1060 lastItem = self.searchResultList.topLevelItem(
1061 self.searchResultList.topLevelItemCount() - 1
1062 )
1063 else:
1064 self.__updateSearchMoreButton(False)
1065 if self.__lastSearchPage == 1:
1066 EricMessageBox.warning(
1067 self,
1068 self.tr("Search PyPI"),
1069 self.tr("""<p>There were no results for <b>{0}</b>.</p>""").format(
1070 searchTerm
1071 ), 739 ),
1072 ) 740 )
1073 self.searchInfoLabel.setText( 741
1074 self.tr("""<p>There were no results for <b>{0}</b>.</p>""").format( 742 @pyqtSlot()
1075 searchTerm 743 def on_searchButton_clicked(self):
1076 ) 744 """
1077 ) 745 Private slot to open a web browser for package searching.
1078 else: 746 """
1079 EricMessageBox.warning( 747 url = QUrl(self.__pip.getIndexUrlSearch())
1080 self, 748
1081 self.tr("Search PyPI"), 749 searchTerm = self.searchEdit.text().strip()
1082 self.tr( 750 if searchTerm:
1083 """<p>There were no more results for <b>{0}</b>.</p>""" 751 searchTerm = bytes(QUrl.toPercentEncoding(searchTerm)).decode()
1084 ).format(searchTerm), 752 urlQuery = QUrlQuery()
1085 ) 753 urlQuery.addQueryItem("q", searchTerm)
1086 lastItem = None 754 url.setQuery(urlQuery)
1087 755
1088 wrapper = textwrap.TextWrapper(width=80) 756 QDesktopServices.openUrl(url)
1089 for result in results: 757
1090 try: 758 @pyqtSlot()
1091 description = "\n".join( 759 def on_searchEdit_returnPressed(self):
1092 [ 760 """
1093 wrapper.fill(line) 761 Private slot to handle the press of the Return key in the search line edit.
1094 for line in result["description"].strip().splitlines() 762 """
1095 ] 763 self.on_searchButton_clicked()
1096 )
1097 except KeyError:
1098 description = ""
1099 date = result["released"] if "released" in result else result["created"]
1100 itm = QTreeWidgetItem(
1101 self.searchResultList,
1102 [
1103 result["name"].strip(),
1104 result["version"],
1105 date.strip(),
1106 description,
1107 ],
1108 )
1109 itm.setData(0, self.SearchVersionRole, result["version"])
1110
1111 if lastItem:
1112 self.searchResultList.scrollToItem(
1113 lastItem, QAbstractItemView.ScrollHint.PositionAtTop
1114 )
1115
1116 header = self.searchResultList.header()
1117 header.setStretchLastSection(False)
1118 header.resizeSections(QHeaderView.ResizeMode.ResizeToContents)
1119 headerSize = 0
1120 for col in range(header.count()):
1121 headerSize += header.sectionSize(col)
1122 if headerSize < header.width():
1123 header.setStretchLastSection(True)
1124
1125 self.__finishSearch()
1126
1127 def __finishSearch(self):
1128 """
1129 Private slot performing the search finishing actions.
1130 """
1131 self.__updateSearchActionButtons()
1132 self.__updateSearchButton()
1133
1134 self.searchNameEdit.setFocus(Qt.FocusReason.OtherFocusReason)
1135
1136 @pyqtSlot()
1137 def on_installButton_clicked(self):
1138 """
1139 Private slot to handle pressing the Install button..
1140 """
1141 packages = [
1142 itm.text(0).strip() for itm in self.searchResultList.selectedItems()
1143 ]
1144 self.executeInstallPackages(packages)
1145
1146 @pyqtSlot()
1147 def on_installUserSiteButton_clicked(self):
1148 """
1149 Private slot to handle pressing the Install to User-Site button..
1150 """
1151 packages = [
1152 itm.text(0).strip() for itm in self.searchResultList.selectedItems()
1153 ]
1154 self.executeInstallPackages(packages, userSite=True)
1155 764
1156 def executeInstallPackages(self, packages, userSite=False): 765 def executeInstallPackages(self, packages, userSite=False):
1157 """ 766 """
1158 Public method to install the given list of packages. 767 Public method to install the given list of packages.
1159 768
1164 """ 773 """
1165 venvName = self.environmentsComboBox.currentText() 774 venvName = self.environmentsComboBox.currentText()
1166 if venvName and packages: 775 if venvName and packages:
1167 self.__pip.installPackages(packages, venvName=venvName, userSite=userSite) 776 self.__pip.installPackages(packages, venvName=venvName, userSite=userSite)
1168 self.on_refreshButton_clicked() 777 self.on_refreshButton_clicked()
1169
1170 @pyqtSlot()
1171 def on_showDetailsButton_clicked(self):
1172 """
1173 Private slot to handle pressing the Show Details button.
1174 """
1175 self.__showSearchedDetails()
1176
1177 @pyqtSlot(QTreeWidgetItem, int)
1178 def on_searchResultList_itemActivated(self, item, column):
1179 """
1180 Private slot reacting on an search result item activation.
1181
1182 @param item reference to the activated item
1183 @type QTreeWidgetItem
1184 @param column activated column
1185 @type int
1186 """
1187 self.__showSearchedDetails(item)
1188
1189 def __showSearchedDetails(self, item=None):
1190 """
1191 Private slot to show details about the selected search result package.
1192
1193 @param item reference to the search result item to show details for
1194 @type QTreeWidgetItem
1195 """
1196 self.showDetailsButton.setEnabled(False)
1197
1198 if not item:
1199 item = self.searchResultList.selectedItems()[0]
1200
1201 packageVersion = item.data(0, self.SearchVersionRole)
1202 packageName = item.text(0)
1203
1204 self.__showPackageDetails(packageName, packageVersion, installable=True)
1205 778
1206 def __showPackageDetails( 779 def __showPackageDetails(
1207 self, 780 self,
1208 packageName, 781 packageName,
1209 packageVersion, 782 packageVersion,

eric ide

mercurial