diff --git a/src/callview/qml/actiontoolbar.qml b/src/callview/qml/actiontoolbar.qml index b4e473c5..a51ddc88 100644 --- a/src/callview/qml/actiontoolbar.qml +++ b/src/callview/qml/actiontoolbar.qml @@ -1,280 +1,283 @@ /*************************************************************************** * Copyright (C) 2015 by Emmanuel Lepage Vallee * * Copyright (C) 2017 by Bluesystems * * Author : Emmanuel Lepage Vallee * * * * 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 . * **************************************************************************/ import QtQuick 2.0 import Ring 1.0 import QtQuick.Layouts 1.0 import RingQmlWidgets 1.0 Rectangle { id: toolbar color: "#55000000" height: actionGrid.contentHeight width:parent.width y:parent.height-toolbar.height -10 z: 100 property var userActionModel: null Timer { id: hideLabel running: false repeat: false interval: 5000 onTriggered: { currentText.visible = false } } // Use a separate label. This allows to use only icons in the buttons, // this reducing the footprint and avoiding a second row. Rectangle { id: currentText clip: true color: "#333333" height: 20 width: 200 radius: 99 // circle visible: false anchors.horizontalCenter: parent.horizontalCenter y: - 30 Text { id: currentTextText anchors.fill: parent color: "white" font.bold : true horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter onContentWidthChanged: parent.width = contentWidth + 20 onContentHeightChanged: parent.height = contentHeight + 10 } Behavior on width { NumberAnimation {duration: 50} } } // Show the accept and hangup buttons in green and red function selectColor(action) { if (action == UserActionModel.HANGUP) return "#550000"; else if(action == UserActionModel.ACCEPT) return "#005500" // Default return "#CC222222" } function selectLabelColor(action) { if (action == UserActionModel.HANGUP || action == UserActionModel.ACCEPT) return "white" // Default - return activePalette.text + return "white" } Component { id: actionDelegate Item { id: mainArea width: actionGrid.cellWidth height: actionGrid.cellHeight Rectangle { id: background color: mouseArea.containsMouse ? "#CC333333" : selectColor(action) radius: 99 // circle anchors.leftMargin: 5 anchors.rightMargin: 5 anchors.fill: parent border.width: mouseArea.containsMouse ? 3 : 0 border.color: "#dd5555" RowLayout { anchors.margins: 15 anchors.fill: parent PixmapWrapper { anchors.verticalCenter: parent.verticalCenter pixmap: decoration width: 30 height: 30 } Text { id: label text: display visible: false horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter color: selectLabelColor(action) font.bold: true anchors.leftMargin: 10 Layout.fillHeight: true Layout.fillWidth: true anchors.verticalCenter: parent.verticalCenter } } MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: true z: 101 onClicked: { userActionModel.execute(action) } onContainsMouseChanged: { if (containsMouse) { currentText.visible = true currentTextText.text = display } hideLabel.restart() } } Behavior on color { ColorAnimation {duration: 300} } Behavior on border.width { NumberAnimation {duration: 200} } StateGroup { id: stateGroup states: [ State { name: "" when: actionGrid.count > 2 || actionGrid.count == 0 PropertyChanges { target: background radius: 99 + anchors.margins: 0 } PropertyChanges { target: mainArea width: 70 } PropertyChanges { target: label visible: false } }, State { name: "single" when: actionGrid.count == 1 PropertyChanges { target: background radius: 5 + anchors.margins: 2 } PropertyChanges { target: mainArea width: (toolbar.width/1) } PropertyChanges { target: label visible: true } }, State { name: "two" when: actionGrid.count == 2 PropertyChanges { target: background radius: 5 + anchors.margins: 2 } PropertyChanges { target: mainArea width: (toolbar.width/2) } PropertyChanges { target: label visible: true } } ] } } } } GridView { id: actionGrid anchors.fill: parent model: CallModel.userActionModel.activeActionModel delegate: actionDelegate cellWidth: 70; cellHeight: 60 StateGroup { id: stateGroup states: [ State { name: "" when: actionGrid.count > 2 || actionGrid.count == 0 PropertyChanges { target: actionGrid cellWidth: 70 } }, State { name: "single2" when: actionGrid.count == 1 PropertyChanges { target: actionGrid cellWidth: (toolbar.width/1) } }, State { name: "two2" when: actionGrid.count == 2 PropertyChanges { target: actionGrid cellWidth: (toolbar.width/2) } } ] } } // Hide the label when the mouse is out MouseArea { z: -100 anchors.fill: parent hoverEnabled: true onContainsMouseChanged: { if (!containsMouse) currentText.visible = false } } onVisibleChanged: { if (!visible) currentText.visible = false } onUserActionModelChanged: { if (!userActionModel) { userActionModel = CallModel.userActionModel return } actionGrid.model = (userActionModel && userActionModel.activeActionModel) ? userActionModel.activeActionModel : CallModel.userActionModel.activeActionModel } } diff --git a/src/dialview/qml/calldelegateitem.qml b/src/dialview/qml/calldelegateitem.qml index 05d03718..d7484a09 100644 --- a/src/dialview/qml/calldelegateitem.qml +++ b/src/dialview/qml/calldelegateitem.qml @@ -1,427 +1,442 @@ /****************************************************************************** * Copyright (C) 2012 by Savoir-Faire Linux * * Author : Emmanuel Lepage Vallee * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) any later version. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the Lesser GNU General Public License * * along with this program. If not, see . * *****************************************************************************/ import QtQuick 2.0 import QtQuick.Layouts 1.0 import Ring 1.0 import RingQmlWidgets 1.0 import org.kde.kirigami 2.2 as Kirigami +import ContactView 1.0 Rectangle { id: callDelegateItem anchors.margins: 2 radius: 5 + border.width: 0 color: selected ? activePalette.highlight: "transparent" height: content.implicitHeight + 20 + errorMessage.height property bool selected: object == CallModel.selectedCall Drag.active: mouseArea.drag.active Drag.dragType: Drag.Automatic Drag.onDragStarted: { var ret = treeHelper.mimeData(model.rootIndex, index) Drag.mimeData = ret } Behavior on height { NumberAnimation {duration: 200; easing.type: Easing.OutQuad } } TreeHelper { id: treeHelper model: CallModel } Drag.onDragFinished: { if (dropAction == Qt.MoveAction) { item.display = "hello" } } OutlineButton { id: closeButton label: i18n("Close") anchors.right: parent.right anchors.top: parent.top anchors.margins: 3 height: 24 visible: false z: 100 alignment: Qt.AlignRight icon: "image://SymbolicColorizer/:/sharedassets/outline/close.svg" onClicked: { object.performAction(Call.REFUSE) } } RowLayout { id: content spacing: 10 width: parent.width - 4 - PixmapWrapper { - pixmap: decoration + ContactPhoto { + contactMethod: object.peerContactMethod height:40 width:40 + drawEmptyOutline: false + defaultColor:callDelegateItem.selected ? + activePalette.highlightedText : activePalette.text anchors.verticalCenter: parent.verticalCenter } Column { Layout.fillWidth: true Text { text: display width: parent.width wrapMode: Text.WrapAnywhere color: callDelegateItem.selected ? activePalette.highlightedText : activePalette.text font.bold: true } Text { text: model.number width: parent.width wrapMode: Text.WrapAnywhere color: callDelegateItem.selected ? activePalette.highlightedText : activePalette.text } } } DropArea { anchors.fill: parent keys: ["text/ring.call.id", "text/plain"] onEntered: { callDelegateItem.color = "red" } onExited: { callDelegateItem.color = "blue" } onDropped: { var formats = drop.formats var ret = {} ret["x-ring/dropaction"] = "join" // stupid lack of official APIs... for(var i=0; i< formats.length; i++) { ret[formats[i]] = drop.getDataAsArrayBuffer(formats[i]) } treeHelper.dropMimeData2(ret, model.rootIndex, index) } } MouseArea { id: mouseArea anchors.fill: parent propagateComposedEvents: true onClicked: { mouse.accepted = true CallModel.selectedCall = object dialView.selectCall(object) } drag.target: callDelegateItem } Loader { id: completionLoader active: false opacity: 0 anchors.bottom: parent.bottom anchors.bottomMargin: 10 width: callDelegateItem.width height: 0 Behavior on height { NumberAnimation {duration: 200; easing.type: Easing.OutQuad } } Behavior on opacity { NumberAnimation {duration: 200; easing.type: Easing.OutQuad } } sourceComponent: Component { ListView { + clip: true anchors.fill: parent anchors.margins: 10 spacing: 10 model: CompletionModel currentIndex: completionSelection.currentIndex delegate: Rectangle { property bool selected: ListView.isCurrentItem height: searchDelegate.height width: callDelegateItem.width - 20 color: selected ? activePalette.highlightedText : "transparent" border.width: 1 border.color: activePalette.highlightedText radius: 5 clip: true CompletionDelegate { id: searchDelegate - textColor: parent.selected ? - activePalette.highlight : activePalette.highlightedText - altTextColor: parent.selected ? - activePalette.highlight : activePalette.highlightedText showPhoto: false showControls: false showSeparator: false - height: 3*fontMetrics.height labelHeight: fontMetrics.height } } onCountChanged: { - completionLoader.height = Math.min(4, count)*(3*fontMetrics.height+10) + completionLoader.height = Math.min(4, count)*(3*fontMetrics.height+12) callDelegateItem.height = content.implicitHeight + Math.min(4, count)*(3*fontMetrics.height+10) + 10 } } } } Loader { id: errorMessage anchors.top: content.bottom active: false width: parent.width height: active ? item.implicitHeight : 0 sourceComponent: Component { CallError { call: object width: parent.width } } } Loader { id: outgoingMessage anchors.top: content.bottom active: false width: parent.width height: active ? item.implicitHeight : 0 sourceComponent: Component { OutgoingCall { call: object width: parent.width } } } Loader { id: finishedMessage anchors.top: content.bottom active: false width: parent.width height: active ? item.implicitHeight : 0 sourceComponent: Component { FinishedCall { call: object width: parent.width } } } Loader { id: currentMessage anchors.top: content.bottom active: false width: parent.width height: active ? item.implicitHeight : 0 sourceComponent: Component { CurrentCall { call: object width: parent.width } } } Loader { id: missedMessage active: false width: parent.width height: active ? item.implicitHeight : 0 sourceComponent: Component { MissedCall { call: object width: parent.width } } } Loader { id: rigningAnimation active: false width: parent.width height: active ? 32 : 0 anchors.top: content.bottom anchors.horizontalCenter: parent.horizontalCenter sourceComponent: Component { IncomingCall { //call: object width: parent.width } } } StateGroup { id: stateGroup states: [ + State { + name: "" + + PropertyChanges { + target: callDelegateItem + border.width: 0 + } + + }, State { name: "dialing" when: selected && object.state == Call.DIALING PropertyChanges { target: callDelegateItem + border.width: 0 height: content.implicitHeight + Math.min(4, count)*(3*fontMetrics.height+10) + 10 } PropertyChanges { target: completionLoader active: true opacity: 1 height: Math.min(4, count)*(3*fontMetrics.height+10) + 10 } PropertyChanges { target: closeButton visible: false } }, State { name: "error" when: lifeCycleState == Call.FINISHED && object.state != Call.OVER && object.state != Call.ABORTED PropertyChanges { target: errorMessage active: true } PropertyChanges { target: callDelegateItem color: "#33ff0000" border.width: 1 border.color: "#55ff0000" } PropertyChanges { target: closeButton visible: true } }, State { name: "missed" when: object.state == Call.OVER && object.missed PropertyChanges { target: missedMessage active: true } PropertyChanges { target: content visible: false } PropertyChanges { target: callDelegateItem height: missedMessage.height color: "#33ff0000" border.width: 1 border.color: "#55ff0000" } PropertyChanges { target: closeButton visible: true } }, State { name: "incoming" when: object.state == 1/*iNCOMING*/ PropertyChanges { target: callDelegateItem + border.width: 0 height: rigningAnimation.height + content.implicitHeight + 3 } PropertyChanges { target: rigningAnimation active: true } PropertyChanges { target: closeButton visible: false } }, State { name: "outgoing" when: object.lifeCycleState == Call.INITIALIZATION && object.direction == 1/*OUTGOING*/ PropertyChanges { target: callDelegateItem + border.width: 0 height: outgoingMessage.height + content.implicitHeight } PropertyChanges { target: outgoingMessage active: true } PropertyChanges { target: closeButton visible: false } }, State { name: "finished" when: object.state == Call.OVER PropertyChanges { target: callDelegateItem + border.width: 0 height: finishedMessage.height + content.implicitHeight } PropertyChanges { target: finishedMessage active: true } PropertyChanges { target: closeButton visible: true } }, State { name: "current" when: object.lifeCycleState == Call.PROGRESS PropertyChanges { target: callDelegateItem + border.width: 0 height: currentMessage.height + content.implicitHeight } PropertyChanges { target: currentMessage active: true } PropertyChanges { target: closeButton visible: false } } ] } } //Call delegate diff --git a/src/dialview/qml/dialpad.qml b/src/dialview/qml/dialpad.qml index 97af1ef6..76de71f9 100644 --- a/src/dialview/qml/dialpad.qml +++ b/src/dialview/qml/dialpad.qml @@ -1,185 +1,184 @@ /****************************************************************************** * Copyright (C) 2012 by Savoir-Faire Linux * * Author : Emmanuel Lepage Vallee * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) any later version. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the Lesser GNU General Public License * * along with this program. If not, see . * *****************************************************************************/ import QtQuick 2.0 Item { id:dialPad property var buttonsText: Array("1","2","3","4","5","6","7","8","9","*","0","#") property var buttonsMap: Array("","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz","","+","","") property var mapper: {} Item { anchors.margins: 5 //Attributes anchors.fill: parent //Signals signal numbrePressed(string number) //Content Grid { columns: 3 spacing: 3 width: (height/4)*3 height: parent.height anchors.horizontalCenter: parent.horizontalCenter Repeater { model: 12 Rectangle { id: key property alias state: stateGroup.state //Attributes width: Math.min((parent.width-3.333)/3 -1, dialPad.height/4 - 2) height: dialPad.height/4 - 2 color: activePalette.highlight radius: 999 border.width: 0 - border.color: activePalette.text + border.color: activePalette.highlightedText Behavior on border.width { NumberAnimation { duration: 200 } } Rectangle { id: overlay - color: activePalette.text + color: activePalette.highlightedText opacity: 0 anchors.fill: parent radius: 999 Behavior on opacity { NumberAnimation { duration: 200 } } } //Content Item { anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter height: label.height + ascii.height Text { anchors.horizontalCenter: parent.horizontalCenter horizontalAlignment: Text.AlignRight text: dialPad.buttonsText[index] color: activePalette.highlightedText font.bold: true id:label } //Text Text { text: dialPad.buttonsMap[index] anchors.top:label.bottom color: activePalette.highlightedText horizontalAlignment: Text.AlignRight anchors.horizontalCenter: parent.horizontalCenter id: ascii } } MouseArea { anchors.fill: parent hoverEnabled: true onClicked: { var call = CallModel.selectedCall if (!call) return call.appendText(dialPad.buttonsText[index]) call.playDTMF(dialPad.buttonsText[index]) } onContainsMouseChanged: { if (stateGroup.state != "played") stateGroup.state = containsMouse ? "hover" : "normal" } } //MouseArea Component.onCompleted: { if (dialPad.mapper == undefined) dialPad.mapper = {} dialPad.mapper[dialPad.buttonsText[index]] = key } Timer { id: animTimer running: false interval: 200 repeat: false onTriggered: { key.state = "normal" } } StateGroup { id: stateGroup states: [ State { name: "normal" PropertyChanges { target: overlay opacity: 0 } PropertyChanges { target: key border.width: 0 } }, State { name: "played" PropertyChanges { target: animTimer running: true } PropertyChanges { target: overlay opacity: 0.5 } PropertyChanges { target: key - border.color: activePalette.text border.width: 2 } }, State { name: "hover" PropertyChanges { target: overlay opacity: 0.5 } } ] } } //Rectangle } //Repeater } //Grid } Connections { target: CallModel onDtmfPlayed: { dialPad.mapper[code].state = "played" } } } diff --git a/src/dialview/qml/dialview.qml b/src/dialview/qml/dialview.qml index 33ea6204..d53ce009 100644 --- a/src/dialview/qml/dialview.qml +++ b/src/dialview/qml/dialview.qml @@ -1,165 +1,184 @@ /*************************************************************************** * Copyright (C) 2017 by Bluesystems * * Author : Emmanuel Lepage Vallee * * * * 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 . * **************************************************************************/ import QtQuick 2.7 import Ring 1.0 import QtQuick.Layouts 1.0 import org.kde.kirigami 2.2 as Kirigami import RingQmlWidgets 1.0 FocusScope { id: dialView focus: true anchors.fill: parent signal selectCall(Call call) SystemPalette { id: inactivePalette colorGroup: SystemPalette.Disabled } SystemPalette { id: activePalette colorGroup: SystemPalette.Active } FontMetrics { id: fontMetrics } TreeHelper { id: completionSelection selectionModel: CompletionModel.selectionModel } CallList { anchors.fill: parent } DialPad { width: parent.width visible: CallModel.supportsDTMF height: 200 anchors.horizontalCenter: dialView.horizontalCenter anchors.bottom: actionToolbar.top anchors.bottomMargin: 50 } ActionToolbar { id: actionToolbar width: parent.width anchors.bottom: parent.bottom } function selectPrevious(call) { if (call.state == Call.DIALING && completionSelection.selectPrevious()) return + completionSelection.clearSelection() + var idx = CallModel.getIndex(call) if (!idx.valid) return var directPrev = CallModel.index(idx.row-1, 0, idx.parent) var nextCall = CallModel.getCall(directPrev) if (!nextCall) return CallModel.selectedCall = nextCall dialView.selectCall(nextCall) } function selectNext(call) { if (call.state == Call.DIALING && completionSelection.selectNext()) return + completionSelection.clearSelection() + var idx = CallModel.getIndex(call) if (!idx.valid) return var directPrev = CallModel.index(idx.row+1, 0, idx.parent) var nextCall = CallModel.getCall(directPrev) if (!nextCall) return CallModel.selectedCall = nextCall dialView.selectCall(nextCall) } + function performCall() { + var call = CallModel.selectedCall + + if (!call) { + call = CallModel.dialingCall() + CallModel.selectedCall = call + } + + // Apply the auto completion + if (call.state == Call.DIALING && CompletionModel.selectedContactMethod) + call.peerContactMethod = CompletionModel.selectedContactMethod + + call.performAction(Call.ACCEPT) + } + Keys.onPressed: { var call = CallModel.selectedCall if (!call) { call = CallModel.dialingCall() CallModel.selectedCall = call } switch (event.key) { case Qt.Key_Up: selectPrevious(call) break case Qt.Key_Down: selectNext(call) break case Qt.Key_Escape: call.performAction(Call.REFUSE) break case Qt.Key_Backspace: call.backspaceItemText() break; case Qt.Key_Return: case Qt.Key_Enter: - call.performAction(Call.ACCEPT) + performCall() break default: call.appendText(event.text) call.playDTMF(event.text) } } Connections { target: CallModel onCallAttentionRequest: { CallModel.selectedCall = call } } Component.onCompleted: { dialView.forceActiveFocus() } MouseArea { z: 9999 anchors.fill: parent propagateComposedEvents: true onClicked: { mouse.accepted = false mouse.refused = true dialView.focus = true dialView.forceActiveFocus() } } } diff --git a/src/qmlwidgets/treehelper.cpp b/src/qmlwidgets/treehelper.cpp index e3c43c76..d20af18b 100644 --- a/src/qmlwidgets/treehelper.cpp +++ b/src/qmlwidgets/treehelper.cpp @@ -1,259 +1,269 @@ /*************************************************************************** * Copyright (C) 2017 by Bluesystems * * Author : Emmanuel Lepage Vallee * * * * 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 . * **************************************************************************/ #include "treehelper.h" #include #include #include class TreeHelperPrivate : public QObject { Q_OBJECT public: QMap m_Payloads {}; //FIXME it leaks static QHash> m_shRoleNameMapper; QAbstractItemModel* m_pModel {nullptr}; QItemSelectionModel* m_pSelectionModel {nullptr}; bool m_KeepSelected {false}; TreeHelper* q_ptr; public Q_SLOTS: void slotCurrentSelectionChanged(); }; QHash> TreeHelperPrivate::m_shRoleNameMapper; TreeHelper::TreeHelper(QObject* parent) : QObject(parent), d_ptr(new TreeHelperPrivate()) { d_ptr->q_ptr = this; } bool TreeHelper::setData(const QModelIndex& index, const QVariant& data, const QString& roleName) { if (!index.isValid()) return false; auto model = const_cast(index.model()); auto ret = d_ptr->m_shRoleNameMapper.value(model); // Map the role names if (!d_ptr->m_shRoleNameMapper.contains(model)) { const auto rn = model->roleNames(); for (auto i = rn.constBegin(); i != rn.constEnd(); i++) ret[i.value()] = i.key(); d_ptr->m_shRoleNameMapper[model] = ret; } if (!ret.contains(roleName)) return false; return model->setData(index, data, ret[roleName]); } TreeHelper::~TreeHelper() { delete d_ptr; } QModelIndex TreeHelper::getIndex(int row, const QModelIndex& parent) { if (!parent.isValid()) return {}; // Q_ASSERT(parent.parent().isValid() == true); // Q_ASSERT(!parent.parent().parent().isValid() == true); return parent.model()->index(row, 0, parent); } QAbstractItemModel* TreeHelper::model() const { return d_ptr->m_pModel; } void TreeHelper::setModel(QAbstractItemModel* model) { d_ptr->m_pModel = model; } QVariant TreeHelper::mimeData(const QModelIndex& parent, int row) const { qDebug() << "\n\nIN WRAP MIME DATA"; if (!d_ptr->m_pModel) return {}; const auto idx = d_ptr->m_pModel->index(row, 0, parent); if (!idx.isValid()) return {}; const auto md = d_ptr->m_pModel->mimeData({idx}); qDebug() << "GOT DATA" << md; QVariantMap ret; const auto formats = md->formats(); for (const auto& mime : qAsConst(formats)) { ret[mime] = QString(md->data(mime)); } return QVariant::fromValue(ret); } bool TreeHelper::dropMimeData(const QVariant& dragEvent, const QModelIndex& parent, int row) { auto obj = qvariant_cast(dragEvent); if (!obj) return false; if (obj->metaObject()->className() != QLatin1String("QQuickDropEvent")) return false; const QStringList formats = obj->property("formats").toStringList(); QMimeData* md = new QMimeData(); for (const auto& mime : qAsConst(formats)) { //stupid unexported qmimedata } return false; } bool TreeHelper::dropMimeData2(const QVariant& dragEvent, const QModelIndex& parent, int row) { qDebug() << dragEvent << dragEvent.canConvert(); QMap map = qvariant_cast(dragEvent); const auto idx = d_ptr->m_pModel->index(row, 0, parent); if (!idx.isValid()) return false; QMimeData* md = new QMimeData(); for(auto i = map.constBegin(); i != map.constEnd(); i++) { md->setData(i.key(), i.value().toByteArray()); } return d_ptr->m_pModel->dropMimeData(md, {}, row, 0, parent); } QItemSelectionModel* TreeHelper::selectionModel() const { return d_ptr->m_pSelectionModel; } void TreeHelper::setSelectionModel(QItemSelectionModel* sm) { if (d_ptr->m_pSelectionModel) disconnect(d_ptr->m_pSelectionModel, &QItemSelectionModel::currentChanged, d_ptr, &TreeHelperPrivate::slotCurrentSelectionChanged); d_ptr->m_pSelectionModel = sm; // if (d_ptr->m_pSelectionModel) connect(d_ptr->m_pSelectionModel, &QItemSelectionModel::currentChanged, d_ptr, &TreeHelperPrivate::slotCurrentSelectionChanged); } bool TreeHelper::selectNext() { if (!d_ptr->m_pSelectionModel) return false; auto cur = d_ptr->m_pSelectionModel->currentIndex(); auto newIdx = d_ptr->m_pSelectionModel->model()->index(cur.row()+1, 0); if (d_ptr->m_KeepSelected && !newIdx.isValid()) return false; d_ptr->m_pSelectionModel->setCurrentIndex( newIdx, QItemSelectionModel::ClearAndSelect ); return currentListIndex() != -1; } +bool TreeHelper::clearSelection() const +{ + if (!d_ptr->m_pSelectionModel) + return false; + + d_ptr->m_pSelectionModel->setCurrentIndex({}, QItemSelectionModel::ClearAndSelect); + + return true; +} + bool TreeHelper::selectPrevious() { if (!d_ptr->m_pSelectionModel) return false; auto cur = d_ptr->m_pSelectionModel->currentIndex(); auto newIdx = d_ptr->m_pSelectionModel->model()->index(cur.row()-1, 0); if (d_ptr->m_KeepSelected && !newIdx.isValid()) return false; d_ptr->m_pSelectionModel->setCurrentIndex( newIdx, QItemSelectionModel::ClearAndSelect ); return currentListIndex() != -1; } bool TreeHelper::selectIndex(int index) { if (!d_ptr->m_pSelectionModel) return false; auto newIdx = d_ptr->m_pSelectionModel->model()->index(index, 0); if (!newIdx.isValid()) return false; d_ptr->m_pSelectionModel->setCurrentIndex( newIdx, QItemSelectionModel::ClearAndSelect ); return true; } int TreeHelper::currentListIndex() const { return d_ptr->m_pSelectionModel ? d_ptr->m_pSelectionModel->currentIndex().row() : -1; } void TreeHelperPrivate::slotCurrentSelectionChanged() { Q_EMIT q_ptr->selectListIndex(m_pSelectionModel->currentIndex().row()); } Q_DECLARE_METATYPE(QItemSelectionModel*) #include diff --git a/src/qmlwidgets/treehelper.h b/src/qmlwidgets/treehelper.h index 1783d170..976dd726 100644 --- a/src/qmlwidgets/treehelper.h +++ b/src/qmlwidgets/treehelper.h @@ -1,63 +1,64 @@ /*************************************************************************** * Copyright (C) 2017 by Bluesystems * * Author : Emmanuel Lepage Vallee * * * * 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 . * **************************************************************************/ #pragma once #include #include class QItemSelectionModel; class TreeHelperPrivate; class TreeHelper : public QObject { Q_OBJECT public: Q_PROPERTY(QAbstractItemModel* model READ model WRITE setModel) Q_PROPERTY(QItemSelectionModel* selectionModel READ selectionModel WRITE setSelectionModel) Q_PROPERTY(int currentIndex READ currentListIndex NOTIFY selectListIndex) Q_INVOKABLE explicit TreeHelper(QObject* parent = nullptr); virtual ~TreeHelper(); Q_INVOKABLE QModelIndex getIndex(int row, const QModelIndex& parent); Q_INVOKABLE bool setData(const QModelIndex& index, const QVariant& data, const QString& roleName); Q_INVOKABLE QVariant mimeData(const QModelIndex& parent, int row) const; Q_INVOKABLE bool dropMimeData(const QVariant& dragEvent, const QModelIndex& parent, int row); Q_INVOKABLE bool dropMimeData2(const QVariant& dragEvent, const QModelIndex& parent, int row); QAbstractItemModel* model() const; void setModel(QAbstractItemModel* model); QItemSelectionModel* selectionModel() const; void setSelectionModel(QItemSelectionModel* sm); Q_INVOKABLE bool selectNext(); Q_INVOKABLE bool selectPrevious(); Q_INVOKABLE bool selectIndex(int index); + Q_INVOKABLE bool clearSelection() const; int currentListIndex() const; Q_SIGNALS: void selectListIndex(int index); private: TreeHelperPrivate* d_ptr; Q_DECLARE_PRIVATE(TreeHelper) }; Q_DECLARE_METATYPE(TreeHelper*) diff --git a/src/timeline/qml/completiondelegate.qml b/src/timeline/qml/completiondelegate.qml index ea42358d..86efa32f 100644 --- a/src/timeline/qml/completiondelegate.qml +++ b/src/timeline/qml/completiondelegate.qml @@ -1,220 +1,145 @@ /*************************************************************************** * Copyright (C) 2017 by Bluesystems * * Author : Emmanuel Lepage Vallee * * * * 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 . * **************************************************************************/ import QtQuick 2.7 import QtQuick.Layouts 1.0 import QtQuick.Controls 2.0 import Ring 1.0 import RingQmlWidgets 1.0 Item { id: componentItem + height: content.implicitHeight + property bool showAccount: AccountModel.hasAmbiguousAccounts property bool showPhoto: true property bool showControls: true property bool showSeparator: true - property var textColor: ListView.isCurrentItem ? - activePalette.highlightedText : activePalette.text - property var altTextColor: ListView.isCurrentItem ? - activePalette.highlightedText : inactivePalette.text - width: parent.width - height: getHeight() + property var textColor: selected ? + activePalette.highlight : activePalette.highlightedText - function getHeight() { - var rowCount = 2 + (showAccount ? 2 : 1) + (supportsRegistry ? 1 : 0) - var controlHeight = (showControls && temporary) ? buttonHeight : 0 + property var altTextColor: selected ? + activePalette.highlight : activePalette.highlightedText - return rowCount*(fontMetrics.height+2) + 16 + controlHeight - } + property var baseColor: selected ? + activePalette.highlightedText : activePalette.highlight + + width: parent.width property QtObject contactMethod: object property double buttonHeight: 30 property double labelHeight: fontMetrics.height*2 TextMetrics { id: accTextMetrics text: accountAlias } RowLayout { + id: content anchors.margins: 3 anchors.fill: parent + spacing: 0 PixmapWrapper { visible: componentItem.showPhoto height: Math.min(46, 4*componentItem.labelHeight + 12) width: Math.min(46, 4*componentItem.labelHeight + 12) pixmap: decoration } ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true RowLayout { spacing: 2 Text { Layout.fillWidth: true text: display font.bold: true color: textColor } Text { visible: componentItem.showPhoto anchors.rightMargin: 5 text: formattedLastUsed color: altTextColor } Item { width: 2 } } RowLayout { Layout.fillWidth: true PixmapWrapper { height: 16 width: 16 pixmap: categoryIcon } Text { text: categoryName+" " color: altTextColor } Text { Layout.fillWidth: true text: uri color: textColor } } - RowLayout { - Layout.fillWidth: true - Text { - color: nameStatus == NumberCompletionModel.SUCCESS ? - "green" : (nameStatus == NumberCompletionModel.IN_PROGRESS ? - "yellow" : - "red") - visible: supportsRegistry - text: nameStatusString - } - Item { - Layout.fillWidth: true - } - Rectangle { - color: activePalette.highlight - radius: 99 - height: componentItem.labelHeight + 4 - visible: componentItem.showAccount && accountAlias != "" - width: accTextMetrics.width + 32 - Text { - id: accountAliasText - anchors.centerIn: parent - anchors.leftMargin: 16 - anchors.rightMargin: 16 - text: accountAlias - color: activePalette.highlightedText - } - } - } + Loader { - active: componentItem.showControls && temporary - visible: componentItem.showControls && temporary - height: componentItem.buttonHeight - Layout.preferredHeight: componentItem.buttonHeight + active: componentItem.showAccount && accountAlias != "" Layout.fillWidth: true - - sourceComponent: RowLayout { + Layout.preferredHeight: active ? componentItem.labelHeight + 14 : 0 + Layout.minimumHeight: active ? componentItem.labelHeight + 14 : 0 + sourceComponent: Item { anchors.fill: parent Rectangle { - id: contactRequestButton - anchors.margins: 3 - Layout.fillWidth: true - Layout.fillHeight: true - color: "transparent" - radius: 5 - border.width: 1 - border.color: textColor - opacity: 0.8 - Behavior on color { - ColorAnimation {duration:100} - } - Text { - anchors.centerIn: parent - text: "Send request" - color: textColor - } - MouseArea { - anchors.fill: parent - hoverEnabled: true - onContainsMouseChanged: { - contactRequestButton.color = containsMouse ? "#55ffffff" : "transparent" - } - } - } - Rectangle { - id: callButton + color: textColor + radius: 99 + implicitHeight: componentItem.labelHeight + 4 + height: implicitHeight + width: accTextMetrics.width + height + + anchors.top: parent.top + anchors.topMargin: 3 + anchors.right: parent.right anchors.margins: 3 - Layout.fillWidth: true - Layout.fillHeight: true - color: "transparent" - radius: 5 - border.width: 1 - border.color: textColor - opacity: 0.8 - Behavior on color { - ColorAnimation {duration:100} - } + Text { + id: accountAliasText anchors.centerIn: parent - text: "Call" - color: textColor - } - MouseArea { - anchors.fill: parent - hoverEnabled: true - onContainsMouseChanged: { - callButton.color = containsMouse ? "#55ffffff" : "transparent" - } + anchors.leftMargin: 16 + anchors.rightMargin: 16 + text: accountAlias + color: baseColor } } } } - Item { - Layout.fillHeight: true - } } } - - Rectangle { - visible: componentItem.showSeparator - anchors.bottom: parent.bottom - anchors.bottomMargin: 3 - width: parent.width - height: 1 - color: ListView.isCurrentItem ? - activePalette.highlightedText : inactivePalette.text - opacity: 0.7 - } - MouseArea { anchors.fill: parent onClicked: { searchView.currentIndex = index contactMethodSelected(object) } } }