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