Warning, file /libraries/libqaccessibilityclient/examples/accessibleapps/eventview.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2012 Frederik Gladhorn <gladhorn@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "eventview.h"
0008 
0009 #include <QTextDocument>
0010 #include <QTextCursor>
0011 #include <QTextObject>
0012 #include <QTimer>
0013 #include <QScrollBar>
0014 #include <QSettings>
0015 #include <QPair>
0016 #include <QSortFilterProxyModel>
0017 #include <QStandardItemModel>
0018 #include <QStandardItem>
0019 #include <QMetaObject>
0020 #include <QMetaEnum>
0021 #include <QDebug>
0022 
0023 class EventsModel : public QStandardItemModel
0024 {
0025 public:
0026     enum Role {
0027         AccessibleRole = 0,
0028         RoleRole = 1,
0029         EventRole = 2,
0030         ActionRole = 3,
0031         EventTypeRole = Qt::UserRole,
0032         UrlRole,
0033         AppNameRole,
0034         AppUrlRole
0035     };
0036     explicit EventsModel(EventsWidget *view) : QStandardItemModel(view), m_view(view) {
0037         clearLog();
0038     }
0039     ~EventsModel() override {}
0040 
0041     QHash<int,QByteArray> roleNames() const override
0042     {
0043         QHash<int, QByteArray> roles;
0044         roles[AccessibleRole] = "accessible";
0045         roles[RoleRole] = "role";
0046         roles[EventRole] = "event";
0047         roles[EventTypeRole] = "eventType";
0048         roles[UrlRole] = "url";
0049         roles[AppNameRole] = "appName";
0050         roles[AppUrlRole] = "appUrl";
0051         return roles;
0052     }
0053 
0054     QString roleLabel(Role role) const
0055     {
0056         switch (role) {
0057             case AccessibleRole: return QStringLiteral("Accessible");
0058             case RoleRole: return QStringLiteral("Role");
0059             case EventRole: return QStringLiteral("Event");
0060             case ActionRole: return QStringLiteral("Action");
0061             case EventTypeRole:
0062             case UrlRole:
0063             case AppNameRole:
0064             case AppUrlRole:
0065                 break;
0066         }
0067         return QString();
0068     }
0069     void clearLog()
0070     {
0071         clear();
0072         m_apps.clear();
0073         setColumnCount(4);
0074         QStringList headerLabels;
0075         for (Role r : QList<Role>() << AccessibleRole << RoleRole << EventRole << ActionRole)
0076             headerLabels << roleLabel(r);
0077         setHorizontalHeaderLabels(headerLabels);
0078     }
0079     struct LogItem {
0080         QStandardItem *appItem;
0081         bool isNewAppItem;
0082         LogItem(QStandardItem *appItem, bool isNewAppItem) : appItem(appItem), isNewAppItem(isNewAppItem) {}
0083     };
0084     LogItem addLog(QList<QStandardItem*> item)
0085     {
0086         QString appUrl = item.first()->data(AppUrlRole).toString();
0087         QStandardItem *appItem = nullptr;
0088         QMap<QString, QStandardItem*>::ConstIterator it = m_apps.constFind(appUrl);
0089         bool isNewAppItem = it == m_apps.constEnd();
0090         if (isNewAppItem) {
0091             QString appName = item.first()->data(AppNameRole).toString();
0092             m_apps[appUrl] = appItem = new QStandardItem(appName);
0093             appItem->setData(appUrl, EventsModel::AppUrlRole);
0094             invisibleRootItem()->appendRow(appItem);
0095         } else {
0096             appItem = it.value();
0097         }
0098         appItem->appendRow(item);
0099         return LogItem(appItem, isNewAppItem);
0100     }
0101 private:
0102     EventsWidget *m_view;
0103     QMap<QString, QStandardItem*> m_apps;
0104 };
0105 
0106 class EventsProxyModel : public QSortFilterProxyModel
0107 {
0108 public:
0109     explicit EventsProxyModel(QWidget *parent = nullptr) : QSortFilterProxyModel(parent), m_types(EventsWidget::AllEvents) {}
0110     EventsWidget::EventTypes filter() const
0111     {
0112         return m_types;
0113     }
0114     QString accessibleFilter() const
0115     {
0116         return m_accessibleFilter;
0117     }
0118     QString roleFilter() const
0119     {
0120         return m_roleFilter;
0121     }
0122     void setFilter(EventsWidget::EventTypes types)
0123     {
0124         m_types = types;
0125         invalidateFilter();
0126     }
0127     void setAccessibleFilter(const QString &filter)
0128     {
0129         m_accessibleFilter = filter;
0130         invalidateFilter();
0131     }
0132     void setRoleFilter(const QString &filter)
0133     {
0134         m_roleFilter = filter;
0135         invalidateFilter();
0136     }
0137 protected:
0138     bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
0139     {
0140         if (!source_parent.isValid())
0141             return true;
0142         if (!m_types.testFlag(EventsWidget::AllEvents)) {
0143             QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
0144             EventsWidget::EventType type = index.data(EventsModel::EventTypeRole).value<EventsWidget::EventType>();
0145             if (!m_types.testFlag(type))
0146                 return false;
0147         }
0148         if (!m_accessibleFilter.isEmpty()) {
0149             QModelIndex index = sourceModel()->index(source_row, EventsModel::AccessibleRole, source_parent);
0150             QString accessibleName = index.data(Qt::DisplayRole).toString();
0151             if (!accessibleName.contains(m_accessibleFilter, Qt::CaseInsensitive))
0152                 return false;
0153         }
0154         if (!m_roleFilter.isEmpty()) {
0155             QModelIndex index = sourceModel()->index(source_row, EventsModel::RoleRole, source_parent);
0156             QString roleName = index.data(Qt::DisplayRole).toString();
0157             if (!roleName.contains(m_roleFilter, Qt::CaseInsensitive))
0158                 return false;
0159         }
0160         return true;
0161     }
0162 private:
0163     EventsWidget::EventTypes m_types;
0164     QString m_accessibleFilter;
0165     QString m_roleFilter;
0166 };
0167 
0168 using namespace QAccessibleClient;
0169 QAccessible::UpdateHandler EventsWidget::m_originalAccessibilityUpdateHandler = nullptr;
0170 QObject *EventsWidget::m_textEditForAccessibilityUpdateHandler = nullptr;
0171 
0172 EventsWidget::EventsWidget(QAccessibleClient::Registry *registry, QWidget *parent)
0173     : QWidget(parent), m_registry(registry), m_model(new EventsModel(this)), m_proxyModel(new EventsProxyModel(this)) {
0174     m_ui.setupUi(this);
0175 
0176     m_ui.eventListView->setAccessibleName(QLatin1String("Events View"));
0177     m_ui.eventListView->setAccessibleDescription(QStringLiteral("Displays all received events"));
0178 
0179     m_proxyModel->setSourceModel(m_model);
0180     m_ui.eventListView->setModel(m_proxyModel);
0181 
0182     connect(m_ui.accessibleFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(accessibleFilterChanged()));
0183     connect(m_ui.roleFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(roleFilterChanged()));
0184 
0185     QStandardItemModel *filerModel = new QStandardItemModel();
0186     QStandardItem *firstFilterItem = new QStandardItem(QStringLiteral("Event Filter"));
0187     firstFilterItem->setFlags(Qt::ItemIsEnabled);
0188     filerModel->appendRow(firstFilterItem);
0189 
0190     QVector< EventType > filterList;
0191     filterList << StateChanged << NameChanged << DescriptionChanged << Window << Focus << Document << Object << Text << Table << Others;
0192     for(int i = 0; i < filterList.count(); ++i) {
0193         EventType t = filterList[i];
0194         QStandardItem* item = new QStandardItem(eventName(t));
0195         item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
0196         item->setData(QVariant::fromValue<EventType>(t), EventsModel::EventTypeRole);
0197         item->setData(Qt::Checked, Qt::CheckStateRole);
0198         filerModel->appendRow(QList<QStandardItem*>() << item);
0199     }
0200     m_ui.filterComboBox->setModel(filerModel);
0201 
0202     m_ui.clearButton->setFixedWidth(QFontMetrics(m_ui.clearButton->font()).boundingRect(m_ui.clearButton->text()).width() + 4);
0203     m_ui.clearButton->setFixedHeight(m_ui.filterComboBox->sizeHint().height());
0204     connect(m_ui.clearButton, SIGNAL(clicked()), this, SLOT(clearLog()));
0205     connect(m_ui.filterComboBox->model(), SIGNAL(itemChanged(QStandardItem*)), this, SLOT(checkStateChanged()));
0206     connect(m_ui.eventListView, SIGNAL(activated(QModelIndex)), this, SLOT(eventActivated(QModelIndex)));
0207 
0208     // Collect multiple addLog calls and process them after 500 ms earliest. This
0209     // makes sure multiple calls to addLog will be compressed to one only one
0210     // view refresh what improves performance.
0211     m_pendingTimer.setInterval(500);
0212     connect(&m_pendingTimer, SIGNAL(timeout()), this, SLOT(processPending()));
0213     m_textEditForAccessibilityUpdateHandler = m_ui.eventListView;
0214     checkStateChanged();
0215 
0216     // We need to wait for a11y to be active for this hack.
0217     QTimer::singleShot(500, this, SLOT(installUpdateHandler()));
0218 }
0219 
0220 void EventsWidget::installUpdateHandler()
0221 {
0222     m_originalAccessibilityUpdateHandler = QAccessible::installUpdateHandler(customUpdateHandler);
0223     if (!m_originalAccessibilityUpdateHandler)
0224         QTimer::singleShot(500, this, SLOT(installUpdateHandler()));
0225 }
0226 
0227 void EventsWidget::customUpdateHandler(QAccessibleEvent *event)
0228 {
0229     QObject *object = event->object();
0230     if (object == m_textEditForAccessibilityUpdateHandler)
0231         return;
0232     //if (m_originalAccessibilityUpdateHandler)
0233     //    m_originalAccessibilityUpdateHandler(object, who, reason);
0234 }
0235 
0236 QString EventsWidget::eventName(EventType eventType) const
0237 {
0238     QString s;
0239     switch (eventType) {
0240         case EventsWidget::Focus:              s = QLatin1String("Focus"); break;
0241         case EventsWidget::StateChanged:       s = QLatin1String("State"); break;
0242         case EventsWidget::NameChanged:        s = QLatin1String("Name"); break;
0243         case EventsWidget::DescriptionChanged: s = QLatin1String("Description"); break;
0244         case EventsWidget::Window:             s = QLatin1String("Window"); break;
0245         case EventsWidget::Document:           s = QLatin1String("Document"); break;
0246         case EventsWidget::Object:             s = QLatin1String("Object"); break;
0247         case EventsWidget::Text:               s = QLatin1String("Text"); break;
0248         case EventsWidget::Table:              s = QLatin1String("Table"); break;
0249         case EventsWidget::Others:             s = QLatin1String("Others"); break;
0250     }
0251     return s;
0252 }
0253 
0254 void EventsWidget::loadSettings(QSettings &settings)
0255 {
0256     settings.beginGroup(QStringLiteral("events"));
0257 
0258     bool eventsFilterOk;
0259     EventTypes eventsFilter = EventTypes(settings.value(QStringLiteral("eventsFilter")).toInt(&eventsFilterOk));
0260     if (!eventsFilterOk)
0261         eventsFilter = AllEvents;
0262 
0263     QAbstractItemModel *model = m_ui.filterComboBox->model();
0264     if (eventsFilter != m_proxyModel->filter()) {
0265         for (int i = 1; i < model->rowCount(); ++i) {
0266             QModelIndex index = model->index(i, 0);
0267             EventType type = model->data(index, EventsModel::EventTypeRole).value<EventType>();
0268             if (eventsFilter.testFlag(type))
0269                 model->setData(index, Qt::Checked, Qt::CheckStateRole);
0270             else
0271                 model->setData(index, Qt::Unchecked, Qt::CheckStateRole);
0272         }
0273         m_proxyModel->setFilter(eventsFilter);
0274     }
0275 
0276     QByteArray eventListViewState = settings.value(QStringLiteral("listViewHeader")).toByteArray();
0277     if (!eventListViewState.isEmpty())
0278         m_ui.eventListView->header()->restoreState(eventListViewState);
0279 
0280     settings.endGroup();
0281 }
0282 
0283 void EventsWidget::saveSettings(QSettings &settings)
0284 {
0285     settings.beginGroup(QStringLiteral("events"));
0286     settings.setValue(QStringLiteral("eventsFilter"), int(m_proxyModel->filter()));
0287 
0288     QByteArray eventListViewState = m_ui.eventListView->header()->saveState();
0289     settings.setValue(QStringLiteral("listViewHeader"), eventListViewState);
0290 
0291     settings.endGroup();
0292 }
0293 
0294 void EventsWidget::clearLog()
0295 {
0296     m_model->clearLog();
0297 }
0298 
0299 void EventsWidget::processPending()
0300 {
0301     m_pendingTimer.stop();
0302     QVector< QList<QStandardItem*> > pendingLogs = m_pendingLogs;
0303     m_pendingLogs.clear();
0304     //bool wasMax = true;//m_ui.eventListView->verticalScrollBar()->value() - 10 >= m_ui.eventListView->verticalScrollBar()->maximum();
0305     QStandardItem *lastItem = nullptr;
0306     QStandardItem *lastAppItem = nullptr;
0307     for(int i = 0; i < pendingLogs.count(); ++i) {
0308         QList<QStandardItem*> item = pendingLogs[i];
0309         EventsModel::LogItem logItem = m_model->addLog(item);
0310 
0311         // Logic to scroll to the last added logItem of the last appItem that is expanded.
0312         // For appItem's not expanded the logItem is added but no scrolling will happen.
0313         if (lastItem && lastAppItem && lastAppItem != logItem.appItem)
0314             lastItem = nullptr;
0315         bool selected = lastItem;
0316         if (lastAppItem != logItem.appItem) {
0317             lastAppItem = logItem.appItem;
0318             QModelIndex index = m_proxyModel->mapFromSource(m_model->indexFromItem(logItem.appItem));
0319             if (logItem.isNewAppItem) {
0320                 m_ui.eventListView->setExpanded(index, true);
0321                 selected = true;
0322             } else {
0323                 selected = m_ui.eventListView->isExpanded(index);
0324             }
0325         }
0326         if (selected)
0327             lastItem = item.first();
0328     }
0329     if (lastItem) { // scroll down to the lastItem.
0330         //m_ui.eventListView->verticalScrollBar()->setValue(m_ui.eventListView->verticalScrollBar()->maximum());
0331 
0332         QModelIndex index = m_proxyModel->mapFromSource(m_model->indexFromItem(lastItem));
0333         m_ui.eventListView->scrollTo(index, QAbstractItemView::PositionAtBottom);
0334         //m_ui.eventListView->scrollTo(index, QAbstractItemView::EnsureVisible);
0335     }
0336 }
0337 
0338 void EventsWidget::addLog(const QAccessibleClient::AccessibleObject &object, EventsWidget::EventType eventType, const QString &text)
0339 {
0340     if (!object.isValid())
0341         return;
0342 
0343     QStandardItem *nameItem = new QStandardItem(object.name());
0344     nameItem->setData(QVariant::fromValue<EventType>(eventType), EventsModel::EventTypeRole);
0345     nameItem->setData(object.url().toString(), EventsModel::UrlRole);
0346 
0347     AccessibleObject app = object.application();
0348     if (app.isValid()) {
0349         nameItem->setData(app.name(), EventsModel::AppNameRole);
0350         nameItem->setData(app.url().toString(), EventsModel::AppUrlRole);
0351     }
0352 
0353     QStandardItem *roleItem = new QStandardItem(object.roleName());
0354     QStandardItem *typeItem = new QStandardItem(eventName(eventType));
0355     QStandardItem *textItem = new QStandardItem(text);
0356     m_pendingLogs.append(QList<QStandardItem*>() << nameItem << roleItem << typeItem << textItem);
0357     if (!m_pendingTimer.isActive()) {
0358         m_pendingTimer.start();
0359     }
0360 }
0361 
0362 void EventsWidget::checkStateChanged()
0363 {
0364     EventTypes types;
0365     QStringList names;
0366     bool allEvents = true;
0367     QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("EventType"));
0368     Q_ASSERT(e.isValid());
0369     QAbstractItemModel *model = m_ui.filterComboBox->model();
0370     for (int i = 1; i < model->rowCount(); ++i) {
0371         QModelIndex index = model->index(i, 0);
0372         bool checked = model->data(index, Qt::CheckStateRole).toBool();
0373         if (checked) {
0374             EventType type = model->data(index, EventsModel::EventTypeRole).value<EventType>();
0375             types |= type;
0376             names.append(QString::fromLatin1(e.valueToKey(type)));
0377         } else {
0378             allEvents = false;
0379         }
0380     }
0381     m_proxyModel->setFilter(types);
0382 }
0383 
0384 void EventsWidget::eventActivated(const QModelIndex &index)
0385 {
0386     Q_ASSERT(index.isValid());
0387     QModelIndex parent = index.parent();
0388     QModelIndex firstIndex = m_proxyModel->index(index.row(), 0, parent);
0389     QString s = m_proxyModel->data(firstIndex, parent.isValid() ? EventsModel::UrlRole : EventsModel::AppUrlRole).toString();
0390     QUrl url(s);
0391     if (!url.isValid()) {
0392         qWarning() << Q_FUNC_INFO << "Invalid url=" << s;
0393         return;
0394     }
0395     Q_EMIT anchorClicked(url);
0396 }
0397 
0398 void EventsWidget::accessibleFilterChanged()
0399 {
0400     m_proxyModel->setAccessibleFilter(m_ui.accessibleFilterEdit->text());
0401 }
0402 
0403 void EventsWidget::roleFilterChanged()
0404 {
0405     m_proxyModel->setRoleFilter(m_ui.roleFilterEdit->text());
0406 }
0407 
0408 #include "moc_eventview.cpp"