set(ADMIN_WORKER_PACKAGE_NAME "kio-admin")
set(FILELIGHT_PACKAGE_NAME "filelight")
+
configure_file(config-dolphin.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-dolphin.h)
add_definitions(
add_library(dolphinprivate SHARED)
+if(NOT QT_NO_ACCESSIBILITY)
+ target_sources(dolphinprivate PRIVATE
+ kitemviews/accessibility/kitemlistcontaineraccessible.cpp
+ kitemviews/accessibility/kitemlistdelegateaccessible.cpp
+ kitemviews/accessibility/kitemlistviewaccessible.cpp
+
+ kitemviews/accessibility/kitemlistcontaineraccessible.h
+ kitemviews/accessibility/kitemlistdelegateaccessible.h
+ kitemviews/accessibility/kitemlistviewaccessible.h
+ )
+endif()
+
target_sources(dolphinprivate PRIVATE
kitemviews/kfileitemlistview.cpp
kitemviews/kfileitemlistwidget.cpp
kitemviews/kitemlistselectionmanager.cpp
kitemviews/kitemliststyleoption.cpp
kitemviews/kitemlistview.cpp
- kitemviews/kitemlistviewaccessible.cpp
kitemviews/kitemlistwidget.cpp
kitemviews/kitemmodelbase.cpp
kitemviews/kitemset.cpp
kitemviews/kitemlistselectionmanager.h
kitemviews/kitemliststyleoption.h
kitemviews/kitemlistview.h
- kitemviews/kitemlistviewaccessible.h
kitemviews/kitemlistwidget.h
kitemviews/kitemmodelbase.h
kitemviews/kitemset.h
"Another contentsContainer has already been prepared. There can only be one.");
contentsContainer->setParent(m_contentsContainerParent);
m_contentsContainerParent->setWidget(contentsContainer);
+ m_contentsContainerParent->setFocusProxy(contentsContainer);
return contentsContainer;
}
#include <QApplication>
#include <QDropEvent>
+#include <QStackedWidget>
DolphinTabWidget::DolphinTabWidget(DolphinNavigatorsWidgetAction *navigatorsWidget, QWidget *parent)
: QTabWidget(parent)
setElideMode(Qt::ElideRight);
setUsesScrollButtons(true);
setTabBarAutoHide(true);
+
+ auto stackWidget{findChild<QStackedWidget *>()};
+ // i18n: This accessible name will be announced any time the user moves keyboard focus e.g. from the toolbar or the places panel towards the main working
+ // area of Dolphin. It gives structure. This container does not only contain the main view but also the status bar, the search panel, filter, and selection
+ // mode bars, so calling it just a "View" is a bit wrong, but hopefully still gets the point across.
+ stackWidget->setAccessibleName(i18nc("accessible name of Dolphin's view container", "Location View")); // Without this call, the non-descript Qt provided
+ // "Layered Pane" role is announced.
}
DolphinTabPage *DolphinTabWidget::currentTabPage() const
--- /dev/null
+/*
+ * SPDX-FileCopyrightText: 2012 Amandeep Singh <aman.dedman@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "kitemlistcontaineraccessible.h"
+
+#include "kitemlistcontaineraccessible.h"
+#include "kitemlistviewaccessible.h"
+#include "kitemviews/kitemlistcontainer.h"
+#include "kitemviews/kitemlistcontroller.h"
+#include "kitemviews/kitemlistselectionmanager.h"
+#include "kitemviews/kitemlistview.h"
+#include "kitemviews/kitemmodelbase.h"
+
+#include <KLocalizedString>
+
+KItemListContainerAccessible::KItemListContainerAccessible(KItemListContainer *container)
+ : QAccessibleWidget(container)
+{
+}
+
+KItemListContainerAccessible::~KItemListContainerAccessible()
+{
+}
+
+QString KItemListContainerAccessible::text(QAccessible::Text t) const
+{
+ Q_UNUSED(t)
+ return QString(); // This class should never have focus. Instead KItemListViewAccessible should be focused and read out.
+}
+
+int KItemListContainerAccessible::childCount() const
+{
+ return 1;
+}
+
+int KItemListContainerAccessible::indexOfChild(const QAccessibleInterface *child) const
+{
+ if (child == KItemListContainerAccessible::child(0)) {
+ return 0;
+ }
+ return -1;
+}
+
+QAccessibleInterface *KItemListContainerAccessible::child(int index) const
+{
+ if (index == 0) {
+ Q_CHECK_PTR(static_cast<KItemListViewAccessible *>(QAccessible::queryAccessibleInterface(container()->controller()->view())));
+ return QAccessible::queryAccessibleInterface(container()->controller()->view());
+ }
+ qWarning("Calling KItemListContainerAccessible::child(index) with index != 0 is always pointless.");
+ return nullptr;
+}
+
+QAccessibleInterface *KItemListContainerAccessible::focusChild() const
+{
+ return child(0);
+}
+
+QAccessible::State KItemListContainerAccessible::state() const
+{
+ auto state = QAccessibleWidget::state();
+ state.focusable = false;
+ state.focused = false;
+ return state;
+}
+
+void KItemListContainerAccessible::doAction(const QString &actionName)
+{
+ auto view = static_cast<KItemListViewAccessible *>(child(0));
+ Q_CHECK_PTR(view); // A container should always have a view. Otherwise it has no reason to exist.
+ if (actionName == setFocusAction() && view) {
+ view->doAction(actionName);
+ return;
+ }
+ QAccessibleWidget::doAction(actionName);
+}
+
+const KItemListContainer *KItemListContainerAccessible::container() const
+{
+ Q_CHECK_PTR(qobject_cast<KItemListContainer *>(object()));
+ return static_cast<KItemListContainer *>(object());
+}
--- /dev/null
+/*
+ * SPDX-FileCopyrightText: 2012 Amandeep Singh <aman.dedman@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef KITEMLISTCONTAINERACCESSIBLE_H
+#define KITEMLISTCONTAINERACCESSIBLE_H
+
+#include "dolphin_export.h"
+
+#include <QAccessibleWidget>
+
+class KItemListContainer;
+class KItemListViewAccessible;
+
+/**
+ * The accessible interface for KItemListContainer.
+ *
+ * Truthfully, there is absolutely no reason for screen reader users to interact with this interface.
+ * It is only there to bridge the gap between custom accessible interfaces and the automatically by Qt and QWidgets provided accessible interfaces.
+ * Really, the main issue is that KItemListContainer itself is the last proper QWidget in the hierarchy while the actual main view is completely custom using
+ * QGraphicsView instead, so focus usually officially goes to KItemListContainer which messes with the custom accessibility hierarchy.
+ */
+class DOLPHIN_EXPORT KItemListContainerAccessible : public QAccessibleWidget
+{
+public:
+ explicit KItemListContainerAccessible(KItemListContainer *container);
+ ~KItemListContainerAccessible() override;
+
+ QString text(QAccessible::Text t) const override;
+
+ QAccessibleInterface *child(int index) const override;
+ QAccessibleInterface *focusChild() const override;
+ int childCount() const override;
+ int indexOfChild(const QAccessibleInterface *child) const override;
+
+ QAccessible::State state() const override;
+ void doAction(const QString &actionName) override;
+
+ /** @returns the object() of this interface cast to its actual class. */
+ const KItemListContainer *container() const;
+};
+
+#endif // KITEMLISTCONTAINERACCESSIBLE_H
--- /dev/null
+/*
+ * SPDX-FileCopyrightText: 2012 Amandeep Singh <aman.dedman@gmail.com>
+ * SPDX-FileCopyrightText: 2024 Felix Ernst <felixernst@kde.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "kitemlistdelegateaccessible.h"
+#include "kitemviews/kfileitemlistwidget.h"
+#include "kitemviews/kfileitemmodel.h"
+#include "kitemviews/kitemlistcontroller.h"
+#include "kitemviews/kitemlistselectionmanager.h"
+#include "kitemviews/kitemlistview.h"
+#include "kitemviews/private/kitemlistviewlayouter.h"
+
+#include <KLocalizedString>
+
+#include <QGraphicsScene>
+#include <QGraphicsView>
+
+KItemListDelegateAccessible::KItemListDelegateAccessible(KItemListView *view, int index)
+ : m_view(view)
+ , m_index(index)
+{
+ Q_ASSERT(index >= 0 && index < view->model()->count());
+}
+
+void *KItemListDelegateAccessible::interface_cast(QAccessible::InterfaceType type)
+{
+ if (type == QAccessible::TableCellInterface) {
+ return static_cast<QAccessibleTableCellInterface *>(this);
+ }
+ return nullptr;
+}
+
+int KItemListDelegateAccessible::columnExtent() const
+{
+ return 1;
+}
+
+int KItemListDelegateAccessible::rowExtent() const
+{
+ return 1;
+}
+
+QList<QAccessibleInterface *> KItemListDelegateAccessible::rowHeaderCells() const
+{
+ return QList<QAccessibleInterface *>();
+}
+
+QList<QAccessibleInterface *> KItemListDelegateAccessible::columnHeaderCells() const
+{
+ return QList<QAccessibleInterface *>();
+}
+
+int KItemListDelegateAccessible::columnIndex() const
+{
+ return m_view->m_layouter->itemColumn(m_index);
+}
+
+int KItemListDelegateAccessible::rowIndex() const
+{
+ return m_view->m_layouter->itemRow(m_index);
+}
+
+bool KItemListDelegateAccessible::isSelected() const
+{
+ return m_view->controller()->selectionManager()->isSelected(m_index);
+}
+
+QAccessibleInterface *KItemListDelegateAccessible::table() const
+{
+ return QAccessible::queryAccessibleInterface(m_view);
+}
+
+QAccessible::Role KItemListDelegateAccessible::role() const
+{
+ return QAccessible::ListItem; // We could also return "Cell" here which would then announce the exact row and column of the item. However, different from
+ // applications that actually have a strong cell workflow -- like LibreOfficeCalc -- we have no advantage of announcing the row or column aside from us
+ // generally being interested in announcing that users in Icon View mode need to use the Left and Right arrow keys to arrive at every item. There are ways
+ // for users to figure this out regardless by paying attention to the index that is being announced for each list item. In KitemListViewAccessible in icon
+ // view mode it is also mentioned that the items are positioned in a grid, so the two-dimensionality should be clear enough.
+}
+
+QAccessible::State KItemListDelegateAccessible::state() const
+{
+ QAccessible::State state;
+
+ state.selectable = true;
+ if (isSelected()) {
+ state.selected = true;
+ }
+
+ state.focusable = true;
+ if (m_view->controller()->selectionManager()->currentItem() == m_index) {
+ state.focused = true;
+ state.active = true;
+ }
+
+ if (m_view->controller()->selectionBehavior() == KItemListController::MultiSelection) {
+ state.multiSelectable = true;
+ }
+
+ if (m_view->supportsItemExpanding() && m_view->model()->isExpandable(m_index)) {
+ state.expandable = true;
+ state.expanded = m_view->model()->isExpanded(m_index);
+ state.collapsed = !state.expanded;
+ }
+
+ return state;
+}
+
+bool KItemListDelegateAccessible::isExpandable() const
+{
+ return m_view->model()->isExpandable(m_index);
+}
+
+QRect KItemListDelegateAccessible::rect() const
+{
+ QRect rect = m_view->itemRect(m_index).toRect();
+
+ if (rect.isNull()) {
+ return QRect();
+ }
+
+ rect.translate(m_view->mapToScene(QPointF(0.0, 0.0)).toPoint());
+ rect.translate(m_view->scene()->views()[0]->mapToGlobal(QPoint(0, 0)));
+ return rect;
+}
+
+QString KItemListDelegateAccessible::text(QAccessible::Text t) const
+{
+ const QHash<QByteArray, QVariant> data = m_view->model()->data(m_index);
+ switch (t) {
+ case QAccessible::Name: {
+ return data["text"].toString();
+ }
+ case QAccessible::Description: {
+ QString description;
+
+ if (data["isHidden"].toBool()) {
+ description += i18nc("@info", "hidden");
+ }
+
+ QString mimeType{data["type"].toString()};
+ if (mimeType.isEmpty()) {
+ const KFileItemModel *model = qobject_cast<KFileItemModel *>(m_view->model());
+ if (model) {
+ mimeType = model->fileItem(m_index).mimeComment();
+ }
+ Q_ASSERT_X(!mimeType.isEmpty(), "KItemListDelegateAccessible::text", "Unable to retrieve mime type.");
+ }
+
+ if (data["isLink"].toBool()) {
+ QString linkDestination{data["destination"].toString()};
+ if (linkDestination.isEmpty()) {
+ const KFileItemModel *model = qobject_cast<KFileItemModel *>(m_view->model());
+ if (model) {
+ linkDestination = model->fileItem(m_index).linkDest();
+ }
+ Q_ASSERT_X(!linkDestination.isEmpty(), "KItemListDelegateAccessible::text", "Unable to retrieve link destination.");
+ }
+
+ description += i18nc("@info enumeration saying this is a link to $1, %1 is mimeType", ", link to %1 at %2", mimeType, linkDestination);
+ } else {
+ description += i18nc("@info enumeration, %1 is mimeType", ", %1", mimeType);
+ }
+ const QList<QByteArray> additionallyShownInformation{m_view->visibleRoles()};
+ const KItemModelBase *model = m_view->model();
+ for (const auto &roleInformation : additionallyShownInformation) {
+ if (roleInformation == "text") {
+ continue;
+ }
+ KFileItemListWidgetInformant informant;
+ const auto roleText{informant.roleText(roleInformation, data, KFileItemListWidgetInformant::ForUsageAs::SpokenText)};
+ if (roleText.isEmpty()) {
+ continue; // No need to announce roles which are empty for this item.
+ }
+ description +=
+ // i18n: The text starts with a comma because multiple occurences of this text can follow after each others as an enumeration.
+ // Normally it would make sense to have a colon between property and value to make the relation between the property and its property value
+ // clear, however this is accessible text that will be read out by screen readers. That's why there is only a space between the two here,
+ // because screen readers would read the colon literally as "colon", which is just a waste of time for users who might go through a list of
+ // hundreds of items. So, if you want to add any more punctation there to improve structure, try to make sure that it will not lead to annoying
+ // announcements when read out by a screen reader.
+ i18nc("@info accessibility enumeration, %1 is property, %2 is value", ", %1 %2", model->roleDescription(roleInformation), roleText);
+ }
+ return description;
+ }
+ default:
+ break;
+ }
+
+ return QString();
+}
+
+void KItemListDelegateAccessible::setText(QAccessible::Text, const QString &)
+{
+}
+
+QAccessibleInterface *KItemListDelegateAccessible::child(int) const
+{
+ return nullptr;
+}
+
+bool KItemListDelegateAccessible::isValid() const
+{
+ return m_view && (m_index >= 0) && (m_index < m_view->model()->count());
+}
+
+QAccessibleInterface *KItemListDelegateAccessible::childAt(int, int) const
+{
+ return nullptr;
+}
+
+int KItemListDelegateAccessible::childCount() const
+{
+ return 0;
+}
+
+int KItemListDelegateAccessible::indexOfChild(const QAccessibleInterface *child) const
+{
+ Q_UNUSED(child)
+ return -1;
+}
+
+QAccessibleInterface *KItemListDelegateAccessible::parent() const
+{
+ return QAccessible::queryAccessibleInterface(m_view);
+}
+
+int KItemListDelegateAccessible::index() const
+{
+ return m_index;
+}
+
+QObject *KItemListDelegateAccessible::object() const
+{
+ return nullptr;
+}
--- /dev/null
+/*
+ * SPDX-FileCopyrightText: 2012 Amandeep Singh <aman.dedman@gmail.com>
+ * SPDX-FileCopyrightText: 2024 Felix Ernst <felixernst@kde.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef KITEMLISTDELEGATEACCESSIBLE_H
+#define KITEMLISTDELEGATEACCESSIBLE_H
+
+#include "dolphin_export.h"
+
+#include <QAccessibleInterface>
+#include <QAccessibleTableCellInterface>
+#include <QPointer>
+
+class KItemListView;
+
+/**
+ * The accessibility class that represents singular files or folders in the main view.
+ */
+class DOLPHIN_EXPORT KItemListDelegateAccessible : public QAccessibleInterface, public QAccessibleTableCellInterface
+{
+public:
+ KItemListDelegateAccessible(KItemListView *view, int m_index);
+
+ void *interface_cast(QAccessible::InterfaceType type) override;
+ QObject *object() const override;
+ bool isValid() const override;
+ QAccessible::Role role() const override;
+ QAccessible::State state() const override;
+ QRect rect() const override;
+ QString text(QAccessible::Text t) const override;
+ void setText(QAccessible::Text t, const QString &text) override;
+
+ QAccessibleInterface *child(int index) const override;
+ int childCount() const override;
+ QAccessibleInterface *childAt(int x, int y) const override;
+ int indexOfChild(const QAccessibleInterface *) const override;
+
+ QAccessibleInterface *parent() const override;
+ bool isExpandable() const;
+
+ // Cell Interface
+ int columnExtent() const override;
+ QList<QAccessibleInterface *> columnHeaderCells() const override;
+ int columnIndex() const override;
+ int rowExtent() const override;
+ QList<QAccessibleInterface *> rowHeaderCells() const override;
+ int rowIndex() const override;
+ bool isSelected() const override;
+ QAccessibleInterface *table() const override;
+
+ int index() const;
+
+private:
+ QPointer<KItemListView> m_view;
+ int m_index;
+};
+
+#endif // KITEMLISTDELEGATEACCESSIBLE_H
--- /dev/null
+/*
+ * SPDX-FileCopyrightText: 2012 Amandeep Singh <aman.dedman@gmail.com>
+ * SPDX-FileCopyrightText: 2024 Felix Ernst <felixernst@kde.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "kitemlistviewaccessible.h"
+#include "kitemlistcontaineraccessible.h"
+#include "kitemlistdelegateaccessible.h"
+
+#include "kitemviews/kitemlistcontainer.h"
+#include "kitemviews/kitemlistcontroller.h"
+#include "kitemviews/kitemlistselectionmanager.h"
+#include "kitemviews/kitemlistview.h"
+#include "kitemviews/kitemmodelbase.h"
+#include "kitemviews/kstandarditemlistview.h"
+#include "kitemviews/private/kitemlistviewlayouter.h"
+
+#include <KLocalizedString>
+
+#include <QApplication> // for figuring out if we should move focus to this view.
+#include <QGraphicsScene>
+#include <QGraphicsView>
+
+KItemListSelectionManager *KItemListViewAccessible::selectionManager() const
+{
+ return view()->controller()->selectionManager();
+}
+
+KItemListViewAccessible::KItemListViewAccessible(KItemListView *view_, KItemListContainerAccessible *parent)
+ : QAccessibleObject(view_)
+ , m_parent(parent)
+{
+ Q_ASSERT(view());
+ Q_CHECK_PTR(parent);
+ m_accessibleDelegates.resize(childCount());
+
+ m_announceDescriptionChangeTimer = new QTimer{view_};
+ m_announceDescriptionChangeTimer->setSingleShot(true);
+ m_announceDescriptionChangeTimer->setInterval(100);
+ KItemListGroupHeader::connect(m_announceDescriptionChangeTimer, &QTimer::timeout, view_, [this]() {
+ // The below will have no effect if one of the list items has focus and not the view itself. Still we announce the accessibility description change
+ // here in case the view itself has focus e.g. after tabbing there or after opening a new location.
+ QAccessibleEvent announceAccessibleDescriptionEvent(this, QAccessible::DescriptionChanged);
+ QAccessible::updateAccessibility(&announceAccessibleDescriptionEvent);
+ });
+}
+
+KItemListViewAccessible::~KItemListViewAccessible()
+{
+ for (AccessibleIdWrapper idWrapper : std::as_const(m_accessibleDelegates)) {
+ if (idWrapper.isValid) {
+ QAccessible::deleteAccessibleInterface(idWrapper.id);
+ }
+ }
+}
+
+void *KItemListViewAccessible::interface_cast(QAccessible::InterfaceType type)
+{
+ switch (type) {
+ case QAccessible::SelectionInterface:
+ return static_cast<QAccessibleSelectionInterface *>(this);
+ case QAccessible::TableInterface:
+ return static_cast<QAccessibleTableInterface *>(this);
+ case QAccessible::ActionInterface:
+ return static_cast<QAccessibleActionInterface *>(this);
+ default:
+ return nullptr;
+ }
+}
+
+void KItemListViewAccessible::modelReset()
+{
+}
+
+QAccessibleInterface *KItemListViewAccessible::accessibleDelegate(int index) const
+{
+ if (index < 0 || index >= view()->model()->count()) {
+ return nullptr;
+ }
+
+ if (m_accessibleDelegates.size() <= index) {
+ m_accessibleDelegates.resize(childCount());
+ }
+ Q_ASSERT(index < m_accessibleDelegates.size());
+
+ AccessibleIdWrapper idWrapper = m_accessibleDelegates.at(index);
+ if (!idWrapper.isValid) {
+ idWrapper.id = QAccessible::registerAccessibleInterface(new KItemListDelegateAccessible(view(), index));
+ idWrapper.isValid = true;
+ m_accessibleDelegates.insert(index, idWrapper);
+ }
+ return QAccessible::accessibleInterface(idWrapper.id);
+}
+
+QAccessibleInterface *KItemListViewAccessible::cellAt(int row, int column) const
+{
+ return accessibleDelegate(columnCount() * row + column);
+}
+
+QAccessibleInterface *KItemListViewAccessible::caption() const
+{
+ return nullptr;
+}
+
+QString KItemListViewAccessible::columnDescription(int) const
+{
+ return QString();
+}
+
+int KItemListViewAccessible::columnCount() const
+{
+ return view()->m_layouter->columnCount();
+}
+
+int KItemListViewAccessible::rowCount() const
+{
+ if (columnCount() <= 0) {
+ return 0;
+ }
+
+ int itemCount = view()->model()->count();
+ int rowCount = itemCount / columnCount();
+
+ if (rowCount <= 0) {
+ return 0;
+ }
+
+ if (itemCount % columnCount()) {
+ ++rowCount;
+ }
+ return rowCount;
+}
+
+int KItemListViewAccessible::selectedCellCount() const
+{
+ return selectionManager()->selectedItems().count();
+}
+
+int KItemListViewAccessible::selectedColumnCount() const
+{
+ return 0;
+}
+
+int KItemListViewAccessible::selectedRowCount() const
+{
+ return 0;
+}
+
+QString KItemListViewAccessible::rowDescription(int) const
+{
+ return QString();
+}
+
+QList<QAccessibleInterface *> KItemListViewAccessible::selectedCells() const
+{
+ QList<QAccessibleInterface *> cells;
+ const auto items = selectionManager()->selectedItems();
+ cells.reserve(items.count());
+ for (int index : items) {
+ cells.append(accessibleDelegate(index));
+ }
+ return cells;
+}
+
+QList<int> KItemListViewAccessible::selectedColumns() const
+{
+ return QList<int>();
+}
+
+QList<int> KItemListViewAccessible::selectedRows() const
+{
+ return QList<int>();
+}
+
+QAccessibleInterface *KItemListViewAccessible::summary() const
+{
+ return nullptr;
+}
+
+bool KItemListViewAccessible::isColumnSelected(int) const
+{
+ return false;
+}
+
+bool KItemListViewAccessible::isRowSelected(int) const
+{
+ return false;
+}
+
+bool KItemListViewAccessible::selectRow(int)
+{
+ return true;
+}
+
+bool KItemListViewAccessible::selectColumn(int)
+{
+ return true;
+}
+
+bool KItemListViewAccessible::unselectRow(int)
+{
+ return true;
+}
+
+bool KItemListViewAccessible::unselectColumn(int)
+{
+ return true;
+}
+
+void KItemListViewAccessible::modelChange(QAccessibleTableModelChangeEvent * /*event*/)
+{
+}
+
+QAccessible::Role KItemListViewAccessible::role() const
+{
+ return QAccessible::List;
+}
+
+QAccessible::State KItemListViewAccessible::state() const
+{
+ QAccessible::State s;
+ s.focusable = true;
+ s.active = true;
+ const KItemListController *controller = view()->m_controller;
+ s.multiSelectable = controller->selectionBehavior() == KItemListController::MultiSelection;
+ s.focused = !childCount() && (view()->hasFocus() || m_parent->container()->hasFocus()); // Usually the children have focus.
+ return s;
+}
+
+QAccessibleInterface *KItemListViewAccessible::childAt(int x, int y) const
+{
+ const QPointF point = QPointF(x, y);
+ const std::optional<int> itemIndex = view()->itemAt(view()->mapFromScene(point));
+ return child(itemIndex.value_or(-1));
+}
+
+QAccessibleInterface *KItemListViewAccessible::parent() const
+{
+ return m_parent;
+}
+
+int KItemListViewAccessible::childCount() const
+{
+ return view()->model()->count();
+}
+
+int KItemListViewAccessible::indexOfChild(const QAccessibleInterface *interface) const
+{
+ const KItemListDelegateAccessible *widget = static_cast<const KItemListDelegateAccessible *>(interface);
+ return widget->index();
+}
+
+QString KItemListViewAccessible::text(QAccessible::Text t) const
+{
+ const KItemListController *controller = view()->m_controller;
+ const KItemModelBase *model = controller->model();
+ const QUrl modelRootUrl = model->directory();
+ if (t == QAccessible::Name) {
+ return modelRootUrl.fileName();
+ }
+ if (t != QAccessible::Description) {
+ return QString();
+ }
+ const auto currentItem = child(controller->selectionManager()->currentItem());
+ if (!currentItem) {
+ return i18nc("@info 1 states that the folder is empty and sometimes why, 2 is the full filesystem path",
+ "%1 at location %2",
+ m_placeholderMessage,
+ modelRootUrl.toDisplayString());
+ }
+
+ const QString selectionStateString{isSelected(currentItem) ? QString()
+ // i18n: There is a comma at the end because this is one property in an enumeration of
+ // properties that a file or folder has. Accessible text for accessibility software like screen
+ // readers.
+ : i18n("not selected,")};
+
+ QString expandableStateString;
+ if (currentItem->state().expandable) {
+ if (currentItem->state().collapsed) {
+ // i18n: There is a comma at the end because this is one property in an enumeration of properties that a folder in a tree view has.
+ // Accessible text for accessibility software like screen readers.
+ expandableStateString = i18n("collapsed,");
+ } else {
+ // i18n: There is a comma at the end because this is one property in an enumeration of properties that a folder in a tree view has.
+ // Accessible text for accessibility software like screen readers.
+ expandableStateString = i18n("expanded,");
+ }
+ }
+
+ const QString selectedItemCountString{selectedItemCount() > 1
+ // i18n: There is a "—" at the beginning because this is a followup sentence to a text that did not properly end
+ // with a period. Accessible text for accessibility software like screen readers.
+ ? i18np("— %1 selected item", "— %1 selected items", selectedItemCount())
+ : QString()};
+
+ // Determine if we should announce the item layout. For end users of the accessibility tree there is an expectation that a list can be scrolled through by
+ // pressing the "Down" key repeatedly. This is not the case in the icon view mode, where pressing "Right" or "Left" moves through the whole list of items.
+ // Therefore we need to announce this layout when in icon view mode.
+ QString layoutAnnouncementString;
+ if (auto standardView = qobject_cast<const KStandardItemListView *>(view())) {
+ if (standardView->itemLayout() == KStandardItemListView::ItemLayout::IconsLayout) {
+ layoutAnnouncementString = i18nc("@info refering to a file or folder", "in a grid layout");
+ }
+ }
+
+ /**
+ * Announce it in this order so the most important information is at the beginning and the potentially very long path at the end:
+ * "$currentlyFocussedItemName, $currentlyFocussedItemDescription, $currentFolderPath".
+ * We do not need to announce the total count of items here because accessibility software like Orca alrady announces this automatically for lists.
+ * Normally for list items the selection and expandadable state are also automatically announced by Orca, however we are building the accessible
+ * description of the view here, so we need to manually add all infomation about the current item we also want to announce.
+ */
+ return i18nc(
+ "@info 1 is currentlyFocussedItemName, 2 is empty or \"not selected, \", 3 is currentlyFocussedItemDescription, 3 is currentFolderName, 4 is "
+ "currentFolderPath",
+ "%1, %2 %3 %4 %5 %6 in location %7",
+ currentItem->text(QAccessible::Name),
+ selectionStateString,
+ expandableStateString,
+ currentItem->text(QAccessible::Description),
+ selectedItemCountString,
+ layoutAnnouncementString,
+ modelRootUrl.toDisplayString());
+}
+
+QRect KItemListViewAccessible::rect() const
+{
+ if (!view()->isVisible()) {
+ return QRect();
+ }
+
+ const QGraphicsScene *scene = view()->scene();
+ if (scene) {
+ const QPoint origin = scene->views().at(0)->mapToGlobal(QPoint(0, 0));
+ const QRect viewRect = view()->geometry().toRect();
+ return viewRect.translated(origin);
+ } else {
+ return QRect();
+ }
+}
+
+QAccessibleInterface *KItemListViewAccessible::child(int index) const
+{
+ if (index >= 0 && index < childCount()) {
+ return accessibleDelegate(index);
+ }
+ return nullptr;
+}
+
+KItemListViewAccessible::AccessibleIdWrapper::AccessibleIdWrapper()
+ : isValid(false)
+ , id(0)
+{
+}
+
+/* Selection interface */
+
+bool KItemListViewAccessible::clear()
+{
+ selectionManager()->clearSelection();
+ return true;
+}
+
+bool KItemListViewAccessible::isSelected(QAccessibleInterface *childItem) const
+{
+ Q_CHECK_PTR(childItem);
+ return static_cast<KItemListDelegateAccessible *>(childItem)->isSelected();
+}
+
+bool KItemListViewAccessible::select(QAccessibleInterface *childItem)
+{
+ selectionManager()->setSelected(indexOfChild(childItem));
+ return true;
+}
+
+bool KItemListViewAccessible::selectAll()
+{
+ selectionManager()->setSelected(0, childCount());
+ return true;
+}
+
+QAccessibleInterface *KItemListViewAccessible::selectedItem(int selectionIndex) const
+{
+ const auto selectedItems = selectionManager()->selectedItems();
+ int i = 0;
+ for (auto it = selectedItems.rbegin(); it != selectedItems.rend(); ++it) {
+ if (i == selectionIndex) {
+ return child(*it);
+ }
+ }
+ return nullptr;
+}
+
+int KItemListViewAccessible::selectedItemCount() const
+{
+ return selectionManager()->selectedItems().count();
+}
+
+QList<QAccessibleInterface *> KItemListViewAccessible::selectedItems() const
+{
+ const auto selectedItems = selectionManager()->selectedItems();
+ QList<QAccessibleInterface *> selectedItemsInterfaces;
+ for (auto it = selectedItems.rbegin(); it != selectedItems.rend(); ++it) {
+ selectedItemsInterfaces.append(child(*it));
+ }
+ return selectedItemsInterfaces;
+}
+
+bool KItemListViewAccessible::unselect(QAccessibleInterface *childItem)
+{
+ selectionManager()->setSelected(indexOfChild(childItem), 1, KItemListSelectionManager::Deselect);
+ return true;
+}
+
+/* Action Interface */
+
+QStringList KItemListViewAccessible::actionNames() const
+{
+ return {setFocusAction()};
+}
+
+void KItemListViewAccessible::doAction(const QString &actionName)
+{
+ if (actionName == setFocusAction()) {
+ view()->setFocus();
+ }
+}
+
+QStringList KItemListViewAccessible::keyBindingsForAction(const QString &actionName) const
+{
+ Q_UNUSED(actionName)
+ return {};
+}
+
+/* Custom non-interface methods */
+
+KItemListView *KItemListViewAccessible::view() const
+{
+ Q_CHECK_PTR(qobject_cast<KItemListView *>(object()));
+ return static_cast<KItemListView *>(object());
+}
+
+void KItemListViewAccessible::announceOverallViewState(const QString &placeholderMessage)
+{
+ m_placeholderMessage = placeholderMessage;
+
+ // Make sure we announce this placeholderMessage. However, do not announce it when the focus is on an unrelated object currently.
+ // We for example do not want to announce "Loading cancelled" when the focus is currently on an error message explaining why the loading was cancelled.
+ if (view()->hasFocus() || !QApplication::focusWidget() || static_cast<QWidget *>(m_parent->object())->isAncestorOf(QApplication::focusWidget())) {
+ view()->setFocus();
+ // If we move focus to an item and right after that the description of the item is changed, the item will be announced twice.
+ // We want to avoid that so we wait until after the description change was announced to move focus.
+ KItemListGroupHeader::connect(
+ m_announceDescriptionChangeTimer,
+ &QTimer::timeout,
+ view(),
+ [this]() {
+ if (view()->hasFocus() || !QApplication::focusWidget()
+ || static_cast<QWidget *>(m_parent->object())->isAncestorOf(QApplication::focusWidget())) {
+ QAccessibleEvent accessibleFocusEvent(this, QAccessible::Focus);
+ QAccessible::updateAccessibility(&accessibleFocusEvent); // This accessibility update is perhaps even too important: It is generally
+ // the last triggered update after changing the currently viewed folder. This call makes sure that we announce the new directory in
+ // full. Furthermore it also serves its original purpose of making sure we announce the placeholderMessage in empty folders.
+ }
+ },
+ Qt::SingleShotConnection);
+ if (!m_announceDescriptionChangeTimer->isActive()) {
+ m_announceDescriptionChangeTimer->start();
+ }
+ }
+}
+
+void KItemListViewAccessible::announceDescriptionChange()
+{
+ m_announceDescriptionChangeTimer->start();
+}
--- /dev/null
+/*
+ * SPDX-FileCopyrightText: 2012 Amandeep Singh <aman.dedman@gmail.com>
+ * SPDX-FileCopyrightText: 2024 Felix Ernst <felixernst@kde.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef KITEMLISTVIEWACCESSIBLE_H
+#define KITEMLISTVIEWACCESSIBLE_H
+
+#include "dolphin_export.h"
+
+#include <QAccessible>
+#include <QAccessibleObject>
+#include <QAccessibleWidget>
+#include <QPointer>
+
+class KItemListView;
+class KItemListContainer;
+class KItemListContainerAccessible;
+class KItemListSelectionManager;
+
+/**
+ * The main class for making the main view accessible.
+ *
+ * Such a class is necessary because the KItemListView is a mostly custom entity. This class provides a lot of the functionality to make it possible to
+ * interact with the view using accessibility tools. It implements various interfaces mostly to generally allow working with the view as a whole. However,
+ * actually interacting with singular items within the view is implemented in KItemListDelegateAccessible.
+ *
+ * @note For documentation of most of the methods within this class, check out the documentation of the methods which are being overriden here.
+ */
+class DOLPHIN_EXPORT KItemListViewAccessible : public QAccessibleObject,
+ public QAccessibleSelectionInterface,
+ public QAccessibleTableInterface,
+ public QAccessibleActionInterface
+{
+public:
+ explicit KItemListViewAccessible(KItemListView *view, KItemListContainerAccessible *parent);
+ ~KItemListViewAccessible() override;
+
+ // QAccessibleObject
+ void *interface_cast(QAccessible::InterfaceType type) override;
+
+ QAccessible::Role role() const override;
+ QAccessible::State state() const override;
+ QString text(QAccessible::Text t) const override;
+ QRect rect() const override;
+
+ QAccessibleInterface *child(int index) const override;
+ int childCount() const override;
+ int indexOfChild(const QAccessibleInterface *) const override;
+ QAccessibleInterface *childAt(int x, int y) const override;
+ QAccessibleInterface *parent() const override;
+
+ // Table interface
+ QAccessibleInterface *cellAt(int row, int column) const override;
+ QAccessibleInterface *caption() const override;
+ QAccessibleInterface *summary() const override;
+ QString columnDescription(int column) const override;
+ QString rowDescription(int row) const override;
+ int columnCount() const override;
+ int rowCount() const override;
+
+ // Selection
+ int selectedCellCount() const override;
+ int selectedColumnCount() const override;
+ int selectedRowCount() const override;
+ QList<QAccessibleInterface *> selectedCells() const override;
+ QList<int> selectedColumns() const override;
+ QList<int> selectedRows() const override;
+ bool isColumnSelected(int column) const override;
+ bool isRowSelected(int row) const override;
+ bool selectRow(int row) override;
+ bool selectColumn(int column) override;
+ bool unselectRow(int row) override;
+ bool unselectColumn(int column) override;
+ void modelChange(QAccessibleTableModelChangeEvent *) override;
+
+ // Selection interface
+ /** Clear selection */
+ bool clear() override;
+ bool isSelected(QAccessibleInterface *childItem) const override;
+ bool select(QAccessibleInterface *childItem) override;
+ bool selectAll() override;
+ QAccessibleInterface *selectedItem(int selectionIndex) const override;
+ int selectedItemCount() const override;
+ QList<QAccessibleInterface *> selectedItems() const override;
+ bool unselect(QAccessibleInterface *childItem) override;
+
+ // Action interface
+ QStringList actionNames() const override;
+ void doAction(const QString &actionName) override;
+ QStringList keyBindingsForAction(const QString &actionName) const override;
+
+ // Custom non-interface methods
+ KItemListView *view() const;
+
+ /**
+ * Moves the focus to the list view itself so an overview over the state can be given.
+ * @param placeholderMessage the message that should be announced when no items are visible (yet). This message is mostly identical to
+ * DolphinView::m_placeholderLabel in both content and purpose. @see DolphinView::updatePlaceHolderLabel().
+ */
+ void announceOverallViewState(const QString &placeholderMessage);
+
+ /**
+ * Announces that the description of the view has changed. The changed description will only be announced if the view has focus (from an accessibility
+ * point of view). This method ensures that multiple calls to this method within a small time frame will only lead to a singular announcement instead of
+ * multiple or already outdated ones, so calling this method instead of manually sending accessibility events for this view is preferred.
+ */
+ void announceDescriptionChange();
+
+protected:
+ virtual void modelReset();
+ /**
+ * @returns a KItemListDelegateAccessible representing the file or folder at the @index. Returns nullptr for invalid indices.
+ * If a KItemListDelegateAccessible for an index does not yet exist, it will be created.
+ * Index is 0-based.
+ */
+ inline QAccessibleInterface *accessibleDelegate(int index) const;
+
+ KItemListSelectionManager *selectionManager() const;
+
+private:
+ /** @see setPlaceholderMessage(). */
+ QString m_placeholderMessage;
+
+ QTimer *m_announceDescriptionChangeTimer;
+
+ class AccessibleIdWrapper
+ {
+ public:
+ AccessibleIdWrapper();
+ bool isValid;
+ QAccessible::Id id;
+ };
+ /**
+ * A list that maps the indices of the children of this KItemListViewAccessible to the accessible ids of the matching KItemListDelegateAccessible objects.
+ * For example: m_accessibleDelegates.at(2) would be the AccessibleIdWrapper with an id which can be used to retrieve the QAccessibleObject that represents
+ * the third file in this view.
+ */
+ mutable QVector<AccessibleIdWrapper> m_accessibleDelegates;
+
+ KItemListContainerAccessible *m_parent;
+};
+
+#endif
return item.isLink();
}
-QString KFileItemListWidgetInformant::roleText(const QByteArray &role, const QHash<QByteArray, QVariant> &values) const
+QString KFileItemListWidgetInformant::roleText(const QByteArray &role, const QHash<QByteArray, QVariant> &values, ForUsageAs forUsageAs) const
{
QString text;
const QVariant roleValue = values.value(role);
// Implementation note: In case if more roles require a custom handling
// use a hash + switch for a linear runtime.
- auto formatDate = [formatter, local](const QDateTime &time) {
+ auto formatDate = [formatter, local, forUsageAs](const QDateTime &time) {
if (ContentDisplaySettings::useShortRelativeDates()) {
- return formatter.formatRelativeDateTime(time, QLocale::ShortFormat);
+ return formatter.formatRelativeDateTime(time,
+ forUsageAs == KStandardItemListWidgetInformant::ForUsageAs::DisplayedText ? QLocale::ShortFormat
+ : QLocale::LongFormat);
} else {
- return local.toString(time, QLocale::ShortFormat);
+ return local.toString(time, forUsageAs == KStandardItemListWidgetInformant::ForUsageAs::DisplayedText ? QLocale::ShortFormat : QLocale::LongFormat);
}
};
break;
}
} else {
- text = KStandardItemListWidgetInformant::roleText(role, values);
+ text = KStandardItemListWidgetInformant::roleText(role, values, forUsageAs);
}
return text;
protected:
QString itemText(int index, const KItemListView *view) const override;
bool itemIsLink(int index, const KItemListView *view) const override;
- QString roleText(const QByteArray &role, const QHash<QByteArray, QVariant> &values) const override;
+ /** @see KStandardItemListWidget::roleText(). */
+ QString roleText(const QByteArray &role, const QHash<QByteArray, QVariant> &values, ForUsageAs forUsageAs = ForUsageAs::DisplayedText) const override;
QFont customizedFontForLinks(const QFont &baseFont) const override;
+
+ friend class KItemListDelegateAccessible;
};
/**
#include <KLocalizedString>
#include <KUrlMimeData>
+#ifndef QT_NO_ACCESSIBILITY
+#include <QAccessible>
+#endif
#include <QElapsedTimer>
#include <QIcon>
#include <QMimeData>
#include "kitemlistview.h"
#include "private/kitemlistsmoothscroller.h"
+#ifndef QT_NO_ACCESSIBILITY
+#include <QAccessibleEvent>
+#endif
#include <QApplication>
#include <QFontMetrics>
#include <QGraphicsScene>
KItemListView *view = m_controller->view();
if (view) {
QApplication::sendEvent(view, event);
+
+ // We need to set the focus to the view or accessibility software will only announce the container (which has no information available itself).
+ // For some reason actively setting the focus to the view needs to be delayed or the focus will immediately go back to this container.
+ QTimer::singleShot(0, this, [this, view]() {
+ view->setFocus();
+#ifndef QT_NO_ACCESSIBILITY
+ QAccessibleEvent accessibleFocusInEvent(this, QAccessible::Focus);
+ accessibleFocusInEvent.setChild(0);
+ QAccessible::updateAccessibility(&accessibleFocusInEvent);
+#endif
+ });
}
}
case Qt::Key_Space:
if (m_selectionBehavior == MultiSelection) {
+#ifndef QT_NO_ACCESSIBILITY
+ // Move accessible focus to the item that is acted upon, so only the state change of this item is announced and not the whole view.
+ QAccessibleEvent accessibilityEvent(view(), QAccessible::Focus);
+ accessibilityEvent.setChild(index);
+ QAccessible::updateAccessibility(&accessibilityEvent);
+#endif
if (controlPressed) {
// Toggle the selection state of the current item.
m_selectionManager->endAnchoredSelection();
#include "kitemlistview.h"
+#ifndef QT_NO_ACCESSIBILITY
+#include "accessibility/kitemlistcontaineraccessible.h"
+#include "accessibility/kitemlistdelegateaccessible.h"
+#include "accessibility/kitemlistviewaccessible.h"
+#endif
#include "dolphindebug.h"
#include "kitemlistcontainer.h"
#include "kitemlistcontroller.h"
#include "kitemlistheader.h"
#include "kitemlistselectionmanager.h"
-#include "kitemlistviewaccessible.h"
#include "kstandarditemlistwidget.h"
#include "private/kitemlistheaderwidget.h"
if (useAlternateBackgrounds()) {
updateAlternateBackgrounds();
}
+#ifndef QT_NO_ACCESSIBILITY
+ if (QAccessible::isActive()) { // Announce that the count of items has changed.
+ static_cast<KItemListViewAccessible *>(QAccessible::queryAccessibleInterface(this))->announceDescriptionChange();
+ }
+#endif
}
void KItemListView::slotItemsRemoved(const KItemRangeList &itemRanges)
if (useAlternateBackgrounds()) {
updateAlternateBackgrounds();
}
+#ifndef QT_NO_ACCESSIBILITY
+ if (QAccessible::isActive()) { // Announce that the count of items has changed.
+ static_cast<KItemListViewAccessible *>(QAccessible::queryAccessibleInterface(this))->announceDescriptionChange();
+ }
+#endif
}
void KItemListView::slotItemsMoved(const KItemRange &itemRange, const QList<int> &movedToIndexes)
doLayout(NoAnimation);
}
+#ifndef QT_NO_ACCESSIBILITY
QAccessibleTableModelChangeEvent ev(this, QAccessibleTableModelChangeEvent::DataChanged);
ev.setFirstRow(itemRange.index);
ev.setLastRow(itemRange.index + itemRange.count);
QAccessible::updateAccessibility(&ev);
+#endif
}
doLayout(NoAnimation);
void KItemListView::slotCurrentChanged(int current, int previous)
{
- Q_UNUSED(previous)
-
// In SingleSelection mode (e.g., in the Places Panel), the current item is
// always the selected item. It is not necessary to highlight the current item then.
if (m_controller->selectionBehavior() != KItemListController::SingleSelection) {
KItemListWidget *currentWidget = m_visibleItems.value(current, nullptr);
if (currentWidget) {
currentWidget->setCurrent(true);
+ if (hasFocus() || (previousWidget && previousWidget->hasFocus())) {
+ currentWidget->setFocus(); // Mostly for accessibility, because keyboard events are handled correctly either way.
+ }
}
}
-
- QAccessibleEvent ev(this, QAccessible::Focus);
- ev.setChild(current);
- QAccessible::updateAccessibility(&ev);
+#ifndef QT_NO_ACCESSIBILITY
+ if (QAccessible::isActive()) {
+ if (current >= 0) {
+ QAccessibleEvent accessibleFocusCurrentItemEvent(this, QAccessible::Focus);
+ accessibleFocusCurrentItemEvent.setChild(current);
+ QAccessible::updateAccessibility(&accessibleFocusCurrentItemEvent);
+ }
+ static_cast<KItemListViewAccessible *>(QAccessible::queryAccessibleInterface(this))->announceDescriptionChange();
+ }
+#endif
}
void KItemListView::slotSelectionChanged(const KItemSet ¤t, const KItemSet &previous)
{
- Q_UNUSED(previous)
-
QHashIterator<int, KItemListWidget *> it(m_visibleItems);
while (it.hasNext()) {
it.next();
const int index = it.key();
KItemListWidget *widget = it.value();
- widget->setSelected(current.contains(index));
+ const bool isSelected(current.contains(index));
+ widget->setSelected(isSelected);
+
+#ifndef QT_NO_ACCESSIBILITY
+ if (!QAccessible::isActive()) {
+ continue;
+ }
+ // Let the screen reader announce "selected" or "not selected" for the active item.
+ const bool wasSelected(previous.contains(index));
+ if (isSelected != wasSelected) {
+ QAccessibleEvent accessibleSelectionChangedEvent(this, QAccessible::Selection);
+ accessibleSelectionChangedEvent.setChild(index);
+ QAccessible::updateAccessibility(&accessibleSelectionChangedEvent);
+ }
}
+ // Usually the below does not have an effect because the view will not have focus at this moment but one of its list items. Still we announce the
+ // change of the accessibility description just in case the user manually moved focus up by one.
+ static_cast<KItemListViewAccessible *>(QAccessible::queryAccessibleInterface(this))->announceDescriptionChange();
+#else
+ }
+ Q_UNUSED(previous)
+#endif
}
void KItemListView::slotAnimationFinished(QGraphicsWidget *widget, KItemListViewAnimation::AnimationType type)
friend class KItemListController;
friend class KItemListControllerTest;
friend class KItemListViewAccessible;
- friend class KItemListAccessibleCell;
+ friend class KItemListDelegateAccessible;
};
/**
+++ /dev/null
-/*
- * SPDX-FileCopyrightText: 2012 Amandeep Singh <aman.dedman@gmail.com>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#ifndef QT_NO_ACCESSIBILITY
-#include "kitemlistviewaccessible.h"
-
-#include "kitemlistcontainer.h"
-#include "kitemlistcontroller.h"
-#include "kitemlistselectionmanager.h"
-#include "kitemlistview.h"
-#include "private/kitemlistviewlayouter.h"
-
-#include <QGraphicsScene>
-#include <QGraphicsView>
-
-KItemListView *KItemListViewAccessible::view() const
-{
- return qobject_cast<KItemListView *>(object());
-}
-
-KItemListViewAccessible::KItemListViewAccessible(KItemListView *view_, KItemListContainerAccessible *parent)
- : QAccessibleObject(view_)
- , m_parent(parent)
-{
- Q_ASSERT(view());
- Q_CHECK_PTR(parent);
- m_cells.resize(childCount());
-}
-
-KItemListViewAccessible::~KItemListViewAccessible()
-{
- for (AccessibleIdWrapper idWrapper : std::as_const(m_cells)) {
- if (idWrapper.isValid) {
- QAccessible::deleteAccessibleInterface(idWrapper.id);
- }
- }
-}
-
-void *KItemListViewAccessible::interface_cast(QAccessible::InterfaceType type)
-{
- if (type == QAccessible::TableInterface) {
- return static_cast<QAccessibleTableInterface *>(this);
- }
- return nullptr;
-}
-
-void KItemListViewAccessible::modelReset()
-{
-}
-
-QAccessibleInterface *KItemListViewAccessible::cell(int index) const
-{
- if (index < 0 || index >= view()->model()->count()) {
- return nullptr;
- }
-
- if (m_cells.size() <= index) {
- m_cells.resize(childCount());
- }
- Q_ASSERT(index < m_cells.size());
-
- AccessibleIdWrapper idWrapper = m_cells.at(index);
- if (!idWrapper.isValid) {
- idWrapper.id = QAccessible::registerAccessibleInterface(new KItemListAccessibleCell(view(), index));
- idWrapper.isValid = true;
- m_cells.insert(index, idWrapper);
- }
- return QAccessible::accessibleInterface(idWrapper.id);
-}
-
-QAccessibleInterface *KItemListViewAccessible::cellAt(int row, int column) const
-{
- return cell(columnCount() * row + column);
-}
-
-QAccessibleInterface *KItemListViewAccessible::caption() const
-{
- return nullptr;
-}
-
-QString KItemListViewAccessible::columnDescription(int) const
-{
- return QString();
-}
-
-int KItemListViewAccessible::columnCount() const
-{
- return view()->m_layouter->columnCount();
-}
-
-int KItemListViewAccessible::rowCount() const
-{
- if (columnCount() <= 0) {
- return 0;
- }
-
- int itemCount = view()->model()->count();
- int rowCount = itemCount / columnCount();
-
- if (rowCount <= 0) {
- return 0;
- }
-
- if (itemCount % columnCount()) {
- ++rowCount;
- }
- return rowCount;
-}
-
-int KItemListViewAccessible::selectedCellCount() const
-{
- return view()->controller()->selectionManager()->selectedItems().count();
-}
-
-int KItemListViewAccessible::selectedColumnCount() const
-{
- return 0;
-}
-
-int KItemListViewAccessible::selectedRowCount() const
-{
- return 0;
-}
-
-QString KItemListViewAccessible::rowDescription(int) const
-{
- return QString();
-}
-
-QList<QAccessibleInterface *> KItemListViewAccessible::selectedCells() const
-{
- QList<QAccessibleInterface *> cells;
- const auto items = view()->controller()->selectionManager()->selectedItems();
- cells.reserve(items.count());
- for (int index : items) {
- cells.append(cell(index));
- }
- return cells;
-}
-
-QList<int> KItemListViewAccessible::selectedColumns() const
-{
- return QList<int>();
-}
-
-QList<int> KItemListViewAccessible::selectedRows() const
-{
- return QList<int>();
-}
-
-QAccessibleInterface *KItemListViewAccessible::summary() const
-{
- return nullptr;
-}
-
-bool KItemListViewAccessible::isColumnSelected(int) const
-{
- return false;
-}
-
-bool KItemListViewAccessible::isRowSelected(int) const
-{
- return false;
-}
-
-bool KItemListViewAccessible::selectRow(int)
-{
- return true;
-}
-
-bool KItemListViewAccessible::selectColumn(int)
-{
- return true;
-}
-
-bool KItemListViewAccessible::unselectRow(int)
-{
- return true;
-}
-
-bool KItemListViewAccessible::unselectColumn(int)
-{
- return true;
-}
-
-void KItemListViewAccessible::modelChange(QAccessibleTableModelChangeEvent * /*event*/)
-{
-}
-
-QAccessible::Role KItemListViewAccessible::role() const
-{
- return QAccessible::Table;
-}
-
-QAccessible::State KItemListViewAccessible::state() const
-{
- QAccessible::State s;
- return s;
-}
-
-QAccessibleInterface *KItemListViewAccessible::childAt(int x, int y) const
-{
- const QPointF point = QPointF(x, y);
- const std::optional<int> itemIndex = view()->itemAt(view()->mapFromScene(point));
- return child(itemIndex.value_or(-1));
-}
-
-QAccessibleInterface *KItemListViewAccessible::parent() const
-{
- return m_parent;
-}
-
-int KItemListViewAccessible::childCount() const
-{
- return view()->model()->count();
-}
-
-int KItemListViewAccessible::indexOfChild(const QAccessibleInterface *interface) const
-{
- const KItemListAccessibleCell *widget = static_cast<const KItemListAccessibleCell *>(interface);
- return widget->index();
-}
-
-QString KItemListViewAccessible::text(QAccessible::Text) const
-{
- return QString();
-}
-
-QRect KItemListViewAccessible::rect() const
-{
- if (!view()->isVisible()) {
- return QRect();
- }
-
- const QGraphicsScene *scene = view()->scene();
- if (scene) {
- const QPoint origin = scene->views().at(0)->mapToGlobal(QPoint(0, 0));
- const QRect viewRect = view()->geometry().toRect();
- return viewRect.translated(origin);
- } else {
- return QRect();
- }
-}
-
-QAccessibleInterface *KItemListViewAccessible::child(int index) const
-{
- if (index >= 0 && index < childCount()) {
- return cell(index);
- }
- return nullptr;
-}
-
-KItemListViewAccessible::AccessibleIdWrapper::AccessibleIdWrapper()
- : isValid(false)
- , id(0)
-{
-}
-
-// Table Cell
-
-KItemListAccessibleCell::KItemListAccessibleCell(KItemListView *view, int index)
- : m_view(view)
- , m_index(index)
-{
- Q_ASSERT(index >= 0 && index < view->model()->count());
-}
-
-void *KItemListAccessibleCell::interface_cast(QAccessible::InterfaceType type)
-{
- if (type == QAccessible::TableCellInterface) {
- return static_cast<QAccessibleTableCellInterface *>(this);
- }
- return nullptr;
-}
-
-int KItemListAccessibleCell::columnExtent() const
-{
- return 1;
-}
-
-int KItemListAccessibleCell::rowExtent() const
-{
- return 1;
-}
-
-QList<QAccessibleInterface *> KItemListAccessibleCell::rowHeaderCells() const
-{
- return QList<QAccessibleInterface *>();
-}
-
-QList<QAccessibleInterface *> KItemListAccessibleCell::columnHeaderCells() const
-{
- return QList<QAccessibleInterface *>();
-}
-
-int KItemListAccessibleCell::columnIndex() const
-{
- return m_view->m_layouter->itemColumn(m_index);
-}
-
-int KItemListAccessibleCell::rowIndex() const
-{
- return m_view->m_layouter->itemRow(m_index);
-}
-
-bool KItemListAccessibleCell::isSelected() const
-{
- return m_view->controller()->selectionManager()->isSelected(m_index);
-}
-
-QAccessibleInterface *KItemListAccessibleCell::table() const
-{
- return QAccessible::queryAccessibleInterface(m_view);
-}
-
-QAccessible::Role KItemListAccessibleCell::role() const
-{
- return QAccessible::Cell;
-}
-
-QAccessible::State KItemListAccessibleCell::state() const
-{
- QAccessible::State state;
-
- state.selectable = true;
- if (isSelected()) {
- state.selected = true;
- }
-
- state.focusable = true;
- if (m_view->controller()->selectionManager()->currentItem() == m_index) {
- state.focused = true;
- }
-
- if (m_view->controller()->selectionBehavior() == KItemListController::MultiSelection) {
- state.multiSelectable = true;
- }
-
- if (m_view->model()->isExpandable(m_index)) {
- if (m_view->model()->isExpanded(m_index)) {
- state.expanded = true;
- } else {
- state.collapsed = true;
- }
- }
-
- return state;
-}
-
-bool KItemListAccessibleCell::isExpandable() const
-{
- return m_view->model()->isExpandable(m_index);
-}
-
-QRect KItemListAccessibleCell::rect() const
-{
- QRect rect = m_view->itemRect(m_index).toRect();
-
- if (rect.isNull()) {
- return QRect();
- }
-
- rect.translate(m_view->mapToScene(QPointF(0.0, 0.0)).toPoint());
- rect.translate(m_view->scene()->views()[0]->mapToGlobal(QPoint(0, 0)));
- return rect;
-}
-
-QString KItemListAccessibleCell::text(QAccessible::Text t) const
-{
- switch (t) {
- case QAccessible::Name: {
- const QHash<QByteArray, QVariant> data = m_view->model()->data(m_index);
- return data["text"].toString();
- }
-
- default:
- break;
- }
-
- return QString();
-}
-
-void KItemListAccessibleCell::setText(QAccessible::Text, const QString &)
-{
-}
-
-QAccessibleInterface *KItemListAccessibleCell::child(int) const
-{
- return nullptr;
-}
-
-bool KItemListAccessibleCell::isValid() const
-{
- return m_view && (m_index >= 0) && (m_index < m_view->model()->count());
-}
-
-QAccessibleInterface *KItemListAccessibleCell::childAt(int, int) const
-{
- return nullptr;
-}
-
-int KItemListAccessibleCell::childCount() const
-{
- return 0;
-}
-
-int KItemListAccessibleCell::indexOfChild(const QAccessibleInterface *child) const
-{
- Q_UNUSED(child)
- return -1;
-}
-
-QAccessibleInterface *KItemListAccessibleCell::parent() const
-{
- return QAccessible::queryAccessibleInterface(m_view);
-}
-
-int KItemListAccessibleCell::index() const
-{
- return m_index;
-}
-
-QObject *KItemListAccessibleCell::object() const
-{
- return nullptr;
-}
-
-// Container Interface
-KItemListContainerAccessible::KItemListContainerAccessible(KItemListContainer *container)
- : QAccessibleWidget(container)
-{
-}
-
-KItemListContainerAccessible::~KItemListContainerAccessible()
-{
-}
-
-int KItemListContainerAccessible::childCount() const
-{
- return 1;
-}
-
-int KItemListContainerAccessible::indexOfChild(const QAccessibleInterface *child) const
-{
- if (child->object() == container()->controller()->view()) {
- return 0;
- }
- return -1;
-}
-
-QAccessibleInterface *KItemListContainerAccessible::child(int index) const
-{
- if (index == 0) {
- return QAccessible::queryAccessibleInterface(container()->controller()->view());
- }
- return nullptr;
-}
-
-const KItemListContainer *KItemListContainerAccessible::container() const
-{
- return qobject_cast<KItemListContainer *>(object());
-}
-
-#endif // QT_NO_ACCESSIBILITY
+++ /dev/null
-/*
- * SPDX-FileCopyrightText: 2012 Amandeep Singh <aman.dedman@gmail.com>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#ifndef KITEMLISTVIEWACCESSIBLE_H
-#define KITEMLISTVIEWACCESSIBLE_H
-
-#ifndef QT_NO_ACCESSIBILITY
-
-#include "dolphin_export.h"
-
-#include <QAccessible>
-#include <QAccessibleObject>
-#include <QAccessibleWidget>
-#include <QPointer>
-
-class KItemListView;
-class KItemListContainer;
-class KItemListContainerAccessible;
-
-class DOLPHIN_EXPORT KItemListViewAccessible : public QAccessibleObject, public QAccessibleTableInterface
-{
-public:
- explicit KItemListViewAccessible(KItemListView *view, KItemListContainerAccessible *parent);
- ~KItemListViewAccessible() override;
-
- void *interface_cast(QAccessible::InterfaceType type) override;
-
- QAccessible::Role role() const override;
- QAccessible::State state() const override;
- QString text(QAccessible::Text t) const override;
- QRect rect() const override;
-
- QAccessibleInterface *child(int index) const override;
- int childCount() const override;
- int indexOfChild(const QAccessibleInterface *) const override;
- QAccessibleInterface *childAt(int x, int y) const override;
- QAccessibleInterface *parent() const override;
-
- // Table interface
- QAccessibleInterface *cellAt(int row, int column) const override;
- QAccessibleInterface *caption() const override;
- QAccessibleInterface *summary() const override;
- QString columnDescription(int column) const override;
- QString rowDescription(int row) const override;
- int columnCount() const override;
- int rowCount() const override;
-
- // Selection
- int selectedCellCount() const override;
- int selectedColumnCount() const override;
- int selectedRowCount() const override;
- QList<QAccessibleInterface *> selectedCells() const override;
- QList<int> selectedColumns() const override;
- QList<int> selectedRows() const override;
- bool isColumnSelected(int column) const override;
- bool isRowSelected(int row) const override;
- bool selectRow(int row) override;
- bool selectColumn(int column) override;
- bool unselectRow(int row) override;
- bool unselectColumn(int column) override;
- void modelChange(QAccessibleTableModelChangeEvent *) override;
-
- KItemListView *view() const;
-
-protected:
- virtual void modelReset();
- /**
- * Create an QAccessibleTableCellInterface representing the table
- * cell at the @index. Index is 0-based.
- */
- inline QAccessibleInterface *cell(int index) const;
-
-private:
- class AccessibleIdWrapper
- {
- public:
- AccessibleIdWrapper();
- bool isValid;
- QAccessible::Id id;
- };
- mutable QVector<AccessibleIdWrapper> m_cells;
-
- KItemListContainerAccessible *m_parent;
-};
-
-class DOLPHIN_EXPORT KItemListAccessibleCell : public QAccessibleInterface, public QAccessibleTableCellInterface
-{
-public:
- KItemListAccessibleCell(KItemListView *view, int m_index);
-
- void *interface_cast(QAccessible::InterfaceType type) override;
- QObject *object() const override;
- bool isValid() const override;
- QAccessible::Role role() const override;
- QAccessible::State state() const override;
- QRect rect() const override;
- QString text(QAccessible::Text t) const override;
- void setText(QAccessible::Text t, const QString &text) override;
-
- QAccessibleInterface *child(int index) const override;
- int childCount() const override;
- QAccessibleInterface *childAt(int x, int y) const override;
- int indexOfChild(const QAccessibleInterface *) const override;
-
- QAccessibleInterface *parent() const override;
- bool isExpandable() const;
-
- // Cell Interface
- int columnExtent() const override;
- QList<QAccessibleInterface *> columnHeaderCells() const override;
- int columnIndex() const override;
- int rowExtent() const override;
- QList<QAccessibleInterface *> rowHeaderCells() const override;
- int rowIndex() const override;
- bool isSelected() const override;
- QAccessibleInterface *table() const override;
-
- inline int index() const;
-
-private:
- QPointer<KItemListView> m_view;
- int m_index;
-};
-
-class DOLPHIN_EXPORT KItemListContainerAccessible : public QAccessibleWidget
-{
-public:
- explicit KItemListContainerAccessible(KItemListContainer *container);
- ~KItemListContainerAccessible() override;
-
- QAccessibleInterface *child(int index) const override;
- int childCount() const override;
- int indexOfChild(const QAccessibleInterface *child) const override;
-
-private:
- const KItemListContainer *container() const;
-};
-
-#endif // QT_NO_ACCESSIBILITY
-
-#endif
return false;
}
-QString KStandardItemListWidgetInformant::roleText(const QByteArray &role, const QHash<QByteArray, QVariant> &values) const
+QString KStandardItemListWidgetInformant::roleText(const QByteArray &role, const QHash<QByteArray, QVariant> &values, ForUsageAs forUsageAs) const
{
if (role == "rating") {
- // Always use an empty text, as the rating is shown by the image m_rating.
- return QString();
+ if (forUsageAs == ForUsageAs::DisplayedText) {
+ // Always use an empty text, as the rating is shown by the image m_rating.
+ return QString();
+ } else {
+ const int rating{values.value(role).toInt()};
+ // Check if there are half stars
+ if (rating % 2) {
+ return i18ncp("@accessible rating", "%1 and a half stars", "%1 and a half stars", rating / 2);
+ }
+ return i18ncp("@accessible rating", "%1 star", "%1 stars", rating / 2);
+ }
}
return values.value(role).toString();
}
*/
virtual bool itemIsLink(int index, const KItemListView *view) const;
+ /** Configure whether the requested text should be optimized for viewing on a screen or for being read out aloud by a text-to-speech engine. */
+ enum class ForUsageAs { DisplayedText, SpokenText };
+
/**
+ * @param role The role the text is being requested for.
+ * @param values The data of the item. All the data is passed because the text might depend on multiple data points.
+ * @param forUsageAs Whether the roleText should be optimized for displaying (i.e. kept somewhat short) or optimized for speaking e.g. by screen readers
+ * or text-to-speech in general (i.e. by prefering announcing a month as July instead of as the number 7).
* @return String representation of the role \a role. The representation of
* a role might depend on other roles, so the values of all roles
* are passed as parameter.
*/
- virtual QString roleText(const QByteArray &role, const QHash<QByteArray, QVariant> &values) const;
+ virtual QString roleText(const QByteArray &role, const QHash<QByteArray, QVariant> &values, ForUsageAs forUsageAs = ForUsageAs::DisplayedText) const;
/**
* @return A font based on baseFont which is customized for symlinks.
// Initialize text label
m_label = new KSqueezedTextLabel(m_text, contentsContainer);
m_label->setTextFormat(Qt::PlainText);
+ m_label->setTextInteractionFlags(Qt::TextBrowserInteraction | Qt::TextSelectableByKeyboard); // for accessibility but also to allow copy-pasting this text.
// Initialize zoom slider's explanatory label
m_zoomLabel = new KSqueezedTextLabel(i18nc("Used as a noun, i.e. 'Here is the zoom level:'", "Zoom:"), contentsContainer);
#include "dolphinitemlistview.h"
#include "dolphinnewfilemenuobserver.h"
#include "draganddrophelper.h"
+#ifndef QT_NO_ACCESSIBILITY
+#include "kitemviews/accessibility/kitemlistviewaccessible.h"
+#endif
#include "kitemviews/kfileitemlistview.h"
#include "kitemviews/kfileitemmodel.h"
#include "kitemviews/kitemlistcontainer.h"
#include <kwidgetsaddons_version.h>
#include <QAbstractItemView>
+#ifndef QT_NO_ACCESSIBILITY
+#include <QAccessible>
+#endif
#include <QActionGroup>
#include <QApplication>
#include <QClipboard>
{
m_placeholderLabel->setText(i18n("Loading…"));
m_placeholderLabel->setVisible(true);
+#ifndef QT_NO_ACCESSIBILITY
+ if (QAccessible::isActive()) {
+ auto accessibleViewInterface = static_cast<KItemListViewAccessible *>(QAccessible::queryAccessibleInterface(m_view));
+ accessibleViewInterface->announceOverallViewState(m_placeholderLabel->text());
+ }
+#endif
}
void DolphinView::updatePlaceholderLabel()
}
m_placeholderLabel->setVisible(true);
+#ifndef QT_NO_ACCESSIBILITY
+ if (QAccessible::isActive()) {
+ auto accessibleViewInterface = static_cast<KItemListViewAccessible *>(QAccessible::queryAccessibleInterface(m_view));
+ accessibleViewInterface->announceOverallViewState(m_placeholderLabel->text());
+ }
+#endif
}
bool DolphinView::tryShowNameToolTip(QHelpEvent *event)