2 * \file FindAndReplace.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Tommaso Cucinotta
8 * Full author contact details are available in file CREDITS.
13 #include "FindAndReplace.h"
16 #include "GuiApplication.h"
18 #include "GuiWorkArea.h"
19 #include "qt_helpers.h"
22 #include "BufferParams.h"
23 #include "BufferList.h"
25 #include "FuncRequest.h"
28 #include "frontends/alert.h"
30 #include "support/debug.h"
31 #include "support/filetools.h"
32 #include "support/FileName.h"
33 #include "support/gettext.h"
34 #include "support/lassert.h"
35 #include "support/lstrings.h"
37 #include <QCloseEvent>
42 using namespace lyx::support;
48 FindAndReplaceWidget::FindAndReplaceWidget(GuiView & view)
49 : QTabWidget(&view), view_(view)
52 find_work_area_->setGuiView(view_);
53 find_work_area_->init();
54 find_work_area_->setFrameStyle(QFrame::StyledPanel);
56 setFocusProxy(find_work_area_);
57 replace_work_area_->setGuiView(view_);
58 replace_work_area_->init();
59 replace_work_area_->setFrameStyle(QFrame::StyledPanel);
61 // We don't want two cursors blinking.
62 find_work_area_->stopBlinkingCursor();
63 replace_work_area_->stopBlinkingCursor();
67 bool FindAndReplaceWidget::eventFilter(QObject * obj, QEvent * event)
69 if (event->type() != QEvent::KeyPress
70 || (obj != find_work_area_ && obj != replace_work_area_))
71 return QWidget::eventFilter(obj, event);
73 QKeyEvent * e = static_cast<QKeyEvent *> (event);
76 if (e->modifiers() == Qt::NoModifier) {
83 case Qt::Key_Return: {
84 // with shift we (temporarily) change search/replace direction
85 bool const searchback = searchbackCB->isChecked();
86 if (e->modifiers() == Qt::ShiftModifier && !searchback)
87 searchbackCB->setChecked(!searchback);
89 if (obj == find_work_area_)
90 on_findNextPB_clicked();
92 on_replacePB_clicked();
94 searchbackCB->setChecked(searchback);
99 if (e->modifiers() == Qt::NoModifier) {
100 if (obj == find_work_area_){
101 LYXERR(Debug::FIND, "Focusing replace WA");
102 replace_work_area_->setFocus();
103 LYXERR(Debug::FIND, "Selecting entire replace buffer");
104 dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
105 dispatch(FuncRequest(LFUN_BUFFER_END_SELECT));
111 case Qt::Key_Backtab:
112 if (obj == replace_work_area_) {
113 LYXERR(Debug::FIND, "Focusing find WA");
114 find_work_area_->setFocus();
115 LYXERR(Debug::FIND, "Selecting entire find buffer");
116 dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
117 dispatch(FuncRequest(LFUN_BUFFER_END_SELECT));
125 // standard event processing
126 return QWidget::eventFilter(obj, event);
130 static vector<string> const & allManualsFiles()
132 static vector<string> v;
133 static const char * files[] = {
134 "Intro", "UserGuide", "Tutorial", "Additional",
135 "EmbeddedObjects", "Math", "Customization", "Shortcuts",
136 "LFUNs", "LaTeXConfig"
140 for (size_t i = 0; i < sizeof(files) / sizeof(files[0]); ++i) {
141 fname = i18nLibFileSearch("doc", files[i], "lyx");
142 v.push_back(fname.absFileName());
149 /** Switch p_buf to point to next document buffer.
151 ** Return true if restarted from master-document buffer.
153 static bool nextDocumentBuffer(Buffer * & buf)
155 ListOfBuffers const children = buf->allRelatives();
156 LYXERR(Debug::FIND, "children.size()=" << children.size());
157 ListOfBuffers::const_iterator it =
158 find(children.begin(), children.end(), buf);
159 LASSERT(it != children.end(), /**/);
161 if (it == children.end()) {
162 buf = *children.begin();
170 /** Switch p_buf to point to previous document buffer.
172 ** Return true if restarted from last child buffer.
174 static bool prevDocumentBuffer(Buffer * & buf)
176 ListOfBuffers const children = buf->allRelatives();
177 LYXERR(Debug::FIND, "children.size()=" << children.size());
178 ListOfBuffers::const_iterator it =
179 find(children.begin(), children.end(), buf);
180 LASSERT(it != children.end(), /**/)
181 if (it == children.begin()) {
193 /** Switch buf to point to next or previous buffer in search scope.
195 ** Return true if restarted from scratch.
197 static bool nextPrevBuffer(Buffer * & buf,
198 FindAndReplaceOptions const & opt)
200 bool restarted = false;
202 case FindAndReplaceOptions::S_BUFFER:
205 case FindAndReplaceOptions::S_DOCUMENT:
207 restarted = nextDocumentBuffer(buf);
209 restarted = prevDocumentBuffer(buf);
211 case FindAndReplaceOptions::S_OPEN_BUFFERS:
213 buf = theBufferList().next(buf);
214 restarted = (buf == *theBufferList().begin());
216 buf = theBufferList().previous(buf);
217 restarted = (buf == *(theBufferList().end() - 1));
220 case FindAndReplaceOptions::S_ALL_MANUALS:
221 vector<string> const & manuals = allManualsFiles();
222 vector<string>::const_iterator it =
223 find(manuals.begin(), manuals.end(), buf->absFileName());
224 if (it == manuals.end())
225 it = manuals.begin();
226 else if (opt.forward) {
228 if (it == manuals.end()) {
229 it = manuals.begin();
233 if (it == manuals.begin()) {
239 FileName const & fname = FileName(*it);
240 if (!theBufferList().exists(fname)) {
241 guiApp->currentView()->setBusy(false);
242 guiApp->currentView()->loadDocument(fname, false);
243 guiApp->currentView()->setBusy(true);
245 buf = theBufferList().getBuffer(fname);
252 /** Find the finest question message to post to the user */
253 docstring getQuestionString(FindAndReplaceOptions const & opt)
257 case FindAndReplaceOptions::S_BUFFER:
260 case FindAndReplaceOptions::S_DOCUMENT:
261 scope = _("Master document");
263 case FindAndReplaceOptions::S_OPEN_BUFFERS:
264 scope = _("Open files");
266 case FindAndReplaceOptions::S_ALL_MANUALS:
267 scope = _("Manuals");
270 docstring message = opt.forward ?
271 bformat(_("%1$s: the end was reached while searching forward.\n"
272 "Continue searching from the beginning?"),
274 bformat(_("%1$s: the beginning was reached while searching backward.\n"
275 "Continue searching from the end?"),
282 /// Return true if a match was found
283 bool FindAndReplaceWidget::findAndReplaceScope(FindAndReplaceOptions & opt, bool replace_all)
285 BufferView * bv = view_.documentBufferView();
288 Buffer * buf = &bv->buffer();
289 Buffer * buf_orig = &bv->buffer();
290 DocIterator cur_orig(bv->cursor());
291 int wrap_answer = -1;
294 FuncRequest cmd(LFUN_WORD_FINDADV, from_utf8(oss.str()));
297 if (opt.scope == FindAndReplaceOptions::S_ALL_MANUALS) {
298 vector<string> const & v = allManualsFiles();
299 if (std::find(v.begin(), v.end(), buf->absFileName()) == v.end()) {
300 FileName const & fname = FileName(*v.begin());
301 if (!theBufferList().exists(fname)) {
302 guiApp->currentView()->setBusy(false);
303 guiApp->currentView()->loadDocument(fname, false);
304 guiApp->currentView()->setBusy(true);
306 buf = theBufferList().getBuffer(fname);
307 lyx::dispatch(FuncRequest(LFUN_BUFFER_SWITCH,
308 buf->absFileName()));
309 bv = view_.documentBufferView();
310 bv->cursor().clear();
311 bv->cursor().push_back(CursorSlice(buf->inset()));
316 LYXERR(Debug::FIND, "Dispatching LFUN_WORD_FINDADV");
318 LYXERR(Debug::FIND, "dispatched");
319 if (bv->cursor().result().dispatched()) {
320 // New match found and selected (old selection replaced if needed)
323 view_.setBusy(false);
327 // No match found in current buffer (however old selection might have been replaced)
328 // select next buffer in scope, if any
329 bool prompt = nextPrevBuffer(buf, opt);
331 if (wrap_answer != -1)
333 docstring q = getQuestionString(opt);
334 view_.setBusy(false);
335 wrap_answer = frontend::Alert::prompt(
336 _("Wrap search?"), q,
337 0, 1, _("&Yes"), _("&No"));
339 if (wrap_answer == 1)
342 if (buf != &view_.documentBufferView()->buffer())
343 lyx::dispatch(FuncRequest(LFUN_BUFFER_SWITCH,
344 buf->absFileName()));
345 bv = view_.documentBufferView();
347 bv->cursor().clear();
348 bv->cursor().push_back(CursorSlice(buf->inset()));
350 //lyx::dispatch(FuncRequest(LFUN_BUFFER_END));
351 bv->cursor().setCursor(doc_iterator_end(buf));
352 bv->cursor().backwardPos();
353 LYXERR(Debug::FIND, "findBackAdv5: cur: "
356 bv->clearSelection();
357 } while (wrap_answer != 1);
358 if (buf_orig != &view_.documentBufferView()->buffer())
359 lyx::dispatch(FuncRequest(LFUN_BUFFER_SWITCH,
360 buf_orig->absFileName()));
361 bv = view_.documentBufferView();
362 // This may happen after a replace occurred
363 if (cur_orig.pos() > cur_orig.lastpos())
364 cur_orig.pos() = cur_orig.lastpos();
365 bv->cursor().setCursor(cur_orig);
366 view_.setBusy(false);
371 /// Return true if a match was found
372 bool FindAndReplaceWidget::findAndReplace(
373 bool casesensitive, bool matchword, bool backwards,
374 bool expandmacros, bool ignoreformat, bool replace,
375 bool keep_case, bool replace_all)
377 Buffer & find_buf = find_work_area_->bufferView().buffer();
378 docstring const & find_buf_name = find_buf.fileName().absoluteFilePath();
380 if (find_buf.text().empty()) {
381 view_.message(_("Nothing to search"));
385 Buffer & repl_buf = replace_work_area_->bufferView().buffer();
386 docstring const & repl_buf_name = replace ?
387 repl_buf.fileName().absoluteFilePath() : docstring();
389 FindAndReplaceOptions::SearchScope scope =
390 FindAndReplaceOptions::S_BUFFER;
391 if (CurrentDocument->isChecked())
392 scope = FindAndReplaceOptions::S_BUFFER;
393 else if (MasterDocument->isChecked())
394 scope = FindAndReplaceOptions::S_DOCUMENT;
395 else if (OpenDocuments->isChecked())
396 scope = FindAndReplaceOptions::S_OPEN_BUFFERS;
397 else if (AllManualsRB->isChecked())
398 scope = FindAndReplaceOptions::S_ALL_MANUALS;
400 LASSERT(false, /**/);
401 LYXERR(Debug::FIND, "FindAndReplaceOptions: "
402 << "find_buf_name=" << find_buf_name
403 << ", casesensitiv=" << casesensitive
404 << ", matchword=" << matchword
405 << ", backwards=" << backwards
406 << ", expandmacros=" << expandmacros
407 << ", ignoreformat=" << ignoreformat
408 << ", repl_buf_name" << repl_buf_name
409 << ", keep_case=" << keep_case
410 << ", scope=" << scope);
411 FindAndReplaceOptions opt(find_buf_name, casesensitive, matchword,
412 !backwards, expandmacros, ignoreformat,
413 repl_buf_name, keep_case, scope);
414 return findAndReplaceScope(opt, replace_all);
418 bool FindAndReplaceWidget::findAndReplace(bool backwards, bool replace, bool replace_all)
420 if (! view_.currentMainWorkArea()) {
421 view_.message(_("No open document(s) in which to search"));
424 // Finalize macros that are being typed, both in main document and in search or replacement WAs
425 if (view_.currentWorkArea()->bufferView().cursor().macroModeClose())
426 view_.currentWorkArea()->bufferView().processUpdateFlags(Update::Force);
427 if (view_.currentMainWorkArea()->bufferView().cursor().macroModeClose())
428 view_.currentMainWorkArea()->bufferView().processUpdateFlags(Update::Force);
430 // FIXME: create a Dialog::returnFocus()
431 // or something instead of this:
432 view_.setCurrentWorkArea(view_.currentMainWorkArea());
433 return findAndReplace(caseCB->isChecked(),
434 wordsCB->isChecked(),
436 expandMacrosCB->isChecked(),
437 ignoreFormatCB->isChecked(),
439 keepCaseCB->isChecked(),
444 void FindAndReplaceWidget::hideDialog()
446 dispatch(FuncRequest(LFUN_DIALOG_TOGGLE, "findreplaceadv"));
450 void FindAndReplaceWidget::on_findNextPB_clicked()
452 findAndReplace(searchbackCB->isChecked(), false);
453 find_work_area_->setFocus();
457 void FindAndReplaceWidget::on_replacePB_clicked()
459 findAndReplace(searchbackCB->isChecked(), true);
460 replace_work_area_->setFocus();
464 void FindAndReplaceWidget::on_replaceallPB_clicked()
466 findAndReplace(searchbackCB->isChecked(), true, true);
467 replace_work_area_->setFocus();
471 void FindAndReplaceWidget::showEvent(QShowEvent * /* ev */)
473 LYXERR(Debug::DEBUG, "showEvent()" << endl);
474 BufferView * bv = view_.documentBufferView();
476 Buffer & doc_buf = bv->buffer();
477 BufferParams & doc_bp = doc_buf.params();
478 string const & lang = doc_bp.language->lang();
479 Buffer & find_buf = find_work_area_->bufferView().buffer();
480 LYXERR(Debug::FIND, "Applying document params to find buffer");
481 find_buf.params().setLanguage(lang);
482 Buffer & replace_buf = replace_work_area_->bufferView().buffer();
483 LYXERR(Debug::FIND, "Applying document params to replace buffer");
484 replace_buf.params().setLanguage(lang);
486 LYXERR(Debug::FIND, "Setting current editing language to " << lang << endl);
487 find_work_area_->bufferView().cursor().current_font.setLanguage(doc_bp.language);
488 replace_work_area_->bufferView().cursor().current_font.setLanguage(doc_bp.language);
491 find_work_area_->installEventFilter(this);
492 replace_work_area_->installEventFilter(this);
494 view_.setCurrentWorkArea(find_work_area_);
495 LYXERR(Debug::FIND, "Selecting entire find buffer");
496 dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
497 dispatch(FuncRequest(LFUN_BUFFER_END_SELECT));
501 void FindAndReplaceWidget::hideEvent(QHideEvent *ev)
503 replace_work_area_->removeEventFilter(this);
504 find_work_area_->removeEventFilter(this);
505 this->QWidget::hideEvent(ev);
509 bool FindAndReplaceWidget::initialiseParams(std::string const & /*params*/)
515 FindAndReplace::FindAndReplace(GuiView & parent,
516 Qt::DockWidgetArea area, Qt::WindowFlags flags)
517 : DockView(parent, "findreplaceadv", qt_("Advanced Find and Replace"),
520 widget_ = new FindAndReplaceWidget(parent);
522 setFocusProxy(widget_);
526 FindAndReplace::~FindAndReplace()
533 bool FindAndReplace::initialiseParams(std::string const & params)
535 return widget_->initialiseParams(params);
539 Dialog * createGuiSearchAdv(GuiView & lv)
541 FindAndReplace * gui = new FindAndReplace(lv, Qt::RightDockWidgetArea);
543 // On Mac show and floating
544 gui->setFloating(true);
550 } // namespace frontend
554 #include "moc_FindAndReplace.cpp"