17 from PyQt6.QtCore import pyqtSlot, Qt, QUrl, QUrlQuery |
17 from PyQt6.QtCore import pyqtSlot, Qt, QUrl, QUrlQuery |
18 from PyQt6.QtGui import QIcon |
18 from PyQt6.QtGui import QIcon |
19 from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest |
19 from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest |
20 from PyQt6.QtWidgets import ( |
20 from PyQt6.QtWidgets import ( |
21 QWidget, QToolButton, QApplication, QHeaderView, QTreeWidgetItem, |
21 QWidget, QToolButton, QApplication, QHeaderView, QTreeWidgetItem, |
22 QMenu, QDialog |
22 QMenu, QDialog, QAbstractItemView |
23 ) |
23 ) |
24 |
24 |
25 from EricWidgets.EricApplication import ericApp |
25 from EricWidgets.EricApplication import ericApp |
26 from EricWidgets import EricMessageBox |
26 from EricWidgets import EricMessageBox |
27 from EricGui.EricOverrideCursor import EricOverrideCursor |
27 from EricGui.EricOverrideCursor import EricOverrideCursor |
203 UI.PixmapCache.getIcon("info")) |
203 UI.PixmapCache.getIcon("info")) |
204 self.searchToggleButton.setIcon( |
204 self.searchToggleButton.setIcon( |
205 UI.PixmapCache.getIcon("find")) |
205 UI.PixmapCache.getIcon("find")) |
206 self.searchButton.setIcon( |
206 self.searchButton.setIcon( |
207 UI.PixmapCache.getIcon("findNext")) |
207 UI.PixmapCache.getIcon("findNext")) |
|
208 self.searchMoreButton.setIcon( |
|
209 UI.PixmapCache.getIcon("plus")) |
208 self.installButton.setIcon( |
210 self.installButton.setIcon( |
209 UI.PixmapCache.getIcon("plus")) |
211 UI.PixmapCache.getIcon("plus")) |
210 self.installUserSiteButton.setIcon( |
212 self.installUserSiteButton.setIcon( |
211 UI.PixmapCache.getIcon("addUser")) |
213 UI.PixmapCache.getIcon("addUser")) |
212 self.showDetailsButton.setIcon( |
214 self.showDetailsButton.setIcon( |
261 self.__updateActionButtons() |
263 self.__updateActionButtons() |
262 self.__updateDepActionButtons() |
264 self.__updateDepActionButtons() |
263 |
265 |
264 self.statusLabel.hide() |
266 self.statusLabel.hide() |
265 self.searchWidget.hide() |
267 self.searchWidget.hide() |
|
268 self.__lastSearchPage = 0 |
266 |
269 |
267 self.__queryName = [] |
270 self.__queryName = [] |
268 self.__querySummary = [] |
271 self.__querySummary = [] |
269 |
272 |
270 self.__replies = [] |
273 self.__replies = [] |
473 self.statusLabel.hide() |
476 self.statusLabel.hide() |
474 |
477 |
475 self.__updateActionButtons() |
478 self.__updateActionButtons() |
476 self.__updateSearchActionButtons() |
479 self.__updateSearchActionButtons() |
477 self.__updateSearchButton() |
480 self.__updateSearchButton() |
|
481 self.__updateSearchMoreButton(False) |
478 |
482 |
479 @pyqtSlot(str) |
483 @pyqtSlot(str) |
480 def on_environmentsComboBox_currentTextChanged(self, name): |
484 def on_environmentsComboBox_currentTextChanged(self, name): |
481 """ |
485 """ |
482 Private slot handling the selection of a Python environment. |
486 Private slot handling the selection of a Python environment. |
793 self.searchButton.setEnabled( |
797 self.searchButton.setEnabled( |
794 bool(self.searchEditName.text()) and |
798 bool(self.searchEditName.text()) and |
795 self.__isPipAvailable() |
799 self.__isPipAvailable() |
796 ) |
800 ) |
797 |
801 |
|
802 def __updateSearchMoreButton(self, enable): |
|
803 """ |
|
804 Private method to update the state of the search more button. |
|
805 |
|
806 @param enable flag indicating the desired enable state |
|
807 @type bool |
|
808 """ |
|
809 self.searchMoreButton.setEnabled( |
|
810 enable and |
|
811 bool(self.searchEditName.text()) and |
|
812 self.__isPipAvailable() |
|
813 ) |
|
814 |
798 @pyqtSlot(bool) |
815 @pyqtSlot(bool) |
799 def on_searchToggleButton_toggled(self, checked): |
816 def on_searchToggleButton_toggled(self, checked): |
800 """ |
817 """ |
801 Private slot to togle the search widget. |
818 Private slot to togle the search widget. |
802 |
819 |
809 self.searchEditName.setFocus(Qt.FocusReason.OtherFocusReason) |
826 self.searchEditName.setFocus(Qt.FocusReason.OtherFocusReason) |
810 self.searchEditName.selectAll() |
827 self.searchEditName.selectAll() |
811 |
828 |
812 self.__updateSearchActionButtons() |
829 self.__updateSearchActionButtons() |
813 self.__updateSearchButton() |
830 self.__updateSearchButton() |
|
831 self.__updateSearchMoreButton(False) |
814 |
832 |
815 @pyqtSlot(str) |
833 @pyqtSlot(str) |
816 def on_searchEditName_textChanged(self, txt): |
834 def on_searchEditName_textChanged(self, txt): |
817 """ |
835 """ |
818 Private slot handling a change of the search term. |
836 Private slot handling a change of the search term. |
829 """ |
847 """ |
830 if ( |
848 if ( |
831 bool(self.searchEditName.text()) and |
849 bool(self.searchEditName.text()) and |
832 self.__isPipAvailable() |
850 self.__isPipAvailable() |
833 ): |
851 ): |
834 self.__search() |
852 self.__searchFirst() |
835 |
853 |
836 @pyqtSlot() |
854 @pyqtSlot() |
837 def on_searchButton_clicked(self): |
855 def on_searchButton_clicked(self): |
838 """ |
856 """ |
839 Private slot handling a press of the search button. |
857 Private slot handling a press of the search button. |
840 """ |
858 """ |
841 self.__search() |
859 self.__searchFirst() |
|
860 |
|
861 @pyqtSlot() |
|
862 def on_searchMoreButton_clicked(self): |
|
863 """ |
|
864 Private slot handling a press of the search more button. |
|
865 """ |
|
866 self.__search(self.__lastSearchPage + 1) |
842 |
867 |
843 @pyqtSlot() |
868 @pyqtSlot() |
844 def on_searchResultList_itemSelectionChanged(self): |
869 def on_searchResultList_itemSelectionChanged(self): |
845 """ |
870 """ |
846 Private slot handling changes of the search result selection. |
871 Private slot handling changes of the search result selection. |
847 """ |
872 """ |
848 self.__updateSearchActionButtons() |
873 self.__updateSearchActionButtons() |
849 |
874 |
850 def __search(self): |
875 def __searchFirst(self): |
851 """ |
876 """ |
852 Private method to perform the search by calling the PyPI search URL. |
877 Private method to perform the search for packages. |
853 """ |
878 """ |
854 self.searchResultList.clear() |
879 self.searchResultList.clear() |
855 self.searchInfoLabel.clear() |
880 self.searchInfoLabel.clear() |
|
881 |
|
882 self.__updateSearchMoreButton(False) |
|
883 |
|
884 self.__search() |
|
885 |
|
886 def __search(self, page=1): |
|
887 """ |
|
888 Private method to perform the search by calling the PyPI search URL. |
|
889 |
|
890 @param page search page to retrieve (defaults to 1) |
|
891 @type int (optional) |
|
892 """ |
|
893 self.__lastSearchPage = page |
856 |
894 |
857 self.searchButton.setEnabled(False) |
895 self.searchButton.setEnabled(False) |
858 |
896 |
859 searchTerm = self.searchEditName.text().strip() |
897 searchTerm = self.searchEditName.text().strip() |
860 searchTerm = bytes(QUrl.toPercentEncoding(searchTerm)).decode() |
898 searchTerm = bytes(QUrl.toPercentEncoding(searchTerm)).decode() |
861 urlQuery = QUrlQuery() |
899 urlQuery = QUrlQuery() |
862 urlQuery.addQueryItem("q", searchTerm) |
900 urlQuery.addQueryItem("q", searchTerm) |
|
901 urlQuery.addQueryItem("page", str(page)) |
863 url = QUrl(self.__pip.getIndexUrlSearch()) |
902 url = QUrl(self.__pip.getIndexUrlSearch()) |
864 url.setQuery(urlQuery) |
903 url.setQuery(urlQuery) |
865 |
904 |
866 request = QNetworkRequest(QUrl(url)) |
905 request = QNetworkRequest(QUrl(url)) |
867 request.setAttribute( |
906 request.setAttribute( |
900 data = bytes(reply.readAll()).decode() |
939 data = bytes(reply.readAll()).decode() |
901 reply.deleteLater() |
940 reply.deleteLater() |
902 |
941 |
903 results = PypiSearchResultsParser(data).getResults() |
942 results = PypiSearchResultsParser(data).getResults() |
904 if results: |
943 if results: |
|
944 # PyPI returns max. 20 entries per page |
905 if len(results) < 20: |
945 if len(results) < 20: |
906 msg = self.tr("%n package(s) found.", "", len(results)) |
946 msg = self.tr("%n package(s) found.", "", |
|
947 (self.__lastSearchPage - 1) * 20 + len(results)) |
|
948 self.__updateSearchMoreButton(False) |
907 else: |
949 else: |
908 msg = self.tr("Showing first 20 packages found.") |
950 msg = self.tr("Showing first {0} packages found.").format( |
|
951 self.__lastSearchPage * 20) |
|
952 self.__updateSearchMoreButton(True) |
909 self.searchInfoLabel.setText(msg) |
953 self.searchInfoLabel.setText(msg) |
|
954 lastItem = self.searchResultList.topLevelItem( |
|
955 self.searchResultList.topLevelItemCount() - 1) |
910 else: |
956 else: |
911 EricMessageBox.warning( |
957 self.__updateSearchMoreButton(False) |
912 self, |
958 if self.__lastSearchPage == 1: |
913 self.tr("Search PyPI"), |
959 EricMessageBox.warning( |
914 self.tr("""<p>There were no results for <b>{0}</b>.</p>""")) |
960 self, |
915 self.searchInfoLabel.setText( |
961 self.tr("Search PyPI"), |
916 self.tr("""<p>There were no results for <b>{0}</b>.</p>""")) |
962 self.tr("""<p>There were no results for <b>{0}</b>.</p>""") |
|
963 .format(searchTerm) |
|
964 ) |
|
965 self.searchInfoLabel.setText( |
|
966 self.tr("""<p>There were no results for <b>{0}</b>.</p>""") |
|
967 .format(searchTerm) |
|
968 ) |
|
969 else: |
|
970 EricMessageBox.warning( |
|
971 self, |
|
972 self.tr("Search PyPI"), |
|
973 self.tr("""<p>There were no more results for""" |
|
974 """ <b>{0}</b>.</p>""").format(searchTerm) |
|
975 ) |
|
976 lastItem = None |
917 |
977 |
918 wrapper = textwrap.TextWrapper(width=80) |
978 wrapper = textwrap.TextWrapper(width=80) |
919 for result in results: |
979 for result in results: |
920 try: |
980 try: |
921 description = "\n".join([ |
981 description = "\n".join([ |
930 result['version'], |
990 result['version'], |
931 result["released"].strip(), |
991 result["released"].strip(), |
932 description, |
992 description, |
933 ]) |
993 ]) |
934 itm.setData(0, self.SearchVersionRole, result['version']) |
994 itm.setData(0, self.SearchVersionRole, result['version']) |
|
995 |
|
996 if lastItem: |
|
997 self.searchResultList.scrollToItem( |
|
998 lastItem, |
|
999 QAbstractItemView.ScrollHint.PositionAtTop) |
935 |
1000 |
936 header = self.searchResultList.header() |
1001 header = self.searchResultList.header() |
937 header.setStretchLastSection(False) |
1002 header.setStretchLastSection(False) |
938 header.resizeSections(QHeaderView.ResizeMode.ResizeToContents) |
1003 header.resizeSections(QHeaderView.ResizeMode.ResizeToContents) |
939 headerSize = 0 |
1004 headerSize = 0 |