File indexing completed on 2024-06-16 05:01:27

0001 /* ============================================================
0002 *
0003 * This file is a part of the rekonq project
0004 *
0005 * Copyright (C) 2008-2012 by Andrea Diamantini <adjam7 at gmail dot com>
0006 * Copyright (C) 2009-2011 by Lionel Chauvin <megabigbug@yahoo.fr>
0007 *
0008 *
0009 * This program is free software; you can redistribute it and/or
0010 * modify it under the terms of the GNU General Public License as
0011 * published by the Free Software Foundation; either version 2 of
0012 * the License or (at your option) version 3 or any later version
0013 * accepted by the membership of KDE e.V. (or its successor approved
0014 * by the membership of KDE e.V.), which shall act as a proxy
0015 * defined in Section 14 of version 3 of the license.
0016 *
0017 * This program is distributed in the hope that it will be useful,
0018 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0019 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0020 * GNU General Public License for more details.
0021 *
0022 * You should have received a copy of the GNU General Public License
0023 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0024 *
0025 * ============================================================ */
0026 
0027 
0028 #include "FindBar.h"
0029 #include <QAbstractScrollArea>
0030 #include <QCheckBox>
0031 #include <QHBoxLayout>
0032 #include <QKeyEvent>
0033 #include <QLabel>
0034 #include <QPushButton>
0035 #include <QScrollBar>
0036 #include <QToolButton>
0037 #include <QWebFrame>
0038 #include <QWebView>
0039 #include "LineEdit.h"
0040 #include "Gui/EmbeddedWebView.h"
0041 #include "UiUtils/Color.h"
0042 #include "UiUtils/IconLoader.h"
0043 
0044 namespace Gui {
0045 
0046 FindBar::FindBar(QWidget *parent)
0047     : QWidget(parent)
0048     , m_lineEdit(new LineEdit(this))
0049     , m_matchCase(new QCheckBox(tr("&Match case"), this))
0050     , m_highlightAll(new QCheckBox(tr("&Highlight all"), this)),
0051       m_associatedWebView(nullptr)
0052 {
0053     QHBoxLayout *layout = new QHBoxLayout;
0054 
0055     // cosmetic
0056     layout->setContentsMargins(2, 0, 2, 0);
0057 
0058     // hide button
0059     QToolButton *hideButton = new QToolButton(this);
0060     hideButton->setAutoRaise(true);
0061     hideButton->setIcon(UiUtils::loadIcon(QStringLiteral("dialog-close")));
0062     hideButton->setShortcut(tr("Esc"));
0063     connect(hideButton, &QAbstractButton::clicked, this, &QWidget::hide);
0064     layout->addWidget(hideButton);
0065     layout->setAlignment(hideButton, Qt::AlignLeft | Qt::AlignTop);
0066 
0067     // label
0068     QLabel *label = new QLabel(tr("Find:"));
0069     layout->addWidget(label);
0070 
0071     // Find Bar signal
0072     connect(this, &FindBar::searchString, this, &FindBar::findText);
0073 
0074     // lineEdit, focusProxy
0075     setFocusProxy(m_lineEdit);
0076     m_lineEdit->setMaximumWidth(250);
0077     connect(m_lineEdit, &QLineEdit::textChanged, this, &FindBar::findText);
0078     layout->addWidget(m_lineEdit);
0079 
0080     // buttons
0081     QPushButton *findNext = new QPushButton(UiUtils::loadIcon(QStringLiteral("go-down")), tr("&Next"), this);
0082     findNext->setShortcut(tr("F3"));
0083     QPushButton *findPrev = new QPushButton(UiUtils::loadIcon(QStringLiteral("go-up")), tr("&Previous"), this);
0084     //: Translators: You can change this shortcut, but button names like Shift should not be localized here.
0085     //: That will break setting the shortcut. Button names will still appear localized in the UI.
0086     findPrev->setShortcut(tr("Shift+F3"));
0087     connect(findNext, &QAbstractButton::clicked, this, &FindBar::findNext);
0088     connect(findPrev, &QAbstractButton::clicked, this, &FindBar::findPrevious);
0089     layout->addWidget(findNext);
0090     layout->addWidget(findPrev);
0091 
0092     // Case sensitivity. Deliberately set so this is off by default.
0093     m_matchCase->setCheckState(Qt::Unchecked);
0094     m_matchCase->setTristate(false);
0095     connect(m_matchCase, &QAbstractButton::toggled, this, &FindBar::matchCaseUpdate);
0096     layout->addWidget(m_matchCase);
0097 
0098     // Hightlight All. On by default
0099     m_highlightAll->setCheckState(Qt::Checked);
0100     m_highlightAll->setTristate(false);
0101     connect(m_highlightAll, &QAbstractButton::toggled, this, &FindBar::updateHighlight);
0102     layout->addWidget(m_highlightAll);
0103 
0104     // stretching widget on the left
0105     layout->addStretch();
0106 
0107     setLayout(layout);
0108 
0109     // we start off hidden
0110     hide();
0111 }
0112 
0113 
0114 void FindBar::keyPressEvent(QKeyEvent *event)
0115 {
0116     if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
0117         if (event->modifiers() == Qt::ShiftModifier) {
0118             findPrevious();
0119         } else {
0120             findNext();
0121         }
0122     }
0123 
0124     QWidget::keyPressEvent(event);
0125 }
0126 
0127 
0128 bool FindBar::matchCase() const
0129 {
0130     return m_matchCase->isChecked();
0131 }
0132 
0133 
0134 bool FindBar::highlightAllState() const
0135 {
0136     return m_highlightAll->isChecked();
0137 }
0138 
0139 void FindBar::resetAssociatedWebView()
0140 {
0141     m_associatedWebView = nullptr;
0142     m_lineEdit->clear();
0143     QWidget::hide();
0144 }
0145 
0146 void FindBar::setVisible(bool visible)
0147 {
0148     QWidget::setVisible(visible);
0149 
0150     if (!m_associatedWebView)
0151         return;
0152 
0153     if (visible) {
0154         const QString selectedText = m_associatedWebView->page()->selectedText();
0155         if (!hasFocus() && !selectedText.isEmpty()) {
0156             const QString previousText = m_lineEdit->text();
0157             m_lineEdit->setText(selectedText);
0158 
0159             if (m_lineEdit->text() != previousText) {
0160                 findPrevious();
0161             } else {
0162                 updateHighlight();
0163             }
0164         } else if (selectedText.isEmpty()) {
0165             emit searchString(m_lineEdit->text());
0166         }
0167 
0168         m_lineEdit->setFocus();
0169         m_lineEdit->selectAll();
0170     } else {
0171         updateHighlight();
0172 
0173         // Clear the selection
0174         m_associatedWebView->page()->findText(QString());
0175     }
0176 }
0177 
0178 
0179 void FindBar::notifyMatch(bool match)
0180 {
0181     if (m_lineEdit->text().isEmpty()) {
0182         m_lineEdit->setPalette(QPalette());
0183     } else {
0184         QColor backgroundTint = match ? QColor(0, 0xff, 0, 0x20) : QColor(0xff, 0, 0, 0x20);
0185         QPalette p;
0186         p.setColor(QPalette::Base, UiUtils::tintColor(p.color(QPalette::Base), backgroundTint));
0187         m_lineEdit->setPalette(p);
0188     }
0189 }
0190 
0191 void FindBar::findText(const QString &search)
0192 {
0193     if (!m_associatedWebView)
0194         return;
0195     _lastStringSearched = search;
0196     updateHighlight();
0197     findNext();
0198 }
0199 
0200 
0201 void FindBar::find(FindBar::FindDirection dir)
0202 {
0203     Q_ASSERT(m_associatedWebView);
0204 
0205     if (isHidden()) {
0206         QPoint previous_position = m_associatedWebView->page()->currentFrame()->scrollPosition();
0207         m_associatedWebView->page()->focusNextPrevChild(true);
0208         m_associatedWebView->page()->currentFrame()->setScrollPosition(previous_position);
0209         return;
0210     }
0211 
0212     QWebPage::FindFlags options = QWebPage::FindWrapsAroundDocument;
0213     if (dir == Backward)
0214         options |= QWebPage::FindBackward;
0215     if (matchCase())
0216         options |= QWebPage::FindCaseSensitively;
0217 
0218     // HACK Because we're using the QWebView inside a QScrollArea container, the attempts
0219     // to scroll the QWebView itself have no direct effect.
0220     // Therefore we temporarily shrink the page viewport to the message viewport (ie. the size it
0221     // can cover at max), then perform the search, store the scrollPosition, restore the page viewport
0222     // and finally scroll the messageview to the gathered scrollPosition, mapped to the message (ie.
0223     // usually offset by the mail header)
0224 
0225     auto emb = qobject_cast<EmbeddedWebView *>(m_associatedWebView);
0226 
0227     QAbstractScrollArea *container = emb ? static_cast<QAbstractScrollArea*>(emb->scrollParent()) : nullptr;
0228     const QSize oldVpS = m_associatedWebView->page()->viewportSize();
0229     const bool useResizeTrick = container ? !!container->verticalScrollBar() : false;
0230     // first shrink the page viewport
0231     if (useResizeTrick) {
0232         m_associatedWebView->setUpdatesEnabled(false); // don't let the user see the flicker we might produce
0233         QSize newVpS = oldVpS.boundedTo(container->size());
0234         m_associatedWebView->page()->setViewportSize(newVpS);
0235     }
0236 
0237     // now perform the search (pot. in the shrinked viewport)
0238     bool found = m_associatedWebView->page()->findText(_lastStringSearched, options);
0239     notifyMatch(found);
0240 
0241     // scroll and reset the page viewport if necessary
0242     if (useResizeTrick) {
0243         Q_ASSERT(container->verticalScrollBar());
0244         // the page has now a usable scroll position ...
0245         int scrollPosition = m_associatedWebView->page()->currentFrame()->scrollPosition().y();
0246         // ... which needs to be extended by the pages offset (usually the header widget)
0247         // NOTICE: QWidget::mapTo() fails as the viewport child position can be negative, so we run ourself
0248         QWidget *runner = m_associatedWebView;
0249         while (runner->parentWidget() != container->viewport()) {
0250             scrollPosition += runner->y();
0251             runner = runner->parentWidget();
0252         }
0253         // reset viewport to original size ...
0254         m_associatedWebView->page()->setViewportSize(oldVpS);
0255         // ... let the user see the change ...
0256         m_associatedWebView->setUpdatesEnabled(true);
0257 
0258         // ... and finally scroll to the desired position
0259         if (found)
0260             container->verticalScrollBar()->setValue(scrollPosition);
0261     }
0262 
0263     if (!found) {
0264         QPoint previous_position = m_associatedWebView->page()->currentFrame()->scrollPosition();
0265         m_associatedWebView->page()->focusNextPrevChild(true);
0266         m_associatedWebView->page()->currentFrame()->setScrollPosition(previous_position);
0267     }
0268 }
0269 
0270 void FindBar::findNext()
0271 {
0272     find(FindBar::Forward);
0273 }
0274 
0275 void FindBar::findPrevious()
0276 {
0277     find(FindBar::Backward);
0278 }
0279 
0280 
0281 void FindBar::matchCaseUpdate()
0282 {
0283     Q_ASSERT(m_associatedWebView);
0284 
0285     m_associatedWebView->page()->findText(_lastStringSearched, QWebPage::FindBackward);
0286     findNext();
0287     updateHighlight();
0288 }
0289 
0290 
0291 void FindBar::updateHighlight()
0292 {
0293     Q_ASSERT(m_associatedWebView);
0294 
0295     QWebPage::FindFlags options = QWebPage::HighlightAllOccurrences;
0296 
0297     m_associatedWebView->page()->findText(QString(), options); //Clear an existing highlight
0298 
0299     if (!isHidden() && highlightAllState())
0300     {
0301         if (matchCase())
0302             options |= QWebPage::FindCaseSensitively;
0303         m_associatedWebView->page()->findText(_lastStringSearched, options);
0304     }
0305 }
0306 
0307 void FindBar::setAssociatedWebView(QWebView *webView)
0308 {
0309     if (m_associatedWebView)
0310         disconnect(m_associatedWebView, nullptr, this, nullptr);
0311 
0312     m_associatedWebView = webView;
0313 
0314     if (m_associatedWebView) {
0315         // highlighting is fancy, but terribly expensive - disable by default for fat messages
0316         if (auto emb = qobject_cast<EmbeddedWebView*>(m_associatedWebView)) {
0317             m_highlightAll->setChecked(!emb->staticWidth());
0318         }
0319         // Automatically hide this FindBar widget when the underlying webview goes away
0320         connect(m_associatedWebView.data(), &QObject::destroyed,
0321                 this, &FindBar::resetAssociatedWebView);
0322     }
0323 }
0324 
0325 }