eric6/E5Gui/E5TextEditSearchWidget.py

branch
maintenance
changeset 6989
8b8cadf8d7e9
parent 6646
51eefa621de4
parent 6942
2602857055c5
child 7286
7eb04391adf7
equal deleted inserted replaced
6938:7926553b7509 6989:8b8cadf8d7e9
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2012 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a horizontal search widget for QTextEdit.
8 """
9
10 from __future__ import unicode_literals
11
12 from PyQt5.QtCore import pyqtSlot, Qt, QMetaObject, QSize
13 from PyQt5.QtGui import QPalette, QBrush, QColor, QTextDocument, QTextCursor
14 from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, \
15 QComboBox, QCheckBox, QToolButton, QSizePolicy
16
17 from E5Gui.E5ComboBox import E5ClearableComboBox
18
19 import UI.PixmapCache
20
21
22 class E5TextEditSearchWidget(QWidget):
23 """
24 Class implementing a horizontal search widget for QTextEdit.
25 """
26 def __init__(self, parent=None, widthForHeight=True):
27 """
28 Constructor
29
30 @param parent reference to the parent widget
31 @type QWidget
32 @param widthForHeight flag indicating to prefer width for height.
33 If this parameter is False, some widgets are shown in a third
34 line.
35 @type bool
36 """
37 super(E5TextEditSearchWidget, self).__init__(parent)
38 self.__setupUi(widthForHeight)
39
40 self.__textedit = None
41 self.__texteditType = ""
42 self.__findBackwards = True
43
44 self.__defaultBaseColor = \
45 self.findtextCombo.lineEdit().palette().color(QPalette.Base)
46 self.__defaultTextColor = \
47 self.findtextCombo.lineEdit().palette().color(QPalette.Text)
48
49 self.findHistory = []
50
51 self.findtextCombo.setCompleter(None)
52 self.findtextCombo.lineEdit().returnPressed.connect(
53 self.__findByReturnPressed)
54
55 self.__setSearchButtons(False)
56 self.infoLabel.hide()
57
58 self.setFocusProxy(self.findtextCombo)
59
60 def __setupUi(self, widthForHeight):
61 """
62 Private method to generate the UI.
63
64 @param widthForHeight flag indicating to prefer width for height
65 @type bool
66 """
67 self.setObjectName("E5TextEditSearchWidget")
68
69 self.verticalLayout = QVBoxLayout(self)
70 self.verticalLayout.setObjectName("verticalLayout")
71 self.verticalLayout.setContentsMargins(0, 0, 0, 0)
72
73 # row 1 of widgets
74 self.horizontalLayout1 = QHBoxLayout()
75 self.horizontalLayout1.setObjectName("horizontalLayout1")
76
77 self.label = QLabel(self)
78 self.label.setObjectName("label")
79 self.label.setText(self.tr("Find:"))
80 self.horizontalLayout1.addWidget(self.label)
81
82 self.findtextCombo = E5ClearableComboBox(self)
83 sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
84 sizePolicy.setHorizontalStretch(0)
85 sizePolicy.setVerticalStretch(0)
86 sizePolicy.setHeightForWidth(
87 self.findtextCombo.sizePolicy().hasHeightForWidth())
88 self.findtextCombo.setSizePolicy(sizePolicy)
89 self.findtextCombo.setMinimumSize(QSize(100, 0))
90 self.findtextCombo.setEditable(True)
91 self.findtextCombo.setInsertPolicy(QComboBox.InsertAtTop)
92 self.findtextCombo.setDuplicatesEnabled(False)
93 self.findtextCombo.setObjectName("findtextCombo")
94 self.horizontalLayout1.addWidget(self.findtextCombo)
95
96 # row 2 (maybe) of widgets
97 self.horizontalLayout2 = QHBoxLayout()
98 self.horizontalLayout2.setObjectName("horizontalLayout2")
99
100 self.caseCheckBox = QCheckBox(self)
101 self.caseCheckBox.setObjectName("caseCheckBox")
102 self.caseCheckBox.setText(self.tr("Match case"))
103 self.horizontalLayout2.addWidget(self.caseCheckBox)
104
105 self.wordCheckBox = QCheckBox(self)
106 self.wordCheckBox.setObjectName("wordCheckBox")
107 self.wordCheckBox.setText(self.tr("Whole word"))
108 self.horizontalLayout2.addWidget(self.wordCheckBox)
109
110 # layout for the navigation buttons
111 self.horizontalLayout3 = QHBoxLayout()
112 self.horizontalLayout3.setSpacing(0)
113 self.horizontalLayout3.setObjectName("horizontalLayout3")
114
115 self.findPrevButton = QToolButton(self)
116 self.findPrevButton.setObjectName("findPrevButton")
117 self.findPrevButton.setToolTip(self.tr(
118 "Press to find the previous occurrence"))
119 self.findPrevButton.setIcon(UI.PixmapCache.getIcon("1leftarrow.png"))
120 self.horizontalLayout3.addWidget(self.findPrevButton)
121
122 self.findNextButton = QToolButton(self)
123 self.findNextButton.setObjectName("findNextButton")
124 self.findNextButton.setToolTip(self.tr(
125 "Press to find the next occurrence"))
126 self.findNextButton.setIcon(UI.PixmapCache.getIcon("1rightarrow.png"))
127 self.horizontalLayout3.addWidget(self.findNextButton)
128
129 self.horizontalLayout2.addLayout(self.horizontalLayout3)
130
131 # info label (in row 2 or 3)
132 self.infoLabel = QLabel(self)
133 self.infoLabel.setText("")
134 self.infoLabel.setObjectName("infoLabel")
135
136 # place everything together
137 self.verticalLayout.addLayout(self.horizontalLayout1)
138 self.__addWidthForHeightLayout(widthForHeight)
139 self.verticalLayout.addWidget(self.infoLabel)
140
141 QMetaObject.connectSlotsByName(self)
142
143 self.setTabOrder(self.findtextCombo, self.caseCheckBox)
144 self.setTabOrder(self.caseCheckBox, self.wordCheckBox)
145 self.setTabOrder(self.wordCheckBox, self.findPrevButton)
146 self.setTabOrder(self.findPrevButton, self.findNextButton)
147
148 def setWidthForHeight(self, widthForHeight):
149 """
150 Public method to set the 'width for height'.
151
152 @param widthForHeight flag indicating to prefer width
153 @type bool
154 """
155 if self.__widthForHeight:
156 self.horizontalLayout1.takeAt(self.__widthForHeightLayoutIndex)
157 else:
158 self.verticalLayout.takeAt(self.__widthForHeightLayoutIndex)
159 self.__addWidthForHeightLayout(widthForHeight)
160
161 def __addWidthForHeightLayout(self, widthForHeight):
162 """
163 Private method to set the middle part of the layout.
164
165 @param widthForHeight flag indicating to prefer width
166 @type bool
167 """
168 if widthForHeight:
169 self.horizontalLayout1.addLayout(self.horizontalLayout2)
170 self.__widthForHeightLayoutIndex = 2
171 else:
172 self.verticalLayout.insertLayout(1, self.horizontalLayout2)
173 self.__widthForHeightLayoutIndex = 1
174
175 self.__widthForHeight = widthForHeight
176
177 def attachTextEdit(self, textedit, editType="QTextEdit"):
178 """
179 Public method to attach a QTextEdit widget.
180
181 @param textedit reference to the edit widget to be attached
182 @type QTextEdit, QWebEngineView or QWebView
183 @param editType type of the attached edit widget
184 @type str (one of "QTextEdit", "QWebEngineView" or "QWebView")
185 """
186 assert editType in ["QTextEdit", "QWebEngineView", "QWebView"]
187
188 self.__textedit = textedit
189 self.__texteditType = editType
190
191 self.wordCheckBox.setVisible(editType == "QTextEdit")
192
193 def keyPressEvent(self, event):
194 """
195 Protected slot to handle key press events.
196
197 @param event reference to the key press event (QKeyEvent)
198 """
199 if self.__textedit and event.key() == Qt.Key_Escape:
200 self.__textedit.setFocus(Qt.ActiveWindowFocusReason)
201 event.accept()
202
203 @pyqtSlot(str)
204 def on_findtextCombo_editTextChanged(self, txt):
205 """
206 Private slot to enable/disable the find buttons.
207
208 @param txt text of the combobox (string)
209 """
210 self.__setSearchButtons(txt != "")
211
212 self.infoLabel.hide()
213 self.__setFindtextComboBackground(False)
214
215 def __setSearchButtons(self, enabled):
216 """
217 Private slot to set the state of the search buttons.
218
219 @param enabled flag indicating the state (boolean)
220 """
221 self.findPrevButton.setEnabled(enabled)
222 self.findNextButton.setEnabled(enabled)
223
224 def __findByReturnPressed(self):
225 """
226 Private slot to handle the returnPressed signal of the findtext
227 combobox.
228 """
229 self.__find(self.__findBackwards)
230
231 @pyqtSlot()
232 def on_findPrevButton_clicked(self):
233 """
234 Private slot to find the previous occurrence.
235 """
236 self.__find(True)
237
238 @pyqtSlot()
239 def on_findNextButton_clicked(self):
240 """
241 Private slot to find the next occurrence.
242 """
243 self.__find(False)
244
245 def __find(self, backwards):
246 """
247 Private method to search the associated text edit.
248
249 @param backwards flag indicating a backwards search (boolean)
250 """
251 if not self.__textedit:
252 return
253
254 self.infoLabel.clear()
255 self.infoLabel.hide()
256 self.__setFindtextComboBackground(False)
257
258 txt = self.findtextCombo.currentText()
259 if not txt:
260 return
261 self.__findBackwards = backwards
262
263 # This moves any previous occurrence of this statement to the head
264 # of the list and updates the combobox
265 if txt in self.findHistory:
266 self.findHistory.remove(txt)
267 self.findHistory.insert(0, txt)
268 self.findtextCombo.clear()
269 self.findtextCombo.addItems(self.findHistory)
270
271 if self.__texteditType == "QTextEdit":
272 ok = self.__findPrevNextQTextEdit(backwards)
273 self.__findNextPrevCallback(ok)
274 elif self.__texteditType == "QWebEngineView":
275 self.__findPrevNextQWebEngineView(backwards)
276 elif self.__texteditType == "QWebView":
277 ok = self.__findPrevNextQWebView(backwards)
278 self.__findNextPrevCallback(ok)
279
280 def __findPrevNextQTextEdit(self, backwards):
281 """
282 Private method to to search the associated edit widget of
283 type QTextEdit.
284
285 @param backwards flag indicating a backwards search
286 @type bool
287 @return flag indicating the search result
288 @rtype bool
289 """
290 if backwards:
291 flags = QTextDocument.FindFlags(QTextDocument.FindBackward)
292 else:
293 flags = QTextDocument.FindFlags()
294 if self.caseCheckBox.isChecked():
295 flags |= QTextDocument.FindCaseSensitively
296 if self.wordCheckBox.isChecked():
297 flags |= QTextDocument.FindWholeWords
298
299 ok = self.__textedit.find(self.findtextCombo.currentText(), flags)
300 if not ok:
301 # wrap around once
302 cursor = self.__textedit.textCursor()
303 if backwards:
304 moveOp = QTextCursor.End # move to end of document
305 else:
306 moveOp = QTextCursor.Start # move to start of document
307 cursor.movePosition(moveOp)
308 self.__textedit.setTextCursor(cursor)
309 ok = self.__textedit.find(self.findtextCombo.currentText(), flags)
310
311 return ok
312
313 def __findPrevNextQWebView(self, backwards):
314 """
315 Private method to to search the associated edit widget of
316 type QWebView.
317
318 @param backwards flag indicating a backwards search
319 @type bool
320 @return flag indicating the search result
321 @rtype bool
322 """
323 from PyQt5.QtWebKitWidgets import QWebPage
324
325 findFlags = QWebPage.FindFlags(QWebPage.FindWrapsAroundDocument)
326 if self.caseCheckBox.isChecked():
327 findFlags |= QWebPage.FindCaseSensitively
328 if backwards:
329 findFlags |= QWebPage.FindBackward
330
331 return self.__textedit.findText(self.findtextCombo.currentText(),
332 findFlags)
333
334 def __findPrevNextQWebEngineView(self, backwards):
335 """
336 Private method to to search the associated edit widget of
337 type QWebEngineView.
338
339 @param backwards flag indicating a backwards search
340 @type bool
341 """
342 from PyQt5.QtWebEngineWidgets import QWebEnginePage
343
344 findFlags = QWebEnginePage.FindFlags()
345 if self.caseCheckBox.isChecked():
346 findFlags |= QWebEnginePage.FindCaseSensitively
347 if backwards:
348 findFlags |= QWebEnginePage.FindBackward
349 self.__textedit.findText(self.findtextCombo.currentText(),
350 findFlags, self.__findNextPrevCallback)
351
352 def __findNextPrevCallback(self, found):
353 """
354 Private method to process the result of the last search.
355
356 @param found flag indicating if the last search succeeded
357 @type bool
358 """
359 if not found:
360 txt = self.findtextCombo.currentText()
361 self.infoLabel.setText(
362 self.tr("'{0}' was not found.").format(txt))
363 self.infoLabel.show()
364 self.__setFindtextComboBackground(True)
365
366 def __setFindtextComboBackground(self, error):
367 """
368 Private slot to change the findtext combo background to indicate
369 errors.
370
371 @param error flag indicating an error condition (boolean)
372 """
373 le = self.findtextCombo.lineEdit()
374 p = le.palette()
375 if error:
376 p.setBrush(QPalette.Base, QBrush(QColor("#FF6666")))
377 p.setBrush(QPalette.Text, QBrush(QColor("#000000")))
378 else:
379 p.setBrush(QPalette.Base, self.__defaultBaseColor)
380 p.setBrush(QPalette.Text, self.__defaultTextColor)
381 le.setPalette(p)
382 le.update()

eric ide

mercurial