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