1 /* ============================================================
3 * This file is a part of the rekonq project
5 * Copyright (C) 2008-2012 by Andrea Diamantini <adjam7 at gmail dot com>
6 * Copyright (C) 2009-2011 by Lionel Chauvin <megabigbug@yahoo.fr>
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2 of
12 * the License or (at your option) version 3 or any later version
13 * accepted by the membership of KDE e.V. (or its successor approved
14 * by the membership of KDE e.V.), which shall act as a proxy
15 * defined in Section 14 of version 3 of the license.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 * ============================================================ */
29 #include <QAbstractScrollArea>
31 #include <QHBoxLayout>
34 #include <QPushButton>
36 #include <QToolButton>
40 #include "Gui/EmbeddedWebView.h"
41 #include "UiUtils/Color.h"
42 #include "UiUtils/IconLoader.h"
46 FindBar::FindBar(QWidget
*parent
)
48 , m_lineEdit(new LineEdit(this))
49 , m_matchCase(new QCheckBox(tr("&Match case"), this))
50 , m_highlightAll(new QCheckBox(tr("&Highlight all"), this)),
51 m_associatedWebView(nullptr)
53 QHBoxLayout
*layout
= new QHBoxLayout
;
56 layout
->setContentsMargins(2, 0, 2, 0);
59 QToolButton
*hideButton
= new QToolButton(this);
60 hideButton
->setAutoRaise(true);
61 hideButton
->setIcon(UiUtils::loadIcon(QStringLiteral("dialog-close")));
62 hideButton
->setShortcut(tr("Esc"));
63 connect(hideButton
, &QAbstractButton::clicked
, this, &QWidget::hide
);
64 layout
->addWidget(hideButton
);
65 layout
->setAlignment(hideButton
, Qt::AlignLeft
| Qt::AlignTop
);
68 QLabel
*label
= new QLabel(tr("Find:"));
69 layout
->addWidget(label
);
72 connect(this, &FindBar::searchString
, this, &FindBar::findText
);
74 // lineEdit, focusProxy
75 setFocusProxy(m_lineEdit
);
76 m_lineEdit
->setMaximumWidth(250);
77 connect(m_lineEdit
, &QLineEdit::textChanged
, this, &FindBar::findText
);
78 layout
->addWidget(m_lineEdit
);
81 QPushButton
*findNext
= new QPushButton(UiUtils::loadIcon(QStringLiteral("go-down")), tr("&Next"), this);
82 findNext
->setShortcut(tr("F3"));
83 QPushButton
*findPrev
= new QPushButton(UiUtils::loadIcon(QStringLiteral("go-up")), tr("&Previous"), this);
84 //: Translators: You can change this shortcut, but button names like Shift should not be localized here.
85 //: That will break setting the shortcut. Button names will still appear localized in the UI.
86 findPrev
->setShortcut(tr("Shift+F3"));
87 connect(findNext
, &QAbstractButton::clicked
, this, &FindBar::findNext
);
88 connect(findPrev
, &QAbstractButton::clicked
, this, &FindBar::findPrevious
);
89 layout
->addWidget(findNext
);
90 layout
->addWidget(findPrev
);
92 // Case sensitivity. Deliberately set so this is off by default.
93 m_matchCase
->setCheckState(Qt::Unchecked
);
94 m_matchCase
->setTristate(false);
95 connect(m_matchCase
, &QAbstractButton::toggled
, this, &FindBar::matchCaseUpdate
);
96 layout
->addWidget(m_matchCase
);
98 // Hightlight All. On by default
99 m_highlightAll
->setCheckState(Qt::Checked
);
100 m_highlightAll
->setTristate(false);
101 connect(m_highlightAll
, &QAbstractButton::toggled
, this, &FindBar::updateHighlight
);
102 layout
->addWidget(m_highlightAll
);
104 // stretching widget on the left
105 layout
->addStretch();
109 // we start off hidden
114 void FindBar::keyPressEvent(QKeyEvent
*event
)
116 if (event
->key() == Qt::Key_Return
|| event
->key() == Qt::Key_Enter
) {
117 if (event
->modifiers() == Qt::ShiftModifier
) {
124 QWidget::keyPressEvent(event
);
128 bool FindBar::matchCase() const
130 return m_matchCase
->isChecked();
134 bool FindBar::highlightAllState() const
136 return m_highlightAll
->isChecked();
139 void FindBar::resetAssociatedWebView()
141 m_associatedWebView
= nullptr;
146 void FindBar::setVisible(bool visible
)
148 QWidget::setVisible(visible
);
150 if (!m_associatedWebView
)
154 const QString selectedText
= m_associatedWebView
->page()->selectedText();
155 if (!hasFocus() && !selectedText
.isEmpty()) {
156 const QString previousText
= m_lineEdit
->text();
157 m_lineEdit
->setText(selectedText
);
159 if (m_lineEdit
->text() != previousText
) {
164 } else if (selectedText
.isEmpty()) {
165 emit
searchString(m_lineEdit
->text());
168 m_lineEdit
->setFocus();
169 m_lineEdit
->selectAll();
173 // Clear the selection
174 m_associatedWebView
->page()->findText(QString());
179 void FindBar::notifyMatch(bool match
)
181 if (m_lineEdit
->text().isEmpty()) {
182 m_lineEdit
->setPalette(QPalette());
184 QColor backgroundTint
= match
? QColor(0, 0xff, 0, 0x20) : QColor(0xff, 0, 0, 0x20);
186 p
.setColor(QPalette::Base
, UiUtils::tintColor(p
.color(QPalette::Base
), backgroundTint
));
187 m_lineEdit
->setPalette(p
);
191 void FindBar::findText(const QString
&search
)
193 if (!m_associatedWebView
)
195 _lastStringSearched
= search
;
201 void FindBar::find(FindBar::FindDirection dir
)
203 Q_ASSERT(m_associatedWebView
);
206 QPoint previous_position
= m_associatedWebView
->page()->currentFrame()->scrollPosition();
207 m_associatedWebView
->page()->focusNextPrevChild(true);
208 m_associatedWebView
->page()->currentFrame()->setScrollPosition(previous_position
);
212 QWebPage::FindFlags options
= QWebPage::FindWrapsAroundDocument
;
214 options
|= QWebPage::FindBackward
;
216 options
|= QWebPage::FindCaseSensitively
;
218 // HACK Because we're using the QWebView inside a QScrollArea container, the attempts
219 // to scroll the QWebView itself have no direct effect.
220 // Therefore we temporarily shrink the page viewport to the message viewport (ie. the size it
221 // can cover at max), then perform the search, store the scrollPosition, restore the page viewport
222 // and finally scroll the messageview to the gathered scrollPosition, mapped to the message (ie.
223 // usually offset by the mail header)
225 auto emb
= qobject_cast
<EmbeddedWebView
*>(m_associatedWebView
);
227 QAbstractScrollArea
*container
= emb
? static_cast<QAbstractScrollArea
*>(emb
->scrollParent()) : nullptr;
228 const QSize oldVpS
= m_associatedWebView
->page()->viewportSize();
229 const bool useResizeTrick
= container
? !!container
->verticalScrollBar() : false;
230 // first shrink the page viewport
231 if (useResizeTrick
) {
232 m_associatedWebView
->setUpdatesEnabled(false); // don't let the user see the flicker we might produce
233 QSize newVpS
= oldVpS
.boundedTo(container
->size());
234 m_associatedWebView
->page()->setViewportSize(newVpS
);
237 // now perform the search (pot. in the shrinked viewport)
238 bool found
= m_associatedWebView
->page()->findText(_lastStringSearched
, options
);
241 // scroll and reset the page viewport if necessary
242 if (useResizeTrick
) {
243 Q_ASSERT(container
->verticalScrollBar());
244 // the page has now a usable scroll position ...
245 int scrollPosition
= m_associatedWebView
->page()->currentFrame()->scrollPosition().y();
246 // ... which needs to be extended by the pages offset (usually the header widget)
247 // NOTICE: QWidget::mapTo() fails as the viewport child position can be negative, so we run ourself
248 QWidget
*runner
= m_associatedWebView
;
249 while (runner
->parentWidget() != container
->viewport()) {
250 scrollPosition
+= runner
->y();
251 runner
= runner
->parentWidget();
253 // reset viewport to original size ...
254 m_associatedWebView
->page()->setViewportSize(oldVpS
);
255 // ... let the user see the change ...
256 m_associatedWebView
->setUpdatesEnabled(true);
258 // ... and finally scroll to the desired position
260 container
->verticalScrollBar()->setValue(scrollPosition
);
264 QPoint previous_position
= m_associatedWebView
->page()->currentFrame()->scrollPosition();
265 m_associatedWebView
->page()->focusNextPrevChild(true);
266 m_associatedWebView
->page()->currentFrame()->setScrollPosition(previous_position
);
270 void FindBar::findNext()
272 find(FindBar::Forward
);
275 void FindBar::findPrevious()
277 find(FindBar::Backward
);
281 void FindBar::matchCaseUpdate()
283 Q_ASSERT(m_associatedWebView
);
285 m_associatedWebView
->page()->findText(_lastStringSearched
, QWebPage::FindBackward
);
291 void FindBar::updateHighlight()
293 Q_ASSERT(m_associatedWebView
);
295 QWebPage::FindFlags options
= QWebPage::HighlightAllOccurrences
;
297 m_associatedWebView
->page()->findText(QString(), options
); //Clear an existing highlight
299 if (!isHidden() && highlightAllState())
302 options
|= QWebPage::FindCaseSensitively
;
303 m_associatedWebView
->page()->findText(_lastStringSearched
, options
);
307 void FindBar::setAssociatedWebView(QWebView
*webView
)
309 if (m_associatedWebView
)
310 disconnect(m_associatedWebView
, nullptr, this, nullptr);
312 m_associatedWebView
= webView
;
314 if (m_associatedWebView
) {
315 // highlighting is fancy, but terribly expensive - disable by default for fat messages
316 if (auto emb
= qobject_cast
<EmbeddedWebView
*>(m_associatedWebView
)) {
317 m_highlightAll
->setChecked(!emb
->staticWidth());
319 // Automatically hide this FindBar widget when the underlying webview goes away
320 connect(m_associatedWebView
.data(), &QObject::destroyed
,
321 this, &FindBar::resetAssociatedWebView
);