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