]> git.lyx.org Git - lyx.git/blob - src/frontends/qt/GuiSearch.cpp
Select search string after inserting from find buffer
[lyx.git] / src / frontends / qt / GuiSearch.cpp
1 /**
2  * \file GuiSearch.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author John Levon
7  * \author Edwin Leuven
8  * \author Angus Leeming
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "GuiApplication.h"
16 #include "GuiSearch.h"
17
18 #include "lyxfind.h"
19 #include "qt_helpers.h"
20 #include "FuncRequest.h"
21 #include "LyX.h"
22 #include "BufferView.h"
23 #include "Buffer.h"
24 #include "Cursor.h"
25 #include "FuncRequest.h"
26 #include "KeyMap.h"
27 #include "GuiKeySymbol.h"
28 #include "GuiView.h"
29
30 #include "support/debug.h"
31 #include "support/gettext.h"
32 #include "frontends/alert.h"
33 #include "frontends/Clipboard.h"
34
35 #include <QClipboard>
36 #include <QPainter>
37 #include <QLineEdit>
38 #include <QSettings>
39 #include <QShowEvent>
40 #include "QSizePolicy"
41
42 using namespace std;
43
44 using lyx::KeySymbol;
45
46 namespace lyx {
47 namespace frontend {
48
49 static void uniqueInsert(QComboBox * box, QString const & text)
50 {
51         for (int i = box->count(); --i >= 0; )
52                 if (box->itemText(i) == text)
53                         return;
54
55         box->insertItem(0, text);
56 }
57
58
59 GuiSearchWidget::GuiSearchWidget(QWidget * parent)
60         :       QWidget(parent)
61 {
62         setupUi(this);
63
64         // fix height to minimum
65         setFixedHeight(sizeHint().height());
66
67         // align items in grid on top
68         gridLayout->setAlignment(Qt::AlignTop);
69
70         connect(findPB, SIGNAL(clicked()), this, SLOT(findClicked()));
71         connect(findPrevPB, SIGNAL(clicked()), this, SLOT(findPrevClicked()));
72         connect(minimizePB, SIGNAL(clicked()), this, SLOT(minimizeClicked()));
73         connect(replacePB, SIGNAL(clicked()), this, SLOT(replaceClicked()));
74         connect(replacePrevPB, SIGNAL(clicked()), this, SLOT(replacePrevClicked()));
75         connect(replaceallPB, SIGNAL(clicked()), this, SLOT(replaceallClicked()));
76         connect(findCO, SIGNAL(editTextChanged(QString)),
77                 this, SLOT(findChanged()));
78         if(qApp->clipboard()->supportsFindBuffer()) {
79                 connect(qApp->clipboard(), SIGNAL(findBufferChanged()),
80                         this, SLOT(findBufferChanged()));
81                 findBufferChanged();
82         }
83
84         setFocusProxy(findCO);
85
86         // Use a FancyLineEdit due to the indicator icons
87         findLE_ = new FancyLineEdit(this);
88         findCO->setLineEdit(findLE_);
89
90         // And a menu in minimal mode
91         menu_ = new QMenu();
92         act_casesense_ = new QAction(qt_("&Case sensitive[[search]]"), this);
93         act_casesense_->setCheckable(true);
94         act_wholewords_ = new QAction(qt_("Wh&ole words"), this);
95         act_wholewords_->setCheckable(true);
96         act_selection_ = new QAction(qt_("Selection onl&y"), this);
97         act_selection_->setCheckable(true);
98         act_immediate_ = new QAction(qt_("Search as yo&u type"), this);
99         act_immediate_->setCheckable(true);
100         act_wrap_ = new QAction(qt_("&Wrap"), this);
101         act_wrap_->setCheckable(true);
102
103         menu_->addAction(act_casesense_);
104         menu_->addAction(act_wholewords_);
105         menu_->addAction(act_selection_);
106         menu_->addAction(act_immediate_);
107         menu_->addAction(act_wrap_);
108         findLE_->setButtonMenu(FancyLineEdit::Right, menu_);
109
110         connect(act_casesense_, SIGNAL(triggered()), this, SLOT(caseSenseActTriggered()));
111         connect(act_wholewords_, SIGNAL(triggered()), this, SLOT(wholeWordsActTriggered()));
112         connect(act_selection_, SIGNAL(triggered()), this, SLOT(searchSelActTriggered()));
113         connect(act_immediate_, SIGNAL(triggered()), this, SLOT(immediateActTriggered()));
114         connect(act_wrap_, SIGNAL(triggered()), this, SLOT(wrapActTriggered()));
115
116         findCO->setCompleter(nullptr);
117         replaceCO->setCompleter(nullptr);
118
119         replacePB->setEnabled(false);
120         replacePrevPB->setEnabled(false);
121         replaceallPB->setEnabled(false);
122 }
123
124
125 bool GuiSearchWidget::initialiseParams(std::string const & str)
126 {
127         if (!str.empty())
128                 findCO->lineEdit()->setText(toqstr(str));
129         return true;
130 }
131
132
133 void GuiSearchWidget::keyPressEvent(QKeyEvent * ev)
134 {
135         KeySymbol sym;
136         setKeySymbol(&sym, ev);
137
138         // catch Return and Shift-Return
139         if (ev->key() == Qt::Key_Return || ev->key() == Qt::Key_Enter) {
140                 doFind(ev->modifiers() == Qt::ShiftModifier);
141                 return;
142         }
143         if (ev->key() == Qt::Key_Escape) {
144                 dispatch(FuncRequest(LFUN_DIALOG_HIDE, "findreplace"));
145                 return;
146         }
147
148         // we catch the key sequences for forward and backwards search
149         if (sym.isOK()) {
150                 KeyModifier mod = lyx::q_key_state(ev->modifiers());
151                 KeySequence keyseq(&theTopLevelKeymap(), &theTopLevelKeymap());
152                 FuncRequest fr = keyseq.addkey(sym, mod);
153                 if (fr == FuncRequest(LFUN_WORD_FIND_FORWARD)
154                     || fr == FuncRequest(LFUN_WORD_FIND)) {
155                         doFind();
156                         return;
157                 }
158                 if (fr == FuncRequest(LFUN_WORD_FIND_BACKWARD)) {
159                         doFind(true);
160                         return;
161                 }
162                 if (fr == FuncRequest(LFUN_DIALOG_TOGGLE, "findreplace")) {
163                         dispatch(fr);
164                         return;
165                 }
166         }
167         QWidget::keyPressEvent(ev);
168 }
169
170
171 void GuiSearchWidget::minimizeClicked(bool const toggle)
172 {
173         if (toggle)
174                 minimized_ = !minimized_;
175
176         replaceLA->setHidden(minimized_);
177         replaceCO->setHidden(minimized_);
178         replacePB->setHidden(minimized_);
179         replacePrevPB->setHidden(minimized_);
180         replaceallPB->setHidden(minimized_);
181         CBFrame->setHidden(minimized_);
182
183         if (minimized_) {
184                 minimizePB->setText(qt_("Ex&pand"));
185                 minimizePB->setToolTip(qt_("Show replace and option widgets"));
186                 // update menu items
187                 blockSignals(true);
188                 act_casesense_->setChecked(caseCB->isChecked());
189                 act_immediate_->setChecked(instantSearchCB->isChecked());
190                 act_selection_->setChecked(selectionCB->isChecked());
191                 act_wholewords_->setChecked(wordsCB->isChecked());
192                 act_wrap_->setChecked(wrapCB->isChecked());
193                 blockSignals(false);
194         } else {
195                 minimizePB->setText(qt_("&Minimize"));
196                 minimizePB->setToolTip(qt_("Hide replace and option widgets"));
197         }
198
199         Q_EMIT needSizeUpdate();
200         Q_EMIT needTitleBarUpdate();
201         handleIndicators();
202 }
203
204
205 void GuiSearchWidget::handleIndicators()
206 {
207         findLE_->setButtonVisible(FancyLineEdit::Right, minimized_);
208
209         QString tip;
210
211         if (minimized_) {
212                 int pms = 0;
213                 if (caseCB->isChecked())
214                         ++pms;
215                 if (wordsCB->isChecked())
216                         ++pms;
217                 if (selectionCB->isChecked())
218                         ++pms;
219                 if (instantSearchCB->isChecked())
220                         ++pms;
221                 if (wrapCB->isChecked())
222                         ++pms;
223
224                 QPixmap bpixmap = getPixmap("images/", "search-options", "svgz,png");
225
226                 if (pms > 0) {
227                         int const gap = 3;
228                         QPixmap tpixmap(pms * (bpixmap.width() + gap), bpixmap.height());
229                         tpixmap.fill(Qt::transparent);
230                         QPainter painter(&tpixmap);
231                         int x = 0;
232                         
233                         tip = qt_("Active options:");
234                         tip += "<ul>";
235                         if (caseCB->isChecked()) {
236                                 tip += "<li>" + qt_("Case sensitive search");
237                                 QPixmap spixmap = getPixmap("images/", "search-case-sensitive", "svgz,png");
238                                 painter.drawPixmap(x, 0, spixmap);
239                                 x += spixmap.width() + gap;
240                         }
241                         if (wordsCB->isChecked()) {
242                                 tip += "<li>" + qt_("Whole words only");
243                                 QPixmap spixmap = getPixmap("images/", "search-whole-words", "svgz,png");
244                                 painter.drawPixmap(x, 0, spixmap);
245                                 x += spixmap.width() + gap;
246                         }
247                         if (selectionCB->isChecked()) {
248                                 tip += "<li>" + qt_("Search only in selection");
249                                 QPixmap spixmap = getPixmap("images/", "search-selection", "svgz,png");
250                                 painter.drawPixmap(x, 0, spixmap);
251                                 x += spixmap.width() + gap;
252                         }
253                         if (instantSearchCB->isChecked()) {
254                                 tip += "<li>" + qt_("Search as you type");
255                                 QPixmap spixmap = getPixmap("images/", "search-instant", "svgz,png");
256                                 painter.drawPixmap(x, 0, spixmap);
257                                 x += spixmap.width() + gap;
258                         }
259                         if (wrapCB->isChecked()) {
260                                 tip += "<li>" + qt_("Wrap search");
261                                 QPixmap spixmap = getPixmap("images/", "search-wrap", "svgz,png");
262                                 painter.drawPixmap(x, 0, spixmap);
263                                 x += spixmap.width() + gap;
264                         }
265                         tip += "</ul>";
266                         painter.end();
267                         if (guiApp && guiApp->isInDarkMode()) {
268                                 QImage img = tpixmap.toImage();
269                                 img.invertPixels();
270                                 tpixmap.convertFromImage(img);
271                         }
272                         findLE_->setButtonPixmap(FancyLineEdit::Right, tpixmap);
273                 } else {
274                         tip = qt_("Click here to change search options");
275                         if (guiApp && guiApp->isInDarkMode()) {
276                                 QImage img = bpixmap.toImage();
277                                 img.invertPixels();
278                                 bpixmap.convertFromImage(img);
279                         }
280                         findLE_->setButtonPixmap(FancyLineEdit::Right, bpixmap);
281                 }
282         }
283         findLE_->setButtonToolTip(FancyLineEdit::Right, tip);
284 }
285
286
287 void GuiSearchWidget::caseSenseActTriggered()
288 {
289         caseCB->setChecked(act_casesense_->isChecked());
290         handleIndicators();
291 }
292
293
294 void GuiSearchWidget::wholeWordsActTriggered()
295 {
296         wordsCB->setChecked(act_wholewords_->isChecked());
297         handleIndicators();
298 }
299
300
301 void GuiSearchWidget::searchSelActTriggered()
302 {
303         selectionCB->setChecked(act_selection_->isChecked());
304         handleIndicators();
305 }
306
307
308 void GuiSearchWidget::immediateActTriggered()
309 {
310         instantSearchCB->setChecked(act_immediate_->isChecked());
311         handleIndicators();
312 }
313
314
315 void GuiSearchWidget::wrapActTriggered()
316 {
317         wrapCB->setChecked(act_wrap_->isChecked());
318         handleIndicators();
319 }
320
321
322 void GuiSearchWidget::showEvent(QShowEvent * e)
323 {
324         findChanged();
325         findPB->setFocus();
326         findCO->lineEdit()->selectAll();
327         QWidget::showEvent(e);
328 }
329
330
331 void GuiSearchWidget::findBufferChanged()
332 {
333         docstring search = theClipboard().getFindBuffer();
334         if (!search.empty()) {
335                 LYXERR(Debug::CLIPBOARD, "from findbuffer: " << search);
336                 findCO->lineEdit()->selectAll();
337                 findCO->lineEdit()->insert(toqstr(search));
338                 findCO->lineEdit()->selectAll();
339         }
340 }
341
342
343 void GuiSearchWidget::findChanged()
344 {
345         bool const emptytext = findCO->currentText().isEmpty();
346         findPB->setEnabled(!emptytext);
347         findPrevPB->setEnabled(!emptytext);
348         bool const replace = !emptytext && bv_ && !bv_->buffer().isReadonly();
349         replacePB->setEnabled(replace);
350         replacePrevPB->setEnabled(replace);
351         replaceallPB->setEnabled(replace);
352         if (instantSearchCB->isChecked())
353                 doFind(false, true);
354 }
355
356
357 void GuiSearchWidget::findClicked()
358 {
359         doFind();
360 }
361
362
363 void GuiSearchWidget::findPrevClicked()
364 {
365         doFind(true);
366 }
367
368
369 void GuiSearchWidget::replaceClicked()
370 {
371         doReplace();
372 }
373
374
375 void GuiSearchWidget::replacePrevClicked()
376 {
377         doReplace(true);
378 }
379
380
381 void GuiSearchWidget::replaceallClicked()
382 {
383         replace(qstring_to_ucs4(findCO->currentText()),
384                 qstring_to_ucs4(replaceCO->currentText()),
385                 caseCB->isChecked(), wordsCB->isChecked(),
386                 true, true, true, selectionCB->isChecked());
387         uniqueInsert(findCO, findCO->currentText());
388         uniqueInsert(replaceCO, replaceCO->currentText());
389 }
390
391
392 void GuiSearchWidget::doFind(bool const backwards, bool const instant)
393 {
394         docstring const needle = qstring_to_ucs4(findCO->currentText());
395         find(needle, caseCB->isChecked(), wordsCB->isChecked(), !backwards,
396              instant, wrapCB->isChecked(), selectionCB->isChecked());
397         uniqueInsert(findCO, findCO->currentText());
398         if (!instant)
399                 findCO->lineEdit()->selectAll();
400 }
401
402
403 void GuiSearchWidget::find(docstring const & search, bool casesensitive,
404                            bool matchword, bool forward, bool instant,
405                            bool wrap, bool onlysel)
406 {
407         docstring const sdata =
408                 find2string(search, casesensitive, matchword,
409                             forward, wrap, instant, onlysel);
410
411         dispatch(FuncRequest(LFUN_WORD_FIND, sdata));
412 }
413
414
415 void GuiSearchWidget::doReplace(bool const backwards)
416 {
417         docstring const needle = qstring_to_ucs4(findCO->currentText());
418         docstring const repl = qstring_to_ucs4(replaceCO->currentText());
419         replace(needle, repl, caseCB->isChecked(), wordsCB->isChecked(),
420                 !backwards, false, wrapCB->isChecked(), selectionCB->isChecked());
421         uniqueInsert(findCO, findCO->currentText());
422         uniqueInsert(replaceCO, replaceCO->currentText());
423 }
424
425
426 void GuiSearchWidget::replace(docstring const & search, docstring const & replace,
427                             bool casesensitive, bool matchword,
428                             bool forward, bool all, bool wrap, bool onlysel)
429 {
430         docstring const sdata =
431                 replace2string(replace, search, casesensitive,
432                                matchword, all, forward, true, wrap, onlysel);
433
434         dispatch(FuncRequest(LFUN_WORD_REPLACE, sdata));
435 }
436
437 void GuiSearchWidget::saveSession(QSettings & settings, QString const & session_key) const
438 {
439         settings.setValue(session_key + "/casesensitive", caseCB->isChecked());
440         settings.setValue(session_key + "/words", wordsCB->isChecked());
441         settings.setValue(session_key + "/instant", instantSearchCB->isChecked());
442         settings.setValue(session_key + "/wrap", wrapCB->isChecked());
443         settings.setValue(session_key + "/selection", selectionCB->isChecked());
444         settings.setValue(session_key + "/minimized", minimized_);
445 }
446
447
448 void GuiSearchWidget::restoreSession(QString const & session_key)
449 {
450         QSettings settings;
451         caseCB->setChecked(settings.value(session_key + "/casesensitive", false).toBool());
452         act_casesense_->setChecked(settings.value(session_key + "/casesensitive", false).toBool());
453         wordsCB->setChecked(settings.value(session_key + "/words", false).toBool());
454         act_wholewords_->setChecked(settings.value(session_key + "/words", false).toBool());
455         instantSearchCB->setChecked(settings.value(session_key + "/instant", false).toBool());
456         act_immediate_->setChecked(settings.value(session_key + "/instant", false).toBool());
457         wrapCB->setChecked(settings.value(session_key + "/wrap", false).toBool());
458         act_wrap_->setChecked(settings.value(session_key + "/wrap", false).toBool());
459         selectionCB->setChecked(settings.value(session_key + "/selection", false).toBool());
460         act_selection_->setChecked(settings.value(session_key + "/selection", false).toBool());
461         minimized_ = settings.value(session_key + "/minimized", false).toBool();
462         // initialize hidings
463         minimizeClicked(false);
464 }
465
466
467 GuiSearch::GuiSearch(GuiView & parent, Qt::DockWidgetArea area, Qt::WindowFlags flags)
468         : DockView(parent, "findreplace", qt_("Search and Replace"), area, flags),
469           widget_(new GuiSearchWidget(this))
470 {
471         setWidget(widget_);
472         widget_->setBufferView(bufferview());
473         setFocusProxy(widget_);
474
475         connect(widget_, SIGNAL(needTitleBarUpdate()), this, SLOT(updateTitle()));
476         connect(widget_, SIGNAL(needSizeUpdate()), this, SLOT(updateSize()));
477 }
478
479 void GuiSearch::onBufferViewChanged()
480 {
481         widget_->setEnabled(static_cast<bool>(bufferview()));
482         widget_->setBufferView(bufferview());
483 }
484
485
486 void GuiSearch::updateView()
487 {
488         updateTitle();
489         updateSize();
490 }
491
492
493 void GuiSearch::saveSession(QSettings & settings) const
494 {
495         Dialog::saveSession(settings);
496         widget_->saveSession(settings, sessionKey());
497 }
498
499
500 void GuiSearch::restoreSession()
501 {
502         DockView::restoreSession();
503         widget_->restoreSession(sessionKey());
504 }
505
506
507 void GuiSearch::updateTitle()
508 {
509         if (widget_->isMinimized()) {
510                 // remove title bar
511                 setTitleBarWidget(new QWidget());
512                 titleBarWidget()->hide();
513         } else
514                 // restore title bar
515                 setTitleBarWidget(nullptr);
516 }
517
518
519 void GuiSearch::updateSize()
520 {
521         widget_->setFixedHeight(widget_->sizeHint().height());
522         if (widget_->isMinimized())
523                 setFixedHeight(widget_->sizeHint().height());
524         else {
525                 // undo setFixedHeight
526                 setMaximumHeight(QWIDGETSIZE_MAX);
527                 setMinimumHeight(0);
528         }
529         update();
530 }
531
532
533 } // namespace frontend
534 } // namespace lyx
535
536
537 #include "moc_GuiSearch.cpp"