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