eric7/WebBrowser/WebBrowserWebSearchWidget.py

branch
eric7
changeset 8312
800c432b34c8
parent 8268
6b8128e0c9d1
child 8318
962bce857696
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/WebBrowser/WebBrowserWebSearchWidget.py	Sat May 15 18:45:04 2021 +0200
@@ -0,0 +1,415 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2009 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a web search widget for the web browser.
+"""
+
+from PyQt5.QtCore import pyqtSignal, QUrl, QModelIndex, QTimer, Qt
+from PyQt5.QtGui import (
+    QStandardItem, QStandardItemModel, QFont, QIcon, QPixmap
+)
+from PyQt5.QtWidgets import QMenu, QCompleter
+from PyQt5.QtWebEngineWidgets import QWebEnginePage
+
+import UI.PixmapCache
+
+import Preferences
+
+from E5Gui.E5LineEdit import E5ClearableLineEdit, E5LineEditSide
+
+from .WebBrowserPage import WebBrowserPage
+
+
+class WebBrowserWebSearchWidget(E5ClearableLineEdit):
+    """
+    Class implementing a web search widget for the web browser.
+    
+    @signal search(QUrl) emitted when the search should be done
+    """
+    search = pyqtSignal(QUrl)
+    
+    def __init__(self, mainWindow, parent=None):
+        """
+        Constructor
+        
+        @param mainWindow reference to the browser main window
+        @type WebBrowserWindow
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super().__init__(parent)
+        
+        from E5Gui.E5LineEditButton import E5LineEditButton
+        from .OpenSearch.OpenSearchManager import OpenSearchManager
+
+        self.__mw = mainWindow
+        
+        self.__openSearchManager = OpenSearchManager(self)
+        self.__openSearchManager.currentEngineChanged.connect(
+            self.__currentEngineChanged)
+        self.__currentEngine = ""
+        
+        self.__enginesMenu = QMenu(self)
+        self.__enginesMenu.triggered.connect(
+            self.__handleEnginesMenuActionTriggered)
+        
+        self.__engineButton = E5LineEditButton(self)
+        self.__engineButton.setMenu(self.__enginesMenu)
+        self.addWidget(self.__engineButton, E5LineEditSide.LEFT)
+        
+        self.__searchButton = E5LineEditButton(self)
+        self.__searchButton.setIcon(UI.PixmapCache.getIcon("webSearch"))
+        self.addWidget(self.__searchButton, E5LineEditSide.LEFT)
+        
+        self.__model = QStandardItemModel(self)
+        self.__completer = QCompleter()
+        self.__completer.setModel(self.__model)
+        self.__completer.setCompletionMode(
+            QCompleter.CompletionMode.UnfilteredPopupCompletion)
+        self.__completer.setWidget(self)
+        
+        self.__searchButton.clicked.connect(self.__searchButtonClicked)
+        self.textEdited.connect(self.__textEdited)
+        self.returnPressed.connect(self.__searchNow)
+        self.__completer.activated[QModelIndex].connect(
+            self.__completerActivated)
+        self.__completer.highlighted[QModelIndex].connect(
+            self.__completerHighlighted)
+        self.__enginesMenu.aboutToShow.connect(self.__showEnginesMenu)
+        
+        self.__suggestionsItem = None
+        self.__suggestions = []
+        self.__suggestTimer = None
+        self.__suggestionsEnabled = Preferences.getWebBrowser(
+            "WebSearchSuggestions")
+        
+        self.__recentSearchesItem = None
+        self.__recentSearches = []
+        self.__maxSavedSearches = 10
+        
+        self.__engine = None
+        self.__loadSearches()
+        self.__setupCompleterMenu()
+        self.__currentEngineChanged()
+    
+    def __searchNow(self):
+        """
+        Private slot to perform the web search.
+        """
+        searchText = self.text()
+        if not searchText:
+            return
+        
+        import WebBrowser.WebBrowserWindow
+        if WebBrowser.WebBrowserWindow.WebBrowserWindow.isPrivate():
+            return
+        
+        if searchText in self.__recentSearches:
+            self.__recentSearches.remove(searchText)
+        self.__recentSearches.insert(0, searchText)
+        if len(self.__recentSearches) > self.__maxSavedSearches:
+            self.__recentSearches = self.__recentSearches[
+                :self.__maxSavedSearches]
+        self.__setupCompleterMenu()
+        
+        self.__mw.currentBrowser().setFocus()
+        self.__mw.currentBrowser().load(
+            self.__openSearchManager.currentEngine().searchUrl(searchText))
+    
+    def __setupCompleterMenu(self):
+        """
+        Private method to create the completer menu.
+        """
+        if (
+            not self.__suggestions or
+            (self.__model.rowCount() > 0 and
+             self.__model.item(0) != self.__suggestionsItem)
+        ):
+            self.__model.clear()
+            self.__suggestionsItem = None
+        else:
+            self.__model.removeRows(1, self.__model.rowCount() - 1)
+        
+        boldFont = QFont()
+        boldFont.setBold(True)
+        
+        if self.__suggestions:
+            if self.__model.rowCount() == 0:
+                if not self.__suggestionsItem:
+                    self.__suggestionsItem = QStandardItem(
+                        self.tr("Suggestions"))
+                    self.__suggestionsItem.setFont(boldFont)
+                self.__model.appendRow(self.__suggestionsItem)
+            
+            for suggestion in self.__suggestions:
+                self.__model.appendRow(QStandardItem(suggestion))
+        
+        if not self.__recentSearches:
+            self.__recentSearchesItem = QStandardItem(
+                self.tr("No Recent Searches"))
+            self.__recentSearchesItem.setFont(boldFont)
+            self.__model.appendRow(self.__recentSearchesItem)
+        else:
+            self.__recentSearchesItem = QStandardItem(
+                self.tr("Recent Searches"))
+            self.__recentSearchesItem.setFont(boldFont)
+            self.__model.appendRow(self.__recentSearchesItem)
+            for recentSearch in self.__recentSearches:
+                self.__model.appendRow(QStandardItem(recentSearch))
+        
+        view = self.__completer.popup()
+        view.setFixedHeight(view.sizeHintForRow(0) * self.__model.rowCount() +
+                            view.frameWidth() * 2)
+        
+        self.__searchButton.setEnabled(
+            bool(self.__recentSearches or self.__suggestions))
+    
+    def __completerActivated(self, index):
+        """
+        Private slot handling the selection of an entry from the completer.
+        
+        @param index index of the item (QModelIndex)
+        """
+        if (
+            self.__suggestionsItem and
+            self.__suggestionsItem.index().row() == index.row()
+        ):
+            return
+        
+        if (
+            self.__recentSearchesItem and
+            self.__recentSearchesItem.index().row() == index.row()
+        ):
+            return
+        
+        self.__searchNow()
+    
+    def __completerHighlighted(self, index):
+        """
+        Private slot handling the highlighting of an entry of the completer.
+        
+        @param index index of the item (QModelIndex)
+        @return flah indicating a successful highlighting (boolean)
+        """
+        if (
+            self.__suggestionsItem and
+            self.__suggestionsItem.index().row() == index.row()
+        ):
+            return False
+        
+        if (
+            self.__recentSearchesItem and
+            self.__recentSearchesItem.index().row() == index.row()
+        ):
+            return False
+        
+        self.setText(index.data())
+        return True
+    
+    def __textEdited(self, txt):
+        """
+        Private slot to handle changes of the search text.
+        
+        @param txt search text (string)
+        """
+        if self.__suggestionsEnabled:
+            if self.__suggestTimer is None:
+                self.__suggestTimer = QTimer(self)
+                self.__suggestTimer.setSingleShot(True)
+                self.__suggestTimer.setInterval(200)
+                self.__suggestTimer.timeout.connect(self.__getSuggestions)
+            self.__suggestTimer.start()
+        else:
+            self.__completer.setCompletionPrefix(txt)
+            self.__completer.complete()
+    
+    def __getSuggestions(self):
+        """
+        Private slot to get search suggestions from the configured search
+        engine.
+        """
+        searchText = self.text()
+        if searchText:
+            self.__openSearchManager.currentEngine().requestSuggestions(
+                searchText)
+    
+    def __newSuggestions(self, suggestions):
+        """
+        Private slot to receive a new list of suggestions.
+        
+        @param suggestions list of suggestions (list of strings)
+        """
+        self.__suggestions = suggestions
+        self.__setupCompleterMenu()
+        self.__completer.complete()
+    
+    def __showEnginesMenu(self):
+        """
+        Private slot to handle the display of the engines menu.
+        """
+        self.__enginesMenu.clear()
+        
+        from .OpenSearch.OpenSearchEngineAction import OpenSearchEngineAction
+        engineNames = self.__openSearchManager.allEnginesNames()
+        for engineName in engineNames:
+            engine = self.__openSearchManager.engine(engineName)
+            action = OpenSearchEngineAction(engine, self.__enginesMenu)
+            action.setData(engineName)
+            self.__enginesMenu.addAction(action)
+            
+            if self.__openSearchManager.currentEngineName() == engineName:
+                action.setCheckable(True)
+                action.setChecked(True)
+        
+        cb = self.__mw.currentBrowser()
+        from .Tools import Scripts
+        script = Scripts.getOpenSearchLinks()
+        cb.page().runJavaScript(
+            script, WebBrowserPage.SafeJsWorld, self.__showEnginesMenuCallback)
+    
+    def __showEnginesMenuCallback(self, res):
+        """
+        Private method handling the open search links callback.
+        
+        @param res result of the JavaScript
+        @type list of dict
+        """
+        cb = self.__mw.currentBrowser()
+        if res:
+            self.__enginesMenu.addSeparator()
+            for entry in res:
+                url = cb.url().resolved(QUrl(entry["url"]))
+                title = entry["title"]
+                if url.isEmpty():
+                    continue
+                if not title:
+                    title = cb.title()
+                
+                action = self.__enginesMenu.addAction(
+                    self.tr("Add '{0}'").format(title))
+                action.setData(url)
+                action.setIcon(cb.icon())
+        
+        self.__enginesMenu.addSeparator()
+        self.__enginesMenu.addAction(self.__mw.searchEnginesAction())
+        
+        if self.__recentSearches:
+            act = self.__enginesMenu.addAction(
+                self.tr("Clear Recent Searches"))
+            act.setData("@@CLEAR@@")
+    
+    def __handleEnginesMenuActionTriggered(self, action):
+        """
+        Private slot to handle an action of the menu being triggered.
+        
+        @param action reference to the action that triggered
+        @type QAction
+        """
+        actData = action.data()
+        if isinstance(actData, QUrl):
+            # add search engine
+            self.__openSearchManager.addEngine(actData)
+        elif isinstance(actData, str):
+            # engine name or special action
+            if actData == "@@CLEAR@@":
+                self.clear()
+            else:
+                self.__openSearchManager.setCurrentEngineName(actData)
+    
+    def __searchButtonClicked(self):
+        """
+        Private slot to show the search menu via the search button.
+        """
+        self.__setupCompleterMenu()
+        self.__completer.complete()
+    
+    def clear(self):
+        """
+        Public method to clear all private data.
+        """
+        self.__recentSearches = []
+        self.__setupCompleterMenu()
+        super().clear()
+        self.clearFocus()
+    
+    def preferencesChanged(self):
+        """
+        Public method to handle the change of preferences.
+        """
+        self.__suggestionsEnabled = Preferences.getWebBrowser(
+            "WebSearchSuggestions")
+        if not self.__suggestionsEnabled:
+            self.__suggestions = []
+            self.__setupCompleterMenu()
+    
+    def saveSearches(self):
+        """
+        Public method to save the recently performed web searches.
+        """
+        Preferences.Prefs.settings.setValue(
+            'WebBrowser/WebSearches', self.__recentSearches)
+    
+    def __loadSearches(self):
+        """
+        Private method to load the recently performed web searches.
+        """
+        searches = Preferences.Prefs.settings.value('WebBrowser/WebSearches')
+        if searches is not None:
+            self.__recentSearches = searches
+    
+    def openSearchManager(self):
+        """
+        Public method to get a reference to the opensearch manager object.
+        
+        @return reference to the opensearch manager object (OpenSearchManager)
+        """
+        return self.__openSearchManager
+    
+    def __currentEngineChanged(self):
+        """
+        Private slot to track a change of the current search engine.
+        """
+        if self.__openSearchManager.engineExists(self.__currentEngine):
+            oldEngine = self.__openSearchManager.engine(self.__currentEngine)
+            oldEngine.imageChanged.disconnect(self.__engineImageChanged)
+            if self.__suggestionsEnabled:
+                oldEngine.suggestions.disconnect(self.__newSuggestions)
+        
+        newEngine = self.__openSearchManager.currentEngine()
+        if newEngine.networkAccessManager() is None:
+            newEngine.setNetworkAccessManager(self.__mw.networkManager())
+        newEngine.imageChanged.connect(self.__engineImageChanged)
+        if self.__suggestionsEnabled:
+            newEngine.suggestions.connect(self.__newSuggestions)
+        
+        self.setInactiveText(self.__openSearchManager.currentEngineName())
+        self.__currentEngine = self.__openSearchManager.currentEngineName()
+        self.__engineButton.setIcon(QIcon(QPixmap.fromImage(
+            self.__openSearchManager.currentEngine().image())))
+        self.__suggestions = []
+        self.__setupCompleterMenu()
+    
+    def __engineImageChanged(self):
+        """
+        Private slot to handle a change of the current search engine icon.
+        """
+        self.__engineButton.setIcon(QIcon(QPixmap.fromImage(
+            self.__openSearchManager.currentEngine().image())))
+    
+    def mousePressEvent(self, evt):
+        """
+        Protected method called by a mouse press event.
+        
+        @param evt reference to the mouse event (QMouseEvent)
+        """
+        if evt.button() == Qt.MouseButton.XButton1:
+            self.__mw.currentBrowser().triggerPageAction(
+                QWebEnginePage.WebAction.Back)
+        elif evt.button() == Qt.MouseButton.XButton2:
+            self.__mw.currentBrowser().triggerPageAction(
+                QWebEnginePage.WebAction.Forward)
+        else:
+            super().mousePressEvent(evt)

eric ide

mercurial