1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 from python_qt_binding.QtCore import Signal, Qt
34 import os
35 import rospy
36
37 from node_manager_fkie.common import package_name
38
39 from .line_edit import EnchancedLineEdit
40 from .text_search_thread import TextSearchThread
41
42
43 try:
44 from python_qt_binding.QtGui import QCheckBox, QFrame, QLabel, QTreeWidget, QTreeWidgetItem, QPushButton, QGroupBox, QDockWidget
45 from python_qt_binding.QtGui import QHBoxLayout, QVBoxLayout, QSpacerItem, QSplitter, QSizePolicy
46 except:
47 from python_qt_binding.QtWidgets import QCheckBox, QFrame, QLabel, QTreeWidget, QTreeWidgetItem, QPushButton, QGroupBox, QDockWidget
48 from python_qt_binding.QtWidgets import QHBoxLayout, QVBoxLayout, QSpacerItem, QSplitter, QSizePolicy
49
50
51 -class TextSearchFrame(QDockWidget):
52 '''
53 A frame to find text in the Editor.
54 '''
55 search_result_signal = Signal(str, bool, str, int)
56 ''' @ivar: A signal emitted after search_threaded was started.
57 (search text, found or not, file, position in text)
58 for each result a signal will be emitted.
59 '''
60 replace_signal = Signal(str, str, int, str)
61 ''' @ivar: A signal emitted to replace string at given position.
62 (search text, file, position in text, replaced by text)
63 '''
64
65 - def __init__(self, tabwidget, parent=None):
66 QDockWidget.__init__(self, "Find", parent)
67 self.setObjectName('SearchFrame')
68 self.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable)
69 self._dockwidget = QFrame(self)
70 self.vbox_layout = QVBoxLayout(self._dockwidget)
71 self.layout().setContentsMargins(0, 0, 0, 0)
72 self.layout().setSpacing(1)
73
74 find_replace_frame = QFrame(self)
75 find_replace_vbox_layout = QVBoxLayout(find_replace_frame)
76 find_replace_vbox_layout.setContentsMargins(0, 0, 0, 0)
77 find_replace_vbox_layout.setSpacing(1)
78
79
80 find_frame = self._create_find_frame()
81 find_replace_vbox_layout.addWidget(find_frame)
82 rplc_frame = self._create_replace_frame()
83 find_replace_vbox_layout.addWidget(rplc_frame)
84
85 self.vbox_layout.addWidget(find_replace_frame)
86 self.vbox_layout.addWidget(self._create_found_frame())
87
88 self.setWidget(self._dockwidget)
89
90 self._tabwidget = tabwidget
91 self.current_search_text = ''
92 self.search_results = []
93 self.search_results_fileset = set()
94 self._search_result_index = -1
95 self._search_recursive = False
96 self._search_thread = None
97
99 find_frame = QFrame(self)
100 find_hbox_layout = QHBoxLayout(find_frame)
101 find_hbox_layout.setContentsMargins(0, 0, 0, 0)
102 find_hbox_layout.setSpacing(1)
103 self.search_field = EnchancedLineEdit(find_frame)
104 self.search_field.setPlaceholderText('search text')
105 self.search_field.textChanged.connect(self.on_search_text_changed)
106 self.search_field.returnPressed.connect(self.on_search)
107 find_hbox_layout.addWidget(self.search_field)
108 self.search_result_label = QLabel(find_frame)
109 self.search_result_label.setText(' ')
110 find_hbox_layout.addWidget(self.search_result_label)
111 self.find_button_back = QPushButton("<")
112 self.find_button_back.setFixedWidth(44)
113 self.find_button_back.clicked.connect(self.on_search_back)
114 find_hbox_layout.addWidget(self.find_button_back)
115 self.find_button = QPushButton(">")
116 self.find_button.setDefault(True)
117
118 self.find_button.setFixedWidth(44)
119 self.find_button.clicked.connect(self.on_search)
120 find_hbox_layout.addWidget(self.find_button)
121 return find_frame
122
124
125 self.rplc_frame = rplc_frame = QFrame(self)
126 rplc_hbox_layout = QHBoxLayout(rplc_frame)
127 rplc_hbox_layout.setContentsMargins(0, 0, 0, 0)
128 rplc_hbox_layout.setSpacing(1)
129 self.replace_field = EnchancedLineEdit(rplc_frame)
130 self.replace_field.setPlaceholderText('replace text')
131 self.replace_field.returnPressed.connect(self.on_replace)
132 rplc_hbox_layout.addWidget(self.replace_field)
133 self.replace_result_label = QLabel(rplc_frame)
134 self.replace_result_label.setText(' ')
135 rplc_hbox_layout.addWidget(self.replace_result_label)
136 self.replace_button = replace_button = QPushButton("> &Replace >")
137 replace_button.setFixedWidth(90)
138 replace_button.clicked.connect(self.on_replace_click)
139 rplc_hbox_layout.addWidget(replace_button)
140 rplc_frame.setVisible(False)
141 return rplc_frame
142
144 ff_frame = QFrame(self)
145 self.found_files_vbox_layout = QVBoxLayout(ff_frame)
146 self.found_files_vbox_layout.setContentsMargins(0, 0, 0, 0)
147 self.recursive_search_box = QCheckBox("recursive search")
148 self.found_files_vbox_layout.addWidget(self.recursive_search_box)
149 self.found_files_list = QTreeWidget(ff_frame)
150 self.found_files_list.setColumnCount(1)
151 self.found_files_list.setFrameStyle(QFrame.StyledPanel)
152 self.found_files_list.setHeaderHidden(True)
153 self.found_files_list.itemActivated.connect(self.on_itemActivated)
154 self.found_files_list.setStyleSheet(
155 "QTreeWidget {"
156 "background-color:transparent;"
157 "}"
158 "QTreeWidget::item {"
159 "background-color:transparent;"
160 "}"
161 "QTreeWidget::item:selected {"
162 "background-color: darkgray;"
163 "}")
164 self.found_files_vbox_layout.addWidget(self.found_files_list)
165 self.recursive_search_box.setChecked(False)
166 return ff_frame
167
168 - def keyPressEvent(self, event):
169 '''
170 Enable the shortcats for search and replace
171 '''
172 self.parent().keyPressEvent(event)
173
174 - def on_search(self):
175 '''
176 Initiate the new search or request a next search result.
177 '''
178 if self.current_search_text != self.search_field.text() or self._search_recursive != self.recursive_search_box.isChecked():
179
180 self._reset()
181 self.current_search_text = self.search_field.text()
182 if self.current_search_text:
183 path_text = {}
184 self._wait_for_result = True
185 for i in range(self._tabwidget.count()):
186 path_text[self._tabwidget.widget(i).filename] = self._tabwidget.widget(i).document().toPlainText()
187 self._search_recursive = self.recursive_search_box.isChecked()
188 self._search_thread = TextSearchThread(self.current_search_text, self._tabwidget.currentWidget().filename, path_text=path_text, recursive=self._search_recursive)
189 self._search_thread.search_result_signal.connect(self.on_search_result)
190 self._search_thread.warning_signal.connect(self.on_warning_result)
191 self._search_thread.start()
192 elif self.search_results:
193 self._check_position()
194 if self.search_results:
195 if self._search_result_index + 1 >= len(self.search_results):
196 self._search_result_index = -1
197 self._search_result_index += 1
198 (id, search_text, found, path, index, linenr, line) = self.search_results[self._search_result_index]
199 self.search_result_signal.emit(search_text, found, path, index)
200 self.replace_button.setEnabled(True)
201 self._update_label()
202
203 - def on_search_back(self):
204 '''
205 Slot to handle the search back function.
206 '''
207 self._check_position(False)
208 if self.search_results:
209 self._search_result_index -= 1
210 if self._search_result_index < 0:
211 self._search_result_index = len(self.search_results) - 1
212 self._update_label()
213 (id, search_text, found, path, index, linenr, line) = self.search_results[self._search_result_index]
214 self.search_result_signal.emit(search_text, found, path, index)
215 self.replace_button.setEnabled(True)
216
217 - def _check_position(self, forward=True):
218 try:
219
220 cur_pos = self._tabwidget.currentWidget().textCursor().position()
221 id, st, _f, pa, idx, lnr, ltxt = self.search_results[self._search_result_index]
222 sear_pos = idx + len(st)
223 if cur_pos != sear_pos:
224 first_idx = self._get_current_index_for_current_file()
225 if first_idx != -1:
226 id, st, _f, pa, idx, lnr, ltxt = self.search_results[first_idx]
227 sear_pos = idx + len(st)
228 while cur_pos > sear_pos and self._tabwidget.currentWidget().filename == pa:
229 first_idx += 1
230 id, st, _f, pa, idx, lnr, ltxt = self.search_results[first_idx]
231 sear_pos = idx + len(st)
232 self._search_result_index = first_idx
233 if forward:
234 self._search_result_index -= 1
235 else:
236 self._reset(True)
237 except:
238 pass
239
241 for index in range(len(self.search_results)):
242 id, _st, _f, pa, _idx = self.search_results[index]
243 if self._tabwidget.currentWidget().filename == pa:
244 return index
245 return -1
246
247 - def on_search_result(self, search_text, found, path, index, linenr, line):
248 '''
249 Slot to handle the signals for search result. This signals are forwarded used
250 search_result_signal.
251 '''
252 if found and search_text == self.current_search_text:
253 id = "%d:%s" % (index, path)
254 self.search_results_fileset.add(path)
255 item = (search_text, found, path, index)
256 if item not in self.search_results:
257 self.search_results.append((id, search_text, found, path, index, linenr, line))
258 if self._wait_for_result:
259 self._search_result_index += 1
260 if index >= self._tabwidget.currentWidget().textCursor().position() or self._tabwidget.currentWidget().filename != path:
261 self._wait_for_result = False
262 self.search_result_signal.emit(search_text, found, path, index)
263 self.replace_button.setEnabled(True)
264 pkg, rpath = package_name(os.path.dirname(path))
265 itemstr = '%s [%s]' % (os.path.basename(path), pkg)
266 if not self.found_files_list.findItems(itemstr, Qt.MatchExactly):
267 list_item = QTreeWidgetItem(self.found_files_list)
268 list_item.setText(0, itemstr)
269 list_item.setToolTip(0, path)
270 self.found_files_list.insertTopLevelItem(0, list_item)
271 self.found_files_list.expandAll()
272 for i in range(self.found_files_list.topLevelItemCount()):
273 top_item = self.found_files_list.topLevelItem(i)
274 if top_item.text(0) == itemstr:
275 sub_item_str = "%d: %s" % (linenr, line)
276 list_item2 = QTreeWidgetItem()
277 list_item2.setText(0, sub_item_str)
278 list_item2.setWhatsThis(0, id)
279 top_item.addChild(list_item2)
280
281 self._update_label()
282
283 - def on_warning_result(self, text):
285
287 self.on_replace()
288 self.on_search()
289
290 - def on_replace(self):
291 '''
292 Emits the replace signal, but only if currently selected text is equal to the searched one.
293 '''
294 if self.search_results:
295 try:
296 id, search_text, _found, path, index, linenr, line_text = self.search_results[self._search_result_index]
297 cursor = self._tabwidget.currentWidget().textCursor()
298 if cursor.selectedText() == search_text:
299 rptxt = self.replace_field.text()
300 for rindex in range(self._search_result_index + 1, len(self.search_results)):
301 iid, st, _f, pa, idx, lnr, ltxt = self.search_results[rindex]
302 if path == pa:
303 self.search_results.pop(rindex)
304 self.search_results.insert(rindex, (iid, st, _f, pa, idx + len(rptxt) - len(st), lnr, ltxt))
305 else:
306 break
307 self._remove_search_result(self._search_result_index)
308 self._search_result_index -= 1
309 self.replace_signal.emit(search_text, path, index, rptxt)
310 else:
311 self.replace_button.setEnabled(False)
312 except:
313 import traceback
314 print traceback.format_exc()
315 pass
316
317 - def on_itemActivated(self, item):
318 '''
319 Go to the results for the selected file entry in the list.
320 '''
321 splits = item.whatsThis(0).split(':')
322 if len(splits) == 2:
323 item_index = int(splits[0])
324 item_path = splits[1]
325 new_search_index = -1
326 tmp_index = -1
327 search_index = -1
328 tmp_search_text = ''
329 for id, search_text, found, path, index, linenr, line_text in self.search_results:
330 new_search_index += 1
331 if item_path == path and item_index == index:
332 self._search_result_index = new_search_index
333 self.search_result_signal.emit(search_text, found, path, index)
334 self._update_label()
335
336 - def on_search_text_changed(self, _text):
337 '''
338 Clear search result if the text was changed.
339 '''
340 self._reset()
341
342 - def _update_label(self, clear_label=False):
343 '''
344 Updates the status label for search results. The info is created from search result lists.
345 '''
346 msg = ' '
347 if self.search_results:
348 count_files = len(self.search_results_fileset)
349 msg = '%d/%d' % (self._search_result_index + 1, len(self.search_results))
350 if count_files > 1:
351 msg = '%s(%d)' % (msg, count_files)
352 if self._search_thread is not None and self._search_thread.is_alive():
353 msg = 'searching..%s' % msg
354 elif not msg.strip() and self.current_search_text:
355 msg = '0 found'
356 self.current_search_text = ''
357 if clear_label:
358 msg = ' '
359 self.search_result_label.setText(msg)
360 self.find_button_back.setEnabled(len(self.search_results))
361 self._select_current_item_in_box(self._search_result_index)
362
363 - def file_changed(self, path):
364 '''
365 Clears search results if for changed file are some search results are available
366 :param path: changed file path
367 :type path: str
368 '''
369 if path in self.search_results_fileset:
370 self._reset()
371
372 - def set_replace_visible(self, value):
373 self.rplc_frame.setVisible(value)
374 self.raise_()
375 self.activateWindow()
376 if value:
377 self.replace_field.setFocus()
378 self.replace_field.selectAll()
379 self.setWindowTitle("Find / Replace")
380 else:
381 self.setWindowTitle("Find")
382 self.search_field.setFocus()
383
385 return self.rplc_frame.isVisible()
386
387 - def _reset(self, force_new_search=False):
388
389 if self._search_thread is not None:
390 self._search_thread.search_result_signal.disconnect()
391 self._search_thread.stop()
392 self._search_thread = None
393 self.current_search_text = ''
394 self.search_results = []
395 self.search_results_fileset = set()
396 self.found_files_list.clear()
397
398 self._update_label(True)
399 self._search_result_index = -1
400 self.find_button_back.setEnabled(False)
401 if force_new_search:
402 self.on_search()
403
405 self.setVisible(True)
406
407 self.raise_()
408 self.activateWindow()
409 self.search_field.setFocus()
410 self.search_field.selectAll()
411
413 try:
414 (id, search_text, found, path, index, linenr, line) = self.search_results[index]
415 for topidx in range(self.found_files_list.topLevelItemCount()):
416 topitem = self.found_files_list.topLevelItem(topidx)
417 for childdx in range(topitem.childCount()):
418 child = topitem.child(childdx)
419 if child.whatsThis(0) == id:
420 child.setSelected(True)
421 elif child.isSelected():
422 child.setSelected(False)
423 except:
424 pass
425
426 - def _remove_search_result(self, index):
427 try:
428 (id, search_text, found, path, index, linenr, line) = self.search_results.pop(index)
429 pkg, rpath = package_name(os.path.dirname(path))
430 itemstr = '%s [%s]' % (os.path.basename(path), pkg)
431 found_items = self.found_files_list.findItems(itemstr, Qt.MatchExactly)
432 for item in found_items:
433 for chi in range(item.childCount()):
434 child = item.child(chi)
435 if child.whatsThis(0) == id:
436 item.removeChild(child)
437 break
438
439 for topidx in range(self.found_files_list.topLevelItemCount()):
440 if self.found_files_list.topLevelItem(topidx).childCount() == 0:
441 self.found_files_list.takeTopLevelItem(topidx)
442 break
443
444 new_path_set = set(path for _id, _st, _fd, path, _idx, lnr, lntxt in self.search_results)
445 self.search_results_fileset = new_path_set
446
447 except:
448 import traceback
449 print traceback.format_exc()
450