11#include <QLoggingCategory>
13#include "AddressCompleter.h"
14#include "AutoCompleteLineEdit.h"
15#include "HistoryDatabase.h"
17static Q_LOGGING_CATEGORY(log,
"WebBrowser.Address")
28 QHBoxLayout *layout =
new QHBoxLayout(
this);
29 layout->setContentsMargins(8, 4, 8, 4);
30 layout->setSpacing(8);
33 m_iconLabel =
new QLabel(
this);
34 m_iconLabel->setFixedSize(16, 16);
35 QPixmap pixmap = icon.pixmap(16, 16);
36 m_iconLabel->setPixmap(pixmap);
37 layout->addWidget(m_iconLabel);
40 m_titleLabel =
new QLabel(title,
this);
41 m_titleLabel->setStyleSheet(
"font-weight: bold;");
42 layout->addWidget(m_titleLabel, 1);
45 m_urlLabel =
new QLabel(url,
this);
47 layout->addWidget(m_urlLabel);
52 setAttribute(Qt::WA_Hover);
57 , m_pLineEdit(nullptr)
58 , m_pShowAnimation(nullptr)
59 , m_pHideAnimation(nullptr)
60 , m_currentSelectedIndex(-1)
61 , m_maxVisibleItems(8)
62 , m_isCompleterVisible(false)
66 m_szEnter = tr(
"Enter '@' show commands") +
"; "
67 + tr(
"Enter a website URL or search content ......");
68 m_szLineEditToolTip = m_szEnter +
"\n\n"
69 + tr(
"Enter ↲ key: Apply current url");
71 m_szListWidgetToolTip += tr(
"Enter ↲ key: Apply current item") +
"\n";
72 m_szListWidgetToolTip += tr(
"Tab ⇆ key: Apply current item") +
"\n";
73 m_szListWidgetToolTip += tr(
"Esc Key: Exit address completer") +
"\n";
74 m_szListWidgetToolTip += tr(
"Space Key: Exit address completer") +
"\n";
75 m_szListWidgetToolTip += tr(
"↑ (Upper arrow) key: Select previous item") +
"\n";
76 m_szListWidgetToolTip += tr(
"↓ (Down arrow) key: Select next item");
78 m_szLineEditToolTipShow = m_szEnter +
"\n\n" + m_szListWidgetToolTip;
79 setToolTip(m_szListWidgetToolTip);
81#if defined(Q_OS_LINUX)
82 if(-1 == QGuiApplication::platformName().indexOf(QRegularExpression(
"wayland.*")))
91 m_pSearchTimer =
new QTimer(
this);
93 m_pSearchTimer->setSingleShot(
true);
94 m_pSearchTimer->setInterval(300);
95 bool check = connect(m_pSearchTimer, &QTimer::timeout,
96 this, &CAddressCompleter::performSearch);
101 m_pShowAnimation =
new QPropertyAnimation(
this,
"geometry",
this);
102 m_pHideAnimation =
new QPropertyAnimation(
this,
"geometry",
this);
108CAddressCompleter::~CAddressCompleter()
112void CAddressCompleter::setupUI()
114 Qt::WindowFlags flags;
115 flags = Qt::FramelessWindowHint | Qt::CustomizeWindowHint;
119 setWindowFlags(flags);
120 setAttribute(Qt::WA_TranslucentBackground);
122 QVBoxLayout *mainLayout =
new QVBoxLayout(
this);
123 mainLayout->setContentsMargins(0, 0, 0, 0);
124 mainLayout->setSpacing(0);
126 m_pListWidget =
new QListWidget(
this);
128 m_pListWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
129 m_pListWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
130 m_pListWidget->setFocusPolicy(Qt::NoFocus);
132 bool check = connect(m_pListWidget, &QListWidget::itemClicked,
133 this, &CAddressCompleter::onItemClicked);
136 mainLayout->addWidget(m_pListWidget);
138 setLayout(mainLayout);
142 int maxHeight = m_maxVisibleItems * itemHeight + 10;
143 setMaximumHeight(maxHeight);
146void CAddressCompleter::attachToLineEdit(QLineEdit *lineEdit)
149 m_pLineEdit->removeEventFilter(
this);
150 m_pLineEdit->setToolTip(m_szOldLineEditToolTip);
153 m_pLineEdit = lineEdit;
155 m_szOldLineEditToolTip = m_pLineEdit->toolTip();
156 m_pLineEdit->setToolTip(m_szLineEditToolTip);
157 m_pLineEdit->installEventFilter(
this);
158 connect(m_pLineEdit, &QLineEdit::textEdited,
159 this, &CAddressCompleter::onTextChanged);
162 m_pLineEdit->setPlaceholderText(m_szEnter);
166void CAddressCompleter::setMaxVisibleItems(
int count)
168 m_maxVisibleItems = count;
170 int maxHeight = m_maxVisibleItems * itemHeight + 10;
171 setMaximumHeight(maxHeight);
174bool CAddressCompleter::eventFilter(QObject *watched, QEvent *event)
176 if (watched == m_pLineEdit) {
177 switch (event->type()) {
178 case QEvent::KeyPress: {
179 QKeyEvent *keyEvent =
static_cast<QKeyEvent*
>(event);
181 switch (keyEvent->key()) {
183 case Qt::Key_PageDown:
184 if (m_isCompleterVisible) {
191 if (m_isCompleterVisible) {
192 moveToPreviousItem();
198 if (m_isCompleterVisible && m_currentSelectedIndex >= 0) {
203 emit urlSelected(m_pLineEdit->text());
210 if(m_pLineEdit->text().startsWith(
'@'))
215 if (m_isCompleterVisible && m_currentSelectedIndex >= 0) {
223 case QEvent::FocusOut:
225 QTimer::singleShot(100,
this, [
this]() {
227 if (!underMouse() && m_pListWidget && !m_pListWidget->underMouse()) {
237 return QWidget::eventFilter(watched, event);
240void CAddressCompleter::showEvent(QShowEvent *event)
243 m_isCompleterVisible =
true;
247 m_pLineEdit->setFocus();
251void CAddressCompleter::hideEvent(QHideEvent *event)
254 m_isCompleterVisible =
false;
255 m_currentSelectedIndex = -1;
258void CAddressCompleter::onTextChanged(
const QString &text)
260 if (text.isEmpty()) {
267 m_pSearchTimer->start();
270void CAddressCompleter::performSearch()
272 qDebug(log) << Q_FUNC_INFO;
273 QString keyword = m_pLineEdit->text().trimmed();
274 if (keyword.isEmpty() || !m_pListWidget) {
280 m_pListWidget->clear();
281 m_currentSelectedIndex = -1;
284 if(keyword.startsWith(
'@')) {
285 QList<Command> lstCommonds;
286 lstCommonds <<
Command{tr(
"Search"),
"@search:", QIcon::fromTheme(
"system-search")};
287 lstCommonds <<
Command{tr(
"Setting"),
"@setting", QIcon::fromTheme(
"system-settings")};
288 lstCommonds <<
Command{tr(
"History"),
"@history", QIcon()};
289 lstCommonds <<
Command{tr(
"Bookmarks"),
"@bookmarks", QIcon::fromTheme(
"user-bookmarks")};
292 std::sort(lstCommonds.begin(), lstCommonds.end(), [keyword](
Command a,
Command b){
294 if (a.cmd.startsWith(keyword) && !b.cmd.startsWith(keyword))
296 if (!a.cmd.startsWith(keyword) && b.cmd.startsWith(keyword))
300 bool aContains = a.cmd.contains(keyword, Qt::CaseInsensitive);
301 bool bContains = b.cmd.contains(keyword, Qt::CaseInsensitive);
302 if (aContains && !bContains) return true;
303 if (!aContains && bContains) return false;
306 return a.cmd < b.cmd;
308 foreach(
auto cmd, lstCommonds) {
309 QListWidgetItem *item =
new QListWidgetItem(m_pListWidget);
310 item->setSizeHint(QSize(0, 40));
311 item->setData(Qt::UserRole, cmd.cmd);
312 CAddressCompleterItem *pCompleterItem =
new CAddressCompleterItem(
313 cmd.title, cmd.cmd, cmd.icon);
315 m_pListWidget->setItemWidget(item, pCompleterItem);
320 QList<HistoryItem> lstHistory;
322 lstHistory = m_pDatabase->searchHistory(keyword);
326 QStringList addedUrls;
328 foreach(
auto i, lstHistory) {
330 QString title = i.title;
333 if (addedUrls.contains(url)) {
338 QListWidgetItem *item =
new QListWidgetItem(m_pListWidget);
339 item->setSizeHint(QSize(0, 40));
342 CAddressCompleterItem *completerItem =
new CAddressCompleterItem(
343 title.isEmpty() ? url : title,
348 completerItem->setToolTip(title +
"\n" + url +
"\n\n" + toolTip());
349 m_pListWidget->setItemWidget(item, completerItem);
351 item->setData(Qt::UserRole, url);
358 CAutoCompleteLineEdit* pEdit = qobject_cast<CAutoCompleteLineEdit*>(m_pLineEdit);
360 pEdit->setCompletions(addedUrls);
363 if (m_pListWidget->count() == 0) {
364 addSearchSuggestions(keyword);
368 if (m_pListWidget->count() > 0) {
370 if(-1 == m_currentSelectedIndex)
371 m_currentSelectedIndex = 0;
373 m_pListWidget->setCurrentRow(m_currentSelectedIndex);
375 m_pListWidget->scrollToItem(m_pListWidget->item(m_currentSelectedIndex));
383void CAddressCompleter::addSearchSuggestions(
const QString &keyword)
385 qDebug(log) << Q_FUNC_INFO;
390 QString searchText = tr(
"Search \"%1\"").arg(keyword);
392 QListWidgetItem *pSearchItem =
new QListWidgetItem(m_pListWidget);
394 pSearchItem->setSizeHint(QSize(0, 40));
395 pSearchItem->setData(Qt::UserRole, QString(
"@search:%1").arg(keyword));
397 CAddressCompleterItem *pCompleterItem =
new CAddressCompleterItem(
399 tr(
"Use default search engine"),
400 QIcon(
":/icons/search.png")
403 m_pListWidget->setItemWidget(pSearchItem, pCompleterItem);
407 QStringList commonSites = {
408 "https://www.bing.com/search?q=%1",
409 "https://www.google.com/search?q=%1",
410 "https://www.baidu.com/s?wd=%1",
411 "https://github.com/search?q=%1"
414 for (
const QString &site : commonSites) {
415 QString url = site.arg(keyword);
416 QString displayUrl = site.left(site.indexOf(
"?"));
418 QListWidgetItem *pSiteItem =
new QListWidgetItem(m_pListWidget);
420 pSiteItem->setSizeHint(QSize(0, 40));
421 pSiteItem->setData(Qt::UserRole, url);
423 CAddressCompleterItem *pSiteCompleterItem =
new CAddressCompleterItem(
424 tr(
"Search in %1").arg(QUrl(displayUrl).host()),
426 getIconForUrl(displayUrl)
428 if(pSiteCompleterItem)
429 m_pListWidget->setItemWidget(pSiteItem, pSiteCompleterItem);
434void CAddressCompleter::onItemClicked(QListWidgetItem *item)
438 QString url = item->data(Qt::UserRole).toString();
441 if (url.startsWith(
"@search:", Qt::CaseInsensitive)) {
442 QString keyword = url.mid(8);
444 emit searchRequested(keyword);
445 }
if(url.startsWith(
"@")) {
446 emit sigCommand(url);
449 emit urlSelected(url);
456 m_pLineEdit->setText(url);
457 m_pLineEdit->setFocus();
461void CAddressCompleter::moveToNextItem()
463 if(!m_pListWidget)
return;
464 int count = m_pListWidget->count();
465 if (count == 0)
return;
467 m_currentSelectedIndex = (m_currentSelectedIndex + 1) % count;
468 m_pListWidget->setCurrentRow(m_currentSelectedIndex);
471 m_pListWidget->scrollToItem(m_pListWidget->item(m_currentSelectedIndex));
474void CAddressCompleter::moveToPreviousItem()
476 if(!m_pListWidget)
return;
477 int count = m_pListWidget->count();
478 if (count == 0)
return;
480 m_currentSelectedIndex = (m_currentSelectedIndex - 1 + count) % count;
481 m_pListWidget->setCurrentRow(m_currentSelectedIndex);
484 m_pListWidget->scrollToItem(m_pListWidget->item(m_currentSelectedIndex));
487void CAddressCompleter::selectCurrentItem()
490 if(!m_pListWidget)
return;
491 QListWidgetItem *item = m_pListWidget->item(m_currentSelectedIndex);
497void CAddressCompleter::showCompleter()
499 if(!m_pListWidget)
return;
500 if (m_isCompleterVisible || m_pListWidget->count() == 0) {
504 m_pLineEdit->setToolTip(m_szLineEditToolTipShow);
505 updateCompleterPosition();
508 QRect startRect = geometry();
509 startRect.setHeight(0);
511 QRect endRect = geometry();
513 int visibleItems = qMin(m_pListWidget->count(), m_maxVisibleItems);
514 int totalHeight = visibleItems * itemHeight + 10;
516 endRect.setHeight(totalHeight);
518 if(m_pShowAnimation) {
519 m_pShowAnimation->setDuration(200);
520 m_pShowAnimation->setStartValue(startRect);
521 m_pShowAnimation->setEndValue(endRect);
522 m_pShowAnimation->setEasingCurve(QEasingCurve::OutCubic);
523 connect(m_pShowAnimation, &QPropertyAnimation::finished,
528 m_pShowAnimation->start();
530 setGeometry(endRect);
536void CAddressCompleter::hideCompleter()
538 qDebug(log) << Q_FUNC_INFO;
539 if (!m_isCompleterVisible) {
543 qDebug(log) << Q_FUNC_INFO <<
"end";
545 m_pLineEdit->setToolTip(m_szLineEditToolTip);
548 QRect startRect = geometry();
549 QRect endRect = geometry();
550 endRect.setHeight(0);
552 if(m_pHideAnimation) {
553 m_pHideAnimation->setDuration(150);
554 m_pHideAnimation->setStartValue(startRect);
555 m_pHideAnimation->setEndValue(endRect);
556 m_pHideAnimation->setEasingCurve(QEasingCurve::InCubic);
558 connect(m_pHideAnimation, &QPropertyAnimation::finished,
559 this, &CAddressCompleter::hide);
561 m_pHideAnimation->start();
566void CAddressCompleter::updateCompleterPosition()
568 if (!m_pLineEdit)
return;
571 QPoint globalPos = m_pLineEdit->mapToGlobal(QPoint(0, m_pLineEdit->height()));
574 int width = m_pLineEdit->width();
577 QScreen *screen = QApplication::screenAt(globalPos);
579 QRect screenRect = screen->availableGeometry();
582 int availableHeight = screenRect.bottom() - globalPos.y();
584 int requiredHeight = qMin(m_pListWidget->count(), m_maxVisibleItems) * itemHeight + 10;
586 if (availableHeight < requiredHeight) {
587 globalPos = m_pLineEdit->mapToGlobal(QPoint(0, -requiredHeight));
591 if (globalPos.x() + width > screenRect.right()) {
592 globalPos.setX(screenRect.right() - width);
597 QWidget* pWin = qobject_cast<QWidget*>(parent());
599 globalPos = pWin->mapFromGlobal(globalPos);
601 qCritical(log) <<
"Current platform plugin is wayland. the parent must be set.";
606 setGeometry(globalPos.x(), globalPos.y(), width, 0);
609QIcon CAddressCompleter::getIconForUrl(
const QString &url)
614 static QIcon defaultIcon;
615 static QIcon httpIcon;
616 static QIcon httpsIcon;
617 static QIcon searchIcon = QIcon::fromTheme(
"system-search");
619 if (url.startsWith(
"https://")) {
621 }
else if (url.startsWith(
"http://")) {
623 }
else if (url.contains(
"@search", Qt::CaseInsensitive)) {
Browser address bar autocomplete feature.
The CHistoryDatabase class.