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