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