1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4/*! \class AbstractFindWidget
5
6 \brief A search bar that is commonly added below a searchable widget.
7
8 \internal
9
10 This widget implements a search bar which becomes visible when the user
11 wants to start searching. It is a modern replacement for the commonly used
12 search dialog. It is usually placed below the target widget using a QVBoxLayout.
13
14 The search is incremental and can be set to case sensitive or whole words
15 using buttons available on the search bar.
16 */
17
18#include "abstractfindwidget.h"
19
20#include <QtWidgets/QCheckBox>
21#include <QtWidgets/QLabel>
22#include <QtWidgets/QLayout>
23#include <QtWidgets/QLineEdit>
24#include <QtWidgets/QSpacerItem>
25#include <QtWidgets/QToolButton>
26
27#include <QtGui/QAction>
28#include <QtGui/QKeyEvent>
29#include <QtGui/QShortcut>
30
31#include <QtCore/QCoreApplication>
32#include <QtCore/QEvent>
33#include <QtCore/QFile>
34#include <QtCore/QTimer>
35
36QT_BEGIN_NAMESPACE
37
38using namespace Qt::StringLiterals;
39
40static QIcon afwCreateIconSet(const QString &name)
41{
42 QStringList candidates = QStringList()
43 << (QString::fromUtf8(utf8: ":/qt-project.org/shared/images/") + name)
44#ifdef Q_OS_MAC
45 << (QString::fromUtf8(":/qt-project.org/shared/images/mac/") + name);
46#else
47 << (QString::fromUtf8(utf8: ":/qt-project.org/shared/images/win/") + name);
48#endif
49
50 for (const QString &f : std::as_const(t&: candidates)) {
51 if (QFile::exists(fileName: f))
52 return QIcon(f);
53 }
54
55 return QIcon();
56}
57
58/*!
59 Constructs an AbstractFindWidget.
60
61 \a flags can change the layout and turn off certain features.
62 \a parent is passed to the QWidget constructor.
63 */
64AbstractFindWidget::AbstractFindWidget(FindFlags flags, QWidget *parent)
65 : QWidget(parent)
66{
67 QBoxLayout *topLayOut;
68 QBoxLayout *layOut;
69 if (flags & NarrowLayout) {
70 topLayOut = new QVBoxLayout(this);
71 layOut = new QHBoxLayout;
72 topLayOut->addLayout(layout: layOut);
73 } else {
74 topLayOut = layOut = new QHBoxLayout(this);
75 }
76#ifndef Q_OS_MAC
77 topLayOut->setSpacing(6);
78 topLayOut->setContentsMargins(QMargins());
79#endif
80
81 m_toolClose = new QToolButton(this);
82 m_toolClose->setIcon(afwCreateIconSet(name: "closetab.png"_L1));
83 m_toolClose->setAutoRaise(true);
84 layOut->addWidget(m_toolClose);
85 connect(sender: m_toolClose, signal: &QAbstractButton::clicked, context: this, slot: &AbstractFindWidget::deactivate);
86
87 m_editFind = new QLineEdit(this);
88 layOut->addWidget(m_editFind);
89 connect(sender: m_editFind, signal: &QLineEdit::returnPressed, context: this, slot: &AbstractFindWidget::findNext);
90 connect(sender: m_editFind, signal: &QLineEdit::textChanged, context: this, slot: &AbstractFindWidget::findCurrentText);
91 connect(sender: m_editFind, signal: &QLineEdit::textChanged, context: this, slot: &AbstractFindWidget::updateButtons);
92
93 m_toolPrevious = new QToolButton(this);
94 m_toolPrevious->setAutoRaise(true);
95 m_toolPrevious->setText(tr(s: "&Previous"));
96 m_toolPrevious->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
97 m_toolPrevious->setIcon(afwCreateIconSet(name: "previous.png"_L1));
98 layOut->addWidget(m_toolPrevious);
99 connect(sender: m_toolPrevious, signal: &QAbstractButton::clicked, context: this, slot: &AbstractFindWidget::findPrevious);
100
101 m_toolNext = new QToolButton(this);
102 m_toolNext->setAutoRaise(true);
103 m_toolNext->setText(tr(s: "&Next"));
104 m_toolNext->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
105 m_toolNext->setIcon(afwCreateIconSet(name: "next.png"_L1));
106 layOut->addWidget(m_toolNext);
107 connect(sender: m_toolNext, signal: &QAbstractButton::clicked, context: this, slot: &AbstractFindWidget::findNext);
108
109 if (flags & NarrowLayout) {
110 QSizePolicy sp(QSizePolicy::Preferred, QSizePolicy::Fixed);
111 m_toolPrevious->setSizePolicy(sp);
112 m_toolPrevious->setMinimumWidth(m_toolPrevious->minimumSizeHint().height());
113 m_toolNext->setSizePolicy(sp);
114 m_toolNext->setMinimumWidth(m_toolNext->minimumSizeHint().height());
115
116 QSpacerItem *spacerItem =
117 new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum);
118 layOut->addItem(spacerItem);
119
120 layOut = new QHBoxLayout;
121 topLayOut->addLayout(layout: layOut);
122 } else {
123 m_editFind->setMinimumWidth(150);
124 }
125
126 if (!(flags & NoCaseSensitive)) {
127 m_checkCase = new QCheckBox(tr(s: "&Case sensitive"), this);
128 layOut->addWidget(m_checkCase);
129 connect(sender: m_checkCase, signal: &QAbstractButton::toggled,
130 context: this, slot: &AbstractFindWidget::findCurrentText);
131 } else {
132 m_checkCase = 0;
133 }
134
135 if (!(flags & NoWholeWords)) {
136 m_checkWholeWords = new QCheckBox(tr(s: "Whole &words"), this);
137 layOut->addWidget(m_checkWholeWords);
138 connect(sender: m_checkWholeWords, signal: &QAbstractButton::toggled,
139 context: this, slot: &AbstractFindWidget::findCurrentText);
140 } else {
141 m_checkWholeWords = 0;
142 }
143
144 m_labelWrapped = new QLabel(this);
145 m_labelWrapped->setTextFormat(Qt::RichText);
146 m_labelWrapped->setAlignment(
147 Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter);
148 m_labelWrapped->setText(
149 tr(s: "<img src=\":/qt-project.org/shared/images/wrap.png\">"
150 "&nbsp;Search wrapped"));
151 m_labelWrapped->hide();
152 layOut->addWidget(m_labelWrapped);
153
154 QSpacerItem *spacerItem =
155 new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum);
156 layOut->addItem(spacerItem);
157
158 setMinimumWidth(minimumSizeHint().width());
159
160 updateButtons();
161 hide();
162}
163
164/*!
165 Destroys the AbstractFindWidget.
166 */
167AbstractFindWidget::~AbstractFindWidget() = default;
168
169/*!
170 Returns the icon set to be used for the action that initiates a search.
171 */
172QIcon AbstractFindWidget::findIconSet()
173{
174 return afwCreateIconSet(name: "searchfind.png"_L1);
175}
176
177/*!
178 Creates an actions with standard icon and shortcut to activate the widget.
179 */
180QAction *AbstractFindWidget::createFindAction(QObject *parent)
181{
182
183 auto result = new QAction(AbstractFindWidget::findIconSet(),
184 tr(s: "&Find in Text..."), parent);
185 connect(sender: result, signal: &QAction::triggered, context: this, slot: &AbstractFindWidget::activate);
186 result->setShortcut(QKeySequence::Find);
187 return result;
188}
189
190/*!
191 Activates the find widget, making it visible and having focus on its input
192 field.
193 */
194void AbstractFindWidget::activate()
195{
196 show();
197 m_editFind->selectAll();
198 m_editFind->setFocus(Qt::ShortcutFocusReason);
199}
200
201/*!
202 Deactivates the find widget, making it invisible and handing focus to any
203 associated QTextEdit.
204 */
205void AbstractFindWidget::deactivate()
206{
207 hide();
208}
209
210void AbstractFindWidget::findNext()
211{
212 findInternal(textToFind: m_editFind->text(), skipCurrent: true, backward: false);
213}
214
215void AbstractFindWidget::findPrevious()
216{
217 findInternal(textToFind: m_editFind->text(), skipCurrent: true, backward: true);
218}
219
220void AbstractFindWidget::findCurrentText()
221{
222 findInternal(textToFind: m_editFind->text(), skipCurrent: false, backward: false);
223}
224
225void AbstractFindWidget::keyPressEvent(QKeyEvent *event)
226{
227 if (event->key() == Qt::Key_Escape) {
228 deactivate();
229 return;
230 }
231
232 QWidget::keyPressEvent(event);
233}
234
235void AbstractFindWidget::updateButtons()
236{
237 const bool en = !m_editFind->text().isEmpty();
238 m_toolPrevious->setEnabled(en);
239 m_toolNext->setEnabled(en);
240}
241
242void AbstractFindWidget::findInternal(const QString &ttf, bool skipCurrent, bool backward)
243{
244 bool found = false;
245 bool wrapped = false;
246 find(textToFind: ttf, skipCurrent, backward, found: &found, wrapped: &wrapped);
247 QPalette p;
248 p.setColor(acg: QPalette::Active, acr: QPalette::Base, acolor: found ? Qt::white : QColor(255, 102, 102));
249 m_editFind->setPalette(p);
250 m_labelWrapped->setVisible(wrapped);
251}
252
253bool AbstractFindWidget::caseSensitive() const
254{
255 return m_checkCase && m_checkCase->isChecked();
256}
257
258bool AbstractFindWidget::wholeWords() const
259{
260 return m_checkWholeWords && m_checkWholeWords->isChecked();
261}
262
263bool AbstractFindWidget::eventFilter(QObject *object, QEvent *e)
264{
265 if (isVisible() && e->type() == QEvent::KeyPress) {
266 QKeyEvent *ke = static_cast<QKeyEvent*>(e);
267 if (ke->key() == Qt::Key_Escape) {
268 hide();
269 return true;
270 }
271 }
272
273 return QWidget::eventFilter(watched: object, event: e);
274}
275
276QT_END_NAMESPACE
277

source code of qttools/src/shared/findwidget/abstractfindwidget.cpp