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 }