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:
Alexandre Jörgensen 2025-12-29 16:21:29 +01:00
parent ca452efe80
commit d2413f33a9
25 changed files with 1269 additions and 712 deletions

View file

@ -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

View file

@ -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

View file

@ -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();

View 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);
}

View 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);
};

View file

@ -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 {

View file

@ -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)
}

View file

@ -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)
}
}
}
}

View file

@ -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
}
}

View file

@ -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 {

View file

@ -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;
}
}

View file

@ -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){

View file

@ -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
}
}
}

View file

@ -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{

View file

@ -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;

View file

@ -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)
}
}

View file

@ -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
}
}
}

View file

@ -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)
}
}
}
}
}

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)