You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
357 lines
11 KiB
357 lines
11 KiB
/* ============================================================ |
|
* Falkon - Qt web browser |
|
* Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com> |
|
* |
|
* This program is free software: you can redistribute it and/or modify |
|
* it under the terms of the GNU General Public License as published by |
|
* the Free Software Foundation, either version 3 of the License, or |
|
* (at your option) any later version. |
|
* |
|
* This program is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU General Public License |
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
* ============================================================ */ |
|
#include "locationcompleterview.h" |
|
#include "locationcompletermodel.h" |
|
#include "locationcompleterdelegate.h" |
|
#include "toolbutton.h" |
|
#include "iconprovider.h" |
|
#include "searchenginesdialog.h" |
|
|
|
#include <QKeyEvent> |
|
#include <QApplication> |
|
#include <QScrollBar> |
|
#include <QVBoxLayout> |
|
|
|
LocationCompleterView::LocationCompleterView() |
|
: QWidget(nullptr) |
|
, m_ignoreNextMouseMove(false) |
|
{ |
|
setAttribute(Qt::WA_ShowWithoutActivating); |
|
setAttribute(Qt::WA_X11NetWmWindowTypeCombo); |
|
|
|
if (qApp->platformName() == QL1S("xcb")) { |
|
setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::BypassWindowManagerHint); |
|
} else { |
|
setWindowFlags(Qt::Popup); |
|
} |
|
|
|
QVBoxLayout *layout = new QVBoxLayout(this); |
|
layout->setContentsMargins(0, 0, 0, 0); |
|
layout->setSpacing(0); |
|
setLayout(layout); |
|
|
|
m_view = new QListView(this); |
|
layout->addWidget(m_view); |
|
|
|
m_view->setUniformItemSizes(true); |
|
m_view->setEditTriggers(QAbstractItemView::NoEditTriggers); |
|
m_view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); |
|
m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
|
m_view->setSelectionBehavior(QAbstractItemView::SelectRows); |
|
m_view->setSelectionMode(QAbstractItemView::SingleSelection); |
|
|
|
m_view->setMouseTracking(true); |
|
qApp->installEventFilter(this); |
|
|
|
m_delegate = new LocationCompleterDelegate(this); |
|
m_view->setItemDelegate(m_delegate); |
|
|
|
QWidget *searchWidget = new QWidget(this); |
|
QHBoxLayout *searchLayout = new QHBoxLayout(searchWidget); |
|
searchLayout->setContentsMargins(2, 2, 2, 2); |
|
searchWidget->setLayout(searchLayout); |
|
|
|
ToolButton *searchSettingsButton = new ToolButton(this); |
|
searchSettingsButton->setToolTip(tr("Manage Search Engines")); |
|
searchSettingsButton->setAutoRaise(true); |
|
searchSettingsButton->setIcon(IconProvider::settingsIcon()); |
|
connect(searchSettingsButton, &ToolButton::clicked, this, &LocationCompleterView::openSearchEnginesDialog); |
|
|
|
searchLayout->addStretch(); |
|
searchLayout->addWidget(searchSettingsButton); |
|
|
|
layout->addWidget(searchWidget); |
|
} |
|
|
|
QAbstractItemModel *LocationCompleterView::model() const |
|
{ |
|
return m_view->model(); |
|
} |
|
|
|
void LocationCompleterView::setModel(QAbstractItemModel *model) |
|
{ |
|
m_view->setModel(model); |
|
} |
|
|
|
QModelIndex LocationCompleterView::currentIndex() const |
|
{ |
|
return m_view->currentIndex(); |
|
} |
|
|
|
void LocationCompleterView::setCurrentIndex(const QModelIndex &index) |
|
{ |
|
m_view->setCurrentIndex(index); |
|
} |
|
|
|
QItemSelectionModel *LocationCompleterView::selectionModel() const |
|
{ |
|
return m_view->selectionModel(); |
|
} |
|
|
|
void LocationCompleterView::setOriginalText(const QString &originalText) |
|
{ |
|
m_delegate->setOriginalText(originalText); |
|
} |
|
|
|
void LocationCompleterView::adjustSize() |
|
{ |
|
const int maxItemsCount = 12; |
|
m_view->setFixedHeight(m_view->sizeHintForRow(0) * qMin(maxItemsCount, model()->rowCount()) + 2 * m_view->frameWidth()); |
|
setFixedHeight(sizeHint().height()); |
|
} |
|
|
|
bool LocationCompleterView::eventFilter(QObject* object, QEvent* event) |
|
{ |
|
// Event filter based on QCompleter::eventFilter from qcompleter.cpp |
|
|
|
if (object == this || object == m_view || !isVisible()) { |
|
return false; |
|
} |
|
|
|
if (object == m_view->viewport()) { |
|
if (event->type() == QEvent::MouseButtonRelease) { |
|
QMouseEvent *e = static_cast<QMouseEvent*>(event); |
|
QModelIndex idx = m_view->indexAt(e->pos()); |
|
if (!idx.isValid()) { |
|
return false; |
|
} |
|
|
|
Qt::MouseButton button = e->button(); |
|
Qt::KeyboardModifiers modifiers = e->modifiers(); |
|
|
|
if (button == Qt::LeftButton && modifiers == Qt::NoModifier) { |
|
emit indexActivated(idx); |
|
return true; |
|
} |
|
|
|
if (button == Qt::MiddleButton || (button == Qt::LeftButton && modifiers == Qt::ControlModifier)) { |
|
emit indexCtrlActivated(idx); |
|
return true; |
|
} |
|
|
|
if (button == Qt::LeftButton && modifiers == Qt::ShiftModifier) { |
|
emit indexShiftActivated(idx); |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
switch (event->type()) { |
|
case QEvent::KeyPress: { |
|
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); |
|
Qt::KeyboardModifiers modifiers = keyEvent->modifiers(); |
|
const QModelIndex idx = m_view->currentIndex(); |
|
const QModelIndex visitSearchIdx = model()->index(0, 0).data(LocationCompleterModel::VisitSearchItemRole).toBool() ? model()->index(0, 0) : QModelIndex(); |
|
|
|
if ((keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down) && m_view->currentIndex() != idx) { |
|
m_view->setCurrentIndex(idx); |
|
} |
|
|
|
switch (keyEvent->key()) { |
|
case Qt::Key_Return: |
|
case Qt::Key_Enter: |
|
if (!idx.isValid()) { |
|
break; |
|
} |
|
|
|
if (modifiers == Qt::NoModifier || modifiers == Qt::KeypadModifier) { |
|
emit indexActivated(idx); |
|
return true; |
|
} |
|
|
|
if (modifiers == Qt::ControlModifier) { |
|
emit indexCtrlActivated(idx); |
|
return true; |
|
} |
|
|
|
if (modifiers == Qt::ShiftModifier) { |
|
emit indexShiftActivated(idx); |
|
return true; |
|
} |
|
break; |
|
|
|
case Qt::Key_End: |
|
if (modifiers & Qt::ControlModifier) { |
|
m_view->setCurrentIndex(model()->index(model()->rowCount() - 1, 0)); |
|
return true; |
|
} else { |
|
close(); |
|
} |
|
break; |
|
|
|
case Qt::Key_Home: |
|
if (modifiers & Qt::ControlModifier) { |
|
m_view->setCurrentIndex(model()->index(0, 0)); |
|
m_view->scrollToTop(); |
|
return true; |
|
} else { |
|
close(); |
|
} |
|
break; |
|
|
|
case Qt::Key_Escape: |
|
close(); |
|
return true; |
|
|
|
case Qt::Key_F4: |
|
if (modifiers == Qt::AltModifier) { |
|
close(); |
|
return false; |
|
} |
|
break; |
|
|
|
case Qt::Key_Tab: |
|
case Qt::Key_Backtab: { |
|
const bool isShift = keyEvent->modifiers() == Qt::ShiftModifier; |
|
if (keyEvent->modifiers() != Qt::NoModifier && !isShift) { |
|
return false; |
|
} |
|
bool isBack = keyEvent->key() == Qt::Key_Backtab; |
|
if (keyEvent->key() == Qt::Key_Tab && isShift) { |
|
isBack = true; |
|
} |
|
QKeyEvent ev(QKeyEvent::KeyPress, isBack ? Qt::Key_Up : Qt::Key_Down, Qt::NoModifier); |
|
QApplication::sendEvent(focusProxy(), &ev); |
|
return true; |
|
} |
|
|
|
case Qt::Key_Up: |
|
if (!idx.isValid() || idx == visitSearchIdx) { |
|
int rowCount = model()->rowCount(); |
|
QModelIndex lastIndex = model()->index(rowCount - 1, 0); |
|
m_view->setCurrentIndex(lastIndex); |
|
} else if (idx.row() == 0) { |
|
m_view->setCurrentIndex(QModelIndex()); |
|
} else { |
|
m_view->setCurrentIndex(model()->index(idx.row() - 1, 0)); |
|
} |
|
return true; |
|
|
|
case Qt::Key_Down: |
|
if (!idx.isValid()) { |
|
QModelIndex firstIndex = model()->index(0, 0); |
|
m_view->setCurrentIndex(firstIndex); |
|
} else if (idx != visitSearchIdx && idx.row() == model()->rowCount() - 1) { |
|
m_view->setCurrentIndex(visitSearchIdx); |
|
m_view->scrollToTop(); |
|
} else { |
|
m_view->setCurrentIndex(model()->index(idx.row() + 1, 0)); |
|
} |
|
return true; |
|
|
|
case Qt::Key_Delete: |
|
if (idx != visitSearchIdx && m_view->viewport()->rect().contains(m_view->visualRect(idx))) { |
|
emit indexDeleteRequested(idx); |
|
return true; |
|
} |
|
break; |
|
|
|
case Qt::Key_PageUp: |
|
case Qt::Key_PageDown: |
|
if (keyEvent->modifiers() != Qt::NoModifier) { |
|
return false; |
|
} |
|
QApplication::sendEvent(m_view, event); |
|
return true; |
|
|
|
case Qt::Key_Shift: |
|
// don't switch if there is no hovered or selected index to not disturb typing |
|
if (idx != visitSearchIdx || underMouse()) { |
|
m_delegate->setShowSwitchToTab(false); |
|
m_view->viewport()->update(); |
|
return true; |
|
} |
|
break; |
|
} // switch (keyEvent->key()) |
|
|
|
if (focusProxy()) { |
|
(static_cast<QObject*>(focusProxy()))->event(keyEvent); |
|
} |
|
return true; |
|
} |
|
|
|
case QEvent::KeyRelease: { |
|
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); |
|
|
|
switch (keyEvent->key()) { |
|
case Qt::Key_Shift: |
|
m_delegate->setShowSwitchToTab(true); |
|
m_view->viewport()->update(); |
|
return true; |
|
} |
|
break; |
|
} |
|
|
|
case QEvent::Show: |
|
// Don't hover item when showing completer and mouse is currently in rect |
|
m_ignoreNextMouseMove = true; |
|
break; |
|
|
|
case QEvent::Wheel: |
|
case QEvent::MouseButtonPress: |
|
if (!underMouse()) { |
|
close(); |
|
return false; |
|
} |
|
break; |
|
|
|
case QEvent::FocusOut: { |
|
QFocusEvent *focusEvent = static_cast<QFocusEvent*>(event); |
|
if (focusEvent->reason() != Qt::PopupFocusReason && focusEvent->reason() != Qt::MouseFocusReason) { |
|
close(); |
|
} |
|
break; |
|
} |
|
|
|
case QEvent::Move: |
|
case QEvent::Resize: { |
|
QWidget *w = qobject_cast<QWidget*>(object); |
|
if (w && w->isWindow() && focusProxy() && w == focusProxy()->window()) { |
|
close(); |
|
} |
|
break; |
|
} |
|
|
|
default: |
|
break; |
|
} // switch (event->type()) |
|
|
|
return false; |
|
} |
|
|
|
void LocationCompleterView::close() |
|
{ |
|
hide(); |
|
m_view->verticalScrollBar()->setValue(0); |
|
|
|
m_delegate->setShowSwitchToTab(true); |
|
|
|
emit closed(); |
|
} |
|
|
|
void LocationCompleterView::openSearchEnginesDialog() |
|
{ |
|
if (!m_searchDialog) { |
|
m_searchDialog = new SearchEnginesDialog(this); |
|
} |
|
|
|
m_searchDialog->open(); |
|
m_searchDialog->raise(); |
|
m_searchDialog->activateWindow(); |
|
}
|
|
|