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