]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/FindAndReplace.cpp
Fixed overlapping of both Find Advanced and Spellchecker Dockwidgets (addressing...
[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         : QTabWidget(&view), 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 /// Return true if a match was found
297 bool FindAndReplaceWidget::findAndReplaceScope(FindAndReplaceOptions & opt, bool replace_all)
298 {
299         BufferView * bv = view_.documentBufferView();
300         if (!bv)
301                 return false;
302         Buffer * buf = &bv->buffer();
303         Buffer * buf_orig = &bv->buffer();
304         DocIterator cur_orig(bv->cursor());
305         int wrap_answer = -1;
306         ostringstream oss;
307         oss << opt;
308         FuncRequest cmd(LFUN_WORD_FINDADV, from_utf8(oss.str()));
309
310         view_.setBusy(true);
311         if (opt.scope == FindAndReplaceOptions::S_ALL_MANUALS) {
312                 vector<string> const & v = allManualsFiles();
313                 if (std::find(v.begin(), v.end(), buf->absFileName()) == v.end()) {
314                         FileName const & fname = FileName(*v.begin());
315                         if (!theBufferList().exists(fname)) {
316                                 guiApp->currentView()->setBusy(false);
317                                 guiApp->currentView()->loadDocument(fname, false);
318                                 guiApp->currentView()->setBusy(true);
319                         }
320                         buf = theBufferList().getBuffer(fname);
321                         lyx::dispatch(FuncRequest(LFUN_BUFFER_SWITCH,
322                                                   buf->absFileName()));
323                         bv = view_.documentBufferView();
324                         bv->cursor().clear();
325                         bv->cursor().push_back(CursorSlice(buf->inset()));
326                 }
327         }
328
329         do {
330                 LYXERR(Debug::FIND, "Dispatching LFUN_WORD_FINDADV");
331                 dispatch(cmd);
332                 LYXERR(Debug::FIND, "dispatched");
333                 if (bv->cursor().result().dispatched()) {
334                         // New match found and selected (old selection replaced if needed)
335                         if (replace_all)
336                                 continue;
337                         view_.setBusy(false);
338                         return true;
339                 }
340
341                 // No match found in current buffer (however old selection might have been replaced)
342                 // select next buffer in scope, if any
343                 bool prompt = nextPrevBuffer(buf, opt);
344                 if (prompt) {
345                         if (wrap_answer != -1)
346                                 break;
347                         docstring q = getQuestionString(opt);
348                         view_.setBusy(false);
349                         wrap_answer = frontend::Alert::prompt(
350                                 _("Wrap search?"), q,
351                                 0, 1, _("&Yes"), _("&No"));
352                         view_.setBusy(true);
353                         if (wrap_answer == 1)
354                                 break;
355                 }
356                 if (buf != &view_.documentBufferView()->buffer())
357                         lyx::dispatch(FuncRequest(LFUN_BUFFER_SWITCH,
358                                                   buf->absFileName()));
359                 bv = view_.documentBufferView();
360                 if (opt.forward) {
361                         bv->cursor().clear();
362                         bv->cursor().push_back(CursorSlice(buf->inset()));
363                 } else {
364                         //lyx::dispatch(FuncRequest(LFUN_BUFFER_END));
365                         bv->cursor().setCursor(doc_iterator_end(buf));
366                         bv->cursor().backwardPos();
367                         LYXERR(Debug::FIND, "findBackAdv5: cur: "
368                                 << bv->cursor());
369                 }
370                 bv->clearSelection();
371         } while (wrap_answer != 1);
372         if (buf_orig != &view_.documentBufferView()->buffer())
373                 lyx::dispatch(FuncRequest(LFUN_BUFFER_SWITCH,
374                                           buf_orig->absFileName()));
375         bv = view_.documentBufferView();
376         // This may happen after a replace occurred
377         if (cur_orig.pos() > cur_orig.lastpos())
378                 cur_orig.pos() = cur_orig.lastpos();
379         bv->cursor().setCursor(cur_orig);
380         view_.setBusy(false);
381         return false;
382 }
383
384
385 /// Return true if a match was found
386 bool FindAndReplaceWidget::findAndReplace(
387         bool casesensitive, bool matchword, bool backwards,
388         bool expandmacros, bool ignoreformat, bool replace,
389         bool keep_case, bool replace_all)
390 {
391         Buffer & find_buf = find_work_area_->bufferView().buffer();
392         docstring const & find_buf_name = find_buf.fileName().absoluteFilePath();
393
394         if (find_buf.text().empty()) {
395                 view_.message(_("Nothing to search"));
396                 return false;
397         }
398
399         Buffer & repl_buf = replace_work_area_->bufferView().buffer();
400         docstring const & repl_buf_name = replace ?
401                 repl_buf.fileName().absoluteFilePath() : docstring();
402
403         FindAndReplaceOptions::SearchScope scope =
404                 FindAndReplaceOptions::S_BUFFER;
405         if (CurrentDocument->isChecked())
406                 scope = FindAndReplaceOptions::S_BUFFER;
407         else if (MasterDocument->isChecked())
408                 scope = FindAndReplaceOptions::S_DOCUMENT;
409         else if (OpenDocuments->isChecked())
410                 scope = FindAndReplaceOptions::S_OPEN_BUFFERS;
411         else if (AllManualsRB->isChecked())
412                 scope = FindAndReplaceOptions::S_ALL_MANUALS;
413         else
414                 LASSERT(false, /**/);
415         LYXERR(Debug::FIND, "FindAndReplaceOptions: "
416                << "find_buf_name=" << find_buf_name
417                << ", casesensitiv=" << casesensitive
418                << ", matchword=" << matchword
419                << ", backwards=" << backwards
420                << ", expandmacros=" << expandmacros
421                << ", ignoreformat=" << ignoreformat
422                << ", repl_buf_name" << repl_buf_name
423                << ", keep_case=" << keep_case
424                << ", scope=" << scope);
425         FindAndReplaceOptions opt(find_buf_name, casesensitive, matchword,
426                                   !backwards, expandmacros, ignoreformat,
427                                   repl_buf_name, keep_case, scope);
428         return findAndReplaceScope(opt, replace_all);
429 }
430
431
432 bool FindAndReplaceWidget::findAndReplace(bool backwards, bool replace, bool replace_all)
433 {
434         if (! view_.currentMainWorkArea()) {
435                 view_.message(_("No open document(s) in which to search"));
436                 return false;
437         }
438         // Finalize macros that are being typed, both in main document and in search or replacement WAs
439         if (view_.currentWorkArea()->bufferView().cursor().macroModeClose())
440                 view_.currentWorkArea()->bufferView().processUpdateFlags(Update::Force);
441         if (view_.currentMainWorkArea()->bufferView().cursor().macroModeClose())
442                 view_.currentMainWorkArea()->bufferView().processUpdateFlags(Update::Force);
443
444         // FIXME: create a Dialog::returnFocus()
445         // or something instead of this:
446         view_.setCurrentWorkArea(view_.currentMainWorkArea());
447         return findAndReplace(caseCB->isChecked(),
448                 wordsCB->isChecked(),
449                 backwards,
450                 expandMacrosCB->isChecked(),
451                 ignoreFormatCB->isChecked(),
452                 replace,
453                 keepCaseCB->isChecked(),
454                 replace_all);
455 }
456
457
458 void FindAndReplaceWidget::hideDialog()
459 {
460         dispatch(FuncRequest(LFUN_DIALOG_TOGGLE, "findreplaceadv"));
461 }
462
463
464 void FindAndReplaceWidget::on_findNextPB_clicked() 
465 {
466         findAndReplace(searchbackCB->isChecked(), false);
467         find_work_area_->setFocus();
468 }
469
470
471 void FindAndReplaceWidget::on_replacePB_clicked()
472 {
473         findAndReplace(searchbackCB->isChecked(), true);
474         replace_work_area_->setFocus();
475 }
476
477
478 void FindAndReplaceWidget::on_replaceallPB_clicked()
479 {
480         findAndReplace(searchbackCB->isChecked(), true, true);
481         replace_work_area_->setFocus();
482 }
483
484
485 void FindAndReplaceWidget::showEvent(QShowEvent * /* ev */)
486 {
487         LYXERR(Debug::DEBUG, "showEvent()" << endl);
488         BufferView * bv = view_.documentBufferView();
489         if (bv) {
490                 Buffer & doc_buf = bv->buffer();
491                 BufferParams & doc_bp = doc_buf.params();
492                 Buffer & find_buf = find_work_area_->bufferView().buffer();
493                 LYXERR(Debug::FIND, "Applying document params to find buffer");
494                 ApplyParams(find_buf, doc_bp);
495                 Buffer & replace_buf = replace_work_area_->bufferView().buffer();
496                 LYXERR(Debug::FIND, "Applying document params to replace buffer");
497                 ApplyParams(replace_buf, doc_bp);
498
499                 string lang = doc_bp.language->lang();
500                 LYXERR(Debug::FIND, "Setting current editing language to " << lang << endl);
501                 FuncRequest cmd(LFUN_LANGUAGE, lang);
502                 find_buf.text().dispatch(find_work_area_->bufferView().cursor(), cmd);
503                 replace_buf.text().dispatch(replace_work_area_->bufferView().cursor(), cmd);
504         }
505
506         find_work_area_->installEventFilter(this);
507         replace_work_area_->installEventFilter(this);
508
509         view_.setCurrentWorkArea(find_work_area_);
510         LYXERR(Debug::FIND, "Selecting entire find buffer");
511         dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
512         dispatch(FuncRequest(LFUN_BUFFER_END_SELECT));
513 }
514
515
516 void FindAndReplaceWidget::hideEvent(QHideEvent *ev)
517 {
518         replace_work_area_->removeEventFilter(this);
519         find_work_area_->removeEventFilter(this);
520         this->QWidget::hideEvent(ev);
521 }
522
523
524 bool FindAndReplaceWidget::initialiseParams(std::string const & /*params*/)
525 {
526         return true;
527 }
528
529
530 FindAndReplace::FindAndReplace(GuiView & parent,
531                 Qt::DockWidgetArea area, Qt::WindowFlags flags)
532         : DockView(parent, "findreplaceadv", qt_("Advanced Find and Replace"),
533                    area, flags)
534 {
535         widget_ = new FindAndReplaceWidget(parent);
536         setWidget(widget_);
537         setFocusProxy(widget_);
538 }
539
540
541 FindAndReplace::~FindAndReplace()
542 {
543         setFocusProxy(0);
544         delete widget_;
545 }
546
547
548 bool FindAndReplace::initialiseParams(std::string const & params)
549 {
550         return widget_->initialiseParams(params);
551 }
552
553
554 Dialog * createGuiSearchAdv(GuiView & lv)
555 {
556         FindAndReplace * gui = new FindAndReplace(lv, Qt::RightDockWidgetArea);
557 #ifdef Q_WS_MACX
558         // On Mac show and floating
559         gui->setFloating(true);
560 #endif
561         return gui;
562 }
563
564
565 } // namespace frontend
566 } // namespace lyx
567
568
569 #include "moc_FindAndReplace.cpp"