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