eric6/Helpviewer/HelpWebSearchWidget.py

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

eric ide

mercurial