WebBrowser/WebBrowserWebSearchWidget.py

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

eric ide

mercurial