| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the tools applications of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| /*! \class AbstractFindWidget |
| |
| \brief A search bar that is commonly added below a searchable widget. |
| |
| \internal |
| |
| This widget implements a search bar which becomes visible when the user |
| wants to start searching. It is a modern replacement for the commonly used |
| search dialog. It is usually placed below the target widget using a QVBoxLayout. |
| |
| The search is incremental and can be set to case sensitive or whole words |
| using buttons available on the search bar. |
| */ |
| |
| #include "abstractfindwidget.h" |
| |
| #include <QtCore/QCoreApplication> |
| #include <QtCore/QEvent> |
| #include <QtCore/QFile> |
| #include <QtCore/QTimer> |
| |
| #include <QtGui/QKeyEvent> |
| |
| #include <QtWidgets/QCheckBox> |
| #include <QtWidgets/QAction> |
| #include <QtWidgets/QLabel> |
| #include <QtWidgets/QLayout> |
| #include <QtWidgets/QLineEdit> |
| #include <QtWidgets/QSpacerItem> |
| #include <QtWidgets/QShortcut> |
| #include <QtWidgets/QToolButton> |
| |
| QT_BEGIN_NAMESPACE |
| |
| static QIcon createIconSet(const QString &name) |
| { |
| QStringList candidates = QStringList() |
| << (QString::fromUtf8(":/qt-project.org/shared/images/") + name) |
| #ifdef Q_OS_MAC |
| << (QString::fromUtf8(":/qt-project.org/shared/images/mac/") + name); |
| #else |
| << (QString::fromUtf8(":/qt-project.org/shared/images/win/") + name); |
| #endif |
| |
| for (const QString &f : qAsConst(candidates)) { |
| if (QFile::exists(f)) |
| return QIcon(f); |
| } |
| |
| return QIcon(); |
| } |
| |
| /*! |
| Constructs an AbstractFindWidget. |
| |
| \a flags can change the layout and turn off certain features. |
| \a parent is passed to the QWidget constructor. |
| */ |
| AbstractFindWidget::AbstractFindWidget(FindFlags flags, QWidget *parent) |
| : QWidget(parent) |
| { |
| QBoxLayout *topLayOut; |
| QBoxLayout *layOut; |
| if (flags & NarrowLayout) { |
| topLayOut = new QVBoxLayout(this); |
| layOut = new QHBoxLayout; |
| topLayOut->addLayout(layOut); |
| } else { |
| topLayOut = layOut = new QHBoxLayout(this); |
| } |
| #ifndef Q_OS_MAC |
| topLayOut->setSpacing(6); |
| topLayOut->setContentsMargins(QMargins()); |
| #endif |
| |
| m_toolClose = new QToolButton(this); |
| m_toolClose->setIcon(createIconSet(QLatin1String("closetab.png"))); |
| m_toolClose->setAutoRaise(true); |
| layOut->addWidget(m_toolClose); |
| connect(m_toolClose, SIGNAL(clicked()), SLOT(deactivate())); |
| |
| m_editFind = new QLineEdit(this); |
| layOut->addWidget(m_editFind); |
| connect(m_editFind, SIGNAL(returnPressed()), SLOT(findNext())); |
| connect(m_editFind, SIGNAL(textChanged(QString)), SLOT(findCurrentText())); |
| connect(m_editFind, SIGNAL(textChanged(QString)), SLOT(updateButtons())); |
| |
| m_toolPrevious = new QToolButton(this); |
| m_toolPrevious->setAutoRaise(true); |
| m_toolPrevious->setText(tr("&Previous")); |
| m_toolPrevious->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); |
| m_toolPrevious->setIcon(createIconSet(QLatin1String("previous.png"))); |
| layOut->addWidget(m_toolPrevious); |
| connect(m_toolPrevious, SIGNAL(clicked()), SLOT(findPrevious())); |
| |
| m_toolNext = new QToolButton(this); |
| m_toolNext->setAutoRaise(true); |
| m_toolNext->setText(tr("&Next")); |
| m_toolNext->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); |
| m_toolNext->setIcon(createIconSet(QLatin1String("next.png"))); |
| layOut->addWidget(m_toolNext); |
| connect(m_toolNext, SIGNAL(clicked()), SLOT(findNext())); |
| |
| if (flags & NarrowLayout) { |
| QSizePolicy sp(QSizePolicy::Preferred, QSizePolicy::Fixed); |
| m_toolPrevious->setSizePolicy(sp); |
| m_toolPrevious->setMinimumWidth(m_toolPrevious->minimumSizeHint().height()); |
| m_toolNext->setSizePolicy(sp); |
| m_toolNext->setMinimumWidth(m_toolNext->minimumSizeHint().height()); |
| |
| QSpacerItem *spacerItem = |
| new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum); |
| layOut->addItem(spacerItem); |
| |
| layOut = new QHBoxLayout; |
| topLayOut->addLayout(layOut); |
| } else { |
| m_editFind->setMinimumWidth(150); |
| } |
| |
| if (!(flags & NoCaseSensitive)) { |
| m_checkCase = new QCheckBox(tr("&Case sensitive"), this); |
| layOut->addWidget(m_checkCase); |
| connect(m_checkCase, SIGNAL(toggled(bool)), SLOT(findCurrentText())); |
| } else { |
| m_checkCase = 0; |
| } |
| |
| if (!(flags & NoWholeWords)) { |
| m_checkWholeWords = new QCheckBox(tr("Whole &words"), this); |
| layOut->addWidget(m_checkWholeWords); |
| connect(m_checkWholeWords, SIGNAL(toggled(bool)), SLOT(findCurrentText())); |
| } else { |
| m_checkWholeWords = 0; |
| } |
| |
| m_labelWrapped = new QLabel(this); |
| m_labelWrapped->setTextFormat(Qt::RichText); |
| m_labelWrapped->setAlignment( |
| Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter); |
| m_labelWrapped->setText( |
| tr("<img src=\":/qt-project.org/shared/images/wrap.png\">" |
| " Search wrapped")); |
| m_labelWrapped->hide(); |
| layOut->addWidget(m_labelWrapped); |
| |
| QSpacerItem *spacerItem = |
| new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum); |
| layOut->addItem(spacerItem); |
| |
| setMinimumWidth(minimumSizeHint().width()); |
| |
| updateButtons(); |
| hide(); |
| } |
| |
| /*! |
| Destroys the AbstractFindWidget. |
| */ |
| AbstractFindWidget::~AbstractFindWidget() = default; |
| |
| /*! |
| Returns the icon set to be used for the action that initiates a search. |
| */ |
| QIcon AbstractFindWidget::findIconSet() |
| { |
| return createIconSet(QLatin1String("searchfind.png")); |
| } |
| |
| /*! |
| Creates an actions with standard icon and shortcut to activate the widget. |
| */ |
| QAction *AbstractFindWidget::createFindAction(QObject *parent) |
| { |
| |
| auto result = new QAction(AbstractFindWidget::findIconSet(), |
| tr("&Find in Text..."), parent); |
| connect(result, &QAction::triggered, this, &AbstractFindWidget::activate); |
| result->setShortcut(QKeySequence::Find); |
| return result; |
| } |
| |
| /*! |
| Activates the find widget, making it visible and having focus on its input |
| field. |
| */ |
| void AbstractFindWidget::activate() |
| { |
| show(); |
| m_editFind->selectAll(); |
| m_editFind->setFocus(Qt::ShortcutFocusReason); |
| } |
| |
| /*! |
| Deactivates the find widget, making it invisible and handing focus to any |
| associated QTextEdit. |
| */ |
| void AbstractFindWidget::deactivate() |
| { |
| hide(); |
| } |
| |
| void AbstractFindWidget::findNext() |
| { |
| findInternal(m_editFind->text(), true, false); |
| } |
| |
| void AbstractFindWidget::findPrevious() |
| { |
| findInternal(m_editFind->text(), true, true); |
| } |
| |
| void AbstractFindWidget::findCurrentText() |
| { |
| findInternal(m_editFind->text(), false, false); |
| } |
| |
| void AbstractFindWidget::keyPressEvent(QKeyEvent *event) |
| { |
| if (event->key() == Qt::Key_Escape) { |
| deactivate(); |
| return; |
| } |
| |
| QWidget::keyPressEvent(event); |
| } |
| |
| void AbstractFindWidget::updateButtons() |
| { |
| const bool en = !m_editFind->text().isEmpty(); |
| m_toolPrevious->setEnabled(en); |
| m_toolNext->setEnabled(en); |
| } |
| |
| void AbstractFindWidget::findInternal(const QString &ttf, bool skipCurrent, bool backward) |
| { |
| bool found = false; |
| bool wrapped = false; |
| find(ttf, skipCurrent, backward, &found, &wrapped); |
| QPalette p; |
| p.setColor(QPalette::Active, QPalette::Base, found ? Qt::white : QColor(255, 102, 102)); |
| m_editFind->setPalette(p); |
| m_labelWrapped->setVisible(wrapped); |
| } |
| |
| bool AbstractFindWidget::caseSensitive() const |
| { |
| return m_checkCase && m_checkCase->isChecked(); |
| } |
| |
| bool AbstractFindWidget::wholeWords() const |
| { |
| return m_checkWholeWords && m_checkWholeWords->isChecked(); |
| } |
| |
| bool AbstractFindWidget::eventFilter(QObject *object, QEvent *e) |
| { |
| if (isVisible() && e->type() == QEvent::KeyPress) { |
| QKeyEvent *ke = static_cast<QKeyEvent*>(e); |
| if (ke->key() == Qt::Key_Escape) { |
| hide(); |
| return true; |
| } |
| } |
| |
| return QWidget::eventFilter(object, e); |
| } |
| |
| QT_END_NAMESPACE |