linphone-desktop/Linphone/view/Control/Display/Contact/ContactListView.qml
Gaelle Braud 27b8b726bd add visible focus on button to ease key navigation
allow call from pressing enter in new call search bar if dialer open

fix key navigation in start call popup when several addresses
2026-02-09 10:39:22 +01:00

249 lines
8.1 KiB
QML

import QtQuick
import QtQuick.Layouts
import QtQuick.Controls.Basic as Control
import Linphone
import UtilsCpp 1.0
import ConstantsCpp 1.0
import SettingsCpp
import 'qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js' as Utils
import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle
ListView {
id: mainItem
property string title
property bool showInitials: true // Display Initials of Display name.
property bool showDefaultAddress: true // Display address below display name.
property bool showDisplayName: true // Display name above address.
property bool showActions: false // Display actions layout (call buttons)
property bool showContactMenu: true // Display the dot menu for contacts.
property bool showFavorites: true // Display the favorites in the header
property bool hideSuggestions: false // Hide not stored contacts (not suggestions)
property string highlightText: searchText // Bold characters in Display name.
property var sourceFlags: LinphoneEnums.MagicSearchSource.All
property bool displayNameCapitalization: true // Capitalize display name.
property bool selectionEnabled: true // Contact can be selected
property bool multiSelectionEnabled: false //Multiple items can be selected.
property list<string> selectedContacts // List of default address on selected contacts.
property FriendGui highlightedContact
// Model properties
// set searchBarText without specifying a model to bold
// matching names
property string searchText
property ConferenceInfoGui confInfoGui
property bool haveFavorites: false
property bool haveContacts: count > 0
property real sectionsPixelSize: Typography.h4.pixelSize
property real sectionsWeight: Typography.h4.weight
property real sectionsSpacing: Utils.getSizeWithScreenRatio(18)
property real itemsRightMargin: Utils.getSizeWithScreenRatio(39)
property bool expanded: true
property real headerHeight: headerItem?.height
signal contactDeletionRequested(FriendGui contact)
signal contactSelected(FriendGui contact) // Click/Space/Enter
signal addContactToSelection(var address)
signal removeContactFromSelection(var indexInSelection)
signal updatePosition()
clip: true
highlightFollowsCurrentItem: false
cacheBuffer: 400
implicitHeight: contentHeight
spacing: expanded ? Utils.getSizeWithScreenRatio(4) : 0
onVisibleChanged: if (visible && !expanded) expanded = true
onYChanged: updatePosition()
// Qt bug: sometimes, containsMouse may not be send and update on each MouseArea.
// So we need to use this variable to switch off all hovered items.
property int lastMouseContainsIndex: -1
property bool _moveToIndex: false
function selectIndex(index){
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()
updatePosition()
_moveToIndex = false
}else{// Move on the next items load.
// If visible, try to wait loading
_moveToIndex = visible
}
}else{
mainItem.currentIndex = -1
mainItem.highlightedContact = null
if(headerItem) {
headerItem.forceActiveFocus()
}
_moveToIndex = false
}
}
onContactSelected: updatePosition()
onExpandedChanged: if(!expanded) updatePosition()
keyNavigationEnabled: false
Keys.onPressed: (event)=> {
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)
event.accepted = true;
}
}else if(event.key == Qt.Key_Down && mainItem.expanded){
if(currentIndex < model.count - 1) {
selectIndex(mainItem.currentIndex+1)
event.accepted = true;
}
}
}
}
Component.onCompleted: {
if (confInfoGui) {
for(var i = 0; i < confInfoGui.core.participants.length; ++i) {
selectedContacts.push(confInfoGui.core.getParticipantAddressAt(i));
}
}
}
Connections {
target: SettingsCpp
function onLdapConfigChanged() {
if (SettingsCpp.syncLdapContacts)
magicSearchProxy.forceUpdate()
}
function onCardDAVAddressBookSynchronized() {
console.log("card dav synchro update")
magicSearchProxy.forceUpdate()
}
}
// Workaround: itemAtIndex and count are decorellated and are not enough to know when the ListView has load all its children.
// So when itemAtIndex is not available, start this timer along count changed signal.
Timer{
id: delaySelection
interval: 100
running: _moveToIndex
onTriggered: {
_moveToIndex = false
if(count > mainItem.currentIndex) selectIndex(mainItem.currentIndex)
else{
_moveToIndex = true
}
}
}
header: FocusScope{
id: headerItem
width: mainItem.width
height: headerContents.implicitHeight
ColumnLayout {
id: headerContents
width: parent.width
spacing: 0
Item{// Do not use directly RowLayout : there is an issue where the layout doesn't update on visible
Layout.fillWidth: true
Layout.preferredHeight: mainItem.count > 0 ? headerTitleLayout.implicitHeight : 0
Layout.bottomMargin: Utils.getSizeWithScreenRatio(4)
RowLayout {
id: headerTitleLayout
anchors.fill: parent
spacing: 0
// Need this because it can stay at 0 on display without manual relayouting (moving position, resize)
visible: mainItem.count > 0
Text {
text: mainItem.title
font {
pixelSize: sectionsPixelSize
weight: sectionsWeight
}
}
Item {
Layout.fillWidth: true
}
RoundButton {
id: headerExpandButton
style: ButtonStyle.noBackground
icon.source: mainItem.expanded ? AppIcons.upArrow : AppIcons.downArrow
Layout.rightMargin: mainItem.itemsRightMargin
focus: true
onClicked: mainItem.expanded = !mainItem.expanded
Rectangle {
anchors.fill: headerExpandButton
radius: headerExpandButton.width/2
visible: headerExpandButton.activeFocus
opacity: 0.5
color: DefaultStyle.main2_200
}
Accessible.name: (mainItem.expanded ?
//: Shrink %1
qsTr("shrink_accessible_name") :
//: Expand %1
qsTr("expand_accessible_name")).arg(mainItem.title)
}
}
}
}
}
delegate: ContactListItem{
id: contactItem
width: mainItem.width
focus: true
visible: mainItem.expanded
searchResultItem: $modelData
showInitials: mainItem.showInitials && isStored
showDefaultAddress: mainItem.showDefaultAddress
showDisplayName: mainItem.showDisplayName
showActions: mainItem.showActions
showContactMenu: mainItem.showContactMenu && searchResultItem.core.isStored
highlightText: mainItem.highlightText
displayNameCapitalization: mainItem.displayNameCapitalization
selectionEnabled: mainItem.selectionEnabled
multiSelectionEnabled: mainItem.multiSelectionEnabled
selectedContacts: mainItem.selectedContacts
isSelected: mainItem.highlightedContact && mainItem.highlightedContact.core == searchResultItem.core
isLastHovered: mainItem.lastMouseContainsIndex == index
previousInitial: mainItem.itemAtIndex(index-1)?.initial
itemsRightMargin: mainItem.itemsRightMargin
onIsSelectedChanged: if(isSelected) mainItem.currentIndex = index
onContactDeletionRequested: (contact) => mainItem.contactDeletionRequested(contact)
onClicked: (mouse) => {
if (mouse && mouse.button == Qt.RightButton) {
friendPopup.open()
} else {
forceActiveFocus()
mainItem.highlightedContact = contactItem.searchResultItem
if (mainItem.multiSelectionEnabled) {
var indexInSelection = mainItem.selectedContacts.indexOf(searchResultItem.core.defaultAddress)
if (indexInSelection == -1) {
mainItem.addContactToSelection(searchResultItem.core.defaultAddress)
}
else {
mainItem.removeContactFromSelection(indexInSelection)
}
}
mainItem.contactSelected(searchResultItem)
}
}
onContainsMouseChanged: (containsMouse) => {
if(containsMouse)
mainItem.lastMouseContainsIndex = index
else if( mainItem.lastMouseContainsIndex == index)
mainItem.lastMouseContainsIndex = -1
}
}
}