mirror of
https://invent.kde.org/network/falkon.git
synced 2024-11-11 09:32:12 +01:00
39af453281
See #817
373 lines
11 KiB
C++
373 lines
11 KiB
C++
/* ============================================================
|
|
* Access Keys Navigation plugin for QupZilla
|
|
* Copyright (C) 2012-2013 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/>.
|
|
* ============================================================ */
|
|
/*
|
|
* Copyright 2008-2009 Benjamin C. Meyer <ben@meyerhome.net>
|
|
*
|
|
* 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 2 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, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301 USA
|
|
*/
|
|
#include "akn_handler.h"
|
|
#include "webview.h"
|
|
#include "webpage.h"
|
|
|
|
#include <QApplication>
|
|
#include <QSettings>
|
|
#include <QWebPage>
|
|
#include <QWebFrame>
|
|
#include <QLabel>
|
|
#include <QTimer>
|
|
#include <QToolTip>
|
|
#include <QKeyEvent>
|
|
|
|
Qt::Key keyFromCode(int code)
|
|
{
|
|
switch (code) {
|
|
case 0:
|
|
return Qt::Key_Control;
|
|
|
|
case 1:
|
|
return Qt::Key_Alt;
|
|
|
|
case 2:
|
|
return Qt::Key_Shift;
|
|
|
|
default:
|
|
// Using default shortcut
|
|
return Qt::Key_Control;
|
|
}
|
|
}
|
|
|
|
AKN_Handler::AKN_Handler(const QString &sPath, QObject* parent)
|
|
: QObject(parent)
|
|
, m_accessKeysVisible(false)
|
|
, m_settingsFile(sPath + "extensions.ini")
|
|
{
|
|
loadSettings();
|
|
}
|
|
|
|
QString AKN_Handler::settingsFile()
|
|
{
|
|
return m_settingsFile;
|
|
}
|
|
|
|
void AKN_Handler::loadSettings()
|
|
{
|
|
QSettings settings(m_settingsFile, QSettings::IniFormat);
|
|
|
|
settings.beginGroup("AccessKeysNavigation");
|
|
m_key = keyFromCode(settings.value("Key", 0).toInt());
|
|
m_isDoublePress = settings.value("DoublePress", true).toBool();
|
|
settings.endGroup();
|
|
}
|
|
|
|
bool AKN_Handler::handleKeyPress(QObject* obj, QKeyEvent* event)
|
|
{
|
|
WebView* view = qobject_cast<WebView*>(obj);
|
|
if (!view) {
|
|
return false;
|
|
}
|
|
|
|
if (m_accessKeysVisible) {
|
|
handleAccessKey(event);
|
|
return true;
|
|
}
|
|
|
|
if (event->key() != m_key) {
|
|
m_lastKeyPressTime = QTime();
|
|
return false;
|
|
}
|
|
|
|
m_view = view;
|
|
|
|
if (!m_isDoublePress) {
|
|
triggerShowAccessKeys();
|
|
}
|
|
else {
|
|
if (!m_lastKeyPressTime.isValid()) {
|
|
// It is the first press of our button
|
|
m_lastKeyPressTime = QTime::currentTime();
|
|
}
|
|
else {
|
|
// It is the second press of our button
|
|
if (QTime(m_lastKeyPressTime).addMSecs(500) >= QTime::currentTime()) {
|
|
triggerShowAccessKeys();
|
|
}
|
|
else {
|
|
m_lastKeyPressTime = QTime::currentTime();
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool AKN_Handler::eventFilter(QObject* obj, QEvent* event)
|
|
{
|
|
if (obj != m_view.data()) {
|
|
return false;
|
|
}
|
|
|
|
switch (event->type()) {
|
|
case QEvent::MouseButtonPress:
|
|
case QEvent::Resize:
|
|
case QEvent::FocusOut:
|
|
case QEvent::Wheel:
|
|
hideAccessKeys();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void AKN_Handler::triggerShowAccessKeys()
|
|
{
|
|
if (m_accessKeysVisible) {
|
|
hideAccessKeys();
|
|
}
|
|
else {
|
|
QTimer::singleShot(0, this, SLOT(showAccessKeys()));
|
|
}
|
|
}
|
|
|
|
void AKN_Handler::handleAccessKey(QKeyEvent* event)
|
|
{
|
|
if (event->key() == m_key) {
|
|
hideAccessKeys();
|
|
return;
|
|
}
|
|
|
|
QString text = event->text();
|
|
if (text.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
if (!m_view) {
|
|
return;
|
|
}
|
|
|
|
QChar key = text.at(0);
|
|
|
|
QChar other(QChar::Null);
|
|
if (key.isLower()) {
|
|
other = key.toUpper();
|
|
}
|
|
else if (key.isUpper()) {
|
|
other = key.toLower();
|
|
}
|
|
|
|
if (!other.isNull()
|
|
&& m_accessKeyNodes.contains(other)
|
|
&& !m_accessKeyNodes.contains(key)) {
|
|
key = other;
|
|
}
|
|
|
|
if (m_accessKeyNodes.contains(key)) {
|
|
QWebElement element = m_accessKeyNodes[key];
|
|
QPoint p = element.geometry().center();
|
|
QWebFrame* frame = element.webFrame();
|
|
|
|
if (!frame) {
|
|
return;
|
|
}
|
|
|
|
do {
|
|
p -= frame->scrollPosition();
|
|
frame = frame->parentFrame();
|
|
}
|
|
while (frame && frame != m_view.data()->page()->currentFrame());
|
|
|
|
QMouseEvent pevent(QEvent::MouseButtonPress, p, Qt::LeftButton, 0, 0);
|
|
qApp->sendEvent(m_view.data(), &pevent);
|
|
|
|
QMouseEvent revent(QEvent::MouseButtonRelease, p, Qt::LeftButton, 0, 0);
|
|
qApp->sendEvent(m_view.data(), &revent);
|
|
|
|
hideAccessKeys();
|
|
}
|
|
}
|
|
|
|
void AKN_Handler::showAccessKeys()
|
|
{
|
|
if (!m_view) {
|
|
return;
|
|
}
|
|
|
|
QWebPage* page = m_view.data()->page();
|
|
|
|
QStringList supportedElement;
|
|
supportedElement << QLatin1String("input")
|
|
<< QLatin1String("a")
|
|
<< QLatin1String("area")
|
|
<< QLatin1String("button")
|
|
<< QLatin1String("label")
|
|
<< QLatin1String("legend")
|
|
<< QLatin1String("textarea");
|
|
|
|
QList<QChar> unusedKeys;
|
|
for (char c = 'A'; c <= 'Z'; ++c) {
|
|
unusedKeys << QLatin1Char(c);
|
|
}
|
|
for (char c = '0'; c <= '9'; ++c) {
|
|
unusedKeys << QLatin1Char(c);
|
|
}
|
|
for (char c = 'a'; c <= 'z'; ++c) {
|
|
unusedKeys << QLatin1Char(c);
|
|
}
|
|
|
|
QRect viewport = QRect(page->currentFrame()->scrollPosition(), page->viewportSize());
|
|
// Priority first goes to elements with accesskey attributes
|
|
QList<QWebElement> alreadyLabeled;
|
|
foreach (const QString &elementType, supportedElement) {
|
|
QList<QWebElement> result = page->currentFrame()->findAllElements(elementType).toList();
|
|
foreach (const QWebElement &element, result) {
|
|
const QRect geometry = element.geometry();
|
|
if (geometry.size().isEmpty()
|
|
|| !viewport.contains(geometry.topLeft())) {
|
|
continue;
|
|
}
|
|
QString accessKeyAttribute = element.attribute(QLatin1String("accesskey")).toUpper();
|
|
if (accessKeyAttribute.isEmpty()) {
|
|
continue;
|
|
}
|
|
QChar accessKey;
|
|
for (int i = 0; i < accessKeyAttribute.count(); i += 2) {
|
|
const QChar &possibleAccessKey = accessKeyAttribute[i];
|
|
if (unusedKeys.contains(possibleAccessKey)) {
|
|
accessKey = possibleAccessKey;
|
|
break;
|
|
}
|
|
}
|
|
if (accessKey.isNull()) {
|
|
continue;
|
|
}
|
|
unusedKeys.removeOne(accessKey);
|
|
makeAccessKeyLabel(accessKey, element);
|
|
alreadyLabeled.append(element);
|
|
}
|
|
}
|
|
|
|
// Pick an access key first from the letters in the text and then from the
|
|
// list of unused access keys
|
|
foreach (const QString &elementType, supportedElement) {
|
|
QWebElementCollection result = page->currentFrame()->findAllElements(elementType);
|
|
foreach (const QWebElement &element, result) {
|
|
const QRect geometry = element.geometry();
|
|
if (unusedKeys.isEmpty()
|
|
|| alreadyLabeled.contains(element)
|
|
|| geometry.size().isEmpty()
|
|
|| !viewport.contains(geometry.topLeft())) {
|
|
continue;
|
|
}
|
|
QChar accessKey;
|
|
QString text = element.toPlainText().toLower();
|
|
for (int i = 0; i < text.count(); ++i) {
|
|
const QChar &c = text.at(i);
|
|
if (unusedKeys.contains(c)) {
|
|
accessKey = c;
|
|
break;
|
|
}
|
|
}
|
|
if (accessKey.isNull()) {
|
|
accessKey = unusedKeys.takeFirst();
|
|
}
|
|
unusedKeys.removeOne(accessKey);
|
|
makeAccessKeyLabel(accessKey, element);
|
|
}
|
|
}
|
|
|
|
// Install event filter and connect loadStarted
|
|
m_accessKeysVisible = !m_accessKeyLabels.isEmpty();
|
|
if (m_accessKeysVisible) {
|
|
m_view.data()->installEventFilter(this);
|
|
connect(m_view.data(), SIGNAL(loadStarted()), this, SLOT(hideAccessKeys()));
|
|
connect(m_view.data()->page(), SIGNAL(scrollRequested(int,int,QRect)), this, SLOT(hideAccessKeys()));
|
|
#if QT_VERSION >= 0x040800
|
|
connect(m_view.data()->page(), SIGNAL(viewportChangeRequested()), this, SLOT(hideAccessKeys()));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void AKN_Handler::hideAccessKeys()
|
|
{
|
|
if (!m_accessKeyLabels.isEmpty() && m_view) {
|
|
// Fixes crash when hiding labels while closing view
|
|
if (!m_view->inherits("WebView")) {
|
|
m_accessKeyLabels.clear();
|
|
m_accessKeyNodes.clear();
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < m_accessKeyLabels.count(); ++i) {
|
|
QLabel* label = m_accessKeyLabels[i];
|
|
label->hide();
|
|
label->deleteLater();
|
|
}
|
|
m_accessKeyLabels.clear();
|
|
m_accessKeyNodes.clear();
|
|
m_view.data()->update();
|
|
|
|
// Uninstall event filter and disconnect loadStarted
|
|
m_view.data()->removeEventFilter(this);
|
|
disconnect(m_view.data(), SIGNAL(loadStarted()), this, SLOT(hideAccessKeys()));
|
|
disconnect(m_view.data()->page(), SIGNAL(scrollRequested(int,int,QRect)), this, SLOT(hideAccessKeys()));
|
|
#if QT_VERSION >= 0x040800
|
|
disconnect(m_view.data()->page(), SIGNAL(viewportChangeRequested()), this, SLOT(hideAccessKeys()));
|
|
#endif
|
|
}
|
|
|
|
m_accessKeysVisible = false;
|
|
}
|
|
|
|
void AKN_Handler::makeAccessKeyLabel(const QChar &accessKey, const QWebElement &element)
|
|
{
|
|
QLabel* label = new QLabel(m_view.data());
|
|
label->setText(QString(QLatin1String("<b>%1</b>")).arg(accessKey));
|
|
|
|
QPalette p = QToolTip::palette();
|
|
QColor color = QColor(220, 243, 253);
|
|
color.setAlpha(175);
|
|
p.setColor(QPalette::Window, color);
|
|
|
|
label->setPalette(p);
|
|
label->setAutoFillBackground(true);
|
|
label->setFrameStyle(QFrame::Box | QFrame::Plain);
|
|
QPoint point = element.geometry().center();
|
|
point -= m_view.data()->page()->currentFrame()->scrollPosition();
|
|
label->move(point);
|
|
label->show();
|
|
point.setX(point.x() - label->width() / 2);
|
|
label->move(point);
|
|
m_accessKeyLabels.append(label);
|
|
m_accessKeyNodes[accessKey] = element;
|
|
}
|