diff --git a/dialer/package/contents/ui/Dialer/ContactsList.qml b/dialer/package/contents/ui/Dialer/ContactsList.qml new file mode 100644 index 00000000..3102a21e --- /dev/null +++ b/dialer/package/contents/ui/Dialer/ContactsList.qml @@ -0,0 +1,228 @@ +/* + * Copyright 2015 Martin Klapetek + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 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 Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.0 +import QtQuick.Controls 1.3 +import QtQuick.Layouts 1.1 +import org.kde.people 1.0 as KPeople +import org.kde.kquickcontrolsaddons 2.0 as ExtraComponents +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.plasma.private.kpeoplehelper 1.0 + +Item { + PlasmaComponents.Label { + anchors.centerIn: parent + text: i18n("No contacts") + visible: contactsModel.count == 0 + } + + ColumnLayout { + anchors.fill: parent + //visible: contactsModel.count > 0 + + PlasmaComponents.ToolBar { + Layout.fillWidth: true + tools: RowLayout { + id: toolBarLayout + PlasmaComponents.TextField { + id: searchField + clearButtonShown: true + Layout.fillWidth: true + Layout.fillHeight: true + placeholderText: i18n("Search...") + } + } + } + + + PlasmaExtras.ScrollArea { + Layout.fillWidth: true + Layout.fillHeight: true + verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff + + contentItem: ListView { + id: contactsList + + property bool delegateSelected: false + + + section.property: "display" + section.criteria: ViewSection.FirstCharacter + clip: true + model:PlasmaCore.SortFilterModel { + sourceModel: KPeople.PersonsSortFilterProxyModel { + sourceModel: KPeopleHelper { + id: contactsModel + } + requiredProperties: "phoneNumber" + } + sortRole: "display" + filterRole: "display" + filterRegExp: ".*"+searchField.text+".*" + sortOrder: Qt.AscendingOrder + } + + // PlasmaCore.SortFilterModel { + // sortRole: "display" + // sourceModel: + // } + + boundsBehavior: Flickable.StopAtBounds + highlight: PlasmaComponents.Highlight { + hover: contactsList.focus + } + highlightMoveDuration: 0 + + delegate: PlasmaComponents.ListItem { + height: units.gridUnit * 6 + enabled: true + clip: true + opacity: contactsList.delegateSelected && contactsList.currentIndex != index ? 0.4 : 1 + + onClicked: { + if (contactsList.delegateSelected) { + contactsList.currentIndex = -1; + contactsList.delegateSelected = false; + } else { + contactsList.currentIndex = index; + contactsList.delegateSelected = true; + } + + contactsList.toggleOverlayButtons(contactsList.delegateSelected); + } + + + RowLayout { + id: mainLayout + anchors.fill: parent + + ExtraComponents.QPixmapItem { + id: avatarLabel + + Layout.maximumWidth: parent.height + Layout.minimumWidth: parent.height + Layout.fillHeight: true + + pixmap: model.decoration + fillMode: ExtraComponents.QPixmapItem.PreserveAspectFit + smooth: true + } + + ColumnLayout { + Layout.fillHeight: true + Layout.fillWidth: true + + Label { + id: nickLabel + + Layout.fillWidth: true + + text: model.display + elide: Text.ElideRight + } + + Label { + id: dataLabel + + Layout.fillWidth: true + + text: model.phoneNumber + elide: Text.ElideRight + visible: dataLabel.text != nickLabel.text + } + + } + } + } + + function toggleOverlayButtons(show) { + if (show) { + settingsRect.parent = contactsList.currentItem + settingsRect.visible = true; + + callRect.parent = contactsList.currentItem + callRect.visible = true; + } else { + settingsRect.visible = false; + callRect.visible = false; + } + } + + Rectangle { + id: settingsRect + height: units.gridUnit * 6 + width: height + units.gridUnit * 2 + radius: 45 + z: 100 + visible: false + color: "lightblue" + + anchors { + left: parent.left + leftMargin: -width/2 + verticalCenter: parent.verticalCenter + } + + + PlasmaCore.IconItem { + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.rightMargin: parent.height / 4 + source: "configure-shortcuts" + } + } + + Rectangle { + id: callRect + height: settingsRect.height + width: settingsRect.width + radius: height + z: 100 + visible: false + color: "lightgreen" + + anchors { + right: parent.right + rightMargin: -width/2 + verticalCenter: parent.verticalCenter + } + + PlasmaCore.IconItem { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: parent.height / 4 + source: "call-start" + } + MouseArea { + anchors.fill: parent + //TODO: needs the proper number + onClicked: call(model.phoneNumber) + } + } + + CustomSectionScroller { + listView: contactsList + } + + } + } + } +} diff --git a/dialer/package/contents/ui/Dialer/CustomSectionScroller.qml b/dialer/package/contents/ui/Dialer/CustomSectionScroller.qml new file mode 100644 index 00000000..6e64f40c --- /dev/null +++ b/dialer/package/contents/ui/Dialer/CustomSectionScroller.qml @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Components project. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.1 +import QtQuick.Layouts 1.1 + +import "private/SectionScroller.js" as Sections +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.plasma.components 2.0 as PlasmaComponents + +/** + * Similar to a ScrollBar or a ScrollDecorator. + * + * It's interactive and works on ListViews that have section.property set, so + * its contents are categorized. + * + * An indicator will say to what category the user scrolled to. Useful for + * things like address books or things sorted by date. Don't use with models + * too big (thousands of items) because it implies loading all the items to + * memory, as well loses precision. + * + * @inherit QtQuick.Item + */ +Item { + id: root + + /** + * The listview the sectionScroller will operate on. This component doesn't + * work with Flickable or GridView. + */ + property ListView listView + + onListViewChanged: { + if (listView && listView.model) + internal.initDirtyObserver(); + } + + Connections { + target: listView + onModelChanged: { + if (listView && listView.model) { + internal.initDirtyObserver() + } + } + } + + implicitWidth: scrollBar.implicitWidth + Behavior on opacity { + NumberAnimation { + duration: units.longDuration + } + } + + anchors { + right: listView.right + top: listView.top + bottom: listView.bottom + } + + + PlasmaComponents.RangeModel { + id: range + + minimumValue: 0 + maximumValue: Math.max(0, listView.contentHeight - listView.height) + stepSize: 0 + //inverted: true + positionAtMinimum: root.width*2 + positionAtMaximum: root.height - root.width*2 + value: listView.contentY + onPositionChanged: { + var section = Sections.closestSection(position/listView.height); + if (section) { + if (listView.section.criteria == ViewSection.FirstCharacter) { + sectionLabel.text = section[0]; + } else { + sectionLabel.text = section; + } + } + } + + } + + PlasmaComponents.ScrollBar { + id: scrollBar + flickableItem: listView + anchors.fill: parent + interactive: true + } + PlasmaCore.FrameSvgItem { + id: tooltip + imagePath: "widgets/background" + width: units.gridUnit * 5 + margins.left + margins.right + height: sectionLabel.height + /*subtitle.height +*/ margins.top + margins.bottom + + ColumnLayout { + anchors.centerIn: parent + + PlasmaExtras.Title { + id: sectionLabel + Layout.fillWidth: true + Layout.fillHeight: true + horizontalAlignment: Text.AlignHCenter + } + +// PlasmaComponents.Label { +// id: subtitle +// Layout.fillWidth: true +// horizontalAlignment: Text.AlignHCenter +// visible: text.length > 0 +// text: "Thursday, 7th" +// } + + } + y: 0 + x: -listView.width/2 - width/2 + + opacity: sectionLabel.text && scrollBar.pressed ? 1 : 0 + Behavior on opacity { + NumberAnimation { + duration: units.longDuration + } + } + } + + + Timer { + id: dirtyTimer + interval: 250 + onTriggered: { + Sections.initSectionData(listView); + internal.modelDirty = false; + tooltip.visible = Sections._sections.length > 1 + } + } + QtObject { + id: internal + + property bool modelDirty: false + function initDirtyObserver() { + Sections.initSectionData(listView); + tooltip.visible = Sections._sections.length > 1 + function dirtyObserver() { + if (!internal.modelDirty) { + internal.modelDirty = true; + dirtyTimer.running = true; + } + } + + if (listView.model.countChanged) + listView.model.countChanged.connect(dirtyObserver); + + if (listView.model.itemsChanged) + listView.model.itemsChanged.connect(dirtyObserver); + + if (listView.model.itemsInserted) + listView.model.itemsInserted.connect(dirtyObserver); + + if (listView.model.itemsMoved) + listView.model.itemsMoved.connect(dirtyObserver); + + if (listView.model.itemsRemoved) + listView.model.itemsRemoved.connect(dirtyObserver); + } + } + Accessible.role: Accessible.ScrollBar +} diff --git a/dialer/package/contents/ui/Dialer/DialPage.qml b/dialer/package/contents/ui/Dialer/DialPage.qml index 7e7bf343..1b9e0574 100644 --- a/dialer/package/contents/ui/Dialer/DialPage.qml +++ b/dialer/package/contents/ui/Dialer/DialPage.qml @@ -33,7 +33,7 @@ Column { History { id: history } - Contacts { + ContactsList { id: contacts } Dialer { diff --git a/dialer/package/contents/ui/Dialer/private/SectionScroller.js b/dialer/package/contents/ui/Dialer/private/SectionScroller.js new file mode 100644 index 00000000..7b987fed --- /dev/null +++ b/dialer/package/contents/ui/Dialer/private/SectionScroller.js @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Components project. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** $QT_END_LICENSE$ +** +****************************************************************************/ + +var _sectionData = []; +var _sections = []; + +function initSectionData(list) { + if (!list || !list.model) return; + _sectionData = []; + _sections = []; + var current = ""; + var prop = list.section.property; + + for (var i = 0, count = list.model.count; i < count; i++) { + var item = list.model.get(i); + if (item[prop] !== current) { + current = item[prop]; + _sections.push(current); + _sectionData.push({ index: i, header: current }); + } + } +} + +function closestSection(pos) { + var tmp = (_sections.length) * pos; + var val = Math.ceil(tmp) // TODO: better algorithm + val = val < 2 ? 1 : val; + return _sections[val-1]; +} + +function indexOf(sectionName) { + var val = _sectionData[_sections.indexOf(sectionName)].index; + return val === 0 || val > 0 ? val : -1; +}