Helpviewer/HelpWebSearchWidget.py

changeset 0
de9c2efb9d02
child 7
c679fb30c8f3
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a web search widget for the web browser.
8 """
9
10 import os
11 import imp
12
13 from PyQt4.QtCore import *
14 from PyQt4.QtGui import *
15 from PyQt4.QtWebKit import QWebSettings
16
17 import UI.PixmapCache
18
19 import Preferences
20
21 from E4Gui.E4LineEdit import E4LineEdit
22
23 from OpenSearch.OpenSearchManager import OpenSearchManager
24 from OpenSearch.OpenSearchEngineAction import OpenSearchEngineAction
25
26 class HelpWebSearchWidget(QWidget):
27 """
28 Class implementing a web search widget for the web browser.
29
30 @signal search(url) emitted when the search should be done
31 """
32 _openSearchManager = None
33
34 def __init__(self, parent = None):
35 """
36 Constructor
37
38 @param inactiveText text to be shown on inactivity (string)
39 @param parent reference to the parent widget (QWidget)
40 """
41 QWidget.__init__(self, parent)
42
43 self.mw = parent
44
45 self.connect(HelpWebSearchWidget.openSearchManager(),
46 SIGNAL("currentEngineChanged()"),
47 self.__currentEngineChanged)
48 self.__currentEngine = ""
49
50 self.__layout = QHBoxLayout(self)
51 self.__layout.setMargin(0)
52 self.__layout.setSpacing(0)
53 self.setLayout(self.__layout)
54
55 self.__enginesMenu = QMenu(self)
56
57 self.__engineButton = QToolButton(self)
58 self.__engineButton.setPopupMode(QToolButton.InstantPopup)
59 self.__engineButton.setMenu(self.__enginesMenu)
60 self.__layout.addWidget(self.__engineButton)
61
62 self.__searchButton = QToolButton(self)
63 self.__searchButton.setIcon(UI.PixmapCache.getIcon("webSearch.png"))
64 self.__layout.addWidget(self.__searchButton)
65
66 self.__searchEdit = E4LineEdit(parent = self)
67 self.__layout.addWidget(self.__searchEdit)
68
69 self.__clearButton = QToolButton(self)
70 self.__clearButton.setIcon(UI.PixmapCache.getIcon("clearLeft.png"))
71 self.__layout.addWidget(self.__clearButton)
72
73 self.__model = QStandardItemModel(self)
74 self.__completer = QCompleter()
75 self.__completer.setModel(self.__model)
76 self.__completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion)
77 self.__completer.setWidget(self.__searchEdit)
78
79 self.connect(self.__searchButton, SIGNAL("clicked()"), self.__searchButtonClicked)
80 self.connect(self.__searchEdit, SIGNAL("textEdited(const QString&)"),
81 self.__textEdited)
82 self.connect(self.__clearButton, SIGNAL("clicked()"), self.__searchEdit.clear)
83 self.connect(self.__searchEdit, SIGNAL("returnPressed()"), self.__searchNow)
84 self.connect(self.__completer, SIGNAL("activated(const QModelIndex &)"),
85 self.__completerActivated)
86 self.connect(self.__completer, SIGNAL("highlighted(const QModelIndex &)"),
87 self.__completerHighlighted)
88 self.connect(self.__enginesMenu, SIGNAL("aboutToShow()"), self.__showEnginesMenu)
89
90 self.__suggestionsItem = None
91 self.__suggestions = []
92 self.__suggestTimer = None
93 self.__suggestionsEnabled = Preferences.getHelp("WebSearchSuggestions")
94
95 self.__recentSearchesItem = None
96 self.__recentSearches = []
97 self.__maxSavedSearches = 10
98
99 self.__engine = None
100 self.__loadSearches()
101 self.__setupCompleterMenu()
102 self.__currentEngineChanged()
103
104 def __searchNow(self):
105 """
106 Private slot to perform the web search.
107 """
108 searchText = self.__searchEdit.text()
109 if not searchText:
110 return
111
112 globalSettings = QWebSettings.globalSettings()
113 if not globalSettings.testAttribute(QWebSettings.PrivateBrowsingEnabled):
114 if searchText in self.__recentSearches:
115 self.__recentSearches.remove(searchText)
116 self.__recentSearches.insert(0, searchText)
117 if len(self.__recentSearches) > self.__maxSavedSearches:
118 self.__recentSearches = self.__recentSearches[:self.__maxSavedSearches]
119 self.__setupCompleterMenu()
120
121 url = HelpWebSearchWidget.openSearchManager()\
122 .currentEngine()\
123 .searchUrl(searchText)
124 self.emit(SIGNAL("search"), url)
125
126 def __setupCompleterMenu(self):
127 """
128 Private method to create the completer menu.
129 """
130 if not self.__suggestions or \
131 (self.__model.rowCount() > 0 and \
132 self.__model.item(0) != self.__suggestionsItem):
133 self.__model.clear()
134 self.__suggestionsItem = None
135 else:
136 self.__model.removeRows(1, self.__model.rowCount() - 1)
137
138 boldFont = QFont()
139 boldFont.setBold(True)
140
141 if self.__suggestions:
142 if self.__model.rowCount() == 0:
143 if not self.__suggestionsItem:
144 self.__suggestionsItem = QStandardItem(self.trUtf8("Suggestions"))
145 self.__suggestionsItem.setFont(boldFont)
146 self.__model.appendRow(self.__suggestionsItem)
147
148 for suggestion in self.__suggestions:
149 self.__model.appendRow(QStandardItem(suggestion))
150
151 if not self.__recentSearches:
152 self.__recentSearchesItem = QStandardItem(self.trUtf8("No Recent Searches"))
153 self.__recentSearchesItem.setFont(boldFont)
154 self.__model.appendRow(self.__recentSearchesItem)
155 else:
156 self.__recentSearchesItem = QStandardItem(self.trUtf8("Recent Searches"))
157 self.__recentSearchesItem.setFont(boldFont)
158 self.__model.appendRow(self.__recentSearchesItem)
159 for recentSearch in self.__recentSearches:
160 self.__model.appendRow(QStandardItem(recentSearch))
161
162 view = self.__completer.popup()
163 view.setFixedHeight(
164 view.sizeHintForRow(0) * self.__model.rowCount() + view.frameWidth() * 2)
165
166 self.__searchButton.setEnabled(
167 bool(self.__recentSearches or self.__suggestions))
168
169 def __completerActivated(self, index):
170 """
171 Private slot handling the selection of an entry from the completer.
172
173 @param index index of the item (QModelIndex)
174 """
175 if self.__suggestionsItem and \
176 self.__suggestionsItem.index().row() == index.row():
177 return
178
179 if self.__recentSearchesItem and \
180 self.__recentSearchesItem.index().row() == index.row():
181 return
182
183 self.__searchNow()
184
185 def __completerHighlighted(self, index):
186 """
187 Private slot handling the highlighting of an entry of the completer.
188
189 @param index index of the item (QModelIndex)
190 """
191 if self.__suggestionsItem and \
192 self.__suggestionsItem.index().row() == index.row():
193 return False
194
195 if self.__recentSearchesItem and \
196 self.__recentSearchesItem.index().row() == index.row():
197 return False
198
199 self.__searchEdit.setText(index.data().toString())
200 return True
201
202 def __textEdited(self, txt):
203 """
204 Private slot to handle changes of the search text.
205
206 @param txt search text (string)
207 """
208 if self.__suggestionsEnabled:
209 if self.__suggestTimer is None:
210 self.__suggestTimer = QTimer(self)
211 self.__suggestTimer.setSingleShot(True)
212 self.__suggestTimer.setInterval(200)
213 self.connect(self.__suggestTimer, SIGNAL("timeout()"),
214 self.__getSuggestions)
215 self.__suggestTimer.start()
216 else:
217 self.__completer.setCompletionPrefix(txt)
218 self.__completer.complete()
219
220 def __getSuggestions(self):
221 """
222 Private slot to get search suggestions from the configured search engine.
223 """
224 searchText = self.__searchEdit.text()
225 if searchText:
226 HelpWebSearchWidget.openSearchManager()\
227 .currentEngine()\
228 .requestSuggestions(searchText)
229
230 def __newSuggestions(self, suggestions):
231 """
232 Private slot to receive a new list of suggestions.
233
234 @param suggestions list of suggestions (list of strings)
235 """
236 self.__suggestions = suggestions
237 self.__setupCompleterMenu()
238 self.__completer.complete()
239
240 def __showEnginesMenu(self):
241 """
242 Private slot to handle the display of the engines menu.
243 """
244 self.__enginesMenu.clear()
245
246 osm = HelpWebSearchWidget.openSearchManager()
247 engineNames = osm.allEnginesNames()
248 for engineName in engineNames:
249 engine = osm.engine(engineName)
250 action = OpenSearchEngineAction(engine, self.__enginesMenu)
251 action.setData(QVariant(engineName))
252 self.connect(action, SIGNAL("triggered()"), self.__changeCurrentEngine)
253 self.__enginesMenu.addAction(action)
254
255 if osm.currentEngineName() == engineName:
256 action.setCheckable(True)
257 action.setChecked(True)
258
259 ct = self.mw.currentBrowser()
260 linkedResources = ct.linkedResources("search")
261
262 if len(linkedResources) > 0:
263 self.__enginesMenu.addSeparator()
264
265 for linkedResource in linkedResources:
266 url = QUrl(linkedResource.href)
267 title = linkedResource.title
268 mimetype = linkedResource.type_
269
270 if mimetype != "application/opensearchdescription+xml":
271 continue
272 if url.isEmpty():
273 continue
274
275 if url.isRelative():
276 url = ct.url().resolved(url)
277
278 if not title:
279 if not ct.title():
280 title = url.host()
281 else:
282 title = ct.title()
283
284 action = self.__enginesMenu.addAction(self.trUtf8("Add '{0}'").format(title),
285 self.__addEngineFromUrl)
286 action.setData(QVariant(url))
287 action.setIcon(ct.icon())
288
289 self.__enginesMenu.addSeparator()
290 self.__enginesMenu.addAction(self.mw.searchEnginesAction())
291
292 if self.__recentSearches:
293 self.__enginesMenu.addAction(self.trUtf8("Clear Recent Searches"),
294 self.clear)
295
296 def __changeCurrentEngine(self):
297 """
298 Private slot to handle the selection of a search engine.
299 """
300 action = self.sender()
301 if action is not None:
302 name = action.data().toString()
303 HelpWebSearchWidget.openSearchManager().setCurrentEngineName(name)
304
305 def __addEngineFromUrl(self):
306 """
307 Private slot to add a search engine given it's URL.
308 """
309 action = self.sender()
310 if action is not None:
311 variant = action.data()
312 if not variant.canConvert(QVariant.Url):
313 return
314 url = variant.toUrl()
315
316 HelpWebSearchWidget.openSearchManager().addEngine(url)
317
318 def __searchButtonClicked(self):
319 """
320 Private slot to show the search menu via the search button.
321 """
322 self.__setupCompleterMenu()
323 self.__completer.complete()
324
325 def clear(self):
326 """
327 Public method to clear all private data.
328 """
329 self.__recentSearches = []
330 self.__setupCompleterMenu()
331 self.__searchEdit.clear()
332 self.clearFocus()
333
334 def preferencesChanged(self):
335 """
336 Public method to handle the change of preferences.
337 """
338 self.__suggestionsEnabled = Preferences.getHelp("WebSearchSuggestions")
339 if not self.__suggestionsEnabled:
340 self.__suggestions = []
341 self.__setupCompleterMenu()
342
343 def saveSearches(self):
344 """
345 Public method to save the recently performed web searches.
346 """
347 Preferences.Prefs.settings.setValue('Help/WebSearches',
348 QVariant(self.__recentSearches))
349
350 def __loadSearches(self):
351 """
352 Public method to load the recently performed web searches.
353 """
354 searches = Preferences.Prefs.settings.value('Help/WebSearches')
355 if searches.isValid():
356 self.__recentSearches = searches.toStringList()
357
358 @classmethod
359 def openSearchManager(cls):
360 """
361 Class method to get a reference to the opensearch manager object.
362
363 @return reference to the opensearch manager object (OpenSearchManager)
364 """
365 if cls._openSearchManager is None:
366 cls._openSearchManager = OpenSearchManager()
367 return cls._openSearchManager
368
369 def __currentEngineChanged(self):
370 """
371 Private slot to track a change of the current search engine.
372 """
373 osm = HelpWebSearchWidget.openSearchManager()
374 if osm.engineExists(self.__currentEngine):
375 oldEngine = osm.engine(self.__currentEngine)
376 self.disconnect(oldEngine, SIGNAL("imageChanged()"),
377 self.__engineImageChanged)
378 if self.__suggestionsEnabled:
379 self.disconnect(oldEngine, SIGNAL("suggestions(const QStringList&)"),
380 self.__newSuggestions)
381
382 newEngine = osm.currentEngine()
383 if newEngine.networkAccessManager() is None:
384 newEngine.setNetworkAccessManager(self.mw.networkAccessManager())
385 self.connect(newEngine, SIGNAL("imageChanged()"),
386 self.__engineImageChanged)
387 if self.__suggestionsEnabled:
388 self.connect(newEngine, SIGNAL("suggestions(const QStringList&)"),
389 self.__newSuggestions)
390
391 self.__searchEdit.setInactiveText(osm.currentEngineName())
392 self.__currentEngine = osm.currentEngineName()
393 self.__engineButton.setIcon(
394 QIcon(QPixmap.fromImage(osm.currentEngine().image())))
395 self.__suggestions = []
396 self.__setupCompleterMenu()
397
398 def __engineImageChanged(self):
399 """
400 Private slot to handle a change of the current search engine icon.
401 """
402 osm = HelpWebSearchWidget.openSearchManager()
403 self.__engineButton.setIcon(
404 QIcon(QPixmap.fromImage(osm.currentEngine().image())))

eric ide

mercurial