File indexing completed on 2025-02-02 03:34:27

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 "accessibleobjecttreemodel.h"
0008 #include "accessiblewrapper.h"
0009 
0010 #include "accessibilityinspector_debug.h"
0011 
0012 #include <KLocalizedString>
0013 
0014 using namespace QAccessibleClient;
0015 
0016 AccessibleObjectTreeModel::AccessibleObjectTreeModel(QObject *parent)
0017     : QAbstractItemModel(parent)
0018 {
0019 }
0020 
0021 AccessibleObjectTreeModel::~AccessibleObjectTreeModel()
0022 {
0023     qDeleteAll(mApps);
0024 }
0025 
0026 QVariant AccessibleObjectTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
0027 {
0028     if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
0029         switch (static_cast<AccessibleObjectTreeModelRoles>(section)) {
0030         case Accessible:
0031             return i18n("Accessible");
0032         case Role:
0033             return i18n("Role");
0034         default:
0035             return {};
0036         }
0037     }
0038     return {};
0039 }
0040 
0041 int AccessibleObjectTreeModel::columnCount(const QModelIndex &parent) const
0042 {
0043     Q_UNUSED(parent);
0044     constexpr int val = static_cast<int>(AccessibleObjectTreeModelRoles::LastColumn) + 1;
0045     return val;
0046 }
0047 
0048 QVariant AccessibleObjectTreeModel::data(const QModelIndex &index, int role) const
0049 {
0050     if (!mRegistry || !index.isValid())
0051         return {};
0052 
0053     if (role != Qt::DisplayRole) {
0054         return {};
0055     }
0056 
0057     AccessibleObject acc = static_cast<AccessibleWrapper *>(index.internalPointer())->acc;
0058 
0059     const int col = index.column();
0060     switch (static_cast<AccessibleObjectTreeModelRoles>(col)) {
0061     case AccessibleObjectTreeModelRoles::Role:
0062         return acc.roleName();
0063     case AccessibleObjectTreeModelRoles::ChildrenCount:
0064         // qDebug() << " AccessibleObjectTreeModelRoles::ChildrenCount " << acc.childCount();
0065         return acc.childCount();
0066     case AccessibleObjectTreeModelRoles::Accessible: {
0067         QString name = acc.name();
0068         if (name.isEmpty())
0069             name = QStringLiteral("[%1]").arg(acc.roleName());
0070         return name;
0071     }
0072     }
0073     return {};
0074 }
0075 
0076 QModelIndex AccessibleObjectTreeModel::index(int row, int column, const QModelIndex &parent) const
0077 {
0078     if (!mRegistry || (column < 0) || (column > 1) || (row < 0)) {
0079         return {};
0080     }
0081 
0082     //     qDebug() << "index:" << row << column << parent;
0083     if (!parent.isValid()) {
0084         if (row < mApps.count()) {
0085             return createIndex(row, column, mApps.at(row));
0086         }
0087     } else {
0088         auto wraper = static_cast<AccessibleWrapper *>(parent.internalPointer());
0089         if (row < wraper->childCount()) {
0090             QModelIndex newIndex = createIndex(row, column, wraper->child(row));
0091             if (newIndex.parent() != parent) {
0092                 qCWarning(ACCESSIBILITYINSPECTOR_LOG) << "Broken navigation: " << parent << row;
0093                 Q_EMIT navigationError(parent);
0094             }
0095             return newIndex;
0096         } else {
0097             qCWarning(ACCESSIBILITYINSPECTOR_LOG) << "Could not access child: " << wraper->acc.name() << wraper->acc.roleName();
0098         }
0099     }
0100 
0101     return {};
0102 }
0103 
0104 QModelIndex AccessibleObjectTreeModel::parent(const QModelIndex &child) const
0105 {
0106     //     qDebug() << "Parent: " << child;
0107     if (child.isValid()) {
0108         auto wraper = static_cast<AccessibleWrapper *>(child.internalPointer());
0109         AccessibleWrapper *parent = wraper->parent();
0110         if (parent) {
0111             // if this is a top-level item, it has no parent
0112             if (parent->parent()) {
0113                 return createIndex(parent->acc.indexInParent(), 0, parent);
0114             } else {
0115                 return createIndex(mApps.indexOf(parent), 0, parent);
0116             }
0117         }
0118     }
0119 
0120     return {};
0121 }
0122 
0123 int AccessibleObjectTreeModel::rowCount(const QModelIndex &parent) const
0124 {
0125     if (!mRegistry || parent.column() > 0)
0126         return 0;
0127 
0128     //     qDebug() << "row count:" << parent << parent.internalPointer();
0129     if (!parent.isValid()) {
0130         return mApps.count();
0131     } else {
0132         if (!parent.internalPointer())
0133             return 0;
0134 
0135         auto wraper = static_cast<AccessibleWrapper *>(parent.internalPointer());
0136         //         qDebug() << "     row count:" << wraper->acc.name() << wraper->acc.roleName() << wraper->childCount();
0137         return wraper->childCount();
0138     }
0139 
0140     return 0;
0141 }
0142 
0143 void AccessibleObjectTreeModel::setRegistry(QAccessibleClient::Registry *registry)
0144 {
0145     mRegistry = registry;
0146     resetModel();
0147 }
0148 
0149 void AccessibleObjectTreeModel::resetModel()
0150 {
0151     beginResetModel();
0152     qDeleteAll(mApps);
0153     mApps.clear();
0154     if (mRegistry) {
0155         const QList<AccessibleObject> children = mRegistry->applications();
0156         for (const AccessibleObject &c : children) {
0157             mApps.append(new AccessibleWrapper(c, nullptr));
0158         }
0159     }
0160     endResetModel();
0161 }
0162 
0163 void AccessibleObjectTreeModel::updateTopLevelApps()
0164 {
0165     QList<AccessibleObject> topLevelApps = mRegistry->applications();
0166     for (int i = mApps.count() - 1; i >= 0; --i) {
0167         AccessibleObject app = mApps.at(i)->acc;
0168         const int indexOfApp = topLevelApps.indexOf(app);
0169         if (indexOfApp < 0) {
0170             removeAccessible(index(i, 0, QModelIndex()));
0171         } else {
0172             topLevelApps.takeAt(i);
0173         }
0174     }
0175 
0176     for (const AccessibleObject &newApp : std::as_const(topLevelApps)) {
0177         addAccessible(newApp);
0178     }
0179 }
0180 
0181 QModelIndex AccessibleObjectTreeModel::indexForAccessible(const AccessibleObject &object)
0182 {
0183     if (!object.isValid())
0184         return {};
0185 
0186     if (object.supportedInterfaces().testFlag(QAccessibleClient::AccessibleObject::ApplicationInterface)) {
0187         // top level
0188         for (int i = 0, total = mApps.size(); i < total; ++i) {
0189             if (mApps.at(i)->acc == object) {
0190                 return createIndex(i, 0, mApps.at(i));
0191             }
0192         }
0193         const int lastIndex = mApps.size();
0194         if (addAccessible(object) && mApps.at(lastIndex)->acc == object)
0195             return createIndex(lastIndex, 0, mApps.at(lastIndex));
0196 
0197     } else {
0198         AccessibleObject parent = object.parent();
0199         if (parent.isValid()) {
0200             const QModelIndex parentIndex = indexForAccessible(parent);
0201             if (!parentIndex.isValid()) {
0202                 if (object.isValid() && object.application().isValid()) {
0203                     qCWarning(ACCESSIBILITYINSPECTOR_LOG)
0204                         << Q_FUNC_INFO << object.application().name() << object.name() << object.roleName() << "Parent model index is invalid: " << object;
0205                 }
0206                 return {};
0207             }
0208             const int indexInParent = object.indexInParent();
0209             if (indexInParent < 0) {
0210                 qCWarning(ACCESSIBILITYINSPECTOR_LOG) << Q_FUNC_INFO << "indexInParent is invalid: " << object;
0211                 return {};
0212             }
0213             const QModelIndex in = index(indexInParent, 0, parentIndex);
0214             // qDebug() << "indexForAccessible: " << object.name() << data(in).toString()  << " parent: " << data(parentIndex).toString();//" row: " <<
0215             // object.indexInParent() << "parentIndex: " << parentIndex;
0216             return in;
0217         } else {
0218             qCWarning(ACCESSIBILITYINSPECTOR_LOG) << Q_FUNC_INFO << "Invalid indexForAccessible: " << object;
0219             // Q_ASSERT(!object.supportedInterfaces().testFlag(QAccessibleClient::AccessibleObject::Application));
0220             // return indexForAccessible(object.application());
0221 
0222             for (const QAccessibleClient::AccessibleObject &child : object.children()) {
0223                 if (child.supportedInterfaces().testFlag(QAccessibleClient::AccessibleObject::ApplicationInterface)) {
0224                     for (int i = 0, total = mApps.size(); i < total; ++i) {
0225                         if (mApps.at(i)->acc == object)
0226                             return createIndex(i, 0, mApps.at(i));
0227                     }
0228                 }
0229             }
0230         }
0231     }
0232     return {};
0233 }
0234 
0235 bool AccessibleObjectTreeModel::addAccessible(const QAccessibleClient::AccessibleObject &object)
0236 {
0237     // qDebug() << Q_FUNC_INFO << object;
0238     QAccessibleClient::AccessibleObject parent = object.parent();
0239 
0240     // We have no parent -> top level.
0241     if (!parent.isValid()) {
0242         if (!object.supportedInterfaces().testFlag(QAccessibleClient::AccessibleObject::ApplicationInterface))
0243             qCWarning(ACCESSIBILITYINSPECTOR_LOG) << Q_FUNC_INFO << "Found top level accessible that does not implement the application interface" << object;
0244 
0245         beginInsertRows(QModelIndex(), mApps.count(), mApps.count());
0246         mApps.append(new AccessibleWrapper(object, nullptr));
0247         endInsertRows();
0248         return true;
0249     }
0250 
0251     // If the parent is not known, add it too.
0252     QModelIndex parentIndex = indexForAccessible(parent);
0253     if (!parentIndex.isValid()) {
0254         if (!addAccessible(parent)) {
0255             qCWarning(ACCESSIBILITYINSPECTOR_LOG) << Q_FUNC_INFO << "Could not add accessible (invalid parent): " << object;
0256             return false;
0257         }
0258         parentIndex = indexForAccessible(parent);
0259         Q_ASSERT(parentIndex.isValid());
0260     }
0261 
0262     // Add this item (or emit dataChanged, if it's there already).
0263     const int idx = object.indexInParent();
0264     if (idx < 0) {
0265         qCWarning(ACCESSIBILITYINSPECTOR_LOG) << Q_FUNC_INFO << "Could not add accessible (invalid index in parent): " << object;
0266         return false;
0267     }
0268     const QModelIndex objectIndex = index(idx, 0, parentIndex);
0269     if (objectIndex.isValid() && static_cast<AccessibleWrapper *>(objectIndex.internalPointer())->acc == object) {
0270         Q_EMIT dataChanged(objectIndex, objectIndex);
0271         return false;
0272     }
0273 
0274     beginInsertRows(parentIndex, idx, idx);
0275     auto parentWrapper = static_cast<AccessibleWrapper *>(parentIndex.internalPointer());
0276     Q_ASSERT(parentWrapper);
0277     parentWrapper->mChildren.insert(idx, new AccessibleWrapper(object, parentWrapper));
0278     endInsertRows();
0279     return true;
0280 }
0281 
0282 bool AccessibleObjectTreeModel::removeAccessible(const QAccessibleClient::AccessibleObject &object)
0283 {
0284     qCDebug(ACCESSIBILITYINSPECTOR_LOG) << Q_FUNC_INFO << object;
0285     const QModelIndex index = indexForAccessible(object);
0286     if (!index.isValid()) {
0287         return false;
0288     }
0289     return removeAccessible(index);
0290 }
0291 
0292 bool AccessibleObjectTreeModel::removeAccessible(const QModelIndex &index)
0293 {
0294     qCDebug(ACCESSIBILITYINSPECTOR_LOG) << Q_FUNC_INFO << index;
0295     Q_ASSERT(index.isValid());
0296     Q_ASSERT(index.model() == this);
0297     QModelIndex parent = index.parent();
0298     const int row = index.row();
0299     bool removed = false;
0300     beginRemoveRows(parent, row, row);
0301     if (parent.isValid()) {
0302         auto wraper = static_cast<AccessibleWrapper *>(parent.internalPointer());
0303         Q_ASSERT(wraper);
0304         delete wraper->mChildren.takeAt(row);
0305         removed = true;
0306     } else {
0307         auto wraper = static_cast<AccessibleWrapper *>(index.internalPointer());
0308         Q_ASSERT(wraper);
0309         Q_ASSERT(mApps[row] == wraper);
0310         if (mApps[row] == wraper) {
0311             qCDebug(ACCESSIBILITYINSPECTOR_LOG) << Q_FUNC_INFO << "Delete application accessible object! indexRow=" << row;
0312             delete mApps.takeAt(row);
0313             removed = true;
0314         }
0315     }
0316     endRemoveRows();
0317     return removed;
0318 }
0319 
0320 void AccessibleObjectTreeModel::updateAccessible(const QAccessibleClient::AccessibleObject &object)
0321 {
0322     const QModelIndex index = indexForAccessible(object);
0323     Q_EMIT dataChanged(index, index);
0324 }
0325 
0326 QList<AccessibleWrapper *> AccessibleObjectTreeModel::apps() const
0327 {
0328     return mApps;
0329 }
0330 
0331 #include "moc_accessibleobjecttreemodel.cpp"