]> git.lyx.org Git - lyx.git/blobdiff - src/frontends/qt/GuiSearch.cpp
Few string fixes from Dan.
[lyx.git] / src / frontends / qt / GuiSearch.cpp
index 3fffb6ea2b7a2b19aa04b699d8f6e16c6c350180..3ca4f4904d649bacfcf1cf44cb80791275a733b9 100644 (file)
 
 #include <config.h>
 
+#include "GuiApplication.h"
+#include "GuiSearch.h"
+
 #include "lyxfind.h"
 #include "qt_helpers.h"
 #include "FuncRequest.h"
+#include "LyX.h"
 #include "BufferView.h"
 #include "Buffer.h"
 #include "Cursor.h"
-#include "GuiSearch.h"
+#include "FuncRequest.h"
+#include "KeyMap.h"
+#include "GuiKeySymbol.h"
 #include "GuiView.h"
 
+#include "qt_helpers.h"
+#include "support/filetools.h"
+#include "support/debug.h"
 #include "support/gettext.h"
+#include "support/FileName.h"
 #include "frontends/alert.h"
+#include "frontends/Clipboard.h"
 
+#include <QClipboard>
+#include <QPainter>
 #include <QLineEdit>
+#include <QSettings>
+#include <QHideEvent>
 #include <QShowEvent>
+#include "QSizePolicy"
+#include <QSvgRenderer>
 
 using namespace std;
