src/eric7/PdfViewer/PdfSearchWidget.py

branch
pdf_viewer
changeset 9704
6e1650b9b3b5
child 9706
c0ff0b4d5657
equal deleted inserted replaced
9702:7c973954919d 9704:6e1650b9b3b5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a Search widget.
8 """
9
10 from PyQt6.QtCore import Qt, pyqtSlot, QModelIndex, pyqtSignal
11 from PyQt6.QtPdf import QPdfSearchModel, QPdfDocument, QPdfLink
12 from PyQt6.QtWidgets import (
13 QWidget, QVBoxLayout, QLabel, QLineEdit, QHBoxLayout, QToolButton,
14 QAbstractItemView, QTreeWidget, QTreeWidgetItem
15 )
16
17 from eric7 import Preferences
18 from eric7.EricGui import EricPixmapCache
19
20
21 class PdfSearchResultsWidget(QTreeWidget):
22 """
23 Class implementing a widget to show the search results.
24
25 @signal rowCountChanged() emitted to indicate a change of the number
26 of items
27 """
28
29 rowCountChanged = pyqtSignal()
30
31 def __init__(self, parent=None):
32 """
33 Constructor
34
35 @param parent reference to the parent widget (defaults to None)
36 @type QWidget (optional)
37 """
38 super().__init__(parent)
39
40 self.setColumnCount(2)
41 self.setHeaderHidden(True)
42 self.setAlternatingRowColors(True)
43 self.setSortingEnabled(False)
44 self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
45 self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
46
47 self.__searchModel = QPdfSearchModel(self)
48 self.__searchModel.modelReset.connect(self.__clear)
49 self.__searchModel.rowsInserted.connect(self.__rowsInserted)
50
51 def setSearchString(self, searchString):
52 """
53 Public method to set the search string.
54
55 @param searchString search string
56 @type str
57 """
58 self.__searchModel.setSearchString(searchString)
59
60 def searchString(self):
61 """
62 Public method to get the current search string.
63
64 @return search string
65 @rtype str
66 """
67 return self.__searchModel.searchString()
68
69 def setDocument(self, document):
70 """
71 Public method to set the PDF document object to be searched.
72
73 @param document reference to the PDF document object
74 @type QPdfDocument
75 """
76 self.__searchModel.setDocument(document)
77
78 def document(self):
79 """
80 Public method to get the reference to the PDF document object.
81
82 @return reference to the PDF document object
83 @rtype QPdfDocument
84 """
85 return self.__searchModel.document()
86
87 @pyqtSlot()
88 def __clear(self):
89 """
90 Private slot to clear the list of search results.
91 """
92 self.clear()
93 self.rowCountChanged.emit()
94
95 @pyqtSlot(QModelIndex, int, int)
96 def __rowsInserted(self, parent, first, last):
97 """
98 Private slot to handle the insertion of rows of the search model.
99
100 @param parent reference to the parent index
101 @type QModelIndex
102 @param first first row inserted
103 @type int
104 @param last last row inserted
105 @type int
106 """
107 contextLength = Preferences.getPdfViewer("PdfSearchContextLength")
108
109 for row in range(first, last + 1):
110 index = self.__searchModel.index(row, 0)
111 itm = QTreeWidgetItem(
112 self,
113 [
114 self.tr("Page {0}").format(
115 self.__searchModel.document().pageLabel(
116 self.__searchModel.data(
117 index, QPdfSearchModel.Role.Page.value
118 )
119 )
120 ),
121 "",
122 ]
123 )
124 contextBefore = self.__searchModel.data(
125 index, QPdfSearchModel.Role.ContextBefore.value
126 )
127 if len(contextBefore) > contextLength:
128 contextBefore = "... {0}".format(contextBefore[-contextLength:])
129 contextAfter = self.__searchModel.data(
130 index, QPdfSearchModel.Role.ContextAfter.value
131 )
132 if len(contextAfter) > contextLength:
133 contextAfter = "{0} ...".format(contextAfter[:contextLength])
134 resultLabel = QLabel(
135 self.tr(
136 "{0}<b>{1}</b>{2}",
137 "context before, search string, context after"
138 ).format(contextBefore, self.searchString(), contextAfter)
139 )
140 self.setItemWidget(itm, 1, resultLabel)
141
142 for column in range(self.columnCount()):
143 self.resizeColumnToContents(column)
144
145 self.rowCountChanged.emit()
146
147 def rowCount(self):
148 """
149 Public method to get the number of rows.
150
151 @return number of rows
152 @rtype int
153 """
154 return self.topLevelItemCount()
155
156 def currentRow(self):
157 """
158 Public method to get the current row.
159
160 @return current row
161 @rtype int
162 """
163 curItem = self.currentItem()
164 if curItem is None:
165 return -1
166 else:
167 return self.indexOfTopLevelItem(curItem)
168
169 def setCurrentRow(self, row):
170 """
171 Public method to set the current row.
172
173 @param row row number to make the current row
174 @type int
175 """
176 if 0 <= row < self.topLevelItemCount():
177 self.setCurrentItem(self.topLevelItem(row))
178
179 def searchResultData(self, item, role):
180 """
181 Public method to get data of a search result item.
182
183 @param item reference to the search result item
184 @type QTreeWidgetItem
185 @param role item data role
186 @type QPdfSearchModel.Role or Qt.ItemDataRole
187 @return requested data
188 @rtype Any
189 """
190 row = self.indexOfTopLevelItem(item)
191 index = self.__searchModel.index(row, 0)
192 return self.__searchModel.data(index, role)
193
194 def getPdfLink(self, item):
195 """
196 Public method to get the PDF link associated with a search result item.
197
198 @param item reference to the search result item
199 @type QTreeWidgetItem
200 @return associated PDF link
201 @rtype QPdfLink
202 """
203 row = self.indexOfTopLevelItem(item)
204 return self.__searchModel.resultAtIndex(row)
205
206
207 class PdfSearchWidget(QWidget):
208 """
209 Class implementing a Search widget.
210 """
211
212 searchResultActivated = pyqtSignal(QPdfLink)
213
214 def __init__(self, document, parent=None):
215 """
216 Constructor
217
218 @param document reference to the PDF document object
219 @type QPdfDocument
220 @param parent reference to the parent widget (defaults to None)
221 @type QWidget (optional)
222 """
223 super().__init__(parent)
224
225 self.__layout = QVBoxLayout(self)
226
227 # Line 1: a header label
228 self.__header = QLabel("<h2>{0}</h2>".format(self.tr("Search")))
229 self.__header.setAlignment(Qt.AlignmentFlag.AlignCenter)
230 self.__layout.addWidget(self.__header)
231
232 # Line 2: search entry and navigation buttons
233 self.__searchLineLayout = QHBoxLayout()
234
235 self.__searchEdit = QLineEdit(self)
236 self.__searchEdit.setPlaceholderText(self.tr("Search ..."))
237 self.__searchEdit.setClearButtonEnabled(True)
238 self.__searchLineLayout.addWidget(self.__searchEdit)
239
240 # layout for the navigation buttons
241 self.__buttonsLayout = QHBoxLayout()
242 self.__buttonsLayout.setSpacing(0)
243
244 self.__findPrevButton = QToolButton(self)
245 self.__findPrevButton.setToolTip(
246 self.tr("Press to move to the previous occurrence")
247 )
248 self.__findPrevButton.setIcon(EricPixmapCache.getIcon("1leftarrow"))
249 self.__buttonsLayout.addWidget(self.__findPrevButton)
250
251 self.__findNextButton = QToolButton(self)
252 self.__findNextButton.setToolTip(self.tr("Press to move to the next occurrence"))
253 self.__findNextButton.setIcon(EricPixmapCache.getIcon("1rightarrow"))
254 self.__buttonsLayout.addWidget(self.__findNextButton)
255
256 self.__searchLineLayout.addLayout(self.__buttonsLayout)
257 self.__layout.addLayout(self.__searchLineLayout)
258
259 self.__resultsWidget = PdfSearchResultsWidget(self)
260 self.__resultsWidget.setDocument(document)
261 self.__layout.addWidget(self.__resultsWidget)
262
263 self.setLayout(self.__layout)
264
265 self.__searchEdit.setEnabled(False)
266 self.__resultsWidget.setEnabled(False)
267 self.__findPrevButton.setEnabled(False)
268 self.__findNextButton.setEnabled(False)
269
270 self.__resultsWidget.itemActivated.connect(self.__entrySelected)
271 document.statusChanged.connect(self.__handleDocumentStatus)
272 self.__searchEdit.returnPressed.connect(self.__search)
273 self.__searchEdit.textChanged.connect(self.__searchTextChanged)
274 self.__resultsWidget.rowCountChanged.connect(self.__updateButtons)
275 self.__resultsWidget.currentItemChanged.connect(
276 self.__updateButtons
277 )
278 self.__findNextButton.clicked.connect(self.__nextResult)
279 self.__findPrevButton.clicked.connect(self.__previousResult)
280
281 @pyqtSlot(QPdfDocument.Status)
282 def __handleDocumentStatus(self, status):
283 """
284 Private slot to handle a change of the document status.
285
286 @param status document status
287 @type QPdfDocument.Status
288 """
289 ready = status == QPdfDocument.Status.Ready
290
291 self.__searchEdit.setEnabled(ready)
292 self.__resultsWidget.setEnabled(ready)
293
294 if not ready:
295 self.__searchEdit.clear()
296
297 @pyqtSlot(str)
298 def __searchTextChanged(self, text):
299 """
300 Private slot to handle a change of the search string.
301
302 @param text search string
303 @type str
304 """
305 if not text:
306 self.__resultsWidget.setSearchString("")
307
308 @pyqtSlot()
309 def __search(self):
310 """
311 Private slot to initiate a new search.
312 """
313 searchString = self.__searchEdit.text()
314 self.__resultsWidget.setSearchString(searchString)
315
316 @pyqtSlot()
317 def __updateButtons(self):
318 """
319 Private slot to update the state of the navigation buttons.
320 """
321 hasSearchResults = bool(self.__resultsWidget.rowCount())
322 currentRow = self.__resultsWidget.currentRow()
323 self.__findPrevButton.setEnabled(hasSearchResults and currentRow > 0)
324 self.__findNextButton.setEnabled(
325 hasSearchResults and currentRow < self.__resultsWidget.rowCount() - 2
326 )
327
328 @pyqtSlot(QTreeWidgetItem)
329 def __entrySelected(self, item):
330 """
331 Private slot to handle the selection of a search result entry.
332
333 @param index index of the activated entry
334 @type QModelIndex
335 """
336 link = self.__resultsWidget.getPdfLink(item)
337 self.searchResultActivated.emit(link)
338
339 @pyqtSlot()
340 def __nextResult(self):
341 """
342 Private slot to activate the next result.
343 """
344 row = self.__resultsWidget.currentRow()
345 if row < self.__resultsWidget.rowCount() - 2:
346 nextItem = self.__resultsWidget.topLevelItem(row + 1)
347 self.__resultsWidget.setCurrentItem(nextItem)
348 self.__entrySelected(nextItem)
349
350 @pyqtSlot()
351 def __previousResult(self):
352 """
353 Private slot to activate the previous result.
354 """
355 row = self.__resultsWidget.currentRow()
356 if row > 0:
357 prevItem = self.__resultsWidget.topLevelItem(row - 1)
358 self.__resultsWidget.setCurrentItem(prevItem)
359 self.__entrySelected(prevItem)

eric ide

mercurial