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