linux.x86.linphone/Linphone/view/Control/Display/Contact/ContactListView.qml
2024-11-19 14:08:08 +00:00

484 lines
17 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
ListView {
id: mainItem
property bool showInitials: true // Display Initials of Display name.
property bool showDefaultAddress: true // Display address below display name.
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 selectedContact//: model.getAt(currentIndex) || null
property bool searchOnInitialization: false
property bool loading: false
property bool pauseSearch: false // true = don't search on text change
// Model properties
// set searchBarText without specifying a model to bold
// matching names
property string searchBarText
property string searchText// Binding is done on searchBarTextChanged
property ConferenceInfoGui confInfoGui
property bool haveFavorites: false
property bool haveContacts: count > 0 || (showFavorites && headerItem.list.count > 0)
property int sectionsPixelSize: 16 * DefaultStyle.dp
property int sectionsWeight: 800 * DefaultStyle.dp
property int sectionsSpacing: 18 * DefaultStyle.dp
property int itemsRightMargin: 39 * DefaultStyle.dp
signal resultsReceived()
signal contactStarredChanged()
signal contactDeletionRequested(FriendGui contact)
signal contactAddedToSelection(string address)
signal contactRemovedFromSelection(string address)
signal contactClicked(FriendGui contact)
clip: true
highlightFollowsCurrentItem: true
cacheBuffer: 400
// Binding loop hack
onContentHeightChanged: Qt.callLater(function(){cacheBuffer = Math.max(0,contentHeight)})
function selectContact(address) {
var index = magicSearchProxy.findFriendIndexByAddress(address)
if (index != -1) {
mainItem.currentIndex = index
}
return index
}
function addContactToSelection(address) {
if (multiSelectionEnabled) {
var indexInSelection = selectedContacts.indexOf(address)
if (indexInSelection == -1) {
selectedContacts.push(address)
contactAddedToSelection(address)
}
}
}
function removeContactFromSelection(indexInSelection) {
var addressToRemove = selectedContacts[indexInSelection]
if (indexInSelection != -1) {
selectedContacts.splice(indexInSelection, 1)
contactRemovedFromSelection(addressToRemove)
}
}
function removeSelectedContactByAddress(address) {
var index = selectedContacts.indexOf(address)
if (index != -1) {
selectedContacts.splice(index, 1)
contactRemovedFromSelection(address)
}
}
function haveAddress(address){
var index = magicSearchProxy.findFriendIndexByAddress(address)
return index != -1
}
onResultsReceived: {
loading = false
mainItem.positionViewAtBeginning()
}
onSearchBarTextChanged: {
loading = true
if(!pauseSearch) {
searchText = searchBarText.length === 0 ? "*" : searchBarText
}
}
onPauseSearchChanged: {
if(!pauseSearch){
searchText = searchBarText.length === 0 ? "*" : searchBarText
}
}
onAtYEndChanged: if(atYEnd) magicSearchProxy.displayMore()
keyNavigationEnabled: false
Keys.onPressed: (event)=> {
if(header.activeFocus) return;
if(event.key == Qt.Key_Up || event.key == Qt.Key_Down){
if (currentIndex == 0 && event.key == Qt.Key_Up) {
if( headerItem.list.count > 0) {
mainItem.highlightFollowsCurrentItem = false
currentIndex = -1
headerItem.list.currentIndex = headerItem.list.count -1
var item = headerItem.list.itemAtIndex(headerItem.list.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
headerItem.updatePosition()
event.accepted = true;
}else{
mainItem.currentIndex = mainItem.count - 1
var item = itemAtIndex(mainItem.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
event.accepted = true;
}
}else if(currentIndex >= mainItem.count -1 && event.key == Qt.Key_Down){
if( headerItem.list.count > 0) {
mainItem.highlightFollowsCurrentItem = false
mainItem.currentIndex = -1
headerItem.list.currentIndex = 0
var item = headerItem.list.itemAtIndex(headerItem.list.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
headerItem.updatePosition()
event.accepted = true;
}else{
mainItem.currentIndex = 0
var item = itemAtIndex(mainItem.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
event.accepted = true;
}
}else if(event.key == Qt.Key_Up){
mainItem.highlightFollowsCurrentItem = true
var item = itemAtIndex(--mainItem.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
event.accepted = true;
}else if(event.key == Qt.Key_Down){
mainItem.highlightFollowsCurrentItem = true
var item = itemAtIndex(++mainItem.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
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
onLdapConfigChanged: {
if (SettingsCpp.syncLdapContacts)
magicSearchProxy.forceUpdate()
}
}
Control.ScrollBar.vertical: ScrollBar {
id: scrollbar
rightPadding: 8 * DefaultStyle.dp
topPadding: mainItem.haveFavorites ? 24 * DefaultStyle.dp : 0 // Avoid to be on top of collapse button
active: true
interactive: true
policy: mainItem.contentHeight > mainItem.height ? Control.ScrollBar.AlwaysOn : Control.ScrollBar.AlwaysOff
}
model: MagicSearchProxy {
id: magicSearchProxy
searchText: mainItem.searchText
aggregationFlag: LinphoneEnums.MagicSearchAggregation.Friend
sourceFlags: mainItem.sourceFlags
hideSuggestions: mainItem.hideSuggestions
showLdapContacts: mainItem.searchText != '*' && mainItem.searchText != '' || SettingsCpp.syncLdapContacts
initialDisplayItems: 20
onLocalFriendCreated: (index) => {
var item = itemAtIndex(index)
if(item){
mainItem.currentIndex = index
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
}
}
onInitialized: {
mainItem.loading = true
magicSearchProxy.forceUpdate()
}
onModelReset: mainItem.resultsReceived()
}
section.property: "isStored"
//section.criteria: ViewSection.FirstCharacter
section.delegate: Item{
width: mainItem.width
height: textItem.implicitHeight + sectionsSpacing * 2
required property bool section
Text {
id: textItem
anchors.fill: parent
text: section ? qsTr("Contacts") : qsTr("Suggestions")
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
font {
pixelSize: sectionsPixelSize
weight: sectionsWeight
}
}
}
header: FocusScope{
id: headerItem
width: mainItem.width
height: favoritesContents.implicitHeight
property alias list: favoriteList
// Hack because changing currentindex change focus.
Timer{
id: focusDelay
interval: 10
onTriggered: {
mainItem.highlightFollowsCurrentItem = !headerItem.activeFocus
}
}
onActiveFocusChanged:focusDelay.restart()
//---------------------------------------------------
function updatePosition(){
var item = favoriteList.itemAtIndex(favoriteList.currentIndex)
if( item){
// For debugging just in case
//var listPosition = item.mapToItem(favoriteList, item.x, item.y)
//var newPosition = favoriteList.mapToItem(mainItem, listPosition.x, listPosition.y)
//console.log("item pos: " +item.x + " / " +item.y)
//console.log("fav pos: " +favoriteList.x + " / " +favoriteList.y)
//console.log("fav content: " +favoriteList.contentX + " / " +favoriteList.contentY)
//console.log("main pos: " +mainItem.x + " / " +mainItem.y)
//console.log("main content: " +mainItem.contentX + " / " +mainItem.contentY)
//console.log("list pos: " +listPosition.x + " / " +listPosition.y)
//console.log("new pos: " +newPosition.x + " / " +newPosition.y)
//console.log("header pos: " +headerItem.x + " / " +headerItem.y)
//console.log("Moving to " + (headerItem.y+item.y))
mainItem.contentY = headerItem.y+item.y
}
}
ColumnLayout {
id: favoritesContents
width: parent.width
spacing: mainItem.haveFavorites ? sectionsSpacing : 0
BusyIndicator {
Layout.alignment: Qt.AlignCenter
Layout.preferredHeight: visible ? 60 * DefaultStyle.dp : 0
Layout.preferredWidth: 60 * DefaultStyle.dp
indicatorHeight: 60 * DefaultStyle.dp
indicatorWidth: 60 * DefaultStyle.dp
visible: mainItem.loading
indicatorColor: DefaultStyle.main1_500_main
}
Item{// Do not use directly RowLayout : there is an issue where the layout doesn't update on visible
Layout.fillWidth: true
Layout.preferredHeight: mainItem.haveFavorites ? favoriteTitle.implicitHeight : 0
RowLayout {
id: favoriteTitle
anchors.fill: parent
spacing: 0
// Need this because it can stay at 0 on display without manual relayouting (moving position, resize)
visible: mainItem.haveFavorites
onVisibleChanged: if(visible) {
Qt.callLater(mainItem.positionViewAtBeginning)// If not later, the view will not move to favoris at startup
}
Text {
//Layout.fillHeight: true
text: qsTr("Favoris")
font {
pixelSize: sectionsPixelSize
weight: sectionsWeight
}
}
Item {
Layout.fillWidth: true
}
Button {
id: favoriteExpandButton
background: Item{}
icon.source: favoriteList.visible ? AppIcons.upArrow : AppIcons.downArrow
Layout.fillHeight: true
Layout.preferredWidth: height
//Layout.preferredWidth: 24 * DefaultStyle.dp
//Layout.preferredHeight: 24 * DefaultStyle.dp
Layout.rightMargin: 23 * DefaultStyle.dp
icon.width: 24 * DefaultStyle.dp
icon.height: 24 * DefaultStyle.dp
focus: true
onClicked: favoriteList.visible = !favoriteList.visible
KeyNavigation.down: favoriteList
}
}
}
ListView{
id: favoriteList
Layout.fillWidth: true
Layout.preferredHeight: count > 0 ? contentHeight : 0// Show full and avoid scrolling
onCountChanged: mainItem.haveFavorites = count > 0
Keys.onPressed: (event)=> {
if(event.key == Qt.Key_Up || event.key == Qt.Key_Down) {
if (favoriteList.currentIndex == 0 && event.key == Qt.Key_Up) {
if( mainItem.count > 0) {
mainItem.highlightFollowsCurrentItem = true
favoriteList.currentIndex = -1
mainItem.currentIndex = mainItem.count-1
var item = mainItem.itemAtIndex(mainItem.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
event.accepted = true;
}else{
favoriteList.currentIndex = favoriteList.count - 1
var item = itemAtIndex(favoriteList.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
event.accepted = true;
}
}else if(currentIndex >= favoriteList.count -1 && event.key == Qt.Key_Down) {
if( mainItem.count > 0) {
mainItem.highlightFollowsCurrentItem = true
favoriteList.currentIndex = -1
mainItem.currentIndex = 0
var item = mainItem.itemAtIndex(mainItem.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
event.accepted = true;
}else{
favoriteList.currentIndex = 0
var item = itemAtIndex(favoriteList.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
event.accepted = true;
}
}else if(event.key == Qt.Key_Up){
mainItem.highlightFollowsCurrentItem = false
var item = itemAtIndex(--favoriteList.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
headerItem.updatePosition()
event.accepted = true;
}else if(event.key == Qt.Key_Down){
mainItem.highlightFollowsCurrentItem = false
var item = itemAtIndex(++favoriteList.currentIndex)
mainItem.selectedContact = item.searchResultItem
item.forceActiveFocus()
headerItem.updatePosition()
event.accepted = true;
}
}
}
property MagicSearchProxy proxy: MagicSearchProxy{
parentProxy: mainItem.model
showFavoritesOnly: true
hideSuggestions: mainItem.hideSuggestions
}
model : showFavorites && mainItem.searchBarText == '' ? proxy : []
delegate: ContactListItem{
width: favoriteList.width
focus: true
searchResultItem: $modelData
showInitials: mainItem.showInitials
showDefaultAddress: mainItem.showDefaultAddress
showActions: mainItem.showActions
showContactMenu: mainItem.showContactMenu
highlightText: mainItem.highlightText
displayNameCapitalization: mainItem.displayNameCapitalization
itemsRightMargin: mainItem.itemsRightMargin
selectionEnabled: mainItem.selectionEnabled
multiSelectionEnabled: mainItem.multiSelectionEnabled
selectedContacts: mainItem.selectedContacts
isSelected: mainItem.selectedContact && mainItem.selectedContact.core == searchResultItem.core
previousInitial: ''//favoriteList.count > 0 ? favoriteList.itemAtIndex(index-1)?.initial : '' // Binding on count
initial: '' // Hide initials but keep space
onIsSelectedChanged: if(isSelected) favoriteList.currentIndex = index
onContactStarredChanged: mainItem.contactStarredChanged()
onContactDeletionRequested: (contact) => mainItem.contactDeletionRequested(contact)
onClicked: (mouse) => {
mainItem.highlightFollowsCurrentItem = false
favoriteList.currentIndex = index
mainItem.selectedContact = searchResultItem
forceActiveFocus()
headerItem.updatePosition()
if (mainItem.multiSelectionEnabled) {
var indexInSelection = mainItem.selectedContacts.indexOf(searchResultItem.core.defaultAddress)
if (indexInSelection == -1) {
mainItem.addContactToSelection(searchResultItem.core.defaultAddress)
}
else {
mainItem.removeContactFromSelection(indexInSelection, 1)
}
}
mainItem.contactClicked(searchResultItem)
}
}
}
}
}
delegate: ContactListItem{
id: contactItem
width: mainItem.width
focus: true
searchResultItem: $modelData
showInitials: mainItem.showInitials && isStored
showDefaultAddress: mainItem.showDefaultAddress
showActions: mainItem.showActions
showContactMenu: searchResultItem.core.isStored
highlightText: mainItem.highlightText
displayNameCapitalization: mainItem.displayNameCapitalization
itemsRightMargin: mainItem.itemsRightMargin
selectionEnabled: mainItem.selectionEnabled
multiSelectionEnabled: mainItem.multiSelectionEnabled
selectedContacts: mainItem.selectedContacts
isSelected: mainItem.selectedContact && mainItem.selectedContact.core == searchResultItem.core
previousInitial: mainItem.itemAtIndex(index-1)?.initial
onIsSelectedChanged: if(isSelected) mainItem.currentIndex = index
onContactStarredChanged: mainItem.contactStarredChanged()
onContactDeletionRequested: (contact) => mainItem.contactDeletionRequested(contact)
onClicked: (mouse) => {
mainItem.highlightFollowsCurrentItem = true
if (mouse && mouse.button == Qt.RightButton) {
friendPopup.open()
} else {
forceActiveFocus()
if(mainItem.selectedContact && mainItem.selectedContact.core != contactItem.searchResultItem.core)
headerItem.list.currentIndex = -1
mainItem.selectedContact = 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, 1)
}
}
mainItem.contactClicked(searchResultItem)
}
}
}
}