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"