1 /**************************************************************************
2 * Otter Browser: Web browser controlled by the user, not vice-versa.
3 * Copyright (C) 2013 - 2018 Michal Dutkiewicz aka Emdek <michal@emdek.pl>
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 *
18 **************************************************************************/
19 
20 #include "CacheContentsWidget.h"
21 #include "../../../core/HistoryManager.h"
22 #include "../../../core/NetworkCache.h"
23 #include "../../../core/NetworkManagerFactory.h"
24 #include "../../../core/ThemesManager.h"
25 #include "../../../core/Utils.h"
26 #include "../../../ui/Action.h"
27 #include "../../../ui/MainWindow.h"
28 
29 #include "ui_CacheContentsWidget.h"
30 
31 #include <QtCore/QCoreApplication>
32 #include <QtCore/QMimeDatabase>
33 #include <QtCore/QTimer>
34 #include <QtGui/QClipboard>
35 #include <QtGui/QMouseEvent>
36 #include <QtWidgets/QMenu>
37 
38 namespace Otter
39 {
40 
CacheContentsWidget(const QVariantMap & parameters,Window * window,QWidget * parent)41 CacheContentsWidget::CacheContentsWidget(const QVariantMap &parameters, Window *window, QWidget *parent) : ContentsWidget(parameters, window, parent),
42 	m_model(new QStandardItemModel(this)),
43 	m_isLoading(true),
44 	m_ui(new Ui::CacheContentsWidget)
45 {
46 	m_ui->setupUi(this);
47 	m_ui->filterLineEditWidget->setClearOnEscape(true);
48 	m_ui->cacheViewWidget->setViewMode(ItemViewWidget::TreeView);
49 	m_ui->cacheViewWidget->installEventFilter(this);
50 	m_ui->cacheViewWidget->viewport()->installEventFilter(this);
51 	m_ui->previewLabel->hide();
52 
53 	if (isSidebarPanel())
54 	{
55 		m_ui->detailsWidget->hide();
56 	}
57 
58 	QTimer::singleShot(100, this, &CacheContentsWidget::populateCache);
59 
60 	connect(m_ui->filterLineEditWidget, &LineEditWidget::textChanged, m_ui->cacheViewWidget, &ItemViewWidget::setFilterString);
61 	connect(m_ui->cacheViewWidget, &ItemViewWidget::doubleClicked, this, &CacheContentsWidget::openEntry);
62 	connect(m_ui->cacheViewWidget, &ItemViewWidget::customContextMenuRequested, this, &CacheContentsWidget::showContextMenu);
63 	connect(m_ui->deleteButton, &QPushButton::clicked, this, &CacheContentsWidget::removeDomainEntriesOrEntry);
64 }
65 
~CacheContentsWidget()66 CacheContentsWidget::~CacheContentsWidget()
67 {
68 	delete m_ui;
69 }
70 
changeEvent(QEvent * event)71 void CacheContentsWidget::changeEvent(QEvent *event)
72 {
73 	ContentsWidget::changeEvent(event);
74 
75 	if (event->type() == QEvent::LanguageChange)
76 	{
77 		m_ui->retranslateUi(this);
78 
79 		m_model->setHorizontalHeaderLabels({tr("Address"), tr("Type"), tr("Size"), tr("Last Modified"), tr("Expires")});
80 	}
81 }
82 
print(QPrinter * printer)83 void CacheContentsWidget::print(QPrinter *printer)
84 {
85 	m_ui->cacheViewWidget->render(printer);
86 }
87 
triggerAction(int identifier,const QVariantMap & parameters,ActionsManager::TriggerType trigger)88 void CacheContentsWidget::triggerAction(int identifier, const QVariantMap &parameters, ActionsManager::TriggerType trigger)
89 {
90 	switch (identifier)
91 	{
92 		case ActionsManager::DeleteAction:
93 			removeDomainEntriesOrEntry();
94 
95 			break;
96 		case ActionsManager::FindAction:
97 		case ActionsManager::QuickFindAction:
98 			m_ui->filterLineEditWidget->setFocus();
99 
100 			break;
101 		case ActionsManager::ActivateContentAction:
102 			m_ui->cacheViewWidget->setFocus();
103 
104 			break;
105 		default:
106 			ContentsWidget::triggerAction(identifier, parameters, trigger);
107 
108 			break;
109 	}
110 }
111 
populateCache()112 void CacheContentsWidget::populateCache()
113 {
114 	m_model->clear();
115 	m_model->setHorizontalHeaderLabels({tr("Address"), tr("Type"), tr("Size"), tr("Last Modified"), tr("Expires")});
116 	m_model->setHeaderData(0, Qt::Horizontal, 500, HeaderViewWidget::WidthRole);
117 	m_model->setHeaderData(2, Qt::Horizontal, 150, HeaderViewWidget::WidthRole);
118 	m_model->setSortRole(Qt::DisplayRole);
119 
120 	const NetworkCache *cache(NetworkManagerFactory::getCache());
121 	const QVector<QUrl> entries(cache->getEntries());
122 
123 	for (int i = 0; i < entries.count(); ++i)
124 	{
125 		handleEntryAdded(entries.at(i));
126 	}
127 
128 	m_model->sort(0);
129 
130 	if (m_isLoading)
131 	{
132 		m_ui->cacheViewWidget->setModel(m_model);
133 		m_ui->cacheViewWidget->setLayoutDirection(Qt::LeftToRight);
134 		m_ui->cacheViewWidget->setFilterRoles({Qt::DisplayRole, Qt::UserRole});
135 
136 		m_isLoading = false;
137 
138 		emit loadingStateChanged(WebWidget::FinishedLoadingState);
139 
140 		connect(cache, &NetworkCache::cleared, this, &CacheContentsWidget::populateCache);
141 		connect(cache, &NetworkCache::entryAdded, this, &CacheContentsWidget::handleEntryAdded);
142 		connect(cache, &NetworkCache::entryRemoved, this, &CacheContentsWidget::handleEntryRemoved);
143 		connect(m_model, &QStandardItemModel::modelReset, this, &CacheContentsWidget::updateActions);
144 		connect(m_ui->cacheViewWidget, &ItemViewWidget::needsActionsUpdate, this, &CacheContentsWidget::updateActions);
145 	}
146 }
147 
removeEntry()148 void CacheContentsWidget::removeEntry()
149 {
150 	const QUrl entry(getEntry(m_ui->cacheViewWidget->currentIndex()));
151 
152 	if (entry.isValid())
153 	{
154 		NetworkManagerFactory::getCache()->remove(entry);
155 	}
156 }
157 
removeDomainEntries()158 void CacheContentsWidget::removeDomainEntries()
159 {
160 	const QModelIndex index(m_ui->cacheViewWidget->currentIndex());
161 	const QStandardItem *domainItem(findDomain((index.isValid() && index.parent() == m_model->invisibleRootItem()->index()) ? index.sibling(index.row(), 0).data(Qt::ToolTipRole).toString() : Utils::extractHost(getEntry(index))));
162 
163 	if (!domainItem)
164 	{
165 		return;
166 	}
167 
168 	NetworkCache *cache(NetworkManagerFactory::getCache());
169 
170 	for (int i = (domainItem->rowCount() - 1); i >= 0; --i)
171 	{
172 		cache->remove(domainItem->index().child(i, 0).data(Qt::UserRole).toUrl());
173 	}
174 }
175 
removeDomainEntriesOrEntry()176 void CacheContentsWidget::removeDomainEntriesOrEntry()
177 {
178 	const QUrl entry(getEntry(m_ui->cacheViewWidget->currentIndex()));
179 
180 	if (entry.isValid())
181 	{
182 		NetworkManagerFactory::getCache()->remove(entry);
183 	}
184 	else
185 	{
186 		removeDomainEntries();
187 	}
188 }
189 
openEntry()190 void CacheContentsWidget::openEntry()
191 {
192 	const QModelIndex index(m_ui->cacheViewWidget->currentIndex());
193 
194 	if (!index.isValid() || index.parent() == m_model->invisibleRootItem()->index())
195 	{
196 		return;
197 	}
198 
199 	const QUrl url(getEntry(index));
200 
201 	if (url.isValid())
202 	{
203 		const QAction *action(qobject_cast<QAction*>(sender()));
204 		MainWindow *mainWindow(MainWindow::findMainWindow(this));
205 
206 		if (mainWindow)
207 		{
208 			mainWindow->triggerAction(ActionsManager::OpenUrlAction, {{QLatin1String("url"), url}, {QLatin1String("hints"), QVariant(action ? static_cast<SessionsManager::OpenHints>(action->data().toInt()) : SessionsManager::DefaultOpen)}});
209 		}
210 	}
211 }
212 
copyEntryLink()213 void CacheContentsWidget::copyEntryLink()
214 {
215 	const QUrl url(getEntry(m_ui->cacheViewWidget->currentIndex()));
216 
217 	if (url.isValid())
218 	{
219 		QApplication::clipboard()->setText(url.toDisplayString());
220 	}
221 }
222 
handleEntryAdded(const QUrl & entry)223 void CacheContentsWidget::handleEntryAdded(const QUrl &entry)
224 {
225 	const QString domain(entry.host());
226 	QStandardItem *domainItem(findDomain(domain));
227 
228 	if (domainItem)
229 	{
230 		for (int i = 0; i < domainItem->rowCount(); ++i)
231 		{
232 			if (domainItem->index().child(i, 0).data(Qt::UserRole).toUrl() == entry)
233 			{
234 				return;
235 			}
236 		}
237 	}
238 	else
239 	{
240 		domainItem = new QStandardItem(HistoryManager::getIcon(QUrl(QStringLiteral("http://%1/").arg(domain))), domain);
241 		domainItem->setToolTip(domain);
242 
243 		m_model->appendRow(domainItem);
244 		m_model->setItem(domainItem->row(), 2, new QStandardItem());
245 
246 		if (sender())
247 		{
248 			m_model->sort(0);
249 		}
250 	}
251 
252 	NetworkCache *cache(NetworkManagerFactory::getCache());
253 	QIODevice *device(cache->data(entry));
254 	const QNetworkCacheMetaData metaData(cache->metaData(entry));
255 	const QList<QPair<QByteArray, QByteArray> > headers(metaData.rawHeaders());
256 	QString type;
257 
258 	for (int i = 0; i < headers.count(); ++i)
259 	{
260 		if (headers.at(i).first == QStringLiteral("Content-Type").toLatin1())
261 		{
262 			type = QString(headers.at(i).second);
263 
264 			break;
265 		}
266 	}
267 
268 	const QMimeType mimeType((type.isEmpty() && device) ? QMimeDatabase().mimeTypeForData(device) : QMimeDatabase().mimeTypeForName(type));
269 	QList<QStandardItem*> entryItems({new QStandardItem(entry.path()), new QStandardItem(mimeType.name()), new QStandardItem(device ? Utils::formatUnit(device->size()) : QString()), new QStandardItem(Utils::formatDateTime(metaData.lastModified())), new QStandardItem(Utils::formatDateTime(metaData.expirationDate()))});
270 	entryItems[0]->setData(entry, Qt::UserRole);
271 	entryItems[0]->setFlags(entryItems[0]->flags() | Qt::ItemNeverHasChildren);
272 	entryItems[1]->setFlags(entryItems[1]->flags() | Qt::ItemNeverHasChildren);
273 	entryItems[2]->setData((device ? device->size() : 0), Qt::UserRole);
274 	entryItems[2]->setFlags(entryItems[2]->flags() | Qt::ItemNeverHasChildren);
275 	entryItems[3]->setFlags(entryItems[3]->flags() | Qt::ItemNeverHasChildren);
276 	entryItems[4]->setFlags(entryItems[4]->flags() | Qt::ItemNeverHasChildren);
277 
278 	if (device)
279 	{
280 		QStandardItem *sizeItem(m_model->item(domainItem->row(), 2));
281 
282 		if (sizeItem)
283 		{
284 			sizeItem->setData((sizeItem->data(Qt::UserRole).toLongLong() + device->size()), Qt::UserRole);
285 			sizeItem->setText(Utils::formatUnit(sizeItem->data(Qt::UserRole).toLongLong()));
286 		}
287 
288 		device->deleteLater();
289 	}
290 
291 	domainItem->appendRow(entryItems);
292 	domainItem->setText(QStringLiteral("%1 (%2)").arg(domain).arg(domainItem->rowCount()));
293 
294 	if (sender())
295 	{
296 		domainItem->sortChildren(0, Qt::DescendingOrder);
297 	}
298 }
299 
handleEntryRemoved(const QUrl & entry)300 void CacheContentsWidget::handleEntryRemoved(const QUrl &entry)
301 {
302 	QStandardItem *domainItem(findDomain(Utils::extractHost(entry)));
303 
304 	if (!domainItem)
305 	{
306 		return;
307 	}
308 
309 	for (int i = 0; i < domainItem->rowCount(); ++i)
310 	{
311 		QStandardItem *entryItem(domainItem->child(i, 0));
312 
313 		if (entryItem && entryItem->data(Qt::UserRole).toUrl() == entry)
314 		{
315 			const qint64 size(domainItem->index().child(entryItem->row(), 2).data(Qt::UserRole).toLongLong());
316 
317 			m_model->removeRow(entryItem->row(), domainItem->index());
318 
319 			if (domainItem->rowCount() == 0)
320 			{
321 				m_model->invisibleRootItem()->removeRow(domainItem->row());
322 			}
323 			else
324 			{
325 				QStandardItem *domainSizeItem(m_model->item(domainItem->row(), 2));
326 
327 				if (domainSizeItem && size > 0)
328 				{
329 					domainSizeItem->setData((domainSizeItem->data(Qt::UserRole).toLongLong() - size), Qt::UserRole);
330 					domainSizeItem->setText(Utils::formatUnit(domainSizeItem->data(Qt::UserRole).toLongLong()));
331 				}
332 
333 				domainItem->setText(QStringLiteral("%1 (%2)").arg(entry.host()).arg(domainItem->rowCount()));
334 			}
335 
336 			break;
337 		}
338 	}
339 }
340 
showContextMenu(const QPoint & position)341 void CacheContentsWidget::showContextMenu(const QPoint &position)
342 {
343 	MainWindow *mainWindow(MainWindow::findMainWindow(this));
344 	const QModelIndex index(m_ui->cacheViewWidget->indexAt(position));
345 	const QUrl entry(getEntry(index));
346 	QMenu menu(this);
347 
348 	if (entry.isValid())
349 	{
350 		menu.addAction(ThemesManager::createIcon(QLatin1String("document-open")), QCoreApplication::translate("actions", "Open"), this, &CacheContentsWidget::openEntry);
351 		menu.addAction(QCoreApplication::translate("actions", "Open in New Tab"), this, &CacheContentsWidget::openEntry)->setData(SessionsManager::NewTabOpen);
352 		menu.addAction(QCoreApplication::translate("actions", "Open in New Background Tab"), this, &CacheContentsWidget::openEntry)->setData(static_cast<int>(SessionsManager::NewTabOpen | SessionsManager::BackgroundOpen));
353 		menu.addSeparator();
354 		menu.addAction(QCoreApplication::translate("actions", "Open in New Window"), this, &CacheContentsWidget::openEntry)->setData(SessionsManager::NewWindowOpen);
355 		menu.addAction(QCoreApplication::translate("actions", "Open in New Background Window"), this, &CacheContentsWidget::openEntry)->setData(static_cast<int>(SessionsManager::NewWindowOpen | SessionsManager::BackgroundOpen));
356 		menu.addSeparator();
357 		menu.addAction(tr("Copy Link to Clipboard"), this, &CacheContentsWidget::copyEntryLink);
358 		menu.addSeparator();
359 		menu.addAction(tr("Remove Entry"), this, &CacheContentsWidget::removeEntry);
360 	}
361 
362 	if (entry.isValid() || (index.isValid() && index.parent() == m_model->invisibleRootItem()->index()))
363 	{
364 		menu.addAction(tr("Remove All Entries from This Domain"), this, &CacheContentsWidget::removeDomainEntries);
365 		menu.addSeparator();
366 	}
367 
368 	menu.addAction(new Action(ActionsManager::ClearHistoryAction, {}, ActionExecutor::Object(mainWindow, mainWindow), &menu));
369 	menu.exec(m_ui->cacheViewWidget->mapToGlobal(position));
370 }
371 
updateActions()372 void CacheContentsWidget::updateActions()
373 {
374 	const QModelIndex index(m_ui->cacheViewWidget->getCurrentIndex());
375 	const QUrl url(getEntry(index));
376 	const QString domain((index.isValid() && index.parent() == m_model->invisibleRootItem()->index()) ? index.sibling(index.row(), 0).data(Qt::ToolTipRole).toString() : url.host());
377 
378 	m_ui->locationLabelWidget->setText({});
379 	m_ui->locationLabelWidget->setUrl({});
380 	m_ui->previewLabel->hide();
381 	m_ui->previewLabel->setPixmap({});
382 	m_ui->deleteButton->setEnabled(!domain.isEmpty());
383 
384 	if (url.isValid())
385 	{
386 		NetworkCache *cache(NetworkManagerFactory::getCache());
387 		QIODevice *device(cache->data(url));
388 		const QNetworkCacheMetaData metaData(cache->metaData(url));
389 		const QList<QPair<QByteArray, QByteArray> > headers(metaData.rawHeaders());
390 		QString type;
391 
392 		for (int i = 0; i < headers.count(); ++i)
393 		{
394 			if (headers.at(i).first == QStringLiteral("Content-Type").toLatin1())
395 			{
396 				type = QString(headers.at(i).second);
397 
398 				break;
399 			}
400 		}
401 
402 		const QMimeType mimeType((type.isEmpty() && device) ? QMimeDatabase().mimeTypeForData(device) : QMimeDatabase().mimeTypeForName(type));
403 		QPixmap preview;
404 		const int size(m_ui->formWidget->contentsRect().height() - 10);
405 
406 		if (mimeType.name().startsWith(QLatin1String("image")) && device)
407 		{
408 			QImage image;
409 			image.load(device, "");
410 
411 			if (image.size().width() > size || image.height() > size)
412 			{
413 				image = image.scaled(size, size, Qt::KeepAspectRatio);
414 			}
415 
416 			preview = QPixmap::fromImage(image);
417 		}
418 
419 		if (preview.isNull() && QIcon::hasThemeIcon(mimeType.iconName()))
420 		{
421 			preview = QIcon::fromTheme(mimeType.iconName(), ThemesManager::createIcon(QLatin1String("unknown"))).pixmap(64, 64);
422 		}
423 
424 		const QUrl localUrl(cache->getPathForUrl(url));
425 
426 		m_ui->addressLabelWidget->setText(url.toString(QUrl::FullyDecoded | QUrl::PreferLocalFile));
427 		m_ui->addressLabelWidget->setUrl(url);
428 		m_ui->locationLabelWidget->setText(localUrl.toString(QUrl::FullyDecoded | QUrl::PreferLocalFile));
429 		m_ui->locationLabelWidget->setUrl(localUrl);
430 		m_ui->typeLabelWidget->setText(mimeType.name());
431 		m_ui->sizeLabelWidget->setText(device ? Utils::formatUnit(device->size(), false, 2) : tr("Unknown"));
432 		m_ui->lastModifiedLabelWidget->setText(Utils::formatDateTime(metaData.lastModified()));
433 		m_ui->expiresLabelWidget->setText(Utils::formatDateTime(metaData.expirationDate()));
434 
435 		if (!preview.isNull())
436 		{
437 			m_ui->previewLabel->show();
438 			m_ui->previewLabel->setPixmap(preview);
439 		}
440 
441 		QStandardItem *typeItem(m_model->itemFromIndex(index.sibling(index.row(), 1)));
442 
443 		if (typeItem && typeItem->text().isEmpty())
444 		{
445 			typeItem->setText(mimeType.name());
446 		}
447 
448 		QStandardItem *lastModifiedItem(m_model->itemFromIndex(index.sibling(index.row(), 3)));
449 
450 		if (lastModifiedItem && lastModifiedItem->text().isEmpty())
451 		{
452 			lastModifiedItem->setText(metaData.lastModified().toString());
453 		}
454 
455 		QStandardItem *expiresItem(m_model->itemFromIndex(index.sibling(index.row(), 4)));
456 
457 		if (expiresItem && expiresItem->text().isEmpty())
458 		{
459 			expiresItem->setText(metaData.expirationDate().toString());
460 		}
461 
462 		if (device)
463 		{
464 			QStandardItem *sizeItem(m_model->itemFromIndex(index.sibling(index.row(), 2)));
465 
466 			if (sizeItem && sizeItem->text().isEmpty())
467 			{
468 				sizeItem->setText(Utils::formatUnit(device->size()));
469 				sizeItem->setData(device->size(), Qt::UserRole);
470 
471 				QStandardItem *domainSizeItem(sizeItem->parent() ? m_model->item(sizeItem->parent()->row(), 2) : nullptr);
472 
473 				if (domainSizeItem)
474 				{
475 					domainSizeItem->setData((domainSizeItem->data(Qt::UserRole).toLongLong() + device->size()), Qt::UserRole);
476 					domainSizeItem->setText(Utils::formatUnit(domainSizeItem->data(Qt::UserRole).toLongLong()));
477 				}
478 			}
479 
480 			device->deleteLater();
481 		}
482 	}
483 	else
484 	{
485 		m_ui->addressLabelWidget->setText({});
486 		m_ui->typeLabelWidget->setText({});
487 		m_ui->sizeLabelWidget->setText({});
488 		m_ui->lastModifiedLabelWidget->setText({});
489 		m_ui->expiresLabelWidget->setText({});
490 
491 		if (!domain.isEmpty())
492 		{
493 			m_ui->addressLabelWidget->setText(domain);
494 		}
495 	}
496 
497 	emit categorizedActionsStateChanged({ActionsManager::ActionDefinition::EditingCategory});
498 }
499 
findDomain(const QString & domain)500 QStandardItem* CacheContentsWidget::findDomain(const QString &domain)
501 {
502 	for (int i = 0; i < m_model->rowCount(); ++i)
503 	{
504 		QStandardItem *domainItem(m_model->item(i, 0));
505 
506 		if (domainItem && domain == domainItem->toolTip())
507 		{
508 			return domainItem;
509 		}
510 	}
511 
512 	return nullptr;
513 }
514 
getTitle() const515 QString CacheContentsWidget::getTitle() const
516 {
517 	return tr("Cache");
518 }
519 
getType() const520 QLatin1String CacheContentsWidget::getType() const
521 {
522 	return QLatin1String("cache");
523 }
524 
getUrl() const525 QUrl CacheContentsWidget::getUrl() const
526 {
527 	return QUrl(QLatin1String("about:cache"));
528 }
529 
getIcon() const530 QIcon CacheContentsWidget::getIcon() const
531 {
532 	return ThemesManager::createIcon(QLatin1String("cache"), false);
533 }
534 
getEntry(const QModelIndex & index) const535 QUrl CacheContentsWidget::getEntry(const QModelIndex &index) const
536 {
537 	return ((index.isValid() && index.parent().isValid() && index.parent().parent() == m_model->invisibleRootItem()->index()) ? index.sibling(index.row(), 0).data(Qt::UserRole).toUrl() : QUrl());
538 }
539 
getActionState(int identifier,const QVariantMap & parameters) const540 ActionsManager::ActionDefinition::State CacheContentsWidget::getActionState(int identifier, const QVariantMap &parameters) const
541 {
542 	if (identifier == ActionsManager::DeleteAction)
543 	{
544 		ActionsManager::ActionDefinition::State state(ActionsManager::getActionDefinition(identifier).getDefaultState());
545 		state.isEnabled = m_ui->deleteButton->isEnabled();
546 
547 		return state;
548 	}
549 
550 	return ContentsWidget::getActionState(identifier, parameters);
551 }
552 
getLoadingState() const553 WebWidget::LoadingState CacheContentsWidget::getLoadingState() const
554 {
555 	return (m_isLoading ? WebWidget::OngoingLoadingState : WebWidget::FinishedLoadingState);
556 }
557 
eventFilter(QObject * object,QEvent * event)558 bool CacheContentsWidget::eventFilter(QObject *object, QEvent *event)
559 {
560 	if (object == m_ui->cacheViewWidget && event->type() == QEvent::KeyPress)
561 	{
562 		const QKeyEvent *keyEvent(static_cast<QKeyEvent*>(event));
563 
564 		switch (keyEvent->key())
565 		{
566 			case Qt::Key_Delete:
567 				removeDomainEntriesOrEntry();
568 
569 				return true;
570 			case Qt::Key_Enter:
571 			case Qt::Key_Return:
572 				openEntry();
573 
574 				return true;
575 			default:
576 				break;
577 		}
578 	}
579 	else if (object == m_ui->cacheViewWidget->viewport() && event->type() == QEvent::MouseButtonRelease)
580 	{
581 		const QMouseEvent *mouseEvent(static_cast<QMouseEvent*>(event));
582 
583 		if ((mouseEvent->button() == Qt::LeftButton && mouseEvent->modifiers() != Qt::NoModifier) || mouseEvent->button() == Qt::MiddleButton)
584 		{
585 			const QModelIndex entryIndex(m_ui->cacheViewWidget->currentIndex());
586 
587 			if (!entryIndex.isValid() || entryIndex.parent() == m_model->invisibleRootItem()->index())
588 			{
589 				return ContentsWidget::eventFilter(object, event);
590 			}
591 
592 			MainWindow *mainWindow(MainWindow::findMainWindow(this));
593 			const QUrl url(entryIndex.sibling(entryIndex.row(), 0).data(Qt::UserRole).toUrl());
594 
595 			if (mainWindow && url.isValid())
596 			{
597 				mainWindow->triggerAction(ActionsManager::OpenUrlAction, {{QLatin1String("url"), url}, {QLatin1String("hints"), QVariant(SessionsManager::calculateOpenHints(SessionsManager::NewTabOpen, mouseEvent->button(), mouseEvent->modifiers()))}});
598 
599 				return true;
600 			}
601 		}
602 	}
603 
604 	return ContentsWidget::eventFilter(object, event);
605 }
606 
607 }
608