eric7/WebBrowser/WebBrowserWebSearchWidget.py

branch
eric7
changeset 8312
800c432b34c8
parent 8268
6b8128e0c9d1
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a web search widget for the web browser.
8 """
9
10 from PyQt5.QtCore import pyqtSignal, QUrl, QModelIndex, QTimer, Qt
11 from PyQt5.QtGui import (
12 QStandardItem, QStandardItemModel, QFont, QIcon, QPixmap
13 )
14 from PyQt5.QtWidgets import QMenu, QCompleter
15 from PyQt5.QtWebEngineWidgets import QWebEnginePage
16
17 import UI.PixmapCache
18
19 import Preferences
20
21 from E5Gui.E5LineEdit import E5ClearableLineEdit, E5LineEditSide
22
23 from .WebBrowserPage import WebBrowserPage
24
25
26 class WebBrowserWebSearchWidget(E5ClearableLineEdit):
27 """
28 Class implementing a web search widget for the web browser.
29
30 @signal search(QUrl) emitted when the search should be done
31 """
32 search = pyqtSignal(QUrl)
33
34 def __init__(self, mainWindow, parent=None):
35 """
36 Constructor
37
38 @param mainWindow reference to the browser main window
39 @type WebBrowserWindow
40 @param parent reference to the parent widget
41 @type QWidget
42 """
43 super().__init__(parent)
44
45 from E5Gui.E5LineEditButton import E5LineEditButton
46 from .OpenSearch.OpenSearchManager import OpenSearchManager
47
48 self.__mw = mainWindow
49
50 self.__openSearchManager = OpenSearchManager(self)
51 self.__openSearchManager.currentEngineChanged.connect(
52 self.__currentEngineChanged)
53 self.__currentEngine = ""
54
55 self.__enginesMenu = QMenu(self)
56 self.__enginesMenu.triggered.connect(
57 self.__handleEnginesMenuActionTriggered)
58
59 self.__engineButton = E5LineEditButton(self)
60 self.__engineButton.setMenu(self.__enginesMenu)
61 self.addWidget(self.__engineButton, E5LineEditSide.LEFT)
62
63 self.__searchButton = E5LineEditButton(self)
64 self.__searchButton.setIcon(UI.PixmapCache.getIcon("webSearch"))
65 self.addWidget(self.__searchButton, E5LineEditSide.LEFT)
66
67 self.__model = QStandardItemModel(self)
68 self.__completer = QCompleter()
69 self.__completer.setModel(self.__model)
70 self.__completer.setCompletionMode(
71 QCompleter.CompletionMode.UnfilteredPopupCompletion)
72 self.__completer.setWidget(self)
73
74 self.__searchButton.clicked.connect(self.__searchButtonClicked)
75 self.textEdited.connect(self.__textEdited)
76 self.returnPressed.connect(self.__searchNow)
77 self.__completer.activated[QModelIndex].connect(
78 self.__completerActivated)
79 self.__completer.highlighted[QModelIndex].connect(
80 self.__completerHighlighted)
81 self.__enginesMenu.aboutToShow.connect(self.__showEnginesMenu)
82
83 self.__suggestionsItem = None
84 self.__suggestions = []
85 self.__suggestTimer = None
86 self.__suggestionsEnabled = Preferences.getWebBrowser(
87 "WebSearchSuggestions")
88
89 self.__recentSearchesItem = None
90 self.__recentSearches = []
91 self.__maxSavedSearches = 10
92
93 self.__engine = None
94 self.__loadSearches()
95 self.__setupCompleterMenu()
96 self.__currentEngineChanged()
97
98 def __searchNow(self):
99 """
100 Private slot to perform the web search.
101 """
102 searchText = self.text()
103 if not searchText:
104 return
105
106 import WebBrowser.WebBrowserWindow
107 if WebBrowser.WebBrowserWindow.WebBrowserWindow.isPrivate():
108 return
109
110 if searchText in self.__recentSearches:
111 self.__recentSearches.remove(searchText)
112 self.__recentSearches.insert(0, searchText)
113 if len(self.__recentSearches) > self.__maxSavedSearches:
114 self.__recentSearches = self.__recentSearches[
115 :self.__maxSavedSearches]
116 self.__setupCompleterMenu()
117
118 self.__mw.currentBrowser().setFocus()
119 self.__mw.currentBrowser().load(
120 self.__openSearchManager.currentEngine().searchUrl(searchText))
121
122 def __setupCompleterMenu(self):
123 """
124 Private method to create the completer menu.
125 """
126 if (
127 not self.__suggestions or
128 (self.__model.rowCount() > 0 and
129 self.__model.item(0) != self.__suggestionsItem)
130 ):
131 self.__model.clear()
132 self.__suggestionsItem = None
133 else:
134 self.__model.removeRows(1, self.__model.rowCount() - 1)
135
136 boldFont = QFont()
137 boldFont.setBold(True)
138
139 if self.__suggestions:
140 if self.__model.rowCount() == 0:
141 if not self.__suggestionsItem:
142 self.__suggestionsItem = QStandardItem(
143 self.tr("Suggestions"))
144 self.__suggestionsItem.setFont(boldFont)
145 self.__model.appendRow(self.__suggestionsItem)
146
147 for suggestion in self.__suggestions:
148 self.__model.appendRow(QStandardItem(suggestion))
149
150 if not self.__recentSearches:
151 self.__recentSearchesItem = QStandardItem(
152 self.tr("No Recent Searches"))
153 self.__recentSearchesItem.setFont(boldFont)
154 self.__model.appendRow(self.__recentSearchesItem)
155 else:
156 self.__recentSearchesItem = QStandardItem(
157 self.tr("Recent Searches"))
158 self.__recentSearchesItem.setFont(boldFont)
159 self.__model.appendRow(self.__recentSearchesItem)
160 for recentSearch in self.__recentSearches:
161 self.__model.appendRow(QStandardItem(recentSearch))
162
163 view = self.__completer.popup()
164 view.setFixedHeight(view.sizeHintForRow(0) * self.__model.rowCount() +
165 view.frameWidth() * 2)
166
167 self.__searchButton.setEnabled(
168 bool(self.__recentSearches or self.__suggestions))
169
170 def __completerActivated(self, index):
171 """
172 Private slot handling the selection of an entry from the completer.
173
174 @param index index of the item (QModelIndex)
175 """
176 if (
177 self.__suggestionsItem and
178 self.__suggestionsItem.index().row() == index.row()
179 ):
180 return
181
182 if (
183 self.__recentSearchesItem and
184 self.__recentSearchesItem.index().row() == index.row()
185 ):
186 return
187
188 self.__searchNow()
189
190 def __completerHighlighted(self, index):
191 """
192 Private slot handling the highlighting of an entry of the completer.
193
194 @param index index of the item (QModelIndex)
195 @return flah indicating a successful highlighting (boolean)
196 """
197 if (
198 self.__suggestionsItem and
199 self.__suggestionsItem.index().row() == index.row()
200 ):
201 return False
202
203 if (
204 self.__recentSearchesItem and
205 self.__recentSearchesItem.index().row() == index.row()
206 ):
207 return False
208
209 self.setText(index.data())
210 return True
211
212 def __textEdited(self, txt):
213 """
214 Private slot to handle changes of the search text.
215
216 @param txt search text (string)
217 """
218 if self.__suggestionsEnabled:
219 if self.__suggestTimer is None:
220 self.__suggestTimer = QTimer(self)
221 self.__suggestTimer.setSingleShot(True)
222 self.__suggestTimer.setInterval(200)
223 self.__suggestTimer.timeout.connect(self.__getSuggestions)
224 self.__suggestTimer.start()
225 else:
226 self.__completer.setCompletionPrefix(txt)
227 self.__completer.complete()
228
229 def __getSuggestions(self):
230 """
231 Private slot to get search suggestions from the configured search
232 engine.
233 """
234 searchText = self.text()
235 if searchText:
236 self.__openSearchManager.currentEngine().requestSuggestions(
237 searchText)
238
239 def __newSuggestions(self, suggestions):
240 """
241 Private slot to receive a new list of suggestions.
242
243 @param suggestions list of suggestions (list of strings)
244 """
245 self.__suggestions = suggestions
246 self.__setupCompleterMenu()
247 self.__completer.complete()
248
249 def __showEnginesMenu(self):
250 """
251 Private slot to handle the display of the engines menu.
252 """
253 self.__enginesMenu.clear()
254
255 from .OpenSearch.OpenSearchEngineAction import OpenSearchEngineAction
256 engineNames = self.__openSearchManager.allEnginesNames()
257 for engineName in engineNames:
258 engine = self.__openSearchManager.engine(engineName)
259 action = OpenSearchEngineAction(engine, self.__enginesMenu)
260 action.setData(engineName)
261 self.__enginesMenu.addAction(action)
262
263 if self.__openSearchManager.currentEngineName() == engineName:
264 action.setCheckable(True)
265 action.setChecked(True)
266
267 cb = self.__mw.currentBrowser()
268 from .Tools import Scripts
269 script = Scripts.getOpenSearchLinks()
270 cb.page().runJavaScript(
271 script, WebBrowserPage.SafeJsWorld, self.__showEnginesMenuCallback)
272
273 def __showEnginesMenuCallback(self, res):
274 """
275 Private method handling the open search links callback.
276
277 @param res result of the JavaScript
278 @type list of dict
279 """
280 cb = self.__mw.currentBrowser()
281 if res:
282 self.__enginesMenu.addSeparator()
283 for entry in res:
284 url = cb.url().resolved(QUrl(entry["url"]))
285 title = entry["title"]
286 if url.isEmpty():
287 continue
288 if not title:
289 title = cb.title()
290
291 action = self.__enginesMenu.addAction(
292 self.tr("Add '{0}'").format(title))
293 action.setData(url)
294 action.setIcon(cb.icon())
295
296 self.__enginesMenu.addSeparator()
297 self.__enginesMenu.addAction(self.__mw.searchEnginesAction())
298
299 if self.__recentSearches:
300 act = self.__enginesMenu.addAction(
301 self.tr("Clear Recent Searches"))
302 act.setData("@@CLEAR@@")
303
304 def __handleEnginesMenuActionTriggered(self, action):
305 """
306 Private slot to handle an action of the menu being triggered.
307
308 @param action reference to the action that triggered
309 @type QAction
310 """
311 actData = action.data()
312 if isinstance(actData, QUrl):
313 # add search engine
314 self.__openSearchManager.addEngine(actData)
315 elif isinstance(actData, str):
316 # engine name or special action
317 if actData == "@@CLEAR@@":
318 self.clear()
319 else:
320 self.__openSearchManager.setCurrentEngineName(actData)
321
322 def __searchButtonClicked(self):
323 """
324 Private slot to show the search menu via the search button.
325 """
326 self.__setupCompleterMenu()
327 self.__completer.complete()
328
329 def clear(self):
330 """
331 Public method to clear all private data.
332 """
333 self.__recentSearches = []
334 self.__setupCompleterMenu()
335 super().clear()
336 self.clearFocus()
337
338 def preferencesChanged(self):
339 """
340 Public method to handle the change of preferences.
341 """
342 self.__suggestionsEnabled = Preferences.getWebBrowser(
343 "WebSearchSuggestions")
344 if not self.__suggestionsEnabled:
345 self.__suggestions = []
346 self.__setupCompleterMenu()
347
348 def saveSearches(self):
349 """
350 Public method to save the recently performed web searches.
351 """
352 Preferences.Prefs.settings.setValue(
353 'WebBrowser/WebSearches', self.__recentSearches)
354
355 def __loadSearches(self):
356 """
357 Private method to load the recently performed web searches.
358 """
359 searches = Preferences.Prefs.settings.value('WebBrowser/WebSearches')
360 if searches is not None:
361 self.__recentSearches = searches
362
363 def openSearchManager(self):
364 """
365 Public method to get a reference to the opensearch manager object.
366
367 @return reference to the opensearch manager object (OpenSearchManager)
368 """
369 return self.__openSearchManager
370
371 def __currentEngineChanged(self):
372 """
373 Private slot to track a change of the current search engine.
374 """
375 if self.__openSearchManager.engineExists(self.__currentEngine):
376 oldEngine = self.__openSearchManager.engine(self.__currentEngine)
377 oldEngine.imageChanged.disconnect(self.__engineImageChanged)
378 if self.__suggestionsEnabled:
379 oldEngine.suggestions.disconnect(self.__newSuggestions)
380
381 newEngine = self.__openSearchManager.currentEngine()
382 if newEngine.networkAccessManager() is None:
383 newEngine.setNetworkAccessManager(self.__mw.networkManager())
384 newEngine.imageChanged.connect(self.__engineImageChanged)
385 if self.__suggestionsEnabled:
386 newEngine.suggestions.connect(self.__newSuggestions)
387
388 self.setInactiveText(self.__openSearchManager.currentEngineName())
389 self.__currentEngine = self.__openSearchManager.currentEngineName()
390 self.__engineButton.setIcon(QIcon(QPixmap.fromImage(
391 self.__openSearchManager.currentEngine().image())))
392 self.__suggestions = []
393 self.__setupCompleterMenu()
394
395 def __engineImageChanged(self):
396 """
397 Private slot to handle a change of the current search engine icon.
398 """
399 self.__engineButton.setIcon(QIcon(QPixmap.fromImage(
400 self.__openSearchManager.currentEngine().image())))
401
402 def mousePressEvent(self, evt):
403 """
404 Protected method called by a mouse press event.
405
406 @param evt reference to the mouse event (QMouseEvent)
407 """
408 if evt.button() == Qt.MouseButton.XButton1:
409 self.__mw.currentBrowser().triggerPageAction(
410 QWebEnginePage.WebAction.Back)
411 elif evt.button() == Qt.MouseButton.XButton2:
412 self.__mw.currentBrowser().triggerPageAction(
413 QWebEnginePage.WebAction.Forward)
414 else:
415 super().mousePressEvent(evt)

eric ide

mercurial