eric6/WebBrowser/WebBrowserWebSearchWidget.py

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

eric ide

mercurial