mirror of
https://github.com/transmission/transmission
synced 2024-12-21 23:32:35 +00:00
Make SqueezeLabel
[more] accessible (#6520)
Expose label text as accessible value instead of accessible name, and get accessible name from buddy label as any proper input widget does. Don't expose label tooltip as accessible description unless it's different from its text (which isn't the case when displayed text is truncated). Notify on label text and selection changes. Switch to `SqueezeLabel` for values in statistics dialog which has similar layout to information tab of torrent properties dialog.
This commit is contained in:
parent
35847b3e75
commit
8e7fc76930
7 changed files with 303 additions and 9 deletions
144
qt/AccessibleSqueezeLabel.cc
Normal file
144
qt/AccessibleSqueezeLabel.cc
Normal file
|
@ -0,0 +1,144 @@
|
|||
// This file Copyright © Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#include <qtguiglobal.h>
|
||||
|
||||
#if QT_CONFIG(accessibility)
|
||||
|
||||
#include <QMetaProperty>
|
||||
|
||||
#include "AccessibleSqueezeLabel.h"
|
||||
#include "SqueezeLabel.h"
|
||||
|
||||
AccessibleSqueezeLabel::AccessibleSqueezeLabel(QWidget* widget)
|
||||
: QAccessibleWidget(widget, QAccessible::EditableText)
|
||||
{
|
||||
}
|
||||
|
||||
QString AccessibleSqueezeLabel::text(QAccessible::Text kind) const
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case QAccessible::Value:
|
||||
return label()->text();
|
||||
|
||||
case QAccessible::Description:
|
||||
return !label()->accessibleDescription().isEmpty() || label()->toolTip() != label()->text() ?
|
||||
QAccessibleWidget::text(kind) :
|
||||
QString{};
|
||||
|
||||
default:
|
||||
return QAccessibleWidget::text(kind);
|
||||
}
|
||||
}
|
||||
|
||||
QAccessible::State AccessibleSqueezeLabel::state() const
|
||||
{
|
||||
auto result = QAccessibleWidget::state();
|
||||
result.readOnly = true;
|
||||
result.selectableText = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
void* AccessibleSqueezeLabel::interface_cast(QAccessible::InterfaceType ifaceType)
|
||||
{
|
||||
if (ifaceType == QAccessible::TextInterface)
|
||||
{
|
||||
return static_cast<QAccessibleTextInterface*>(this);
|
||||
}
|
||||
|
||||
return QAccessibleWidget::interface_cast(ifaceType);
|
||||
}
|
||||
|
||||
void AccessibleSqueezeLabel::selection(int selectionIndex, int* startOffset, int* endOffset) const
|
||||
{
|
||||
if (selectionIndex != 0)
|
||||
{
|
||||
*startOffset = 0;
|
||||
*endOffset = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
*startOffset = label()->selectionStart();
|
||||
*endOffset = *startOffset + label()->selectedText().size();
|
||||
}
|
||||
|
||||
int AccessibleSqueezeLabel::selectionCount() const
|
||||
{
|
||||
return label()->hasSelectedText() ? 1 : 0;
|
||||
}
|
||||
|
||||
void AccessibleSqueezeLabel::addSelection(int startOffset, int endOffset)
|
||||
{
|
||||
setSelection(0, startOffset, endOffset);
|
||||
}
|
||||
|
||||
void AccessibleSqueezeLabel::removeSelection(int selectionIndex)
|
||||
{
|
||||
setSelection(selectionIndex, 0, 0);
|
||||
}
|
||||
|
||||
void AccessibleSqueezeLabel::setSelection(int selectionIndex, int startOffset, int endOffset)
|
||||
{
|
||||
if (selectionIndex != 0 || startOffset > endOffset)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
label()->setSelection(startOffset, endOffset - startOffset);
|
||||
}
|
||||
|
||||
int AccessibleSqueezeLabel::cursorPosition() const
|
||||
{
|
||||
// NOTE: Due to QLabel implementation specifics, this will return -1 unless some part of text is selected :(
|
||||
return label()->selectionStart();
|
||||
}
|
||||
|
||||
void AccessibleSqueezeLabel::setCursorPosition(int position)
|
||||
{
|
||||
setSelection(0, position, position);
|
||||
}
|
||||
|
||||
QString AccessibleSqueezeLabel::text(int startOffset, int endOffset) const
|
||||
{
|
||||
return startOffset > endOffset ? QString{} : label()->text().mid(startOffset, endOffset - startOffset);
|
||||
}
|
||||
|
||||
int AccessibleSqueezeLabel::characterCount() const
|
||||
{
|
||||
return label()->text().size();
|
||||
}
|
||||
|
||||
QRect AccessibleSqueezeLabel::characterRect(int /*offset*/) const
|
||||
{
|
||||
// NOTE: Can't be easily implemented as needed info is internal to QLabel :(
|
||||
return {};
|
||||
}
|
||||
|
||||
int AccessibleSqueezeLabel::offsetAtPoint(QPoint const& /*point*/) const
|
||||
{
|
||||
// NOTE: Can't be easily implemented as needed info is internal to QLabel :(
|
||||
return -1;
|
||||
}
|
||||
|
||||
void AccessibleSqueezeLabel::scrollToSubstring(int startIndex, int endIndex)
|
||||
{
|
||||
setCursorPosition(endIndex);
|
||||
setCursorPosition(startIndex);
|
||||
}
|
||||
|
||||
QString AccessibleSqueezeLabel::attributes(int offset, int* startOffset, int* endOffset) const
|
||||
{
|
||||
*startOffset = offset;
|
||||
*endOffset = offset;
|
||||
return {};
|
||||
}
|
||||
|
||||
SqueezeLabel* AccessibleSqueezeLabel::label() const
|
||||
{
|
||||
return qobject_cast<SqueezeLabel*>(object());
|
||||
}
|
||||
|
||||
#endif // QT_CONFIG(accessibility)
|
47
qt/AccessibleSqueezeLabel.h
Normal file
47
qt/AccessibleSqueezeLabel.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
// This file Copyright © Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <qtguiglobal.h>
|
||||
|
||||
#if QT_CONFIG(accessibility)
|
||||
|
||||
#include <QAccessibleWidget>
|
||||
|
||||
class SqueezeLabel;
|
||||
|
||||
class AccessibleSqueezeLabel
|
||||
: public QAccessibleWidget
|
||||
, public QAccessibleTextInterface
|
||||
{
|
||||
public:
|
||||
explicit AccessibleSqueezeLabel(QWidget* widget);
|
||||
|
||||
// QAccessibleWidget
|
||||
[[nodiscard]] QString text(QAccessible::Text kind) const override;
|
||||
[[nodiscard]] QAccessible::State state() const override;
|
||||
void* interface_cast(QAccessible::InterfaceType ifaceType) override;
|
||||
|
||||
// QAccessibleTextInterface
|
||||
void selection(int selectionIndex, int* startOffset, int* endOffset) const override;
|
||||
[[nodiscard]] int selectionCount() const override;
|
||||
void addSelection(int startOffset, int endOffset) override;
|
||||
void removeSelection(int selectionIndex) override;
|
||||
void setSelection(int selectionIndex, int startOffset, int endOffset) override;
|
||||
[[nodiscard]] int cursorPosition() const override;
|
||||
void setCursorPosition(int position) override;
|
||||
[[nodiscard]] QString text(int startOffset, int endOffset) const override;
|
||||
[[nodiscard]] int characterCount() const override;
|
||||
[[nodiscard]] QRect characterRect(int offset) const override;
|
||||
[[nodiscard]] int offsetAtPoint(QPoint const& point) const override;
|
||||
void scrollToSubstring(int startIndex, int endIndex) override;
|
||||
QString attributes(int offset, int* startOffset, int* endOffset) const override;
|
||||
|
||||
private:
|
||||
[[nodiscard]] SqueezeLabel* label() const;
|
||||
};
|
||||
|
||||
#endif // QT_CONFIG(accessibility)
|
|
@ -25,6 +25,10 @@
|
|||
#include <QDBusReply>
|
||||
#endif
|
||||
|
||||
#if QT_CONFIG(accessibility)
|
||||
#include <QAccessible>
|
||||
#endif
|
||||
|
||||
#include <libtransmission/transmission.h>
|
||||
|
||||
#include <libtransmission/tr-getopt.h>
|
||||
|
@ -32,6 +36,7 @@
|
|||
#include <libtransmission/values.h>
|
||||
#include <libtransmission/version.h>
|
||||
|
||||
#include "AccessibleSqueezeLabel.h"
|
||||
#include "AddData.h"
|
||||
#include "InteropHelper.h"
|
||||
#include "MainWindow.h"
|
||||
|
@ -90,6 +95,25 @@ bool loadTranslation(QTranslator& translator, QString const& name, QLocale const
|
|||
return QIcon{ QStringLiteral(":/icons/transmission.svg") };
|
||||
}
|
||||
|
||||
#if QT_CONFIG(accessibility)
|
||||
|
||||
QAccessibleInterface* accessibleFactory(QString const& className, QObject* object)
|
||||
{
|
||||
auto* widget = qobject_cast<QWidget*>(object);
|
||||
|
||||
if (widget != nullptr)
|
||||
{
|
||||
if (className == QStringLiteral("SqueezeLabel"))
|
||||
{
|
||||
return new AccessibleSqueezeLabel(widget);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif // QT_CONFIG(accessibility)
|
||||
|
||||
} // namespace
|
||||
|
||||
Application::Application(int& argc, char** argv)
|
||||
|
@ -273,6 +297,10 @@ Application::Application(int& argc, char** argv)
|
|||
minimized = false;
|
||||
}
|
||||
|
||||
#if QT_CONFIG(accessibility)
|
||||
QAccessible::installFactory(&accessibleFactory);
|
||||
#endif
|
||||
|
||||
session_ = std::make_unique<Session>(config_dir, *prefs_);
|
||||
model_ = std::make_unique<TorrentModel>(*prefs_);
|
||||
window_ = std::make_unique<MainWindow>(*session_, *prefs_, *model_, minimized);
|
||||
|
|
|
@ -9,6 +9,8 @@ target_sources(${TR_NAME}-qt
|
|||
PRIVATE
|
||||
AboutDialog.cc
|
||||
AboutDialog.h
|
||||
AccessibleSqueezeLabel.cc
|
||||
AccessibleSqueezeLabel.h
|
||||
AddData.cc
|
||||
AddData.h
|
||||
Application.cc
|
||||
|
|
|
@ -42,6 +42,11 @@
|
|||
#include <QPainter>
|
||||
#include <QStyle>
|
||||
#include <QStyleOption>
|
||||
#include <QTimer>
|
||||
|
||||
#if QT_CONFIG(accessibility)
|
||||
#include <QAccessible>
|
||||
#endif
|
||||
|
||||
#include "SqueezeLabel.h"
|
||||
|
||||
|
@ -57,6 +62,11 @@ SqueezeLabel::SqueezeLabel(QWidget* parent)
|
|||
|
||||
void SqueezeLabel::paintEvent(QPaintEvent* paint_event)
|
||||
{
|
||||
#if QT_CONFIG(accessibility)
|
||||
// NOTE: QLabel doesn't notify on text/cursor changes so we're checking for it when repaint is requested
|
||||
updateAccessibilityIfNeeded();
|
||||
#endif
|
||||
|
||||
if (hasFocus() && (textInteractionFlags() & (Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse)) != 0)
|
||||
{
|
||||
return QLabel::paintEvent(paint_event);
|
||||
|
@ -74,3 +84,48 @@ void SqueezeLabel::paintEvent(QPaintEvent* paint_event)
|
|||
setToolTip(full_text != elided_text ? full_text : QString{});
|
||||
#endif
|
||||
}
|
||||
|
||||
#if QT_CONFIG(accessibility)
|
||||
|
||||
void SqueezeLabel::updateAccessibilityIfNeeded()
|
||||
{
|
||||
// NOTE: Dispatching events asynchronously to avoid blocking the painting
|
||||
|
||||
if (auto const new_text = text(); new_text != old_text_)
|
||||
{
|
||||
if (QAccessible::isActive())
|
||||
{
|
||||
QTimer::singleShot(
|
||||
0,
|
||||
this,
|
||||
[this, old_text = old_text_, new_text]()
|
||||
{
|
||||
QAccessibleTextUpdateEvent event(this, 0, old_text, new_text);
|
||||
event.setCursorPosition(selectionStart());
|
||||
QAccessible::updateAccessibility(&event);
|
||||
});
|
||||
}
|
||||
|
||||
old_text_ = new_text;
|
||||
}
|
||||
|
||||
// NOTE: Due to QLabel implementation specifics, this block will never be entered :(
|
||||
if (auto const new_position = selectionStart(); new_position != old_position_ && !hasSelectedText())
|
||||
{
|
||||
if (QAccessible::isActive())
|
||||
{
|
||||
QTimer::singleShot(
|
||||
0,
|
||||
this,
|
||||
[this, new_position]()
|
||||
{
|
||||
QAccessibleTextCursorEvent event(this, new_position);
|
||||
QAccessible::updateAccessibility(&event);
|
||||
});
|
||||
}
|
||||
|
||||
old_position_ = new_position;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // QT_CONFIG(accessibility)
|
||||
|
|
|
@ -57,4 +57,15 @@ public:
|
|||
protected:
|
||||
// QWidget
|
||||
void paintEvent(QPaintEvent* paint_event) override;
|
||||
|
||||
private:
|
||||
#if QT_CONFIG(accessibility)
|
||||
void updateAccessibilityIfNeeded();
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if QT_CONFIG(accessibility)
|
||||
QString old_text_;
|
||||
int old_position_ = -1;
|
||||
#endif
|
||||
};
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="currentUploadedValueLabel">
|
||||
<widget class="SqueezeLabel" name="currentUploadedValueLabel">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
|
@ -60,7 +60,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="currentDownloadedValueLabel">
|
||||
<widget class="SqueezeLabel" name="currentDownloadedValueLabel">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
|
@ -86,7 +86,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="currentRatioValueLabel">
|
||||
<widget class="SqueezeLabel" name="currentRatioValueLabel">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
|
@ -112,7 +112,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="currentDurationValueLabel">
|
||||
<widget class="SqueezeLabel" name="currentDurationValueLabel">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
|
@ -137,7 +137,7 @@
|
|||
</property>
|
||||
<layout class="QGridLayout" name="totalSectionLayout" columnstretch="0,1">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="startCountLabel">
|
||||
<widget class="SqueezeLabel" name="startCountLabel">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
|
@ -163,7 +163,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="totalUploadedValueLabel">
|
||||
<widget class="SqueezeLabel" name="totalUploadedValueLabel">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
|
@ -189,7 +189,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="totalDownloadedValueLabel">
|
||||
<widget class="SqueezeLabel" name="totalDownloadedValueLabel">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
|
@ -215,7 +215,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="totalRatioValueLabel">
|
||||
<widget class="SqueezeLabel" name="totalRatioValueLabel">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
|
@ -241,7 +241,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLabel" name="totalDurationValueLabel">
|
||||
<widget class="SqueezeLabel" name="totalDurationValueLabel">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
|
@ -271,6 +271,13 @@
|
|||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>SqueezeLabel</class>
|
||||
<extends>QLabel</extends>
|
||||
<header>SqueezeLabel.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
|
|
Loading…
Reference in a new issue