]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/FindAndReplace.cpp
53a19b56b0dc39ec17bdf2be2e3276913db69ffb
[lyx.git] / src / frontends / qt4 / FindAndReplace.cpp
1 /**
2  * \file FindAndReplace.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Tommaso Cucinotta
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "FindAndReplace.h"
14
15 #include "GuiApplication.h"
16 #include "qt_helpers.h"
17 #include "GuiView.h"
18 #include "GuiWorkArea.h"
19
20 #include "buffer_funcs.h"
21 #include "BufferParams.h"
22 #include "BufferList.h"
23 #include "Cursor.h"
24 #include "FuncRequest.h"
25 #include "lyxfind.h"
26 #include "OutputParams.h"
27 #include "output_latex.h"
28 #include "TexRow.h"
29 #include "alert.h"
30
31 #include "support/debug.h"
32 #include "support/FileName.h"
33 #include "support/gettext.h"
34 #include "support/lassert.h"
35 #include "support/filetools.h"
36
37 #include <QCloseEvent>
38 #include <QLineEdit>
39
40 #include <iostream>
41
42 using namespace std;
43 using namespace lyx::support;
44
45 namespace lyx {
46 namespace frontend {
47
48
49 FindAndReplaceWidget::FindAndReplaceWidget(GuiView & view)
50         :       view_(view)
51 {
52         setupUi(this);
53 #if QT_VERSION < 0x040400
54         scrollArea->setWidget(scrollAreaWidgetContents);
55 #endif
56         find_work_area_->setGuiView(view_);
57         find_work_area_->init();
58         setFocusProxy(find_work_area_);
59         replace_work_area_->setGuiView(view_);
60         replace_work_area_->init();
61         // We don't want two cursors blinking.
62         replace_work_area_->stopBlinkingCursor();
63 }
64
65
66 bool FindAndReplaceWidget::eventFilter(QObject *obj, QEvent *event)
67 {
68         if (obj == find_work_area_ && event->type() == QEvent::KeyPress) {
69                 QKeyEvent *e = static_cast<QKeyEvent *> (event);
70                 if (e->key() == Qt::Key_Escape && e->modifiers() == Qt::NoModifier) {
71                         on_closePB_clicked();
72                         return true;
73                 } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
74                         if (e->modifiers() == Qt::ShiftModifier) {
75                                 on_findPrevPB_clicked();
76                                 return true;
77                         } else if (e->modifiers() == Qt::NoModifier) {
78                                 on_findNextPB_clicked();
79                                 return true;
80                         }
81                 } else if (e->key() == Qt::Key_Tab && e->modifiers() == Qt::NoModifier) {
82                         LYXERR(Debug::FIND, "Focusing replace WA");
83                         replace_work_area_->setFocus();
84                         return true;
85                 }
86         }
87         if (obj == replace_work_area_ && event->type() == QEvent::KeyPress) {
88                 QKeyEvent *e = static_cast<QKeyEvent *> (event);
89                 if (e->key() == Qt::Key_Escape && e->modifiers() == Qt::NoModifier) {
90                         on_closePB_clicked();
91                         return true;
92                 } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
93                         if (e->modifiers() == Qt::ShiftModifier) {
94                                 on_replacePrevPB_clicked();
95                                 return true;
96                         } else if (e->modifiers() == Qt::NoModifier) {
97                                 on_replaceNextPB_clicked();
98                                 return true;
99                         }
100                 } else if (e->key() == Qt::Key_Backtab) {
101                         LYXERR(Debug::FIND, "Focusing find WA");
102                         find_work_area_->setFocus();
103                         return true;
104                 }
105         }
106         // standard event processing
107         return QWidget::eventFilter(obj, event);
108 }
109
110 static docstring buffer_to_latex(Buffer & buffer) {
111         OutputParams runparams(&buffer.params().encoding());
112         odocstringstream os;
113         runparams.nice = true;
114         runparams.flavor = OutputParams::LATEX;
115         runparams.linelen = 80; //lyxrc.plaintext_linelen;
116         // No side effect of file copying and image conversion
117         runparams.dryrun = true;
118         buffer.texrow().reset();
119         ParagraphList::const_iterator pit = buffer.paragraphs().begin();
120         ParagraphList::const_iterator const end = buffer.paragraphs().end();
121         for (; pit != end; ++pit) {
122                 TeXOnePar(buffer, buffer.text(), pit, os, buffer.texrow(), runparams);
123                 LYXERR(Debug::FIND, "searchString up to here: " << os.str());
124         }
125         return os.str();
126 }
127
128
129 static vector<string> const & allManualsFiles() {
130         static vector<string> v;
131         static const char * files[] = {
132                 "Intro", "UserGuide", "Tutorial", "Additional", "EmbeddedObjects",
133                 "Math", "Customization", "Shortcuts", "LFUNs", "LaTeXConfig"
134         };
135         if (v.empty()) {
136                 FileName fname;
137                 for (size_t i = 0; i < sizeof(files) / sizeof(files[0]); ++i) {
138                         fname = i18nLibFileSearch("doc", files[i], "lyx");
139                         v.push_back(fname.absFilename());
140                 }
141         }
142         return v;
143 }
144
145
146 /** Switch p_buf to point to next document buffer.
147  **
148  ** Return true if restarted from master-document buffer.
149  **
150  ** @note
151  ** Not using p_buf->allRelatives() here, because I'm not sure
152  ** whether or not the returned order is independent of p_buf.
153  **/
154 static bool next_document_buffer(Buffer * & p_buf) {
155         Buffer *p_master = p_buf;
156         Buffer *p_old;
157         do {
158                 p_old = p_master;
159                 p_master = const_cast<Buffer *>(p_master->masterBuffer());
160                 LYXERR(Debug::FIND, "p_old=" << p_old << ", p_master=" << p_master);
161         } while (p_master != p_old);
162         LASSERT(p_master != NULL, /**/);
163         vector<Buffer *> v_children;
164         /* Root master added as first buffer in the vector */
165         v_children.push_back(p_master);
166         p_master->getChildren(v_children, true);
167         LYXERR(Debug::FIND, "v_children.size()=" << v_children.size());
168         vector<Buffer *>::const_iterator it = find(v_children.begin(), v_children.end(), p_buf);
169         LASSERT(it != v_children.end(), /**/)
170         ++it;
171         if (it == v_children.end()) {
172                 p_buf = *v_children.begin();
173                 return true;
174         }
175         p_buf = *it;
176         return false;
177 }
178
179
180 /** Switch p_buf to point to previous document buffer.
181  **
182  ** Return true if restarted from last child buffer.
183  **
184  ** @note
185  ** Not using p_buf->allRelatives() here, because I'm not sure
186  ** whether or not the returned order is independent of p_buf.
187  **/
188 static bool prev_document_buffer(Buffer * & p_buf) {
189         Buffer *p_master = p_buf;
190         Buffer *p_old;
191         do {
192                 p_old = p_master;
193                 p_master = const_cast<Buffer *>(p_master->masterBuffer());
194                 LYXERR(Debug::FIND, "p_old=" << p_old << ", p_master=" << p_master);
195         } while (p_master != p_old);
196         LASSERT(p_master != NULL, /**/);
197         vector<Buffer *> v_children;
198         /* Root master added as first buffer in the vector */
199         v_children.push_back(p_master);
200         p_master->getChildren(v_children, true);
201         LYXERR(Debug::FIND, "v_children.size()=" << v_children.size());
202         vector<Buffer *>::const_iterator it = find(v_children.begin(), v_children.end(), p_buf);
203         LASSERT(it != v_children.end(), /**/)
204         if (it == v_children.begin()) {
205                 it = v_children.end();
206                 --it;
207                 p_buf = *it;
208                 return true;
209         }
210         --it;
211         p_buf = *it;
212         return false;
213 }
214
215
216 /** Switch buf to point to next or previous buffer in search scope.
217  **
218  ** Return true if restarted from scratch.
219  **/
220 static bool next_prev_buffer(Buffer * & buf, FindAndReplaceOptions const & opt) {
221         bool restarted = false;
222         switch (opt.scope) {
223         case FindAndReplaceOptions::S_BUFFER:
224                 restarted = true;
225                 break;
226         case FindAndReplaceOptions::S_DOCUMENT:
227                 if (opt.forward)
228                         restarted = next_document_buffer(buf);
229                 else
230                         restarted = prev_document_buffer(buf);
231                 break;
232         case FindAndReplaceOptions::S_OPEN_BUFFERS:
233                 if (opt.forward) {
234                         buf = theBufferList().next(buf);
235                         restarted = buf == *theBufferList().begin();
236                 } else {
237                         buf = theBufferList().previous(buf);
238                         restarted = buf == *(theBufferList().end() - 1);
239                 }
240                 break;
241         case FindAndReplaceOptions::S_ALL_MANUALS:
242                 vector<string> const & v = allManualsFiles();
243                 vector<string>::const_iterator it = find(v.begin(), v.end(), buf->absFileName());
244                 if (it == v.end()) {
245                         it = v.begin();
246                 } else if (opt.forward) {
247                         ++it;
248                         if (it == v.end()) {
249                                 it = v.begin();
250                                 restarted = true;
251                         }
252                 } else {
253                         if (it == v.begin()) {
254                                 it = v.end();
255                                 restarted = true;
256                         }
257                         --it;
258                 }
259                 FileName const & fname = FileName(*it);
260                 if (!theBufferList().exists(fname))
261                         guiApp->currentView()->loadDocument(fname, false);
262                 buf = theBufferList().getBuffer(fname);
263                 break;
264         }
265         return restarted;
266 }
267
268
269 /** Find the finest question message to post to the user */
270 docstring question_string(FindAndReplaceOptions const & opt)
271 {
272         docstring cur_pos = opt.forward ? _("End") : _("Begin");
273         docstring new_pos = opt.forward ? _("begin") : _("end");
274         docstring scope;
275         switch (opt.scope) {
276         case FindAndReplaceOptions::S_BUFFER:
277                 scope = _("file");
278                 break;
279         case FindAndReplaceOptions::S_DOCUMENT:
280                 scope = _("master document");
281                 break;
282         case FindAndReplaceOptions::S_OPEN_BUFFERS:
283                 scope = _("open files");
284                 break;
285         case FindAndReplaceOptions::S_ALL_MANUALS:
286                 scope = _("manuals");
287                 break;
288         }
289         docstring dir = opt.forward ? _("forward") : _("backwards");
290         return cur_pos + _(" of ") + scope
291                 + _(" reached while searching ") + dir + ".\n"
292                 + "\n"
293                 + _("Continue searching from ") + new_pos + " ?";
294 }
295
296
297 void FindAndReplaceWidget::findAndReplaceScope(FindAndReplaceOptions & opt) {
298         int wrap_answer = -1;
299         ostringstream oss;
300         oss << opt;
301         FuncRequest cmd(LFUN_WORD_FINDADV, from_utf8(oss.str()));
302         BufferView * bv = view_.documentBufferView();
303         Buffer * buf = &bv->buffer();
304
305         Buffer * buf_orig = &bv->buffer();
306         Cursor cur_orig(bv->cursor());
307
308         if (opt.scope == FindAndReplaceOptions::S_ALL_MANUALS) {
309                 vector<string> const & v = allManualsFiles();
310                 if (std::find(v.begin(), v.end(), buf->absFileName()) == v.end()) {
311                         FileName const & fname = FileName(*v.begin());
312                         if (!theBufferList().exists(fname))
313                                 guiApp->currentView()->loadDocument(fname, false);
314                         buf = theBufferList().getBuffer(fname);
315                         lyx::dispatch(FuncRequest(LFUN_BUFFER_SWITCH,
316                                                   buf->absFileName()));
317                         bv = view_.documentBufferView();
318                         bv->cursor().clear();
319                         bv->cursor().push_back(CursorSlice(buf->inset()));
320                 }
321         }
322
323         do {
324                 LYXERR(Debug::FIND, "Dispatching LFUN_WORD_FINDADV");
325                 dispatch(cmd);
326                 if (bv->cursor().result().dispatched()) {
327                         // Match found, selected and replaced if needed
328                         return;
329                 }
330
331                 // No match found in current buffer: select next buffer in scope, if any
332                 bool prompt = next_prev_buffer(buf, opt);
333                 if (prompt) {
334                         if (wrap_answer != -1)
335                                 break;
336                         docstring q = question_string(opt);
337                         wrap_answer = frontend::Alert::prompt(
338                                 _("Wrap search?"), q,
339                                 0, 1, _("&Yes"), _("&No"));
340                         if (wrap_answer == 1)
341                                 break;
342                 }
343                 lyx::dispatch(FuncRequest(LFUN_BUFFER_SWITCH,
344                                           buf->absFileName()));
345                 bv = view_.documentBufferView();
346                 if (opt.forward) {
347                         bv->cursor().clear();
348                         bv->cursor().push_back(CursorSlice(buf->inset()));
349                 } else {
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: " << bv->cursor());
354                 }
355                 bv->clearSelection();
356         } while (wrap_answer != 1);
357         lyx::dispatch(FuncRequest(LFUN_BUFFER_SWITCH,
358                                   buf_orig->absFileName()));
359         bv = view_.documentBufferView();
360         bv->cursor() = cur_orig;
361 }
362
363
364 void FindAndReplaceWidget::findAndReplace(
365         bool casesensitive, bool matchword, bool backwards,
366         bool expandmacros, bool ignoreformat, bool replace,
367         bool keep_case)
368 {
369         Buffer & buffer = find_work_area_->bufferView().buffer();
370         docstring searchString;
371         if (!ignoreformat) {
372                 searchString = buffer_to_latex(buffer);
373         } else {
374                 ParIterator it = buffer.par_iterator_begin();
375                 ParIterator end = buffer.par_iterator_end();
376                 OutputParams runparams(&buffer.params().encoding());
377                 odocstringstream os;
378                 runparams.nice = true;
379                 runparams.flavor = OutputParams::LATEX;
380                 runparams.linelen = 100000; //lyxrc.plaintext_linelen;
381                 runparams.dryrun = true;
382                 for (; it != end; ++it) {
383                         LYXERR(Debug::FIND, "Adding to search string: '" << it->asString(false) << "'");
384                         searchString += it->stringify(pos_type(0), it->size(), AS_STR_INSETS, runparams);
385                 }
386         }
387         if (to_utf8(searchString).empty()) {
388                 buffer.message(_("Nothing to search"));
389                 return;
390         }
391         bool const regexp = to_utf8(searchString).find("\\regexp") != std::string::npos;
392         docstring replaceString;
393         if (replace) {
394                 Buffer & repl_buffer = replace_work_area_->bufferView().buffer();
395                 ostringstream oss;
396                 repl_buffer.write(oss);
397                 replaceString = from_utf8(oss.str()); //buffer_to_latex(replace_buffer);
398         } else {
399                 replaceString = from_utf8(LYX_FR_NULL_STRING);
400         }
401         FindAndReplaceOptions::SearchScope scope = FindAndReplaceOptions::S_BUFFER;
402         if (CurrentDocument->isChecked())
403                 scope = FindAndReplaceOptions::S_BUFFER;
404         else if (MasterDocument->isChecked())
405                 scope = FindAndReplaceOptions::S_DOCUMENT;
406         else if (OpenDocuments->isChecked())
407                 scope = FindAndReplaceOptions::S_OPEN_BUFFERS;
408         else if (AllManualsRB->isChecked())
409                 scope = FindAndReplaceOptions::S_ALL_MANUALS;
410         else
411                 LASSERT(false, /**/);
412         LYXERR(Debug::FIND, "FindAndReplaceOptions: "
413                << "searchstring=" << searchString
414                << ", casesensitiv=" << casesensitive
415                << ", matchword=" << matchword
416                << ", backwards=" << backwards
417                << ", expandmacros=" << expandmacros
418                << ", ignoreformat=" << ignoreformat
419                << ", regexp=" << regexp
420                << ", replaceString" << replaceString
421                << ", keep_case=" << keep_case
422                << ", scope=" << scope);
423         FindAndReplaceOptions opt(searchString, casesensitive, matchword, ! backwards,
424                 expandmacros, ignoreformat, regexp, replaceString, keep_case, scope);
425         findAndReplaceScope(opt);
426 }
427
428
429 void FindAndReplaceWidget::findAndReplace(bool backwards, bool replace)
430 {
431         if (! view_.currentMainWorkArea()) {
432                 view_.message(_("No open document(s) in which to search"));
433                 return;
434         }
435         // FIXME: create a Dialog::returnFocus() or something instead of this:
436         view_.setCurrentWorkArea(view_.currentMainWorkArea());
437         findAndReplace(caseCB->isChecked(),
438                 wordsCB->isChecked(),
439                 backwards,
440                 expandMacrosCB->isChecked(),
441                 ignoreFormatCB->isChecked(),
442                 replace,
443                 keepCaseCB->isChecked());
444 }
445
446
447 void FindAndReplaceWidget::on_regexpInsertCombo_currentIndexChanged(int index)
448 {
449         static char const * regexps[] = {
450                 ".*", ".+", "[a-z]+", "[0-9]+", ""
451         };
452         LYXERR(Debug::FIND, "Index: " << index);
453         if (index >= 1 && index < 1 + int(sizeof(regexps)/sizeof(regexps[0]))) {
454                 find_work_area_->setFocus();
455                 Cursor & cur = find_work_area_->bufferView().cursor();
456                 if (! cur.inRegexped())
457                         dispatch(FuncRequest(LFUN_REGEXP_MODE));
458                 dispatch(FuncRequest(LFUN_SELF_INSERT, regexps[index - 1]));
459                 regexpInsertCombo->setCurrentIndex(0);
460         }
461 }
462
463
464 void FindAndReplaceWidget::on_closePB_clicked()
465 {
466         dispatch(FuncRequest(LFUN_DIALOG_TOGGLE, "findreplaceadv"));
467 }
468
469
470 void FindAndReplaceWidget::on_findNextPB_clicked() {
471         findAndReplace(false, false);
472         find_work_area_->setFocus();
473 }
474
475
476 void FindAndReplaceWidget::on_findPrevPB_clicked() {
477         findAndReplace(true, false);
478         find_work_area_->setFocus();
479 }
480
481
482 void FindAndReplaceWidget::on_replaceNextPB_clicked()
483 {
484         findAndReplace(false, true);
485         replace_work_area_->setFocus();
486 }
487
488
489 void FindAndReplaceWidget::on_replacePrevPB_clicked()
490 {
491         findAndReplace(true, true);
492         replace_work_area_->setFocus();
493 }
494
495
496 void FindAndReplaceWidget::on_replaceallPB_clicked()
497 {
498         replace_work_area_->setFocus();
499 }
500
501
502 void FindAndReplaceWidget::showEvent(QShowEvent * /* ev */)
503 {
504         view_.setCurrentWorkArea(find_work_area_);
505         LYXERR(Debug::FIND, "Selecting entire find buffer");
506         dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
507         dispatch(FuncRequest(LFUN_BUFFER_END_SELECT));
508         find_work_area_->installEventFilter(this);
509         replace_work_area_->installEventFilter(this);
510 }
511
512
513 void FindAndReplaceWidget::hideEvent(QHideEvent *ev)
514 {
515         replace_work_area_->removeEventFilter(this);
516         find_work_area_->removeEventFilter(this);
517         this->QWidget::hideEvent(ev);
518 }
519
520
521 bool FindAndReplaceWidget::initialiseParams(std::string const & /* params */)
522 {
523         return true;
524 }
525
526
527 FindAndReplace::FindAndReplace(GuiView & parent,
528                 Qt::DockWidgetArea area, Qt::WindowFlags flags)
529         : DockView(parent, "Find LyX", qt_("Find LyX Dialog"), area, flags)
530 {
531         widget_ = new FindAndReplaceWidget(parent);
532         setWidget(widget_);
533         setFocusProxy(widget_);
534 }
535
536
537 FindAndReplace::~FindAndReplace()
538 {
539         setFocusProxy(0);
540         delete widget_;
541 }
542
543
544 bool FindAndReplace::initialiseParams(std::string const & params)
545 {
546         return widget_->initialiseParams(params);
547 }
548
549
550 Dialog * createGuiSearchAdv(GuiView & lv)
551 {
552         return new FindAndReplace(lv, Qt::RightDockWidgetArea);
553 }
554
555
556 } // namespace frontend
557 } // namespace lyx
558
559
560 #include "moc_FindAndReplace.cpp"