]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/FindAndReplace.cpp
Correct early return position for if use_pixmap_cache_ check
[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.h"
23 #include "BufferParams.h"
24 #include "BufferList.h"
25 #include "BufferView.h"
26 #include "Text.h"
27 #include "TextClass.h"
28 #include "Cursor.h"
29 #include "FuncRequest.h"
30 #include "LyX.h"
31 #include "lyxfind.h"
32
33 #include "frontends/alert.h"
34
35 #include "support/debug.h"
36 #include "support/filetools.h"
37 #include "support/FileName.h"
38 #include "support/gettext.h"
39 #include "support/lassert.h"
40 #include "support/lstrings.h"
41
42 #include <QCloseEvent>
43 #include <QLineEdit>
44 #include <QMenu>
45
46 using namespace std;
47 using namespace lyx::support;
48
49 namespace lyx {
50 namespace frontend {
51
52
53 FindAndReplaceWidget::FindAndReplaceWidget(GuiView & view)
54         : QTabWidget(&view), view_(view)
55 {
56         setupUi(this);
57         find_work_area_->setGuiView(view_);
58         find_work_area_->init();
59         find_work_area_->setFrameStyle(QFrame::StyledPanel);
60
61         setFocusProxy(find_work_area_);
62         replace_work_area_->setGuiView(view_);
63         replace_work_area_->init();
64         replace_work_area_->setFrameStyle(QFrame::StyledPanel);
65
66         // We don't want two cursors blinking.
67         find_work_area_->stopBlinkingCursor();
68         replace_work_area_->stopBlinkingCursor();
69 }
70
71
72 void FindAndReplaceWidget::dockLocationChanged(Qt::DockWidgetArea area)
73 {
74         if (area == Qt::RightDockWidgetArea || area == Qt::LeftDockWidgetArea) {
75                 dynamicLayoutBasic_->setDirection(QBoxLayout::TopToBottom);
76                 dynamicLayoutAdvanced_->setDirection(QBoxLayout::TopToBottom);
77         } else {
78                 dynamicLayoutBasic_->setDirection(QBoxLayout::LeftToRight);
79                 dynamicLayoutAdvanced_->setDirection(QBoxLayout::LeftToRight);
80         }
81 }
82
83
84 bool FindAndReplaceWidget::eventFilter(QObject * obj, QEvent * event)
85 {
86         if (event->type() != QEvent::KeyPress
87                   || (obj != find_work_area_ && obj != replace_work_area_))
88                 return QWidget::eventFilter(obj, event);
89
90         QKeyEvent * e = static_cast<QKeyEvent *> (event);
91         switch (e->key()) {
92         case Qt::Key_Escape:
93                 if (e->modifiers() == Qt::NoModifier) {
94                         hideDialog();
95                         return true;
96                 }
97                 break;
98
99         case Qt::Key_Enter:
100         case Qt::Key_Return: {
101                 // with shift we (temporarily) change search/replace direction
102                 bool const searchback = searchbackCB->isChecked();
103                 if (e->modifiers() == Qt::ShiftModifier && !searchback)
104                         searchbackCB->setChecked(!searchback);
105
106                 if (obj == find_work_area_)
107                         on_findNextPB_clicked();
108                 else
109                         on_replacePB_clicked();
110                 // back to how it was
111                 searchbackCB->setChecked(searchback);
112                 return true;
113         }
114
115         case Qt::Key_Tab:
116                 if (e->modifiers() == Qt::NoModifier) {
117                         if (obj == find_work_area_){
118                                 LYXERR(Debug::FIND, "Focusing replace WA");
119                                 replace_work_area_->setFocus();
120                                 LYXERR(Debug::FIND, "Selecting entire replace buffer");
121                                 dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
122                                 dispatch(FuncRequest(LFUN_BUFFER_END_SELECT));
123                                 return true;
124                         }
125                 }
126                 break;
127
128         case Qt::Key_Backtab:
129                 if (obj == replace_work_area_) {
130                         LYXERR(Debug::FIND, "Focusing find WA");
131                         find_work_area_->setFocus();
132                         LYXERR(Debug::FIND, "Selecting entire find buffer");
133                         dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
134                         dispatch(FuncRequest(LFUN_BUFFER_END_SELECT));
135                         return true;
136                 }
137                 break;
138
139         default:
140                 break;
141         }
142         // standard event processing
143         return QWidget::eventFilter(obj, event);
144 }
145
146
147 static vector<string> const & allManualsFiles()
148 {
149         static const char * files[] = {
150                 "Intro", "UserGuide", "Tutorial", "Additional",
151                 "EmbeddedObjects", "Math", "Customization", "Shortcuts",
152                 "LFUNs", "LaTeXConfig"
153         };
154
155         static vector<string> v;
156         if (v.empty()) {
157                 FileName fname;
158                 for (size_t i = 0; i < sizeof(files) / sizeof(files[0]); ++i) {
159                         fname = i18nLibFileSearch("doc", files[i], "lyx");
160                         v.push_back(fname.absFileName());
161                 }
162         }
163
164         return v;
165 }
166
167
168 /** Switch buf to point to next document buffer.
169  **
170  ** Return true if restarted from master-document buffer.
171  **/
172 static bool nextDocumentBuffer(Buffer * & buf)
173 {
174         ListOfBuffers const children = buf->allRelatives();
175         LYXERR(Debug::FIND, "children.size()=" << children.size());
176         ListOfBuffers::const_iterator it =
177                 find(children.begin(), children.end(), buf);
178         LASSERT(it != children.end(), return false);
179         ++it;
180         if (it == children.end()) {
181                 buf = *children.begin();
182                 return true;
183         }
184         buf = *it;
185         return false;
186 }
187
188
189 /** Switch p_buf to point to previous document buffer.
190  **
191  ** Return true if restarted from last child buffer.
192  **/
193 static bool prevDocumentBuffer(Buffer * & buf)
194 {
195         ListOfBuffers const children = buf->allRelatives();
196         LYXERR(Debug::FIND, "children.size()=" << children.size());
197         ListOfBuffers::const_iterator it =
198                 find(children.begin(), children.end(), buf);
199         LASSERT(it != children.end(), return false)
200         if (it == children.begin()) {
201                 it = children.end();
202                 --it;
203                 buf = *it;
204                 return true;
205         }
206         --it;
207         buf = *it;
208         return false;
209 }
210
211
212 /** Switch buf to point to next or previous buffer in search scope.
213  **
214  ** Return true if restarted from scratch.
215  **/
216 static bool nextPrevBuffer(Buffer * & buf,
217                              FindAndReplaceOptions const & opt)
218 {
219         bool restarted = false;
220         switch (opt.scope) {
221         case FindAndReplaceOptions::S_BUFFER:
222                 restarted = true;
223                 break;
224         case FindAndReplaceOptions::S_DOCUMENT:
225                 if (opt.forward)
226                         restarted = nextDocumentBuffer(buf);
227                 else
228                         restarted = prevDocumentBuffer(buf);
229                 break;
230         case FindAndReplaceOptions::S_OPEN_BUFFERS:
231                 if (opt.forward) {
232                         buf = theBufferList().next(buf);
233                         restarted = (buf == *theBufferList().begin());
234                 } else {
235                         buf = theBufferList().previous(buf);
236                         restarted = (buf == *(theBufferList().end() - 1));
237                 }
238                 break;
239         case FindAndReplaceOptions::S_ALL_MANUALS:
240                 vector<string> const & manuals = allManualsFiles();
241                 vector<string>::const_iterator it =
242                         find(manuals.begin(), manuals.end(), buf->absFileName());
243                 if (it == manuals.end())
244                         it = manuals.begin();
245                 else if (opt.forward) {
246                         ++it;
247                         if (it == manuals.end()) {
248                                 it = manuals.begin();
249                                 restarted = true;
250                         }
251                 } else {
252                         if (it == manuals.begin()) {
253                                 it = manuals.end();
254                                 restarted = true;
255                         }
256                         --it;
257                 }
258                 FileName const & fname = FileName(*it);
259                 if (!theBufferList().exists(fname)) {
260                         guiApp->currentView()->setBusy(false);
261                         guiApp->currentView()->loadDocument(fname, false);
262                         guiApp->currentView()->setBusy(true);
263                 }
264                 buf = theBufferList().getBuffer(fname);
265                 break;
266         }
267         return restarted;
268 }
269
270
271 /** Find the finest question message to post to the user */
272 docstring getQuestionString(FindAndReplaceOptions const & opt)
273 {
274         docstring scope;
275         switch (opt.scope) {
276         case FindAndReplaceOptions::S_BUFFER:
277                 scope = _("File");
278                 break;
279         case FindAndReplaceOptions::S_DOCUMENT:
280                 scope = _("Master document");
281                 break;
282         case FindAndReplaceOptions::S_OPEN_BUFFERS:
283                 scope = _("Open files");
284                 break;
285         case FindAndReplaceOptions::S_ALL_MANUALS:
286                 scope = _("Manuals");
287                 break;
288         }
289         docstring message = opt.forward ?
290                 bformat(_("%1$s: the end was reached while searching forward.\n"
291                           "Continue searching from the beginning?"),
292                         scope) :
293                 bformat(_("%1$s: the beginning was reached while searching backward.\n"
294                           "Continue searching from the end?"),
295                         scope);
296
297         return message;
298 }
299
300
301 /// Return true if a match was found
302 bool FindAndReplaceWidget::findAndReplaceScope(FindAndReplaceOptions & opt, bool replace_all)
303 {
304         BufferView * bv = view_.documentBufferView();
305         if (!bv)
306                 return false;
307         Buffer * buf = &bv->buffer();
308         Buffer * buf_orig = &bv->buffer();
309         DocIterator cur_orig(bv->cursor());
310         int wrap_answer = -1;
311         ostringstream oss;
312         oss << opt;
313         FuncRequest cmd(LFUN_WORD_FINDADV, from_utf8(oss.str()));
314
315         view_.message(_("Advanced search in progress (press ESC to cancel) . . ."));
316         theApp()->startLongOperation();
317         view_.setBusy(true);
318         if (opt.scope == FindAndReplaceOptions::S_ALL_MANUALS) {
319                 vector<string> const & v = allManualsFiles();
320                 if (std::find(v.begin(), v.end(), buf->absFileName()) == v.end()) {
321                         FileName const & fname = FileName(*v.begin());
322                         if (!theBufferList().exists(fname)) {
323                                 guiApp->currentView()->setBusy(false);
324                                 theApp()->stopLongOperation();
325                                 guiApp->currentView()->loadDocument(fname, false);
326                                 theApp()->startLongOperation();
327                                 guiApp->currentView()->setBusy(true);
328                         }
329                         buf = theBufferList().getBuffer(fname);
330                         if (!buf) {
331                                 view_.setBusy(false);
332                                 return false;
333                         }
334
335                         lyx::dispatch(FuncRequest(LFUN_BUFFER_SWITCH,
336                                                   buf->absFileName()));
337                         bv = view_.documentBufferView();
338                         bv->cursor().clear();
339                         bv->cursor().push_back(CursorSlice(buf->inset()));
340                 }
341         }
342
343         UndoGroupHelper helper(buf);
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                         if (replace_all)
352                                 continue;
353                         view_.setBusy(false);
354                         theApp()->stopLongOperation();
355                         return true;
356                 } else if (replace_all)
357                         bv->clearSelection();
358
359                 if (theApp()->longOperationCancelled()) {
360                         // Search aborted by user
361                         view_.message(_("Advanced search cancelled by user"));
362                         view_.setBusy(false);
363                         theApp()->stopLongOperation();
364                         return false;
365                 }
366
367                 // No match found in current buffer (however old selection might have been replaced)
368                 // select next buffer in scope, if any
369                 bool const prompt = nextPrevBuffer(buf, opt);
370                 if (!buf)
371                         break;
372                 if (prompt) {
373                         if (wrap_answer != -1)
374                                 break;
375                         docstring q = getQuestionString(opt);
376                         view_.setBusy(false);
377                         theApp()->stopLongOperation();
378                         wrap_answer = frontend::Alert::prompt(
379                                 _("Wrap search?"), q,
380                                 0, 1, _("&Yes"), _("&No"));
381                         theApp()->startLongOperation();
382                         view_.setBusy(true);
383                         if (wrap_answer == 1)
384                                 break;
385                 }
386                 if (buf != &view_.documentBufferView()->buffer())
387                         lyx::dispatch(FuncRequest(LFUN_BUFFER_SWITCH,
388                                                   buf->absFileName()));
389
390                 helper.resetBuffer(buf);
391
392                 bv = view_.documentBufferView();
393                 if (opt.forward) {
394                         bv->cursor().clear();
395                         bv->cursor().push_back(CursorSlice(buf->inset()));
396                 } else {
397                         //lyx::dispatch(FuncRequest(LFUN_BUFFER_END));
398                         bv->cursor().setCursor(doc_iterator_end(buf));
399                         bv->cursor().backwardPos();
400                         LYXERR(Debug::FIND, "findBackAdv5: cur: "
401                                 << bv->cursor());
402                 }
403                 bv->clearSelection();
404         } while (wrap_answer != 1);
405
406         if (buf_orig != &view_.documentBufferView()->buffer())
407                 lyx::dispatch(FuncRequest(LFUN_BUFFER_SWITCH,
408                                           buf_orig->absFileName()));
409         bv = view_.documentBufferView();
410         // This may happen after a replace occurred
411         if (cur_orig.pos() > cur_orig.lastpos())
412                 cur_orig.pos() = cur_orig.lastpos();
413         bv->cursor().setCursor(cur_orig);
414         view_.setBusy(false);
415         theApp()->stopLongOperation();
416         return false;
417 }
418
419
420 /// Return true if a match was found
421 bool FindAndReplaceWidget::findAndReplace(
422         bool casesensitive, bool matchword, bool backwards,
423         bool expandmacros, bool ignoreformat, bool replace,
424         bool keep_case, bool replace_all)
425 {
426         Buffer & find_buf = find_work_area_->bufferView().buffer();
427         docstring const & find_buf_name = find_buf.fileName().absoluteFilePath();
428
429         if (find_buf.text().empty()) {
430                 view_.message(_("Nothing to search"));
431                 return false;
432         }
433
434         Buffer & repl_buf = replace_work_area_->bufferView().buffer();
435         docstring const & repl_buf_name = replace ?
436                 repl_buf.fileName().absoluteFilePath() : docstring();
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                 LATTEST(false);
450
451         FindAndReplaceOptions::SearchRestriction restr =
452                 OnlyMaths->isChecked()
453                         ? FindAndReplaceOptions::R_ONLY_MATHS
454                         : FindAndReplaceOptions::R_EVERYTHING;
455
456         LYXERR(Debug::FIND, "FindAndReplaceOptions: "
457                << "find_buf_name=" << find_buf_name
458                << ", casesensitiv=" << casesensitive
459                << ", matchword=" << matchword
460                << ", backwards=" << backwards
461                << ", expandmacros=" << expandmacros
462                << ", ignoreformat=" << ignoreformat
463                << ", repl_buf_name" << repl_buf_name
464                << ", keep_case=" << keep_case
465                << ", scope=" << scope
466                << ", restr=" << restr);
467
468         FindAndReplaceOptions opt(find_buf_name, casesensitive, matchword,
469                                   !backwards, expandmacros, ignoreformat,
470                                   repl_buf_name, keep_case, scope, restr);
471         return findAndReplaceScope(opt, replace_all);
472 }
473
474
475 bool FindAndReplaceWidget::findAndReplace(bool backwards, bool replace, bool replace_all)
476 {
477         if (! view_.currentMainWorkArea()) {
478                 view_.message(_("No open document(s) in which to search"));
479                 return false;
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         return findAndReplace(caseCB->isChecked(),
491                 wordsCB->isChecked(),
492                 backwards,
493                 expandMacrosCB->isChecked(),
494                 ignoreFormatCB->isChecked(),
495                 replace,
496                 keepCaseCB->isChecked(),
497                 replace_all);
498 }
499
500
501 void FindAndReplaceWidget::hideDialog()
502 {
503         dispatch(FuncRequest(LFUN_DIALOG_TOGGLE, "findreplaceadv"));
504 }
505
506
507 void FindAndReplaceWidget::on_findNextPB_clicked()
508 {
509         findAndReplace(searchbackCB->isChecked(), false);
510         find_work_area_->setFocus();
511 }
512
513
514 void FindAndReplaceWidget::on_replacePB_clicked()
515 {
516         findAndReplace(searchbackCB->isChecked(), true);
517         replace_work_area_->setFocus();
518 }
519
520
521 void FindAndReplaceWidget::on_replaceallPB_clicked()
522 {
523         findAndReplace(searchbackCB->isChecked(), true, true);
524         replace_work_area_->setFocus();
525 }
526
527
528 /** Copy selected elements from bv's BufferParams to the dest_bv's one
529  ** We don't want to copy'em all, e.g., not the default master **/
530 static void copy_params(BufferView const & bv, BufferView & dest_bv) {
531         Buffer const & doc_buf = bv.buffer();
532         BufferParams const & doc_bp = doc_buf.params();
533         string const & lang = doc_bp.language->lang();
534         string const & doc_class = doc_bp.documentClass().name();
535         Buffer & dest_buf = dest_bv.buffer();
536         dest_buf.params().setLanguage(lang);
537         dest_buf.params().setBaseClass(doc_class);
538         dest_bv.makeDocumentClass();
539         dest_bv.cursor().current_font.setLanguage(doc_bp.language);
540 }
541
542
543 void FindAndReplaceWidget::showEvent(QShowEvent * /* ev */)
544 {
545         LYXERR(Debug::DEBUG, "showEvent()" << endl);
546         BufferView * bv = view_.documentBufferView();
547         if (bv) {
548                 copy_params(*bv, find_work_area_->bufferView());
549                 copy_params(*bv, replace_work_area_->bufferView());
550         }
551
552         find_work_area_->installEventFilter(this);
553         replace_work_area_->installEventFilter(this);
554
555         view_.setCurrentWorkArea(find_work_area_);
556         LYXERR(Debug::FIND, "Selecting entire find buffer");
557         dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
558         dispatch(FuncRequest(LFUN_BUFFER_END_SELECT));
559 }
560
561
562 void FindAndReplaceWidget::hideEvent(QHideEvent *ev)
563 {
564         replace_work_area_->removeEventFilter(this);
565         find_work_area_->removeEventFilter(this);
566         this->QWidget::hideEvent(ev);
567 }
568
569
570 bool FindAndReplaceWidget::initialiseParams(std::string const & /*params*/)
571 {
572         return true;
573 }
574
575
576 void FindAndReplace::updateView()
577 {
578         widget_->updateGUI();
579 }
580
581
582 FindAndReplace::FindAndReplace(GuiView & parent,
583                 Qt::DockWidgetArea area, Qt::WindowFlags flags)
584         : DockView(parent, "findreplaceadv", qt_("Advanced Find and Replace"),
585                    area, flags)
586 {
587         widget_ = new FindAndReplaceWidget(parent);
588         setWidget(widget_);
589         setFocusProxy(widget_);
590
591         connect(this, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)),
592                 widget_, SLOT(dockLocationChanged(Qt::DockWidgetArea)));
593 }
594
595
596 FindAndReplace::~FindAndReplace()
597 {
598         setFocusProxy(0);
599         delete widget_;
600 }
601
602
603 bool FindAndReplace::initialiseParams(std::string const & params)
604 {
605         return widget_->initialiseParams(params);
606 }
607
608
609 void FindAndReplaceWidget::updateGUI()
610 {
611         bool replace_enabled = view_.documentBufferView()
612                 && !view_.documentBufferView()->buffer().isReadonly();
613         replace_work_area_->setEnabled(replace_enabled);
614         replacePB->setEnabled(replace_enabled);
615         replaceallPB->setEnabled(replace_enabled);
616 }
617
618
619 Dialog * createGuiSearchAdv(GuiView & lv)
620 {
621         FindAndReplace * gui = new FindAndReplace(lv, Qt::RightDockWidgetArea);
622 #ifdef Q_OS_MAC
623         // On Mac show and floating
624         gui->setFloating(true);
625 #endif
626         return gui;
627 }
628
629
630 } // namespace frontend
631 } // namespace lyx
632
633
634 #include "moc_FindAndReplace.cpp"