mirror of
https://github.com/mumble-voip/mumble.git
synced 2025-10-26 11:19:16 +00:00
FIX(a11y): Make global shortcut settings more accessible
This commit switches the global shortcut tree to a new MultiColumnTreeWidget and makes it keyboard navigatable. It also makes screen readers read actual column values. Fixes #2293
This commit is contained in:
parent
6a68696609
commit
fffcfaa03a
@ -12,6 +12,7 @@
|
||||
#include "EnvUtils.h"
|
||||
#include "MainWindow.h"
|
||||
#include "ServerHandler.h"
|
||||
#include "widgets/EventFilters.h"
|
||||
#include "Global.h"
|
||||
#include "GlobalShortcutButtons.h"
|
||||
|
||||
@ -43,43 +44,70 @@ static ConfigRegistrar registrarGlobalShortcut(1200, GlobalShortcutConfigDialogN
|
||||
|
||||
static const QString UPARROW = QString::fromUtf8("\xE2\x86\x91 ");
|
||||
|
||||
ShortcutActionWidget::ShortcutActionWidget(QWidget *p) : MUComboBox(p) {
|
||||
ShortcutActionWidget::ShortcutActionWidget(QWidget *p) : QWidget(p) {
|
||||
int idx = 0;
|
||||
|
||||
insertItem(idx, tr("Unassigned"));
|
||||
setItemData(idx, -1);
|
||||
m_comboBox = new MUComboBox();
|
||||
m_comboBox->insertItem(idx, tr("Unassigned"));
|
||||
m_comboBox->setItemData(idx, -1);
|
||||
#ifndef Q_OS_MAC
|
||||
setSizeAdjustPolicy(AdjustToContents);
|
||||
m_comboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||
#endif
|
||||
|
||||
idx++;
|
||||
|
||||
QFontMetrics fontMetrics = m_comboBox->fontMetrics();
|
||||
int maxWidth = m_comboBox->minimumWidth();
|
||||
|
||||
foreach (GlobalShortcut *gs, GlobalShortcutEngine::engine->qmShortcuts) {
|
||||
insertItem(idx, gs->name);
|
||||
setItemData(idx, gs->idx);
|
||||
if (!gs->qsToolTip.isEmpty())
|
||||
setItemData(idx, gs->qsToolTip, Qt::ToolTipRole);
|
||||
if (!gs->qsWhatsThis.isEmpty())
|
||||
setItemData(idx, gs->qsWhatsThis, Qt::WhatsThisRole);
|
||||
m_comboBox->insertItem(idx, gs->name);
|
||||
m_comboBox->setItemData(idx, gs->idx);
|
||||
if (!gs->qsToolTip.isEmpty()) {
|
||||
m_comboBox->setItemData(idx, gs->qsToolTip, Qt::ToolTipRole);
|
||||
}
|
||||
if (!gs->qsWhatsThis.isEmpty()) {
|
||||
m_comboBox->setItemData(idx, gs->qsWhatsThis, Qt::WhatsThisRole);
|
||||
}
|
||||
idx++;
|
||||
|
||||
maxWidth = std::max(maxWidth, fontMetrics.horizontalAdvance(gs->name));
|
||||
}
|
||||
|
||||
// Sort the ShortcutActionWidget items
|
||||
QSortFilterProxyModel *proxy = new QSortFilterProxyModel(this);
|
||||
proxy->setSourceModel(model());
|
||||
proxy->setSourceModel(m_comboBox->model());
|
||||
|
||||
model()->setParent(proxy);
|
||||
setModel(proxy);
|
||||
m_comboBox->model()->setParent(proxy);
|
||||
m_comboBox->setModel(proxy);
|
||||
|
||||
model()->sort(0);
|
||||
m_comboBox->model()->sort(0);
|
||||
|
||||
m_comboBox->setFocusPolicy(Qt::NoFocus);
|
||||
|
||||
setMinimumWidth(maxWidth);
|
||||
m_comboBox->view()->setMinimumWidth(maxWidth);
|
||||
m_comboBox->adjustSize();
|
||||
|
||||
m_comboBox->setParent(this);
|
||||
|
||||
adjustSize();
|
||||
|
||||
KeyEventObserver *eventFilter = new KeyEventObserver(this, QEvent::KeyPress, true, { Qt::Key_Space });
|
||||
connect(eventFilter, &KeyEventObserver::keyEventObserved, this, [=]() { m_comboBox->showPopup(); });
|
||||
installEventFilter(eventFilter);
|
||||
|
||||
QTreeWidget *treeWidget = qobject_cast< QTreeWidget * >(p->parentWidget());
|
||||
if (treeWidget) {
|
||||
treeWidget->resizeColumnToContents(0);
|
||||
}
|
||||
}
|
||||
|
||||
void ShortcutActionWidget::setIndex(unsigned int idx) {
|
||||
setCurrentIndex(findData(idx));
|
||||
m_comboBox->setCurrentIndex(m_comboBox->findData(idx));
|
||||
}
|
||||
|
||||
unsigned int ShortcutActionWidget::index() const {
|
||||
return itemData(currentIndex()).toUInt();
|
||||
return m_comboBox->itemData(m_comboBox->currentIndex()).toUInt();
|
||||
}
|
||||
|
||||
ShortcutToggleWidget::ShortcutToggleWidget(QWidget *p) : MUComboBox(p) {
|
||||
@ -344,6 +372,7 @@ void ShortcutTargetDialog::on_qpbRemove_clicked() {
|
||||
ShortcutTargetWidget::ShortcutTargetWidget(QWidget *p) : QFrame(p) {
|
||||
qleTarget = new QLineEdit();
|
||||
qleTarget->setReadOnly(true);
|
||||
qleTarget->setFocusPolicy(Qt::NoFocus);
|
||||
|
||||
qtbEdit = new QToolButton();
|
||||
qtbEdit->setText(tr("..."));
|
||||
@ -355,9 +384,38 @@ ShortcutTargetWidget::ShortcutTargetWidget(QWidget *p) : QFrame(p) {
|
||||
l->addWidget(qleTarget, 1);
|
||||
l->addWidget(qtbEdit);
|
||||
|
||||
KeyEventObserver *eventFilter = new KeyEventObserver(this, QEvent::KeyPress, true, { Qt::Key_Space });
|
||||
connect(eventFilter, &KeyEventObserver::keyEventObserved, this, [=]() { qtbEdit->click(); });
|
||||
installEventFilter(eventFilter);
|
||||
|
||||
QMetaObject::connectSlotsByName(this);
|
||||
}
|
||||
|
||||
TextEditWidget::TextEditWidget(QWidget *p) : QWidget(p) {
|
||||
m_lineEdit = new QLineEdit();
|
||||
m_lineEdit->setFocusPolicy(Qt::ClickFocus);
|
||||
|
||||
QHBoxLayout *l = new QHBoxLayout(this);
|
||||
l->setContentsMargins(0, 0, 0, 0);
|
||||
l->setSpacing(0);
|
||||
l->addWidget(m_lineEdit);
|
||||
|
||||
KeyEventObserver *eventFilter = new KeyEventObserver(this, QEvent::KeyPress, true, { Qt::Key_Space });
|
||||
connect(eventFilter, &KeyEventObserver::keyEventObserved, this,
|
||||
[=]() { m_lineEdit->setFocus(Qt::MouseFocusReason); });
|
||||
installEventFilter(eventFilter);
|
||||
|
||||
QMetaObject::connectSlotsByName(this);
|
||||
}
|
||||
|
||||
QString TextEditWidget::currentString() const {
|
||||
return m_lineEdit->text();
|
||||
}
|
||||
|
||||
void TextEditWidget::setCurrentString(const QString &str) {
|
||||
m_lineEdit->setText(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns a textual representation of the given shortcut target st.
|
||||
*/
|
||||
@ -461,7 +519,7 @@ ShortcutDelegate::ShortcutDelegate(QObject *p) : QStyledItemDelegate(p) {
|
||||
new QStandardItemEditorCreator< ShortcutTargetWidget >());
|
||||
factory->registerEditor(static_cast< int >(QVariant::fromValue(ChannelTarget()).userType()),
|
||||
new QStandardItemEditorCreator< ChannelSelectWidget >());
|
||||
factory->registerEditor(QVariant::String, new QStandardItemEditorCreator< QLineEdit >());
|
||||
factory->registerEditor(QVariant::String, new QStandardItemEditorCreator< TextEditWidget >());
|
||||
factory->registerEditor(QVariant::Invalid, new QStandardItemEditorCreator< QWidget >());
|
||||
setItemEditorFactory(factory);
|
||||
}
|
||||
@ -556,11 +614,16 @@ GlobalShortcutConfig::GlobalShortcutConfig(Settings &st) : ConfigWidget(st) {
|
||||
qtwShortcuts->setColumnCount(canSuppress ? 4 : 3);
|
||||
qtwShortcuts->setItemDelegate(new ShortcutDelegate(qtwShortcuts));
|
||||
|
||||
qtwShortcuts->headerItem()->setData(0, Qt::AccessibleTextRole, tr("Shortcut action"));
|
||||
qtwShortcuts->headerItem()->setData(1, Qt::AccessibleTextRole, tr("Shortcut data"));
|
||||
qtwShortcuts->headerItem()->setData(2, Qt::AccessibleTextRole, tr("Shortcut input combinations"));
|
||||
|
||||
qtwShortcuts->header()->setSectionResizeMode(0, QHeaderView::Fixed);
|
||||
qtwShortcuts->header()->resizeSection(0, 150);
|
||||
qtwShortcuts->header()->setSectionResizeMode(2, QHeaderView::Stretch);
|
||||
if (canSuppress)
|
||||
if (canSuppress) {
|
||||
qtwShortcuts->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
|
||||
}
|
||||
|
||||
qcbEnableGlobalShortcuts->setVisible(canDisable);
|
||||
|
||||
@ -670,6 +733,7 @@ void GlobalShortcutConfig::on_qpbAdd_clicked(bool) {
|
||||
sc.bSuppress = false;
|
||||
qlShortcuts << sc;
|
||||
reload();
|
||||
qtwShortcuts->setFocus(Qt::TabFocusReason);
|
||||
}
|
||||
|
||||
void GlobalShortcutConfig::on_qpbRemove_clicked(bool) {
|
||||
@ -705,6 +769,17 @@ void GlobalShortcutConfig::on_qtwShortcuts_itemChanged(QTreeWidgetItem *item, in
|
||||
if (gs && sc.qvData.userType() != gs->qvDefault.userType()) {
|
||||
item->setData(1, Qt::DisplayRole, gs->qvDefault);
|
||||
}
|
||||
|
||||
if (gs) {
|
||||
item->setData(0, Qt::AccessibleTextRole, gs->name);
|
||||
} else {
|
||||
item->setData(0, Qt::AccessibleTextRole, tr("Unassigned"));
|
||||
}
|
||||
item->setData(1, Qt::AccessibleTextRole, sc.qvData);
|
||||
item->setData(3, Qt::AccessibleDescriptionRole,
|
||||
item->checkState(3) == Qt::Checked ? tr("checked") : tr("unchecked"));
|
||||
|
||||
qtwShortcuts->resizeColumnToContents(0);
|
||||
}
|
||||
|
||||
QString GlobalShortcutConfig::title() const {
|
||||
@ -811,6 +886,7 @@ void GlobalShortcutConfig::reload() {
|
||||
foreach (const Shortcut &sc, qlShortcuts) {
|
||||
QTreeWidgetItem *item = itemForShortcut(sc);
|
||||
qtwShortcuts->addTopLevelItem(item);
|
||||
on_qtwShortcuts_itemChanged(item, 0);
|
||||
}
|
||||
#ifdef Q_OS_MAC
|
||||
if (!Global::get().s.bSuppressMacEventTapWarning) {
|
||||
|
||||
@ -51,11 +51,14 @@ public:
|
||||
*
|
||||
* @see GlobalShortcutEngine
|
||||
*/
|
||||
class ShortcutActionWidget : public MUComboBox {
|
||||
class ShortcutActionWidget : public QWidget {
|
||||
private:
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(ShortcutActionWidget)
|
||||
Q_PROPERTY(unsigned int index READ index WRITE setIndex USER true)
|
||||
|
||||
MUComboBox *m_comboBox;
|
||||
|
||||
public:
|
||||
ShortcutActionWidget(QWidget *p = nullptr);
|
||||
unsigned int index() const;
|
||||
@ -84,6 +87,20 @@ public:
|
||||
void setCurrentChannel(const ChannelTarget &);
|
||||
};
|
||||
|
||||
class TextEditWidget : public QWidget {
|
||||
private:
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(TextEditWidget)
|
||||
Q_PROPERTY(QString currentString READ currentString WRITE setCurrentString USER true)
|
||||
|
||||
QLineEdit *m_lineEdit;
|
||||
|
||||
public:
|
||||
TextEditWidget(QWidget *p = nullptr);
|
||||
QString currentString() const;
|
||||
void setCurrentString(const QString &);
|
||||
};
|
||||
|
||||
/**
|
||||
* Dialog which is used to select the targets of a targeted shortcut like Whisper.
|
||||
*/
|
||||
|
||||
@ -124,13 +124,16 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="qtwShortcuts">
|
||||
<widget class="MultiColumnTreeWidget" name="qtwShortcuts">
|
||||
<property name="toolTip">
|
||||
<string>List of configured shortcuts</string>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Configured shortcuts</string>
|
||||
</property>
|
||||
<property name="accessibleDescription">
|
||||
<string>Use up and down keys to navigate through your added shortcuts. Use left and right keys to navigate between actions and options for a single shortcut. Entries can be added and deleted with the buttons below.</string>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::AllEditTriggers</set>
|
||||
</property>
|
||||
@ -186,6 +189,12 @@
|
||||
<property name="whatsThis">
|
||||
<string>This will add a new global shortcut</string>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Add unassigned shortcut</string>
|
||||
</property>
|
||||
<property name="accessibleDescription">
|
||||
<string>This adds a new empty entry to the "Configured Shortcut" tree above. The tree will be automatically focused. Assign a key or an action by selecting the entry in the tree above.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Add</string>
|
||||
</property>
|
||||
@ -202,6 +211,12 @@
|
||||
<property name="whatsThis">
|
||||
<string>This will permanently remove a selected shortcut.</string>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Remove selected shortcut</string>
|
||||
</property>
|
||||
<property name="accessibleDescription">
|
||||
<string>This removes the selected entry from the "Configured Shortcut" tree above</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Remove</string>
|
||||
</property>
|
||||
@ -271,6 +286,13 @@ Without this option enabled, using Mumble's global shortcuts in privileged appli
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>MultiColumnTreeWidget</class>
|
||||
<extends>QTreeWidget</extends>
|
||||
<header>widgets/MultiColumnTreeWidget.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
@ -10,6 +10,8 @@
|
||||
|
||||
#include "Global.h"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
GlobalShortcutButtons::GlobalShortcutButtons(QWidget *parent) : QDialog(parent), m_ui(new Ui::GlobalShortcutButtons) {
|
||||
m_ui->setupUi(this);
|
||||
|
||||
@ -50,6 +52,16 @@ void GlobalShortcutButtons::setButtons(const QList< QVariant > &buttons) {
|
||||
}
|
||||
|
||||
adjustSize();
|
||||
|
||||
// Without this the new dialog window will not be focused and the
|
||||
// shortcut edit dialog is closed when TAB is pressed...
|
||||
// Due to Qt action processing weirdness the timer is needed, otherwise
|
||||
// the call has no effect.
|
||||
if (buttons.isEmpty()) {
|
||||
QTimer::singleShot(0, [&]() { m_ui->addButton->setFocus(Qt::TabFocusReason); });
|
||||
} else {
|
||||
QTimer::singleShot(0, [&]() { m_ui->buttonTree->setFocus(Qt::TabFocusReason); });
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalShortcutButtons::addItem(const QVariant &button) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user