+using namespace lyx::support;
+
+using lyx::KeySymbol;
 
 namespace lyx {
 namespace frontend {
@@ -42,8 +62,8 @@ static void uniqueInsert(QComboBox * box, QString const & text)
 }
 
 
-GuiSearch::GuiSearch(GuiView & lv)
-       : GuiDialog(lv, "findreplace", qt_("Find and Replace"))
+GuiSearchWidget::GuiSearchWidget(QWidget * parent, GuiView & view)
+       :       QWidget(parent), view_(view)
 {
        setupUi(this);
 
@@ -51,106 +71,606 @@ GuiSearch::GuiSearch(GuiView & lv)
        setFixedHeight(sizeHint().height());
 
        // align items in grid on top
-       mainGridLayout->setAlignment(Qt::AlignTop);
+       gridLayout->setAlignment(Qt::AlignTop);
 
-       connect(buttonBox, SIGNAL(clicked(QAbstractButton *)),
-               this, SLOT(slotButtonBox(QAbstractButton *)));
        connect(findPB, SIGNAL(clicked()), this, SLOT(findClicked()));
+       connect(findPrevPB, SIGNAL(clicked()), this, SLOT(findPrevClicked()));
+       connect(minimizePB, SIGNAL(clicked()), this, SLOT(minimizeClicked()));
        connect(replacePB, SIGNAL(clicked()), this, SLOT(replaceClicked()));
+       connect(replacePrevPB, SIGNAL(clicked()), this, SLOT(replacePrevClicked()));
        connect(replaceallPB, SIGNAL(clicked()), this, SLOT(replaceallClicked()));
+       connect(instantSearchCB, SIGNAL(clicked()), this, SLOT(immediateClicked()));
        connect(findCO, SIGNAL(editTextChanged(QString)),
                this, SLOT(findChanged()));
+       if(qApp->clipboard()->supportsFindBuffer()) {
+               connect(qApp->clipboard(), SIGNAL(findBufferChanged()),
+                       this, SLOT(findBufferChanged()));
+               findBufferChanged();
+       }
 
        setFocusProxy(findCO);
 
-       bc().setPolicy(ButtonPolicy::NoRepeatedApplyReadOnlyPolicy);
-       bc().setCancel(buttonBox->button(QDialogButtonBox::Close));
-       bc().addReadOnly(replaceCO);
-       bc().addReadOnly(replacePB);
-       bc().addReadOnly(replaceallPB);
-
-       findCO->setCompleter(0);
-       replaceCO->setCompleter(0);
+       // Use a FancyLineEdit due to the indicator icons
+       findLE_ = new FancyLineEdit(this);
+       findCO->setLineEdit(findLE_);
+
+       // And a menu in minimal mode
+       menu_ = new QMenu();
+       act_casesense_ = new QAction(qt_("&Case sensitive[[search]]"), this);
+       act_casesense_->setCheckable(true);
+       act_wholewords_ = new QAction(qt_("Wh&ole words"), this);
+       act_wholewords_->setCheckable(true);
+       act_selection_ = new QAction(qt_("Selection onl&y"), this);
+       act_selection_->setCheckable(true);
+       act_immediate_ = new QAction(qt_("Search as yo&u type"), this);
+       act_immediate_->setCheckable(true);
+       act_wrap_ = new QAction(qt_("&Wrap"), this);
+       act_wrap_->setCheckable(true);
+
+       menu_->addAction(act_casesense_);
+       menu_->addAction(act_wholewords_);
+       menu_->addAction(act_selection_);
+       menu_->addAction(act_immediate_);
+       menu_->addAction(act_wrap_);
+       findLE_->setButtonMenu(FancyLineEdit::Right, menu_);
+
+       connect(act_casesense_, SIGNAL(triggered()), this, SLOT(caseSenseActTriggered()));
+       connect(act_wholewords_, SIGNAL(triggered()), this, SLOT(wholeWordsActTriggered()));
+       connect(act_selection_, SIGNAL(triggered()), this, SLOT(searchSelActTriggered()));
+       connect(act_immediate_, SIGNAL(triggered()), this, SLOT(immediateActTriggered()));
+       connect(act_wrap_, SIGNAL(triggered()), this, SLOT(wrapActTriggered()));
+
+       findCO->setCompleter(nullptr);
+       replaceCO->setCompleter(nullptr);
 
        replacePB->setEnabled(false);
+       replacePrevPB->setEnabled(false);
        replaceallPB->setEnabled(false);
 }
 
 
-void GuiSearch::showEvent(QShowEvent * e)
+bool GuiSearchWidget::initialiseParams(std::string const & str)
 {
-       findPB->setFocus();
+       if (!str.empty()) {
+               // selectAll & insert rather than setText in order to keep undo stack
+               findCO->lineEdit()->selectAll();
+               findCO->lineEdit()->insert(toqstr(str));
+       }
+       findCO->setFocus();
        findCO->lineEdit()->selectAll();
-       GuiDialog::showEvent(e);
+       return true;
+}
+
+
+void GuiSearchWidget::keyPressEvent(QKeyEvent * ev)
+{
+       KeySymbol sym;
+       setKeySymbol(&sym, ev);
+
+       // catch Return and Shift-Return
+       if (ev->key() == Qt::Key_Return || ev->key() == Qt::Key_Enter) {
+               doFind(ev->modifiers() == Qt::ShiftModifier);
+               return;
+       }
+       if (ev->key() == Qt::Key_Escape) {
+               Qt::KeyboardModifiers mod = ev->modifiers();
+               if (mod & Qt::AltModifier) {
+                       QWidget::keyPressEvent(ev);
+                       return;
+               }
+
+               dispatch(FuncRequest(LFUN_DIALOG_HIDE, "findreplace"));
+               view_.setFocus();
+               bv_->buffer().updateBuffer();
+               return;
+       }
+
+       // we catch the key sequences for forward and backwards search
+       if (sym.isOK()) {
+               KeyModifier mod = lyx::q_key_state(ev->modifiers());
+               KeySequence keyseq(&theTopLevelKeymap(), &theTopLevelKeymap());
+               FuncRequest fr = keyseq.addkey(sym, mod);
+               if (fr == FuncRequest(LFUN_WORD_FIND_FORWARD)
+                   || fr == FuncRequest(LFUN_WORD_FIND)) {
+                       doFind();
+                       return;
+               }
+               if (fr == FuncRequest(LFUN_WORD_FIND_BACKWARD)) {
+                       doFind(true);
+                       return;
+               }
+               if (fr == FuncRequest(LFUN_DIALOG_TOGGLE, "findreplace")) {
+                       dispatch(fr);
+                       return;
+               }
+       }
+       QWidget::keyPressEvent(ev);
 }
 
 
-void GuiSearch::findChanged()
+void GuiSearchWidget::minimizeClicked(bool const toggle)
 {
-       if (findCO->currentText().isEmpty()) {
-               findPB->setEnabled(false);
-               replacePB->setEnabled(false);
-               replaceallPB->setEnabled(false);
+       if (toggle)
+               minimized_ = !minimized_;
+
+       replaceLA->setHidden(minimized_);
+       replaceCO->setHidden(minimized_);
+       replacePB->setHidden(minimized_);
+       replacePrevPB->setHidden(minimized_);
+       replaceallPB->setHidden(minimized_);
+       CBFrame->setHidden(minimized_);
+
+       if (minimized_) {
+               minimizePB->setText(qt_("Ex&pand"));
+               minimizePB->setToolTip(qt_("Show replace and option widgets"));
+               // update menu items
+               blockSignals(true);
+               act_casesense_->setChecked(caseCB->isChecked());
+               act_immediate_->setChecked(instantSearchCB->isChecked());
+               act_selection_->setChecked(selectionCB->isChecked());
+               act_selection_->setEnabled(!instantSearchCB->isChecked());
+               act_wholewords_->setChecked(wordsCB->isChecked());
+               act_wrap_->setChecked(wrapCB->isChecked());
+               blockSignals(false);
        } else {
-               findPB->setEnabled(true);
-               replacePB->setEnabled(!isBufferReadonly());
-               replaceallPB->setEnabled(!isBufferReadonly());
+               minimizePB->setText(qt_("&Minimize"));
+               minimizePB->setToolTip(qt_("Hide replace and option widgets"));
        }
+
+       Q_EMIT needSizeUpdate();
+       Q_EMIT needTitleBarUpdate();
+       handleIndicators();
 }
 
 
-void GuiSearch::findClicked()
+void GuiSearchWidget::handleIndicators()
 {
-       docstring const needle = qstring_to_ucs4(findCO->currentText());
-       find(needle, caseCB->isChecked(), wordsCB->isChecked(),
-               !backwardsCB->isChecked());
-       uniqueInsert(findCO, findCO->currentText());
-       findCO->lineEdit()->selectAll();
+       findLE_->setButtonVisible(FancyLineEdit::Right, minimized_);
+
+       QString tip;
+
+       if (minimized_) {
+               int pms = 0;
+               if (caseCB->isChecked())
+                       ++pms;
+               if (wordsCB->isChecked())
+                       ++pms;
+               if (selectionCB->isChecked())
+                       ++pms;
+               if (instantSearchCB->isChecked())
+                       ++pms;
+               if (wrapCB->isChecked())
+                       ++pms;
+
+               bool const dark_mode = guiApp && guiApp->isInDarkMode();
+               qreal dpr = 1.0;
+               // Consider device/pixel ratio (HiDPI)
+               if (guiApp && guiApp->currentView())
+                       dpr = guiApp->currentView()->devicePixelRatio();
+               QString imagedir = "images/";
+               QPixmap bpixmap = getPixmap("images/", "search-options", "svgz,png");
+               QPixmap pm = bpixmap;
+
+               if (pms > 0) {
+                       int const gap = 3;
+                       QPixmap scaled_pm = QPixmap(bpixmap.size() * dpr);
+                       pm = QPixmap(pms * scaled_pm.width() + ((pms - 1) * gap),
+                                    scaled_pm.height());
+                       pm.fill(Qt::transparent);
+                       QPainter painter(&pm);
+                       int x = 0;
+
+                       tip = qt_("Active options:");
+                       tip += "<ul>";
+                       if (caseCB->isChecked()) {
+                               tip += "<li>" + qt_("Case sensitive search");
+                               QPixmap spixmap = getPixmap("images/", "search-case-sensitive", "svgz,png");
+                               // We render SVG directly for HiDPI scalability
+                               FileName fname = imageLibFileSearch(imagedir, "search-case-sensitive", "svgz,png");
+                               QString fpath = toqstr(fname.absFileName());
+                               if (!fpath.isEmpty()) {
+                                       QSvgRenderer svgRenderer(fpath);
+                                       if (svgRenderer.isValid())
+                                               svgRenderer.render(&painter, QRectF(0, 0, spixmap.width() * dpr,
+                                                                                   spixmap.height() * dpr));
+                               }
+                               x += (spixmap.width() * dpr) + gap;
+                       }
+                       if (wordsCB->isChecked()) {
+                               tip += "<li>" + qt_("Whole words only");
+                               QPixmap spixmap = getPixmap("images/", "search-whole-words", "svgz,png");
+                               FileName fname = imageLibFileSearch(imagedir, "search-whole-words", "svgz,png");
+                               QString fpath = toqstr(fname.absFileName());
+                               if (!fpath.isEmpty()) {
+                                       QSvgRenderer svgRenderer(fpath);
+                                       if (svgRenderer.isValid())
+                                               svgRenderer.render(&painter, QRectF(x, 0, spixmap.width() * dpr,
+                                                                                   spixmap.height() * dpr));
+                               }
+                               x += (spixmap.width() * dpr) + gap;
+                       }
+                       if (selectionCB->isChecked()) {
+                               tip += "<li>" + qt_("Search only in selection");
+                               QPixmap spixmap = getPixmap("images/", "search-selection", "svgz,png");
+                               FileName fname = imageLibFileSearch(imagedir, "search-selection", "svgz,png");
+                               QString fpath = toqstr(fname.absFileName());
+                               if (!fpath.isEmpty()) {
+                                       QSvgRenderer svgRenderer(fpath);
+                                       if (svgRenderer.isValid())
+                                               svgRenderer.render(&painter, QRectF(x, 0, spixmap.width() * dpr,
+                                                                                   spixmap.height() * dpr));
+                               }
+                               x += (spixmap.width() * dpr) + gap;
+                       }
+                       if (instantSearchCB->isChecked()) {
+                               tip += "<li>" + qt_("Search as you type");
+                               QPixmap spixmap = getPixmap("images/", "search-instant", "svgz,png");
+                               FileName fname = imageLibFileSearch(imagedir, "search-instant", "svgz,png");
+                               QString fpath = toqstr(fname.absFileName());
+                               if (!fpath.isEmpty()) {
+                                       QSvgRenderer svgRenderer(fpath);
+                                       if (svgRenderer.isValid())
+                                               svgRenderer.render(&painter, QRectF(x, 0, spixmap.width() * dpr,
+                                                                                   spixmap.height() * dpr));
+                               }
+                               x += (spixmap.width() * dpr) + gap;
+                       }
+                       if (wrapCB->isChecked()) {
+                               tip += "<li>" + qt_("Wrap search");
+                               QPixmap spixmap = getPixmap("images/", "search-wrap", "svgz,png");
+                               FileName fname = imageLibFileSearch(imagedir, "search-wrap", "svgz,png");
+                               QString fpath = toqstr(fname.absFileName());
+                               if (!fpath.isEmpty()) {
+                                       QSvgRenderer svgRenderer(fpath);
+                                       if (svgRenderer.isValid())
+                                               svgRenderer.render(&painter, QRectF(x, 0, spixmap.width() * dpr,
+                                                                                   spixmap.height() * dpr));
+                               }
+                               x += (spixmap.width() * dpr) + gap;
+                       }
+                       tip += "</ul>";
+                       pm.setDevicePixelRatio(dpr);
+                       painter.end();
+               } else {
+                       tip = qt_("Click here to change search options");
+                       // We render SVG directly for HiDPI scalability
+                       FileName fname = imageLibFileSearch(imagedir, "search-options", "svgz,png");
+                       QString fpath = toqstr(fname.absFileName());
+                       if (!fpath.isEmpty()) {
+                               QSvgRenderer svgRenderer(fpath);
+                               if (svgRenderer.isValid()) {
+                                       pm = QPixmap(bpixmap.size() * dpr);
+                                       pm.fill(Qt::transparent);
+                                       QPainter painter(&pm);
+                                       svgRenderer.render(&painter);
+                                       pm.setDevicePixelRatio(dpr);
+                               }
+                       }
+               }
+               if (dark_mode) {
+                       QImage img = pm.toImage();
+                       img.invertPixels();
+                       pm.convertFromImage(img);
+               }
+               findLE_->setButtonPixmap(FancyLineEdit::Right, pm);
+       }
+       findLE_->setButtonToolTip(FancyLineEdit::Right, tip);
 }
 
 
-void GuiSearch::replaceClicked()
+void GuiSearchWidget::caseSenseActTriggered()
 {
-       docstring const needle = qstring_to_ucs4(findCO->currentText());
-       docstring const repl = qstring_to_ucs4(replaceCO->currentText());
-       replace(needle, repl, caseCB->isChecked(), wordsCB->isChecked(),
-               !backwardsCB->isChecked(), false);
-       uniqueInsert(findCO, findCO->currentText());
-       uniqueInsert(replaceCO, replaceCO->currentText());
+       caseCB->setChecked(act_casesense_->isChecked());
+       handleIndicators();
+}
+
+
+void GuiSearchWidget::wholeWordsActTriggered()
+{
+       wordsCB->setChecked(act_wholewords_->isChecked());
+       handleIndicators();
+}
+
+
+void GuiSearchWidget::searchSelActTriggered()
+{
+       selectionCB->setChecked(act_selection_->isChecked());
+       handleIndicators();
+}
+
+
+void GuiSearchWidget::immediateActTriggered()
+{
+       bool const immediate = act_immediate_->isChecked();
+       instantSearchCB->setChecked(immediate);
+       // FIXME: make these two work together eventually.
+       selectionCB->setEnabled(!immediate);
+       act_selection_->setEnabled(!immediate);
+       if (immediate) {
+               selectionCB->setChecked(false);
+               act_selection_->setChecked(false);
+       }
+       handleIndicators();
+}
+
+
+void GuiSearchWidget::immediateClicked()
+{
+       // FIXME: make these two work together eventually.
+       bool const immediate = instantSearchCB->isChecked();
+       selectionCB->setEnabled(!immediate);
+       if (immediate)
+               selectionCB->setChecked(false);
+}
+
+
+void GuiSearchWidget::wrapActTriggered()
+{
+       wrapCB->setChecked(act_wrap_->isChecked());
+       handleIndicators();
+}
+
+
+void GuiSearchWidget::showEvent(QShowEvent * e)
+{
+       findChanged();
+       QWidget::showEvent(e);
+}
+
+
+void GuiSearchWidget::hideEvent(QHideEvent *)
+{
+       dispatch(FuncRequest(LFUN_DIALOG_HIDE, "findreplace"));
+       view_.setFocus();
+       // update toolbar status
+       if (bv_)
+               bv_->buffer().updateBuffer();
+}
+
+
+void GuiSearchWidget::findBufferChanged()
+{
+       docstring search = theClipboard().getFindBuffer();
+       // update from find buffer, but only if the strings differ (else we
+       // might end up in loops with search as you type)
+       if (!search.empty() && toqstr(search) != findCO->lineEdit()->text()) {
+               LYXERR(Debug::CLIPBOARD, "from findbuffer: " << search);
+               // selectAll & insert rather than setText in order to keep undo stack
+               findCO->lineEdit()->selectAll();
+               findCO->lineEdit()->insert(toqstr(search));
+       }
+}
+
+
+void GuiSearchWidget::findChanged()
+{
+       bool const emptytext = findCO->currentText().isEmpty();
+       findPB->setEnabled(!emptytext);
+       findPrevPB->setEnabled(!emptytext);
+       bool const replace = !emptytext && bv_ && !bv_->buffer().isReadonly();
+       replacePB->setEnabled(replace);
+       replacePrevPB->setEnabled(replace);
+       replaceallPB->setEnabled(replace);
+       if (instantSearchCB->isChecked())
+               doFind(false, true);
+}
+
+
+void GuiSearchWidget::findClicked()
+{
+       doFind();
+}
+
+
+void GuiSearchWidget::findPrevClicked()
+{
+       doFind(true);
+}
+
+
+void GuiSearchWidget::replaceClicked()
+{
+       doReplace();
 }
 
 
-void GuiSearch::replaceallClicked()
+void GuiSearchWidget::replacePrevClicked()
+{
+       doReplace(true);
+}
+
+
+void GuiSearchWidget::replaceallClicked()
 {
        replace(qstring_to_ucs4(findCO->currentText()),
                qstring_to_ucs4(replaceCO->currentText()),
-               caseCB->isChecked(), wordsCB->isChecked(), true, true);
+               caseCB->isChecked(), wordsCB->isChecked(),
+               true, true, true, selectionCB->isChecked());
        uniqueInsert(findCO, findCO->currentText());
        uniqueInsert(replaceCO, replaceCO->currentText());
 }
 
 
-void GuiSearch::find(docstring const & search, bool casesensitive,
-                        bool matchword, bool forward)
+void GuiSearchWidget::doFind(bool const backwards, bool const instant)
+{
+       docstring const needle = qstring_to_ucs4(findCO->currentText());
+       find(needle, caseCB->isChecked(), wordsCB->isChecked(), !backwards,
+            instant, wrapCB->isChecked(), selectionCB->isChecked());
+       uniqueInsert(findCO, findCO->currentText());
+       if (!instant)
+               findCO->lineEdit()->selectAll();
+}
+
+
+void GuiSearchWidget::find(docstring const & search, bool casesensitive,
+                          bool matchword, bool forward, bool instant,
+                          bool wrap, bool onlysel)
 {
        docstring const sdata =
-               find2string(search, casesensitive, matchword, forward);
+               find2string(search, casesensitive, matchword,
+                           forward, wrap, instant, onlysel);
+
        dispatch(FuncRequest(LFUN_WORD_FIND, sdata));
 }
 
 
-void GuiSearch::replace(docstring const & search, docstring const & replace,
+void GuiSearchWidget::doReplace(bool const backwards)
+{
+       docstring const needle = qstring_to_ucs4(findCO->currentText());
+       docstring const repl = qstring_to_ucs4(replaceCO->currentText());
+       replace(needle, repl, caseCB->isChecked(), wordsCB->isChecked(),
+               !backwards, false, wrapCB->isChecked(), selectionCB->isChecked());
+       uniqueInsert(findCO, findCO->currentText());
+       uniqueInsert(replaceCO, replaceCO->currentText());
+}
+
+
+void GuiSearchWidget::replace(docstring const & search, docstring const & replace,
                            bool casesensitive, bool matchword,
-                           bool forward, bool all)
+                           bool forward, bool all, bool wrap, bool onlysel)
 {
        docstring const sdata =
                replace2string(replace, search, casesensitive,
-                                    matchword, all, forward);
+                              matchword, all, forward, true, wrap, onlysel);
+
        dispatch(FuncRequest(LFUN_WORD_REPLACE, sdata));
 }
 
