mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-04-17 11:58:27 +00:00
Accessibility fixs :
* Fix focus on first relevant element after changing page with navbar #LINQT-2202 * Improve focus navigation on call history list #LINQT-2201 * Fix missing accessible button name in dialer #LINQT-2221 * Switch from ScrollView to Flickable in parameters * Add auto scroll on keyboard navigation in settings #LINQT-2219 * Correct back button in settings #LINQT-2209 * Fix focus when open settings page #LINQT-2208 * Arrow in vertical tab bar now change button focus instead of changing page #LINQT-2194 * Fix focus and accessibility label in magic search bar #LINQT-2205
This commit is contained in:
parent
ca452efe80
commit
d2413f33a9
25 changed files with 1269 additions and 712 deletions
|
|
@ -114,6 +114,7 @@
|
|||
#include "tool/request/RequestDialog.hpp"
|
||||
#include "tool/thread/Thread.hpp"
|
||||
#include "tool/ui/DashRectangle.hpp"
|
||||
#include "tool/ui/FocusNavigator.hpp"
|
||||
|
||||
#if defined(Q_OS_MACOS)
|
||||
#include "core/event-count-notifier/EventCountNotifierMacOs.hpp"
|
||||
|
|
@ -714,6 +715,7 @@ void App::initCore() {
|
|||
#endif
|
||||
mEngine->rootContext()->setContextProperty("applicationName", APPLICATION_NAME);
|
||||
mEngine->rootContext()->setContextProperty("executableName", EXECUTABLE_NAME);
|
||||
mEngine->rootContext()->setContextProperty("FocusNavigator", new FocusNavigator(mEngine));
|
||||
|
||||
initCppInterfaces();
|
||||
mEngine->addImageProvider(ImageProvider::ProviderId, new ImageProvider());
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -27,6 +27,7 @@ list(APPEND _LINPHONEAPP_SOURCES
|
|||
tool/file/TemporaryFile.cpp
|
||||
|
||||
tool/ui/DashRectangle.cpp
|
||||
tool/ui/FocusNavigator.cpp
|
||||
|
||||
tool/accessibility/AccessibilityHelper.cpp
|
||||
tool/accessibility/KeyboardShortcuts.cpp
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ bool FocusHelperAttached::eventFilter(QObject *watched, QEvent *event) {
|
|||
auto fe = static_cast<QFocusEvent *>(event);
|
||||
if (fe) {
|
||||
int focusReason = fe->reason();
|
||||
// qDebug() << "FocusReason" << focusReason; // Usefull to debug focus problems
|
||||
m_keyboardFocus = (focusReason == Qt::TabFocusReason || focusReason == Qt::BacktabFocusReason);
|
||||
m_otherFocus = focusReason == Qt::OtherFocusReason;
|
||||
emit keyboardFocusChanged();
|
||||
|
|
|
|||
56
Linphone/tool/ui/FocusNavigator.cpp
Normal file
56
Linphone/tool/ui/FocusNavigator.cpp
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "FocusNavigator.hpp"
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QQuickItem>
|
||||
|
||||
FocusNavigator::FocusNavigator(QObject *parent) : QObject(parent) {
|
||||
connect(qApp, &QGuiApplication::focusObjectChanged, this, &FocusNavigator::onFocusObjectChanged);
|
||||
qApp->installEventFilter(this);
|
||||
}
|
||||
|
||||
bool FocusNavigator::doesLastFocusWasKeyboard() {
|
||||
return mLastFocusWasKeyboard;
|
||||
}
|
||||
|
||||
bool FocusNavigator::eventFilter(QObject *, QEvent *event) {
|
||||
switch (event->type()) {
|
||||
case QEvent::FocusIn: {
|
||||
auto fe = static_cast<QFocusEvent *>(event);
|
||||
if (fe) {
|
||||
int focusReason = fe->reason();
|
||||
mLastFocusWasKeyboard = (focusReason == Qt::TabFocusReason || focusReason == Qt::BacktabFocusReason);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FocusNavigator::onFocusObjectChanged(QObject *obj) {
|
||||
// qDebug() << "New focus object" << obj; // Usefull to debug focus problems
|
||||
auto item = qobject_cast<QQuickItem *>(obj);
|
||||
if (!item) return;
|
||||
emit focusChanged(item, mLastFocusWasKeyboard);
|
||||
}
|
||||
42
Linphone/tool/ui/FocusNavigator.hpp
Normal file
42
Linphone/tool/ui/FocusNavigator.hpp
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QQuickItem>
|
||||
|
||||
class FocusNavigator : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FocusNavigator(QObject *parent = nullptr);
|
||||
Q_INVOKABLE bool doesLastFocusWasKeyboard();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
|
||||
signals:
|
||||
void focusChanged(QQuickItem *item, bool keyboardFocus);
|
||||
|
||||
private:
|
||||
bool mLastFocusWasKeyboard = false;
|
||||
void onFocusObjectChanged(QObject *obj);
|
||||
};
|
||||
|
|
@ -23,7 +23,8 @@ Button {
|
|||
: mainItem.hovered || mainItem.hasNavigationFocus
|
||||
? mainItem.hoveredColor
|
||||
: mainItem.color
|
||||
border.color: mainItem.borderColor
|
||||
border.color: mainItem.keyboardFocus ? mainItem.keyboardFocusedBorderColor : mainItem.borderColor
|
||||
border.width: mainItem.keyboardFocus ? mainItem.keyboardFocusedBorderWidth : mainItem.borderWidth
|
||||
}
|
||||
|
||||
contentItem: EffectImage {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ Control.TabBar {
|
|||
property AccountGui defaultAccount
|
||||
|
||||
property int visibleCount: 0
|
||||
|
||||
signal enterPressed()
|
||||
signal spacePressed()
|
||||
|
||||
// Call it after model is ready. If done before, Repeater will not be updated
|
||||
function initButtons(){
|
||||
|
|
@ -96,6 +99,8 @@ Control.TabBar {
|
|||
onVisibleChanged: mainItem.updateVisibleCount()
|
||||
text: modelData.accessibilityLabel
|
||||
property bool keyboardFocus: FocusHelper.keyboardFocus
|
||||
focusPolicy: Qt.StrongFocus
|
||||
activeFocusOnTab: true
|
||||
UnreadNotification {
|
||||
unread: !defaultAccount
|
||||
? -1
|
||||
|
|
@ -165,6 +170,23 @@ Control.TabBar {
|
|||
mainItem.implicitWidth = Math.max(mainItem.implicitWidth, advanceWidth + buttonIcon.buttonSize)
|
||||
}
|
||||
}
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
|
||||
mainItem.enterPressed()
|
||||
} else if(event.key === Qt.Key_Space){
|
||||
mainItem.spacePressed()
|
||||
} else if(event.key === Qt.Key_Down){
|
||||
event.accepted = true;
|
||||
if(TabBar.index >= mainItem.visibleCount - 1)
|
||||
return;
|
||||
tabButton.nextItemInFocusChain(true).forceActiveFocus(Qt.TabFocusReason)
|
||||
} else if(event.key === Qt.Key_Up){
|
||||
event.accepted = true;
|
||||
if(TabBar.index <= 0)
|
||||
return;
|
||||
tabButton.nextItemInFocusChain(false).forceActiveFocus(Qt.BacktabFocusReason)
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
mainItem.setCurrentIndex(TabBar.index)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,17 +5,22 @@ import QtQuick.Controls.Basic as Control
|
|||
import Linphone
|
||||
import UtilsCpp
|
||||
import SettingsCpp
|
||||
import CustomControls 1.0
|
||||
import "qrc:/qt/qml/Linphone/view/Style/buttonStyle.js" as ButtonStyle
|
||||
import "qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js" as Utils
|
||||
|
||||
ListView {
|
||||
id: mainItem
|
||||
clip: true
|
||||
keyNavigationEnabled: false // We will reimplement the keyNavigation
|
||||
activeFocusOnTab: true
|
||||
|
||||
property SearchBar searchBar
|
||||
property bool loading: false
|
||||
property string searchText: searchBar?.text
|
||||
property real busyIndicatorSize: Utils.getSizeWithScreenRatio(60)
|
||||
property bool keyboardFocus: FocusHelper.keyboardFocus
|
||||
property bool lastFocusByNavigationKeyboard: false // Workaround to get the correct focusReason
|
||||
|
||||
signal resultsReceived
|
||||
|
||||
|
|
@ -41,7 +46,26 @@ ListView {
|
|||
Keys.onPressed: event => {
|
||||
if (event.key == Qt.Key_Escape) {
|
||||
console.log("Back")
|
||||
searchBar.forceActiveFocus()
|
||||
searchBar.forceActiveFocus(Qt.BacktabFocusReason)
|
||||
event.accepted = true
|
||||
}
|
||||
|
||||
// Re-implement key navigation to have Qt.TabFocusReason and Qt.BacktabFocusReason instead of Qt.OtherFocusReason when using arrows to navigate in listView
|
||||
else if (event.key === Qt.Key_Up) {
|
||||
if(currentIndex === 0){
|
||||
searchBar.forceActiveFocus(Qt.BacktabFocusReason)
|
||||
lastFocusByNavigationKeyboard = false
|
||||
}else{
|
||||
decrementCurrentIndex()
|
||||
currentItem.forceActiveFocus(Qt.BacktabFocusReason) // The focusReason is created by QT later, need to create a workaround
|
||||
lastFocusByNavigationKeyboard = true
|
||||
}
|
||||
event.accepted = true
|
||||
}
|
||||
else if(event.key === Qt.Key_Down){
|
||||
incrementCurrentIndex()
|
||||
currentItem.forceActiveFocus(Qt.TabFocusReason) // The focusReason is created by QT later, need to create a workaround
|
||||
lastFocusByNavigationKeyboard = true
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
|
@ -113,6 +137,8 @@ ListView {
|
|||
delegate: FocusScope {
|
||||
width: mainItem.width
|
||||
height: Utils.getSizeWithScreenRatio(56)
|
||||
Accessible.role: Accessible.ListItem
|
||||
|
||||
RowLayout {
|
||||
z: 1
|
||||
anchors.fill: parent
|
||||
|
|
@ -188,12 +214,14 @@ ListView {
|
|||
}
|
||||
}
|
||||
BigButton {
|
||||
id: callButton
|
||||
visible: !modelData.core.isConference || !SettingsCpp.disableMeetingsFeature
|
||||
style: ButtonStyle.noBackground
|
||||
icon.source: AppIcons.phone
|
||||
focus: true
|
||||
focus: false
|
||||
activeFocusOnTab: false
|
||||
asynchronous: false
|
||||
|
||||
//: Call %1
|
||||
Accessible.name: qsTr("call_name_accessible_button").arg(historyAvatar.displayNameVal)
|
||||
onClicked: {
|
||||
|
|
@ -206,12 +234,38 @@ ListView {
|
|||
UtilsCpp.createCall(modelData.core.remoteAddress)
|
||||
}
|
||||
}
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Left){
|
||||
backgroundMouseArea.forceActiveFocus(Qt.BacktabFocusReason)
|
||||
lastFocusByNavigationKeyboard = true;
|
||||
}
|
||||
}
|
||||
onActiveFocusChanged: {
|
||||
if (!activeFocus) {
|
||||
console.log("Unfocus button");
|
||||
callButton.focus = false // Make sure to be unfocusable (could be when called by forceActiveFocus)
|
||||
backgroundMouseArea.focus = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
id: backgroundMouseArea
|
||||
hoverEnabled: true
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
property bool keyboardFocus: FocusHelper.keyboardFocus || activeFocus && lastFocusByNavigationKeyboard
|
||||
|
||||
//: %1 - %2 - %3 - right arrow for call-back button
|
||||
Accessible.name: qsTr("call_history_entry_accessible_name").arg(
|
||||
//: "Appel manqué"
|
||||
modelData.core.status === LinphoneEnums.CallStatus.Missed ? qsTr("notification_missed_call_title")
|
||||
//: "Appel sortant"
|
||||
: modelData.core.isOutgoing ? qsTr("call_outgoing")
|
||||
//: "Appel entrant"
|
||||
: qsTr("call_audio_incoming")
|
||||
).arg(historyAvatar.displayNameVal).arg(UtilsCpp.formatDate(modelData.core.date))
|
||||
|
||||
onContainsMouseChanged: {
|
||||
if (containsMouse)
|
||||
mainItem.lastMouseContainsIndex = index
|
||||
|
|
@ -224,12 +278,20 @@ ListView {
|
|||
radius: Utils.getSizeWithScreenRatio(8)
|
||||
color: mainItem.currentIndex
|
||||
=== index ? DefaultStyle.main2_200 : DefaultStyle.main2_100
|
||||
border.color: backgroundMouseArea.keyboardFocus ? DefaultStyle.main2_900 : "transparent"
|
||||
border.width: backgroundMouseArea.keyboardFocus ? Utils.getSizeWithScreenRatio(3) : 0
|
||||
visible: mainItem.lastMouseContainsIndex === index
|
||||
|| mainItem.currentIndex === index
|
||||
}
|
||||
onPressed: {
|
||||
mainItem.currentIndex = model.index
|
||||
mainItem.forceActiveFocus()
|
||||
mainItem.lastFocusByNavigationKeyboard = false
|
||||
}
|
||||
Keys.onPressed: event => {
|
||||
if(event.key === Qt.Key_Right){
|
||||
callButton.forceActiveFocus(Qt.TabFocusReason)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ Flickable {
|
|||
direction)
|
||||
if (newItem) {
|
||||
newItem.selectIndex(
|
||||
direction > 0 ? -1 : newItem.model.count - 1)
|
||||
direction > 0 ? -1 : newItem.model.count - 1, direction > 0 ? Qt.BacktabFocusReason : Qt.TabFocusReason)
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import Linphone
|
|||
import UtilsCpp
|
||||
import ConstantsCpp
|
||||
import SettingsCpp
|
||||
import CustomControls 1.0
|
||||
import "qrc:/qt/qml/Linphone/view/Style/buttonStyle.js" as ButtonStyle
|
||||
import "qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js" as Utils
|
||||
|
||||
|
|
@ -149,6 +150,9 @@ FocusScope {
|
|||
onClicked: UtilsCpp.createCall(mainItem.addressFromFilter)
|
||||
KeyNavigation.left: chatButton
|
||||
KeyNavigation.right: videoCallButton
|
||||
//: "Call %1"
|
||||
Accessible.name: qsTr("call_with_contact_name_accessible_button").arg(mainItem.displayName)
|
||||
keyboardFocus: FocusHelper.keyboardFocus || FocusHelper.otherFocus
|
||||
}
|
||||
IconButton {
|
||||
id: videoCallButton
|
||||
|
|
@ -164,6 +168,9 @@ FocusScope {
|
|||
onClicked: UtilsCpp.createCall(mainItem.addressFromFilter, {"localVideoEnabled": true})
|
||||
KeyNavigation.left: callButton
|
||||
KeyNavigation.right: chatButton
|
||||
//: "Video call %1"
|
||||
Accessible.name: qsTr("video_call_with_contact_name_accessible_button").arg(mainItem.displayName)
|
||||
keyboardFocus: FocusHelper.keyboardFocus || FocusHelper.otherFocus
|
||||
}
|
||||
IconButton {
|
||||
id: chatButton
|
||||
|
|
@ -184,6 +191,9 @@ FocusScope {
|
|||
console.debug("[ContactListItem.qml] Open conversation")
|
||||
mainWindow.displayChatPage(mainItem.addressFromFilter)
|
||||
}
|
||||
//: "Message %1"
|
||||
Accessible.name: qsTr("message_with_contact_name_accessible_button").arg(mainItem.displayName)
|
||||
keyboardFocus: FocusHelper.keyboardFocus || FocusHelper.otherFocus
|
||||
}
|
||||
}
|
||||
PopupButton {
|
||||
|
|
|
|||
|
|
@ -68,13 +68,13 @@ ListView {
|
|||
|
||||
property bool _moveToIndex: false
|
||||
|
||||
function selectIndex(index){
|
||||
function selectIndex(index, focusReason = Qt.OtherFocusReason){
|
||||
if(mainItem.expanded && index >= 0){
|
||||
mainItem.currentIndex = index
|
||||
var item = itemAtIndex(mainItem.currentIndex)
|
||||
if(item){// Item is ready and available
|
||||
mainItem.highlightedContact = item.searchResultItem
|
||||
item.forceActiveFocus()
|
||||
item.forceActiveFocus(focusReason)
|
||||
updatePosition()
|
||||
_moveToIndex = false
|
||||
}else{// Move on the next items load.
|
||||
|
|
@ -85,7 +85,7 @@ ListView {
|
|||
mainItem.currentIndex = -1
|
||||
mainItem.highlightedContact = null
|
||||
if(headerItem) {
|
||||
headerItem.forceActiveFocus()
|
||||
headerItem.forceActiveFocus(focusReason)
|
||||
}
|
||||
_moveToIndex = false
|
||||
}
|
||||
|
|
@ -97,12 +97,12 @@ ListView {
|
|||
if(event.key == Qt.Key_Up || event.key == Qt.Key_Down){
|
||||
if(event.key == Qt.Key_Up && !headerItem.activeFocus) {
|
||||
if(currentIndex >= 0 ) {
|
||||
selectIndex(mainItem.currentIndex-1)
|
||||
selectIndex(mainItem.currentIndex-1, Qt.BacktabFocusReason)
|
||||
event.accepted = true;
|
||||
}
|
||||
}else if(event.key == Qt.Key_Down && mainItem.expanded){
|
||||
if(currentIndex < model.count - 1) {
|
||||
selectIndex(mainItem.currentIndex+1)
|
||||
selectIndex(mainItem.currentIndex+1, Qt.TabFocusReason)
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ Item {
|
|||
|
||||
//: %1 settings
|
||||
Accessible.name: qsTr("setting_tab_accessible_name").arg(titleText)
|
||||
Accessible.role: Accessible.ListItem
|
||||
|
||||
Keys.onPressed: (event)=>{
|
||||
if(event.key == Qt.Key_Space || event.key == Qt.Key_Return || event.key == Qt.Key_Enter){
|
||||
|
|
|
|||
|
|
@ -172,6 +172,9 @@ FocusScope {
|
|||
radius: Utils.getSizeWithScreenRatio(71)
|
||||
style: ButtonStyle.numericPad
|
||||
|
||||
//: %1 longpress %2
|
||||
Accessible.name: longPressText.text ? qsTr("numpad_longpress_accessible_name").arg(pressText.text).arg(longPressText.text) : pressText.text
|
||||
|
||||
contentItem: Item {
|
||||
anchors.fill: parent
|
||||
Text {
|
||||
|
|
@ -214,7 +217,10 @@ FocusScope {
|
|||
icon.height: Utils.getSizeWithScreenRatio(32)
|
||||
radius: Utils.getSizeWithScreenRatio(71)
|
||||
style: ButtonStyle.phoneGreen
|
||||
|
||||
|
||||
//: Call
|
||||
Accessible.name: qsTr("call_accessible_name")
|
||||
|
||||
onClicked: mainItem.launchCall()
|
||||
|
||||
KeyNavigation.left: eraseButton
|
||||
|
|
@ -237,6 +243,9 @@ FocusScope {
|
|||
Layout.Layout.preferredWidth: Utils.getSizeWithScreenRatio(38)
|
||||
Layout.Layout.preferredHeight: Utils.getSizeWithScreenRatio(38)
|
||||
|
||||
//: Erase
|
||||
Accessible.name: qsTr("erase_accessible_name")
|
||||
|
||||
onClicked: mainItem.wipe()
|
||||
|
||||
KeyNavigation.left: launchCallButton
|
||||
|
|
@ -244,8 +253,13 @@ FocusScope {
|
|||
KeyNavigation.up: numPadGrid.getButtonAt(11)
|
||||
KeyNavigation.down: numPadGrid.getButtonAt(1)
|
||||
|
||||
background: Item {
|
||||
visible: false
|
||||
background: Rectangle {
|
||||
width: eraseButton.width
|
||||
height: eraseButton.height
|
||||
color: "transparent"
|
||||
|
||||
border.color: eraseButton.keyboardFocus ? eraseButton.keyboardFocusedBorderColor : "transparent"
|
||||
border.width: eraseButton.keyboardFocus ? eraseButton.keyboardFocusedBorderWidth : eraseButton.borderWidth
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,8 @@ Control.Popup {
|
|||
icon.height: Utils.getSizeWithScreenRatio(24)
|
||||
style: ButtonStyle.noBackground
|
||||
onClicked: mainItem.close()
|
||||
//: Close numeric pad
|
||||
Accessible.name: qsTr("close_numeric_pad_accessible_name")
|
||||
}
|
||||
}
|
||||
contentItem: NumericPad{
|
||||
|
|
|
|||
|
|
@ -807,6 +807,17 @@ function infoDialog(window, message) {
|
|||
}, function (status) {})
|
||||
}
|
||||
|
||||
// Ensure that the item is visible in the view
|
||||
function ensureVisibleY(item, view){
|
||||
const itemPosition = item.mapToItem(view, 0, 0)
|
||||
|
||||
if (itemPosition.y < 0){
|
||||
view.contentY = view.contentY + itemPosition.y
|
||||
} else if (itemPosition.y + item.height > view.height){
|
||||
view.contentY = itemPosition.y + view.contentY + item.height - view.height
|
||||
}
|
||||
}
|
||||
|
||||
// Set position of list.currentItem into the scrollItem
|
||||
function updatePosition(scrollItem, list){
|
||||
if(scrollItem.height == 0) return;
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ AbstractMainPage {
|
|||
onClicked: {
|
||||
mainItem.goBackRequested()
|
||||
}
|
||||
//: Back to previous menu
|
||||
Accessible.name: qsTr("back_previous_menu_accessible_name")
|
||||
}
|
||||
Text {
|
||||
text: titleText
|
||||
|
|
@ -69,6 +71,9 @@ AbstractMainPage {
|
|||
property int selectedIndex: mainItem.defaultIndex != -1 ? mainItem.defaultIndex : 0
|
||||
activeFocusOnTab: true
|
||||
spacing: Utils.getSizeWithScreenRatio(5)
|
||||
Accessible.role: Accessible.List
|
||||
//: Settings page selection
|
||||
Accessible.name: qsTr("settings_page_selection_accessible_name")
|
||||
|
||||
delegate: SettingsMenuItem {
|
||||
titleText: modelData.title
|
||||
|
|
@ -86,7 +91,9 @@ AbstractMainPage {
|
|||
let initialEntry = mainItem.families[familiesList.selectedIndex]
|
||||
rightPanelStackView.push(layoutUrl(initialEntry.layout), { titleText: initialEntry.title, model: initialEntry.model, container: rightPanelStackView})
|
||||
familiesList.currentIndex = familiesList.selectedIndex
|
||||
backButton.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
Control.StackView.onActivated: {
|
||||
familiesList.forceActiveFocus(FocusNavigator.doesLastFocusWasKeyboard() ? Qt.TabFocusReason : Qt.MouseFocusReason)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ Item {
|
|||
id: mainItem
|
||||
property var callObj
|
||||
property var contextualMenuOpenedComponent: undefined
|
||||
property bool focusPageOnNextLoad: false // Focus the page on next load - usefull cause of loader
|
||||
|
||||
signal addAccountRequest
|
||||
signal openNewCallRequest
|
||||
|
|
@ -184,6 +185,18 @@ Item {
|
|||
mainStackView.currentItem.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus the page when user select the page with keyboard.
|
||||
* Do not add this behavior on the arrows
|
||||
*/
|
||||
onEnterPressed: {
|
||||
focusPageOnNextLoad = true
|
||||
}
|
||||
onSpacePressed: {
|
||||
focusPageOnNextLoad = true
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (SettingsCpp.shortcutCount > 0) {
|
||||
var shortcuts = SettingsCpp.shortcuts;
|
||||
|
|
@ -639,6 +652,10 @@ Item {
|
|||
openContextualMenuComponent(page);
|
||||
}
|
||||
}
|
||||
onLoaded: {
|
||||
if(focusPageOnNextLoad) item.forceActiveFocus(Qt.TabFocusReason)
|
||||
focusPageOnNextLoad = false
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
active: mainStackLayout.currentIndex === 1
|
||||
|
|
@ -654,6 +671,10 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
onLoaded: {
|
||||
if(focusPageOnNextLoad) item.forceActiveFocus(Qt.TabFocusReason)
|
||||
focusPageOnNextLoad = false
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
active: mainStackLayout.currentIndex === 2
|
||||
|
|
@ -672,6 +693,10 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
onLoaded: {
|
||||
if(focusPageOnNextLoad) item.forceActiveFocus(Qt.TabFocusReason)
|
||||
focusPageOnNextLoad = false
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
|
|
@ -688,6 +713,10 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
onLoaded: {
|
||||
if(focusPageOnNextLoad) item.forceActiveFocus(Qt.TabFocusReason)
|
||||
focusPageOnNextLoad = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,17 +100,13 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
}
|
||||
Control.ScrollView {
|
||||
Flickable {
|
||||
id: scrollView
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.top: header.bottom
|
||||
anchors.topMargin: Utils.getSizeWithScreenRatio(16)
|
||||
// Workaround while the CI is made with Qt6.5.3
|
||||
// When updated to 6.8, remove this Item and
|
||||
// change the ScrollView with a Flickable
|
||||
Item{anchors.fill: parent}
|
||||
contentHeight: contentListView.contentHeight
|
||||
Control.ScrollBar.vertical: ScrollBar {
|
||||
active: contentListView.contentHeight > scrollView.height
|
||||
|
|
@ -130,21 +126,21 @@ Rectangle {
|
|||
model: mainItem.contentModel
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: Utils.getSizeWithScreenRatio(45)
|
||||
anchors.rightMargin: Utils.getSizeWithScreenRatio(45)
|
||||
anchors.leftMargin: Utils.getSizeWithScreenRatio(45)
|
||||
anchors.rightMargin: Utils.getSizeWithScreenRatio(45)
|
||||
height: contentHeight
|
||||
spacing: Utils.getSizeWithScreenRatio(10)
|
||||
spacing: Utils.getSizeWithScreenRatio(10)
|
||||
delegate: ColumnLayout {
|
||||
visible: modelData.visible != undefined ? modelData.visible: true
|
||||
Component.onCompleted: if (!visible) height = 0
|
||||
spacing: Utils.getSizeWithScreenRatio(16)
|
||||
visible: modelData.visible != undefined ? modelData.visible: true
|
||||
Component.onCompleted: if (!visible) height = 0
|
||||
spacing: Utils.getSizeWithScreenRatio(16)
|
||||
width: contentListView.width
|
||||
Rectangle {
|
||||
visible: index !== 0
|
||||
Layout.topMargin: Utils.getSizeWithScreenRatio(modelData.hideTopSeparator ? 0 : 16)
|
||||
Layout.bottomMargin: Utils.getSizeWithScreenRatio(16)
|
||||
Layout.topMargin: Utils.getSizeWithScreenRatio(modelData.hideTopSeparator ? 0 : 16)
|
||||
Layout.bottomMargin: Utils.getSizeWithScreenRatio(16)
|
||||
Layout.fillWidth: true
|
||||
height: Utils.getSizeWithScreenRatio(1)
|
||||
height: Utils.getSizeWithScreenRatio(1)
|
||||
color: modelData.hideTopSeparator ? 'transparent' : DefaultStyle.main2_500_main
|
||||
}
|
||||
GridLayout {
|
||||
|
|
@ -152,12 +148,12 @@ Rectangle {
|
|||
columns: mainItem.useVerticalLayout ? 1 : 2
|
||||
Layout.fillWidth: true
|
||||
// Layout.preferredWidth: parent.width
|
||||
rowSpacing: modelData.title.length > 0 || modelData.subTitle.length > 0 ? Utils.getSizeWithScreenRatio(20) : 0
|
||||
columnSpacing: Utils.getSizeWithScreenRatio(47)
|
||||
rowSpacing: modelData.title.length > 0 || modelData.subTitle.length > 0 ? Utils.getSizeWithScreenRatio(20) : 0
|
||||
columnSpacing: Utils.getSizeWithScreenRatio(47)
|
||||
ColumnLayout {
|
||||
Layout.preferredWidth: Utils.getSizeWithScreenRatio(341)
|
||||
Layout.maximumWidth: Utils.getSizeWithScreenRatio(341)
|
||||
spacing: Utils.getSizeWithScreenRatio(3)
|
||||
Layout.preferredWidth: Utils.getSizeWithScreenRatio(341)
|
||||
Layout.maximumWidth: Utils.getSizeWithScreenRatio(341)
|
||||
spacing: Utils.getSizeWithScreenRatio(3)
|
||||
Text {
|
||||
text: modelData.title
|
||||
visible: modelData.title.length > 0
|
||||
|
|
@ -179,10 +175,10 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.topMargin: modelData.hideTopMargin ? 0 : Utils.getSizeWithScreenRatio(mainItem.useVerticalLayout ? 10 : 21)
|
||||
Layout.bottomMargin: Utils.getSizeWithScreenRatio(21)
|
||||
Layout.leftMargin: mainItem.useVerticalLayout ? 0 : Utils.getSizeWithScreenRatio(17)
|
||||
Layout.preferredWidth: Utils.getSizeWithScreenRatio(modelData.customWidth > 0 ? modelData.customWidth : 545)
|
||||
Layout.topMargin: modelData.hideTopMargin ? 0 : Utils.getSizeWithScreenRatio(mainItem.useVerticalLayout ? 10 : 21)
|
||||
Layout.bottomMargin: Utils.getSizeWithScreenRatio(21)
|
||||
Layout.leftMargin: mainItem.useVerticalLayout ? 0 : Utils.getSizeWithScreenRatio(17)
|
||||
Layout.preferredWidth: Utils.getSizeWithScreenRatio(modelData.customWidth > 0 ? modelData.customWidth : 545)
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Loader {
|
||||
id: contentLoader
|
||||
|
|
@ -190,12 +186,22 @@ Rectangle {
|
|||
sourceComponent: modelData.contentComponent
|
||||
}
|
||||
Item {
|
||||
Layout.preferredWidth: Utils.getSizeWithScreenRatio(modelData.customRightMargin > 0 ? modelData.customRightMargin : 17)
|
||||
Layout.preferredWidth: Utils.getSizeWithScreenRatio(modelData.customRightMargin > 0 ? modelData.customRightMargin : 17)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: FocusNavigator
|
||||
|
||||
function onFocusChanged(item, keyboardFocus) {
|
||||
if(Utils.isDescendant(item,scrollView) && keyboardFocus){
|
||||
Utils.ensureVisibleY(item, scrollView)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,14 @@ AbstractMainPage {
|
|||
goToCallHistory()
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus on the first pertinent element in the page (LINQT-2202)
|
||||
* @override
|
||||
*/
|
||||
function forceActiveFocus(reason = undefined){
|
||||
listStackView.currentItem?.newCallButton?.forceActiveFocus(reason)
|
||||
}
|
||||
|
||||
//Group call properties
|
||||
property ConferenceInfoGui confInfoGui
|
||||
property AccountProxy accounts: AccountProxy {
|
||||
|
|
@ -134,6 +142,8 @@ AbstractMainPage {
|
|||
FocusScope {
|
||||
objectName: "historyListItem"
|
||||
property alias listView: historyListView
|
||||
property alias newCallButton: newCallButton
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
|
@ -187,6 +197,7 @@ AbstractMainPage {
|
|||
}
|
||||
Button {
|
||||
id: newCallButton
|
||||
focus: true
|
||||
style: ButtonStyle.noBackground
|
||||
icon.source: AppIcons.newCall
|
||||
Layout.preferredWidth: Utils.getSizeWithScreenRatio(34)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,14 @@ AbstractMainPage {
|
|||
emptyListText: qsTr("chat_empty_title")
|
||||
newItemIconSource: AppIcons.plusCircle
|
||||
|
||||
/**
|
||||
* Focus on the first pertinent element in the page (LINQT-2202)
|
||||
* @override
|
||||
*/
|
||||
function forceActiveFocus(reason = undefined){
|
||||
listStackView.currentItem?.newChatButton?.forceActiveFocus(reason)
|
||||
}
|
||||
|
||||
property AccountProxy accounts: AccountProxy {
|
||||
id: accountProxy
|
||||
}
|
||||
|
|
@ -116,6 +124,7 @@ AbstractMainPage {
|
|||
FocusScope {
|
||||
objectName: "chatListItem"
|
||||
property alias listView: chatListView
|
||||
property alias newChatButton: newChatButton
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
|
@ -158,6 +167,7 @@ AbstractMainPage {
|
|||
}
|
||||
Button {
|
||||
id: newChatButton
|
||||
focus: true
|
||||
style: ButtonStyle.noBackground
|
||||
icon.source: AppIcons.plusCircle
|
||||
Layout.preferredWidth: Utils.getSizeWithScreenRatio(28)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@ AbstractMainPage {
|
|||
emptyListText: qsTr("contacts_list_empty")
|
||||
newItemIconSource: AppIcons.plusCircle
|
||||
|
||||
/**
|
||||
* Focus on the first pertinent element in the page (LINQT-2202)
|
||||
* @override
|
||||
*/
|
||||
function forceActiveFocus(reason = undefined){
|
||||
createContactButton.forceActiveFocus(reason)
|
||||
}
|
||||
|
||||
// disable left panel contact list interaction while a contact is being edited
|
||||
property bool leftPanelEnabled: !rightPanelStackView.currentItem
|
||||
|| rightPanelStackView.currentItem.objectName
|
||||
|
|
@ -243,6 +251,7 @@ AbstractMainPage {
|
|||
visible: !rightPanelStackView.currentItem
|
||||
|| rightPanelStackView.currentItem.objectName !== "contactEdition"
|
||||
style: ButtonStyle.noBackground
|
||||
focus: true
|
||||
icon.source: AppIcons.plusCircle
|
||||
Layout.preferredWidth: Utils.getSizeWithScreenRatio(28)
|
||||
Layout.preferredHeight: Utils.getSizeWithScreenRatio(28)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,15 @@ AbstractMainPage {
|
|||
rightPanelStackTopMargin: Utils.getSizeWithScreenRatio(45)
|
||||
rightPanelStackBottomMargin: Utils.getSizeWithScreenRatio(30)
|
||||
|
||||
/**
|
||||
* Focus on the first pertinent element in the page (LINQT-2202)
|
||||
* @override
|
||||
*/
|
||||
function forceActiveFocus(reason = undefined){
|
||||
leftPanelStackView.currentItem?.newConfButton?.forceActiveFocus(reason)
|
||||
}
|
||||
|
||||
|
||||
function createPreFilledMeeting(subject, addresses) {
|
||||
mainItem.selectedConference = Qt.createQmlObject('import Linphone
|
||||
ConferenceInfoGui{
|
||||
|
|
@ -137,6 +146,7 @@ AbstractMainPage {
|
|||
id: listLayout
|
||||
FocusScope{
|
||||
property string objectName: "listLayout"
|
||||
property alias newConfButton: newConfButton
|
||||
Control.StackView.onDeactivated: {
|
||||
mainItem.selectedConference = null
|
||||
}
|
||||
|
|
@ -166,6 +176,7 @@ AbstractMainPage {
|
|||
}
|
||||
Button {
|
||||
id: newConfButton
|
||||
focus: true
|
||||
style: ButtonStyle.noBackground
|
||||
icon.source: AppIcons.plusCircle
|
||||
Layout.preferredWidth: Utils.getSizeWithScreenRatio(28)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue