]> git.lyx.org Git - features.git/blob - src/frontends/qt/GuiSearch.cpp
323ac8affd2a4e6f29d495a4f66ef138c59146e4
[features.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();
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                         findLE_->setButtonPixmap(FancyLineEdit::Right, tpixmap);
267                 } else {
268                         tip = qt_("Click here to change search options");
269                         findLE_->setButtonPixmap(FancyLineEdit::Right, bpixmap);
270                 }
271         }
272         findLE_->setButtonToolTip(FancyLineEdit::Right, tip);
273 }
274
275
276 void GuiSearchWidget::caseSenseActTriggered()
277 {
278         caseCB->setChecked(act_casesense_->isChecked());
279         handleIndicators();
280 }
281
282
283 void GuiSearchWidget::wholeWordsActTriggered()
284 {
285         wordsCB->setChecked(act_wholewords_->isChecked());
286         handleIndicators();
287 }
288
289
290 void GuiSearchWidget::searchSelActTriggered()
291 {
292         selectionCB->setChecked(act_selection_->isChecked());
293         handleIndicators();
294 }
295
296
297 void GuiSearchWidget::immediateActTriggered()
298 {
299         instantSearchCB->setChecked(act_immediate_->isChecked());
300         handleIndicators();
301 }
302
303
304 void GuiSearchWidget::wrapActTriggered()
305 {
306         wrapCB->setChecked(act_wrap_->isChecked());
307         handleIndicators();
308 }
309
310
311 void GuiSearchWidget::showEvent(QShowEvent * e)
312 {
313         findChanged();
314         findPB->setFocus();
315         findCO->lineEdit()->selectAll();
316         QWidget::showEvent(e);
317 }
318
319
320 void GuiSearchWidget::findBufferChanged()
321 {
322         docstring search = theClipboard().getFindBuffer();
323         if (!search.empty()) {
324                 LYXERR(Debug::CLIPBOARD, "from findbuffer: " << search);
325                 findCO->lineEdit()->selectAll();
326                 findCO->lineEdit()->insert(toqstr(search));
327         }
328 }
329
330
331 void GuiSearchWidget::findChanged()
332 {
333         bool const emptytext = findCO->currentText().isEmpty();
334         findPB->setEnabled(!emptytext);
335         findPrevPB->setEnabled(!emptytext);
336         bool const replace = !emptytext && bv_ && !bv_->buffer().isReadonly();
337         replacePB->setEnabled(replace);
338         replacePrevPB->setEnabled(replace);
339         replaceallPB->setEnabled(replace);
340         if (instantSearchCB->isChecked())
341                 doFind(false, true);
342 }
343
344
345 void GuiSearchWidget::findClicked()
346 {
347         doFind();
348 }
349
350
351 void GuiSearchWidget::findPrevClicked()
352 {
353         doFind(true);
354 }
355
356
357 void GuiSearchWidget::replaceClicked()
358 {
359         doReplace();
360 }
361
362
363 void GuiSearchWidget::replacePrevClicked()
364 {
365         doReplace(true);
366 }
367
368
369 void GuiSearchWidget::replaceallClicked()
370 {
371         replace(qstring_to_ucs4(findCO->currentText()),
372                 qstring_to_ucs4(replaceCO->currentText()),
373                 caseCB->isChecked(), wordsCB->isChecked(),
374                 true, true, true, selectionCB->isChecked());
375         uniqueInsert(findCO, findCO->currentText());
376         uniqueInsert(replaceCO, replaceCO->currentText());
377 }
378
379
380 void GuiSearchWidget::doFind(bool const backwards, bool const instant)
381 {
382         docstring const needle = qstring_to_ucs4(findCO->currentText());
383         find(needle, caseCB->isChecked(), wordsCB->isChecked(), !backwards,
384              instant, wrapCB->isChecked(), selectionCB->isChecked());
385         uniqueInsert(findCO, findCO->currentText());
386         if (!instant)
387                 findCO->lineEdit()->selectAll();
388 }
389
390
391 void GuiSearchWidget::find(docstring const & search, bool casesensitive,
392                            bool matchword, bool forward, bool instant,
393                            bool wrap, bool onlysel)
394 {
395         docstring const sdata =
396                 find2string(search, casesensitive, matchword,
397                             forward, wrap, instant, onlysel);
398
399         dispatch(FuncRequest(LFUN_WORD_FIND, sdata));
400 }
401
402
403 void GuiSearchWidget::doReplace(bool const backwards)
404 {
405         docstring const needle = qstring_to_ucs4(findCO->currentText());
406         docstring const repl = qstring_to_ucs4(replaceCO->currentText());
407         replace(needle, repl, caseCB->isChecked(), wordsCB->isChecked(),
408                 !backwards, false, wrapCB->isChecked(), selectionCB->isChecked());
409         uniqueInsert(findCO, findCO->currentText());
410         uniqueInsert(replaceCO, replaceCO->currentText());
411 }
412
413
414 void GuiSearchWidget::replace(docstring const & search, docstring const & replace,
415                             bool casesensitive, bool matchword,
416                             bool forward, bool all, bool wrap, bool onlysel)
417 {
418         docstring const sdata =
419                 replace2string(replace, search, casesensitive,
420                                matchword, all, forward, true, wrap, onlysel);
421
422         dispatch(FuncRequest(LFUN_WORD_REPLACE, sdata));
423 }
424
425 void GuiSearchWidget::saveSession(QSettings & settings, QString const & session_key) const
426 {
427         settings.setValue(session_key + "/casesensitive", caseCB->isChecked());
428         settings.setValue(session_key + "/words", wordsCB->isChecked());
429         settings.setValue(session_key + "/instant", instantSearchCB->isChecked());
430         settings.setValue(session_key + "/wrap", wrapCB->isChecked());
431         settings.setValue(session_key + "/selection", selectionCB->isChecked());
432         settings.setValue(session_key + "/minimized", minimized_);
433 }
434
435
436 void GuiSearchWidget::restoreSession(QString const & session_key)
437 {
438         QSettings settings;
439         caseCB->setChecked(settings.value(session_key + "/casesensitive", false).toBool());
440         act_casesense_->setChecked(settings.value(session_key + "/casesensitive", false).toBool());
441         wordsCB->setChecked(settings.value(session_key + "/words", false).toBool());
442         act_wholewords_->setChecked(settings.value(session_key + "/words", false).toBool());
443         instantSearchCB->setChecked(settings.value(session_key + "/instant", false).toBool());
444         act_immediate_->setChecked(settings.value(session_key + "/instant", false).toBool());
445         wrapCB->setChecked(settings.value(session_key + "/wrap", false).toBool());
446         act_wrap_->setChecked(settings.value(session_key + "/wrap", false).toBool());
447         selectionCB->setChecked(settings.value(session_key + "/selection", false).toBool());
448         act_selection_->setChecked(settings.value(session_key + "/selection", false).toBool());
449         minimized_ = settings.value(session_key + "/minimized", false).toBool();
450         // initialize hidings
451         minimizeClicked(false);
452 }
453
454
455 GuiSearch::GuiSearch(GuiView & parent, Qt::DockWidgetArea area, Qt::WindowFlags flags)
456         : DockView(parent, "findreplace", qt_("Search and Replace"), area, flags),
457           widget_(new GuiSearchWidget(this))
458 {
459         setWidget(widget_);
460         widget_->setBufferView(bufferview());
461         setFocusProxy(widget_);
462
463         connect(widget_, SIGNAL(needTitleBarUpdate()), this, SLOT(updateTitle()));
464         connect(widget_, SIGNAL(needSizeUpdate()), this, SLOT(updateSize()));
465 }
466
467 void GuiSearch::onBufferViewChanged()
468 {
469         widget_->setEnabled(static_cast<bool>(bufferview()));
470         widget_->setBufferView(bufferview());
471 }
472
473
474 void GuiSearch::updateView()
475 {
476         updateTitle();
477         updateSize();
478 }
479
480
481 void GuiSearch::saveSession(QSettings & settings) const
482 {
483         Dialog::saveSession(settings);
484         widget_->saveSession(settings, sessionKey());
485 }
486
487
488 void GuiSearch::restoreSession()
489 {
490         DockView::restoreSession();
491         widget_->restoreSession(sessionKey());
492 }
493
494
495 void GuiSearch::updateTitle()
496 {
497         if (widget_->isMinimized()) {
498                 // remove title bar
499                 setTitleBarWidget(new QWidget());
500                 titleBarWidget()->hide();
501         } else
502                 // restore title bar
503                 setTitleBarWidget(nullptr);
504 }
505
506
507 void GuiSearch::updateSize()
508 {
509         widget_->setFixedHeight(widget_->sizeHint().height());
510         if (widget_->isMinimized())
511                 setFixedHeight(widget_->sizeHint().height());
512         else {
513                 // undo setFixedHeight
514                 setMaximumHeight(QWIDGETSIZE_MAX);
515                 setMinimumHeight(0);
516         }
517         update();
518 }
519
520
521 } // namespace frontend
522 } // namespace lyx
523
524
525 #include "moc_GuiSearch.cpp"