+void GuiSearchWidget::saveSession(QSettings & settings, QString const & session_key) const
+{
+       settings.setValue(session_key + "/casesensitive", caseCB->isChecked());
+       settings.setValue(session_key + "/words", wordsCB->isChecked());
+       settings.setValue(session_key + "/instant", instantSearchCB->isChecked());
+       settings.setValue(session_key + "/wrap", wrapCB->isChecked());
+       settings.setValue(session_key + "/selection", selectionCB->isChecked());
+       settings.setValue(session_key + "/minimized", minimized_);
+}
+
+
+void GuiSearchWidget::restoreSession(QString const & session_key)
+{
+       QSettings settings;
+       caseCB->setChecked(settings.value(session_key + "/casesensitive", false).toBool());
+       act_casesense_->setChecked(settings.value(session_key + "/casesensitive", false).toBool());
+       wordsCB->setChecked(settings.value(session_key + "/words", false).toBool());
+       act_wholewords_->setChecked(settings.value(session_key + "/words", false).toBool());
+       bool const immediate = settings.value(session_key + "/instant", false).toBool();
+       instantSearchCB->setChecked(immediate);
+       act_immediate_->setChecked(immediate);
+       wrapCB->setChecked(settings.value(session_key + "/wrap", true).toBool());
+       act_wrap_->setChecked(settings.value(session_key + "/wrap", true).toBool());
+       selectionCB->setChecked(settings.value(session_key + "/selection", false).toBool() && !immediate);
+       selectionCB->setEnabled(!immediate);
+       act_selection_->setChecked(settings.value(session_key + "/selection", false).toBool());
+       minimized_ = settings.value(session_key + "/minimized", false).toBool();
+       // initialize hidings
+       minimizeClicked(false);
+}
+
+
+GuiSearch::GuiSearch(GuiView & parent, Qt::DockWidgetArea area, Qt::WindowFlags flags)
+       : DockView(parent, "findreplace", qt_("Search and Replace"), area, flags),
+         widget_(new GuiSearchWidget(this, parent))
+{
+       setWidget(widget_);
+       widget_->setBufferView(bufferview());
+       setFocusProxy(widget_);
+
+       connect(widget_, SIGNAL(needTitleBarUpdate()), this, SLOT(updateTitle()));
+       connect(widget_, SIGNAL(needSizeUpdate()), this, SLOT(updateSize()));
+}
+
+
+void GuiSearch::mousePressEvent(QMouseEvent * event)
+{
+       if (isFloating() && event->button() == Qt::LeftButton) {
+#if QT_VERSION >= 0x060000
+               dragPosition = event->globalPosition().toPoint() - frameGeometry().topLeft();
+#else
+               dragPosition = event->globalPos() - frameGeometry().topLeft();
+#endif
+               event->accept();
+       } else
+               DockView::mousePressEvent(event);
+}
+
+
+void GuiSearch::mouseMoveEvent(QMouseEvent * event)
+{
+       if (isFloating() && event->buttons() & Qt::LeftButton) {
+#if QT_VERSION >= 0x060000
+               move(event->globalPosition().toPoint() - dragPosition);
+#else
+               move(event->globalPos() - dragPosition);
+#endif
+               event->accept();
+       } else
+               DockView::mouseMoveEvent(event);
+}
+
+
+void GuiSearch::mouseDoubleClickEvent(QMouseEvent * event)
+{
+       if (event->button() == Qt::LeftButton)
+               setFloating(!isFloating());
+       else
+               DockView::mouseDoubleClickEvent(event);
+}
+
+
+void GuiSearch::onBufferViewChanged()
+{
+       widget_->setEnabled(static_cast<bool>(bufferview()));
+       widget_->setBufferView(bufferview());
+}
+
+
+void GuiSearch::updateView()
+{
+       updateTitle();
+       updateSize();
+}
+
+
+void GuiSearch::saveSession(QSettings & settings) const
+{
+       Dialog::saveSession(settings);
+       widget_->saveSession(settings, sessionKey());
+}
+
+
+void GuiSearch::restoreSession()
+{
+       DockView::restoreSession();
+       widget_->restoreSession(sessionKey());
+}
 
-Dialog * createGuiSearch(GuiView & lv) { return new GuiSearch(lv); }
+
+void GuiSearch::updateTitle()
+{
+       if (widget_->isMinimized()) {
+               // remove title bar
+               setTitleBarWidget(new QWidget());
+               titleBarWidget()->hide();
+       } else if (titleBarWidget()) {
+               // restore title bar
+               setTitleBarWidget(nullptr);
+       }
+}
+
+
+void GuiSearch::updateSize()
+{
+       widget_->setFixedHeight(widget_->sizeHint().height());
+       if (widget_->isMinimized())
+               setFixedHeight(widget_->sizeHint().height());
+       else {
+               // undo setFixedHeight
+               setMaximumHeight(QWIDGETSIZE_MAX);
+               setMinimumHeight(0);
+       }
+       update();
+}
 
 
 } // namespace frontend