Falkon Develop
Cross-platform Qt-based web browser
locationcompleterview.cpp
Go to the documentation of this file.
1/* ============================================================
2* Falkon - Qt web browser
3* Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com>
4*
5* This program is free software: you can redistribute it and/or modify
6* it under the terms of the GNU General Public License as published by
7* the Free Software Foundation, either version 3 of the License, or
8* (at your option) any later version.
9*
10* This program is distributed in the hope that it will be useful,
11* but WITHOUT ANY WARRANTY; without even the implied warranty of
12* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13* GNU General Public License for more details.
14*
15* You should have received a copy of the GNU General Public License
16* along with this program. If not, see <http://www.gnu.org/licenses/>.
17* ============================================================ */
21#include "toolbutton.h"
22#include "iconprovider.h"
23#include "mainapplication.h"
25#include "loadrequest.h"
26
27#include <QKeyEvent>
28#include <QApplication>
29#include <QScrollBar>
30#include <QVBoxLayout>
31#include <QHBoxLayout>
32#include <QLabel>
33
35 : QWidget(nullptr)
36{
37 setAttribute(Qt::WA_ShowWithoutActivating);
38 setAttribute(Qt::WA_X11NetWmWindowTypeCombo);
39
40 if (qApp->platformName() == QL1S("xcb")) {
41 setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::BypassWindowManagerHint);
42 } else {
43 setWindowFlags(Qt::Popup);
44 }
45
46 auto *layout = new QVBoxLayout(this);
47 layout->setContentsMargins(0, 0, 0, 0);
48 layout->setSpacing(0);
49
50 m_view = new QListView(this);
51 layout->addWidget(m_view);
52
53 m_view->setUniformItemSizes(true);
54 m_view->setEditTriggers(QAbstractItemView::NoEditTriggers);
55 m_view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
56 m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
57 m_view->setSelectionBehavior(QAbstractItemView::SelectRows);
58 m_view->setSelectionMode(QAbstractItemView::SingleSelection);
59
60 m_view->setMouseTracking(true);
61 qApp->installEventFilter(this);
62
63 m_delegate = new LocationCompleterDelegate(this);
64 m_view->setItemDelegate(m_delegate);
65
66 auto *searchFrame = new QFrame(this);
67 searchFrame->setFrameStyle(QFrame::StyledPanel | QFrame::Raised);
68 auto *searchLayout = new QHBoxLayout(searchFrame);
69 searchLayout->setContentsMargins(10, 4, 4, 4);
70
71 auto *searchSettingsButton = new ToolButton(this);
72 searchSettingsButton->setIcon(IconProvider::settingsIcon());
73 searchSettingsButton->setToolTip(tr("Manage Search Engines"));
74 searchSettingsButton->setAutoRaise(true);
75 searchSettingsButton->setIconSize(QSize(16, 16));
76 connect(searchSettingsButton, &ToolButton::clicked, this, &LocationCompleterView::searchEnginesDialogRequested);
77
78 auto *searchLabel = new QLabel(tr("Search with:"));
79 m_searchEnginesLayout = new QHBoxLayout();
80
81 setupSearchEngines();
82 connect(mApp->searchEnginesManager(), &SearchEnginesManager::enginesChanged, this, &LocationCompleterView::setupSearchEngines);
83
84 searchLayout->addWidget(searchLabel);
85 searchLayout->addLayout(m_searchEnginesLayout);
86 searchLayout->addStretch();
87 searchLayout->addWidget(searchSettingsButton);
88
89 layout->addWidget(searchFrame);
90}
91
92QAbstractItemModel *LocationCompleterView::model() const
93{
94 return m_view->model();
95}
96
97void LocationCompleterView::setModel(QAbstractItemModel *model)
98{
99 m_view->setModel(model);
100}
101
103{
104 return m_view->currentIndex();
105}
106
107void LocationCompleterView::setCurrentIndex(const QModelIndex &index)
108{
109 m_view->setCurrentIndex(index);
110}
111
112QItemSelectionModel *LocationCompleterView::selectionModel() const
113{
114 return m_view->selectionModel();
115}
116
118{
119 const int maxItemsCount = 12;
120 const int newHeight = m_view->sizeHintForRow(0) * qMin(maxItemsCount, model()->rowCount()) + 2 * m_view->frameWidth();
121
122 m_view->setFixedHeight(newHeight);
123 setMaximumHeight(sizeHint().height());
124 setFixedHeight(sizeHint().height());
125 resize(width(), sizeHint().height());
126}
127
128bool LocationCompleterView::eventFilter(QObject* object, QEvent* event)
129{
130 // Event filter based on QCompleter::eventFilter from qcompleter.cpp
131
132 if (object == this || object == m_view || !isVisible()) {
133 return false;
134 }
135
136 if (object == m_view->viewport()) {
137 if (event->type() == QEvent::MouseButtonRelease) {
138 auto *e = static_cast<QMouseEvent*>(event);
139 QModelIndex idx = m_view->indexAt(e->pos());
140 if (!idx.isValid()) {
141 return false;
142 }
143
144 Qt::MouseButton button = e->button();
145 Qt::KeyboardModifiers modifiers = e->modifiers();
146
147 if (button == Qt::LeftButton && modifiers == Qt::NoModifier) {
148 Q_EMIT indexActivated(idx);
149 return true;
150 }
151
152 if (button == Qt::MiddleButton || (button == Qt::LeftButton && modifiers == Qt::ControlModifier)) {
153 Q_EMIT indexCtrlActivated(idx);
154 return true;
155 }
156
157 if (button == Qt::LeftButton && modifiers == Qt::ShiftModifier) {
158 Q_EMIT indexShiftActivated(idx);
159 return true;
160 }
161 }
162 return false;
163 }
164
165 switch (event->type()) {
166 case QEvent::KeyPress: {
167 auto* keyEvent = static_cast<QKeyEvent*>(event);
168 Qt::KeyboardModifiers modifiers = keyEvent->modifiers();
169 const QModelIndex idx = m_view->currentIndex();
170 const QModelIndex visitSearchIdx = model()->index(0, 0).data(LocationCompleterModel::VisitSearchItemRole).toBool() ? model()->index(0, 0) : QModelIndex();
171
172 if ((keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down) && m_view->currentIndex() != idx) {
173 m_view->setCurrentIndex(idx);
174 }
175
176 switch (keyEvent->key()) {
177 case Qt::Key_Return:
178 case Qt::Key_Enter:
179 if (!idx.isValid()) {
180 break;
181 }
182
183 if (modifiers == Qt::NoModifier || modifiers == Qt::KeypadModifier) {
184 Q_EMIT indexActivated(idx);
185 return true;
186 }
187
188 if (modifiers == Qt::ControlModifier) {
189 Q_EMIT indexCtrlActivated(idx);
190 return true;
191 }
192
193 if (modifiers == Qt::ShiftModifier) {
194 Q_EMIT indexShiftActivated(idx);
195 return true;
196 }
197 break;
198
199 case Qt::Key_End:
200 if (modifiers & Qt::ControlModifier) {
201 m_view->setCurrentIndex(model()->index(model()->rowCount() - 1, 0));
202 return true;
203 } else {
204 close();
205 }
206 break;
207
208 case Qt::Key_Home:
209 if (modifiers & Qt::ControlModifier) {
210 m_view->setCurrentIndex(model()->index(0, 0));
211 m_view->scrollToTop();
212 return true;
213 } else {
214 close();
215 }
216 break;
217
218 case Qt::Key_Escape:
219 close();
220 return true;
221
222 case Qt::Key_F4:
223 if (modifiers == Qt::AltModifier) {
224 close();
225 return false;
226 }
227 break;
228
229 case Qt::Key_Tab:
230 case Qt::Key_Backtab: {
231 const bool isShift = keyEvent->modifiers() == Qt::ShiftModifier;
232 if (keyEvent->modifiers() != Qt::NoModifier && !isShift) {
233 return false;
234 }
235 bool isBack = keyEvent->key() == Qt::Key_Backtab;
236 if (keyEvent->key() == Qt::Key_Tab && isShift) {
237 isBack = true;
238 }
239 QKeyEvent ev(QKeyEvent::KeyPress, isBack ? Qt::Key_Up : Qt::Key_Down, Qt::NoModifier);
240 QApplication::sendEvent(focusProxy(), &ev);
241 return true;
242 }
243
244 case Qt::Key_Up:
245 case Qt::Key_PageUp: {
246 if (keyEvent->modifiers() != Qt::NoModifier) {
247 return false;
248 }
249 const int step = keyEvent->key() == Qt::Key_PageUp ? 5 : 1;
250 if (!idx.isValid() || idx == visitSearchIdx) {
251 int rowCount = model()->rowCount();
252 QModelIndex lastIndex = model()->index(rowCount - 1, 0);
253 m_view->setCurrentIndex(lastIndex);
254 } else if (idx.row() == 0) {
255 m_view->setCurrentIndex(QModelIndex());
256 } else {
257 m_view->setCurrentIndex(model()->index(qMax(0, idx.row() - step), 0));
258 }
259 return true;
260 }
261
262 case Qt::Key_Down:
263 case Qt::Key_PageDown: {
264 if (keyEvent->modifiers() != Qt::NoModifier) {
265 return false;
266 }
267 const int step = keyEvent->key() == Qt::Key_PageDown ? 5 : 1;
268 if (!idx.isValid()) {
269 QModelIndex firstIndex = model()->index(0, 0);
270 m_view->setCurrentIndex(firstIndex);
271 } else if (idx != visitSearchIdx && idx.row() == model()->rowCount() - 1) {
272 m_view->setCurrentIndex(visitSearchIdx);
273 m_view->scrollToTop();
274 } else {
275 m_view->setCurrentIndex(model()->index(qMin(model()->rowCount() - 1, idx.row() + step), 0));
276 }
277 return true;
278 }
279
280 case Qt::Key_Delete:
281 if (idx != visitSearchIdx && m_view->viewport()->rect().contains(m_view->visualRect(idx))) {
282 Q_EMIT indexDeleteRequested(idx);
283 return true;
284 }
285 break;
286
287 case Qt::Key_Shift:
288 m_delegate->setForceVisitItem(true);
289 m_view->viewport()->update();
290 return true;
291 } // switch (keyEvent->key())
292
293 if (focusProxy()) {
294 (static_cast<QObject*>(focusProxy()))->event(keyEvent);
295 }
296 return true;
297 }
298
299 case QEvent::KeyRelease: {
300 auto* keyEvent = static_cast<QKeyEvent*>(event);
301
302 switch (keyEvent->key()) {
303 case Qt::Key_Shift:
304 m_delegate->setForceVisitItem(false);
305 m_view->viewport()->update();
306 return true;
307 }
308 break;
309 }
310
311 case QEvent::Wheel:
312 case QEvent::MouseButtonPress:
313 if (!underMouse()) {
314 close();
315 return false;
316 }
317 break;
318
319 case QEvent::FocusOut: {
320 auto *focusEvent = static_cast<QFocusEvent*>(event);
321 if (focusEvent->reason() != Qt::PopupFocusReason && focusEvent->reason() != Qt::MouseFocusReason) {
322 close();
323 }
324 break;
325 }
326
327 case QEvent::Move:
328 case QEvent::Resize: {
329 QWidget *w = qobject_cast<QWidget*>(object);
330 if (w && w->isWindow() && focusProxy() && w == focusProxy()->window()) {
331 close();
332 }
333 break;
334 }
335
336 default:
337 break;
338 } // switch (event->type())
339
340 return false;
341}
342
344{
345 hide();
346 m_view->verticalScrollBar()->setValue(0);
347 m_delegate->setForceVisitItem(false);
348
349 Q_EMIT closed();
350}
351
352void LocationCompleterView::setupSearchEngines()
353{
354 while (m_searchEnginesLayout->count() != 0) {
355 delete m_searchEnginesLayout->takeAt(0);
356 }
357
358 const auto engines = mApp->searchEnginesManager()->allEngines();
359 for (const SearchEngine &engine : engines) {
360 auto *button = new ToolButton(this);
361 button->setIcon(engine.icon);
362 button->setToolTip(engine.name);
363 button->setAutoRaise(true);
364 button->setIconSize(QSize(16, 16));
365 connect(button, &ToolButton::clicked, this, [=]() {
366 const QString text = model()->index(0, 0).data(LocationCompleterModel::SearchStringRole).toString();
367 Q_EMIT loadRequested(mApp->searchEnginesManager()->searchResult(engine, text));
368 });
369 m_searchEnginesLayout->addWidget(button);
370 }
371}
static QIcon settingsIcon()
void setModel(QAbstractItemModel *model)
void indexCtrlActivated(const QModelIndex &index)
void indexShiftActivated(const QModelIndex &index)
void indexDeleteRequested(const QModelIndex &index)
QAbstractItemModel * model() const
QItemSelectionModel * selectionModel() const
void searchEnginesDialogRequested()
void indexActivated(const QModelIndex &index)
void setCurrentIndex(const QModelIndex &index)
bool eventFilter(QObject *object, QEvent *event) override
void loadRequested(const LoadRequest &request)
QModelIndex currentIndex() const
#define mApp
#define QL1S(x)
Definition: qzcommon.h:44