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)
65 m_szEnter = tr(
"Enter '@' show commands") +
"; "
66 + tr(
"Enter a website URL or search content ......");
67 m_szLineEditToolTip = m_szEnter +
"\n\n"
68 + tr(
"Enter ↲ key: Apply current url");
70 m_szListWidgetToolTip += tr(
"Enter ↲ key: Apply current item") +
"\n";
71 m_szListWidgetToolTip += tr(
"Tab ⇆ key: Apply current item") +
"\n";
72 m_szListWidgetToolTip += tr(
"Esc Key: Exit address completer") +
"\n";
73 m_szListWidgetToolTip += tr(
"Space Key: Exit address completer") +
"\n";
74 m_szListWidgetToolTip += tr(
"↑ (Upper arrow) key: Select previous item") +
"\n";
75 m_szListWidgetToolTip += tr(
"↓ (Down arrow) key: Select next item");
77 m_szLineEditToolTipShow = m_szEnter +
"\n\n" + m_szListWidgetToolTip;
78 setToolTip(m_szListWidgetToolTip);
83 m_pSearchTimer =
new QTimer(
this);
85 m_pSearchTimer->setSingleShot(
true);
86 m_pSearchTimer->setInterval(300);
87 bool check = connect(m_pSearchTimer, &QTimer::timeout,
88 this, &CAddressCompleter::performSearch);
93 m_pShowAnimation =
new QPropertyAnimation(
this,
"geometry",
this);
94 m_pHideAnimation =
new QPropertyAnimation(
this,
"geometry",
this);
100CAddressCompleter::~CAddressCompleter()
104void CAddressCompleter::setupUI()
106 setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint);
107 setAttribute(Qt::WA_TranslucentBackground);
109 QVBoxLayout *mainLayout =
new QVBoxLayout(
this);
110 mainLayout->setContentsMargins(0, 0, 0, 0);
111 mainLayout->setSpacing(0);
113 m_pListWidget =
new QListWidget(
this);
115 m_pListWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
116 m_pListWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
117 m_pListWidget->setFocusPolicy(Qt::NoFocus);
119 bool check = connect(m_pListWidget, &QListWidget::itemClicked,
120 this, &CAddressCompleter::onItemClicked);
123 mainLayout->addWidget(m_pListWidget);
125 setLayout(mainLayout);
129 int maxHeight = m_maxVisibleItems * itemHeight + 10;
130 setMaximumHeight(maxHeight);
133void CAddressCompleter::attachToLineEdit(QLineEdit *lineEdit)
136 m_pLineEdit->removeEventFilter(
this);
137 m_pLineEdit->setToolTip(m_szOldLineEditToolTip);
140 m_pLineEdit = lineEdit;
142 m_szOldLineEditToolTip = m_pLineEdit->toolTip();
143 m_pLineEdit->setToolTip(m_szLineEditToolTip);
144 m_pLineEdit->installEventFilter(
this);
145 connect(m_pLineEdit, &QLineEdit::textEdited,
146 this, &CAddressCompleter::onTextChanged);
149 m_pLineEdit->setPlaceholderText(m_szEnter);
153void CAddressCompleter::setMaxVisibleItems(
int count)
155 m_maxVisibleItems = count;
157 int maxHeight = m_maxVisibleItems * itemHeight + 10;
158 setMaximumHeight(maxHeight);
161bool CAddressCompleter::eventFilter(QObject *watched, QEvent *event)
163 if (watched == m_pLineEdit) {
164 switch (event->type()) {
165 case QEvent::KeyPress: {
166 QKeyEvent *keyEvent =
static_cast<QKeyEvent*
>(event);
168 switch (keyEvent->key()) {
170 case Qt::Key_PageDown:
171 if (m_isCompleterVisible) {
178 if (m_isCompleterVisible) {
179 moveToPreviousItem();
185 if (m_isCompleterVisible && m_currentSelectedIndex >= 0) {
190 emit urlSelected(m_pLineEdit->text());
197 if(m_pLineEdit->text().startsWith(
'@'))
202 if (m_isCompleterVisible && m_currentSelectedIndex >= 0) {
210 case QEvent::FocusOut:
212 QTimer::singleShot(100,
this, [
this]() {
214 if (!underMouse() && m_pListWidget && !m_pListWidget->underMouse()) {
224 return QWidget::eventFilter(watched, event);
227void CAddressCompleter::showEvent(QShowEvent *event)
230 m_isCompleterVisible =
true;
234 m_pLineEdit->setFocus();
238void CAddressCompleter::hideEvent(QHideEvent *event)
241 m_isCompleterVisible =
false;
242 m_currentSelectedIndex = -1;
245void CAddressCompleter::onTextChanged(
const QString &text)
247 if (text.isEmpty()) {
254 m_pSearchTimer->start();
257void CAddressCompleter::performSearch()
259 qDebug(log) << Q_FUNC_INFO;
260 QString keyword = m_pLineEdit->text().trimmed();
261 if (keyword.isEmpty() || !m_pListWidget) {
267 m_pListWidget->clear();
268 m_currentSelectedIndex = -1;
271 if(keyword.startsWith(
'@')) {
272 QList<Command> lstCommonds;
273 lstCommonds << Command{tr(
"Search"),
"@search:", QIcon::fromTheme(
"system-search")};
274 lstCommonds << Command{tr(
"Setting"),
"@setting", QIcon::fromTheme(
"system-settings")};
275 lstCommonds << Command{tr(
"History"),
"@history", QIcon()};
276 lstCommonds << Command{tr(
"Bookmarks"),
"@bookmarks", QIcon::fromTheme(
"user-bookmarks")};
279 std::sort(lstCommonds.begin(), lstCommonds.end(), [keyword](Command a, Command b){
281 if (a.cmd.startsWith(keyword) && !b.cmd.startsWith(keyword))
283 if (!a.cmd.startsWith(keyword) && b.cmd.startsWith(keyword))
287 bool aContains = a.cmd.contains(keyword, Qt::CaseInsensitive);
288 bool bContains = b.cmd.contains(keyword, Qt::CaseInsensitive);
289 if (aContains && !bContains) return true;
290 if (!aContains && bContains) return false;
293 return a.cmd < b.cmd;
295 foreach(
auto cmd, lstCommonds) {
296 QListWidgetItem *item =
new QListWidgetItem(m_pListWidget);
297 item->setSizeHint(QSize(0, 40));
298 item->setData(Qt::UserRole, cmd.cmd);
300 cmd.title, cmd.cmd, cmd.icon);
302 m_pListWidget->setItemWidget(item, pCompleterItem);
307 QList<HistoryItem> lstHistory;
309 lstHistory = m_pDatabase->searchHistory(keyword);
313 QStringList addedUrls;
315 foreach(
auto i, lstHistory) {
317 QString title = i.title;
320 if (addedUrls.contains(url)) {
325 QListWidgetItem *item =
new QListWidgetItem(m_pListWidget);
326 item->setSizeHint(QSize(0, 40));
330 title.isEmpty() ? url : title,
335 completerItem->setToolTip(title +
"\n" + url +
"\n\n" + toolTip());
336 m_pListWidget->setItemWidget(item, completerItem);
338 item->setData(Qt::UserRole, url);
347 pEdit->setCompletions(addedUrls);
350 if (m_pListWidget->count() == 0) {
351 addSearchSuggestions(keyword);
355 if (m_pListWidget->count() > 0) {
357 if(-1 == m_currentSelectedIndex)
358 m_currentSelectedIndex = 0;
360 m_pListWidget->setCurrentRow(m_currentSelectedIndex);
362 m_pListWidget->scrollToItem(m_pListWidget->item(m_currentSelectedIndex));
370void CAddressCompleter::addSearchSuggestions(
const QString &keyword)
372 qDebug(log) << Q_FUNC_INFO;
377 QString searchText = tr(
"Search \"%1\"").arg(keyword);
379 QListWidgetItem *pSearchItem =
new QListWidgetItem(m_pListWidget);
381 pSearchItem->setSizeHint(QSize(0, 40));
382 pSearchItem->setData(Qt::UserRole, QString(
"@search:%1").arg(keyword));
386 tr(
"Use default search engine"),
387 QIcon(
":/icons/search.png")
390 m_pListWidget->setItemWidget(pSearchItem, pCompleterItem);
394 QStringList commonSites = {
395 "https://www.bing.com/search?q=%1",
396 "https://www.google.com/search?q=%1",
397 "https://www.baidu.com/s?wd=%1",
398 "https://github.com/search?q=%1"
401 for (
const QString &site : commonSites) {
402 QString url = site.arg(keyword);
403 QString displayUrl = site.left(site.indexOf(
"?"));
405 QListWidgetItem *pSiteItem =
new QListWidgetItem(m_pListWidget);
407 pSiteItem->setSizeHint(QSize(0, 40));
408 pSiteItem->setData(Qt::UserRole, url);
411 tr(
"Search in %1").arg(QUrl(displayUrl).host()),
413 getIconForUrl(displayUrl)
415 if(pSiteCompleterItem)
416 m_pListWidget->setItemWidget(pSiteItem, pSiteCompleterItem);
421void CAddressCompleter::onItemClicked(QListWidgetItem *item)
425 QString url = item->data(Qt::UserRole).toString();
428 if (url.startsWith(
"@search:", Qt::CaseInsensitive)) {
429 QString keyword = url.mid(8);
431 emit searchRequested(keyword);
432 }
if(url.startsWith(
"@")) {
433 emit sigCommand(url);
436 emit urlSelected(url);
443 m_pLineEdit->setText(url);
444 m_pLineEdit->setFocus();
448void CAddressCompleter::moveToNextItem()
450 if(!m_pListWidget)
return;
451 int count = m_pListWidget->count();
452 if (count == 0)
return;
454 m_currentSelectedIndex = (m_currentSelectedIndex + 1) % count;
455 m_pListWidget->setCurrentRow(m_currentSelectedIndex);
458 m_pListWidget->scrollToItem(m_pListWidget->item(m_currentSelectedIndex));
461void CAddressCompleter::moveToPreviousItem()
463 if(!m_pListWidget)
return;
464 int count = m_pListWidget->count();
465 if (count == 0)
return;
467 m_currentSelectedIndex = (m_currentSelectedIndex - 1 + count) % count;
468 m_pListWidget->setCurrentRow(m_currentSelectedIndex);
471 m_pListWidget->scrollToItem(m_pListWidget->item(m_currentSelectedIndex));
474void CAddressCompleter::selectCurrentItem()
477 if(!m_pListWidget)
return;
478 QListWidgetItem *item = m_pListWidget->item(m_currentSelectedIndex);
484void CAddressCompleter::showCompleter()
486 if(!m_pListWidget)
return;
487 if (m_isCompleterVisible || m_pListWidget->count() == 0) {
491 m_pLineEdit->setToolTip(m_szLineEditToolTipShow);
492 updateCompleterPosition();
495 QRect startRect = geometry();
496 startRect.setHeight(0);
498 QRect endRect = geometry();
500 int visibleItems = qMin(m_pListWidget->count(), m_maxVisibleItems);
501 int totalHeight = visibleItems * itemHeight + 10;
503 endRect.setHeight(totalHeight);
505 if(m_pShowAnimation) {
506 m_pShowAnimation->setDuration(200);
507 m_pShowAnimation->setStartValue(startRect);
508 m_pShowAnimation->setEndValue(endRect);
509 m_pShowAnimation->setEasingCurve(QEasingCurve::OutCubic);
510 connect(m_pShowAnimation, &QPropertyAnimation::finished,
511 this, &CAddressCompleter::show);
512 m_pShowAnimation->start();
514 setGeometry(endRect);
519void CAddressCompleter::hideCompleter()
521 qDebug(log) << Q_FUNC_INFO;
522 if (!m_isCompleterVisible) {
526 qDebug(log) << Q_FUNC_INFO <<
"end";
528 m_pLineEdit->setToolTip(m_szLineEditToolTip);
531 QRect startRect = geometry();
532 QRect endRect = geometry();
533 endRect.setHeight(0);
535 if(m_pHideAnimation) {
536 m_pHideAnimation->setDuration(150);
537 m_pHideAnimation->setStartValue(startRect);
538 m_pHideAnimation->setEndValue(endRect);
539 m_pHideAnimation->setEasingCurve(QEasingCurve::InCubic);
541 connect(m_pHideAnimation, &QPropertyAnimation::finished,
542 this, &CAddressCompleter::hide);
544 m_pHideAnimation->start();
549void CAddressCompleter::updateCompleterPosition()
551 if (!m_pLineEdit)
return;
554 QPoint globalPos = m_pLineEdit->mapToGlobal(QPoint(0, m_pLineEdit->height()));
557 int width = m_pLineEdit->width();
560 QScreen *screen = QApplication::screenAt(globalPos);
562 QRect screenRect = screen->availableGeometry();
565 int availableHeight = screenRect.bottom() - globalPos.y();
567 int requiredHeight = qMin(m_pListWidget->count(), m_maxVisibleItems) * itemHeight + 10;
569 if (availableHeight < requiredHeight) {
570 globalPos = m_pLineEdit->mapToGlobal(QPoint(0, -requiredHeight));
574 if (globalPos.x() + width > screenRect.right()) {
575 globalPos.setX(screenRect.right() - width);
580 setGeometry(globalPos.x(), globalPos.y(), width, 0);
583QIcon CAddressCompleter::getIconForUrl(
const QString &url)
588 static QIcon defaultIcon;
589 static QIcon httpIcon;
590 static QIcon httpsIcon;
591 static QIcon searchIcon = QIcon::fromTheme(
"system-search");
593 if (url.startsWith(
"https://")) {
595 }
else if (url.startsWith(
"http://")) {
597 }
else if (url.contains(
"@search", Qt::CaseInsensitive)) {
The CHistoryDatabase class