diff --git a/src/contactview/mainpage.cpp b/src/contactview/mainpage.cpp index 11d673b0..58c0c530 100644 --- a/src/contactview/mainpage.cpp +++ b/src/contactview/mainpage.cpp @@ -1,382 +1,382 @@ /*************************************************************************** * 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 "mainpage.h" #include #include #include #include #include #include #include #include #include #include #include #include <../ringapplication.h> #include "peerstimelinemodel.h" #include "phonedirectorymodel.h" #include #include #include #include #include #include #include // Remove inactive calls from the CallModel class ActiveCallProxy2 : public QSortFilterProxyModel { Q_OBJECT public: explicit ActiveCallProxy2(QObject* parent) : QSortFilterProxyModel(parent) {} protected: virtual bool filterAcceptsRow(int row, const QModelIndex & srcParent ) const override; }; class MainPagePrivate final : public QObject { Q_OBJECT public: QSharedPointer m_CallsModel; Individual* m_Invididual {nullptr}; QSharedPointer m_TimelineModel; QQuickItem* m_pItem {nullptr}; QQuickItem* m_pHeader {nullptr}; QList< QTimer* > m_lTimers; MainPage* q_ptr; public Q_SLOTS: void slotWindowChanged(); }; MainPage::MainPage(QQuickItem* parent) : QQuickItem(parent), d_ptr(new MainPagePrivate) { d_ptr->q_ptr = this; auto cp = new ActiveCallProxy2(Session::instance()->callModel()); cp->setSourceModel(Session::instance()->callModel()); QFile file(QStringLiteral(":/assets/welcome.html")); if (file.open(QIODevice::ReadOnly)) RingApplication::engine()->rootContext()->setContextProperty(QStringLiteral("welcomeMessage"), file.readAll()); RingApplication::engine()->rootContext()->setContextProperty(QStringLiteral("ActiveCallProxyModel"), cp); connect(this, &QQuickItem::windowChanged, d_ptr, &MainPagePrivate::slotWindowChanged); connect(Session::instance()->callModel(), &CallModel::callAttentionRequest, this, &MainPage::showVideo); installEventFilter(this); // Wait a bit, the timeline will change while it is loading, so before // picking something at random, give it a chance to load some things. QTimer::singleShot(500, [this]() { if (d_ptr->m_Invididual) return; - auto i = PeersTimelineModel::instance().mostRecentIndividual(); + auto i = Session::instance()->peersTimelineModel()->mostRecentIndividual(); // Select whatever is first (if any) - if ((!i) && PeersTimelineModel::instance().rowCount()) + if ((!i) && Session::instance()->peersTimelineModel()->rowCount()) if (auto rawI = qvariant_cast( - PeersTimelineModel::instance().index(0,0).data((int)Ring::Role::Object) + Session::instance()->peersTimelineModel()->index(0,0).data((int)Ring::Role::Object) )) i = rawI; // There is nothing yet, wait if (!i) { QMetaObject::Connection c; - c = connect(&PeersTimelineModel::instance(), &QAbstractItemModel::rowsInserted, this, [c, this]() { + c = connect(Session::instance()->peersTimelineModel(), &QAbstractItemModel::rowsInserted, this, [c, this]() { if (d_ptr->m_Invididual) { disconnect(c); return; } - if (auto i = PeersTimelineModel::instance().mostRecentIndividual()) { + if (auto i = Session::instance()->peersTimelineModel()->mostRecentIndividual()) { disconnect(c); setIndividual(i); - const auto idx = PeersTimelineModel::instance().individualIndex(i); + const auto idx = Session::instance()->peersTimelineModel()->individualIndex(i); emit suggestSelection(i, idx); } }); return; } setIndividual(i); if (i) { - const auto idx = PeersTimelineModel::instance().individualIndex(i); + const auto idx = Session::instance()->peersTimelineModel()->individualIndex(i); emit suggestSelection(i, idx); } }); } MainPage::~MainPage() { d_ptr->m_TimelineModel.clear(); d_ptr->m_Invididual = nullptr; d_ptr->m_CallsModel.clear(); // Release the shared pointer reference from the timer. for (auto t : qAsConst(d_ptr->m_lTimers)) { disconnect(t); delete t; } delete d_ptr; } bool MainPage::eventFilter(QObject *obj, QEvent *event) { Q_UNUSED(obj) Q_UNUSED(event) //TODO the context menu return false; } void MainPage::setIndividual(Individual* ind) { if (!ind) return; if (d_ptr->m_TimelineModel) { disconnect(d_ptr->m_TimelineModel.data(), &QAbstractItemModel::rowsInserted, this, &MainPage::contextInserted); } d_ptr->m_Invididual = ind; d_ptr->m_TimelineModel = ind->timelineModel(); d_ptr->m_CallsModel = ind->eventAggregate()->unsortedListView(); Q_ASSERT(d_ptr->m_CallsModel); d_ptr->m_pItem->setProperty( "currentContactMethod", QVariant::fromValue( static_cast(nullptr) )); d_ptr->m_pItem->setProperty( "currentIndividual", QVariant::fromValue( ind )); d_ptr->m_pItem->setProperty( "timelineModel", QVariant::fromValue(d_ptr->m_TimelineModel)); d_ptr->m_pItem->setProperty( "unsortedListView", QVariant::fromValue(d_ptr->m_CallsModel)); if (d_ptr->m_TimelineModel) { connect(d_ptr->m_TimelineModel.data(), &QAbstractItemModel::rowsInserted, this, &MainPage::contextInserted); } } void MainPage::setContactMethod(ContactMethod* cm) { if ((!cm) || (!d_ptr->m_pItem)) return; cm = PhoneDirectoryModel::instance().fromTemporary(cm); // Keep a reference for 5 minutes to avoid double free from QML for (auto ptr : {d_ptr->m_CallsModel, d_ptr->m_TimelineModel}) if (ptr) { auto t = new QTimer(this); t->setInterval(5 * 60 * 1000); t->setSingleShot(true); connect(t, &QTimer::timeout, this, [t, this, ptr]() { this->d_ptr->m_lTimers.removeAll(t); }); t->start(); d_ptr->m_lTimers << t; } if (d_ptr->m_TimelineModel) { disconnect(d_ptr->m_TimelineModel.data(), &QAbstractItemModel::rowsInserted, this, &MainPage::contextInserted); } // Keep a strong reference because QML won't d_ptr->m_Invididual = cm->individual(); d_ptr->m_CallsModel = cm->individual()->eventAggregate()->unsortedListView(); Q_ASSERT(d_ptr->m_CallsModel); d_ptr->m_TimelineModel = cm->individual()->timelineModel(); d_ptr->m_pItem->setProperty( "currentContactMethod", QVariant::fromValue(cm)); d_ptr->m_pItem->setProperty( "currentIndividual", QVariant::fromValue( cm->individual() )); d_ptr->m_pItem->setProperty( "timelineModel", QVariant::fromValue(d_ptr->m_TimelineModel)); d_ptr->m_pItem->setProperty( "unsortedListView", QVariant::fromValue(d_ptr->m_CallsModel)); if (d_ptr->m_TimelineModel) { connect(d_ptr->m_TimelineModel.data(), &QAbstractItemModel::rowsInserted, this, &MainPage::contextInserted); } } void MainPage::setPerson(Person* p) { if (d_ptr->m_TimelineModel) { disconnect(d_ptr->m_TimelineModel.data(), &QAbstractItemModel::rowsInserted, this, &MainPage::contextInserted); } auto ind = p->individual(); d_ptr->m_Invididual = ind; d_ptr->m_pItem->setProperty( "currentPerson", QVariant::fromValue(p)); d_ptr->m_pItem->setProperty( "currentIndividual", QVariant::fromValue( ind )); d_ptr->m_CallsModel = ind->eventAggregate()->unsortedListView(); Q_ASSERT(d_ptr->m_CallsModel); d_ptr->m_pItem->setProperty( "unsortedListView", QVariant::fromValue(d_ptr->m_CallsModel)); if (d_ptr->m_TimelineModel) { connect(d_ptr->m_TimelineModel.data(), &QAbstractItemModel::rowsInserted, this, &MainPage::contextInserted); } } void MainPage::setCurrentPage(MainPage::Pages page) { QString name; switch (page) { case MainPage::Pages::INFORMATION: name = QStringLiteral("INFORMATION"); break; case MainPage::Pages::TIMELINE: name = QStringLiteral("TIMELINE"); break; case MainPage::Pages::CALL_HISTORY: name = QStringLiteral("CALL_HISTORY"); break; case MainPage::Pages::RECORDINGS: name = QStringLiteral("RECORDINGS"); break; case MainPage::Pages::SEARCH: name = QStringLiteral("SEARCH"); break; case MainPage::Pages::MEDIA: name = QStringLiteral("MEDIA"); break; } d_ptr->m_pItem->setProperty( "currentPage", name); } bool ActiveCallProxy2::filterAcceptsRow(int row, const QModelIndex& srcParent ) const { // Conferences are always active if (srcParent.isValid()) return true; return sourceModel()->index(row, 0) .data((int)Call::Role::LifeCycleState) == QVariant::fromValue(Call::LifeCycleState::PROGRESS); } void MainPagePrivate::slotWindowChanged() { auto ctx = QQmlEngine::contextForObject(q_ptr); Q_ASSERT(ctx); QQmlComponent comp(ctx->engine(), QStringLiteral("qrc:/ViewContact.qml"), q_ptr); m_pItem = qobject_cast(comp.create(ctx)); if (!m_pItem) { qDebug() << "Previous error" << comp.errorString(); } Q_ASSERT(m_pItem); m_pItem->setParentItem(q_ptr); auto anchors = qvariant_cast(m_pItem->property("anchors")); anchors->setProperty("fill", QVariant::fromValue(q_ptr)); m_pItem->setProperty( "unsortedListView", QVariant::fromValue(nullptr)); } void MainPage::showVideo(Call* c) { // Ignore clicks on dialing calls if ((!c) || c->state() == Call::State::NEW || c->state() == Call::State::DIALING) return; setContactMethod(c->peerContactMethod()); QMetaObject::invokeMethod(d_ptr->m_pItem, "showVideo"); } QQuickItem* MainPage::page() const { return d_ptr->m_pItem; } QQuickItem* MainPage::header() const { return d_ptr->m_pHeader; } void MainPage::setHeader(QQuickItem* item) { d_ptr->m_pItem->setProperty("contactHeader", QVariant::fromValue(item)); d_ptr->m_pHeader = item; } bool MainPage::isMobile() const { return d_ptr->m_pItem->property("mobile").toBool(); } void MainPage::setMobile(bool v) { d_ptr->m_pItem->setProperty("mobile", v); } Individual* MainPage::individual() const { if (d_ptr->m_Invididual) return qobject_cast(d_ptr->m_Invididual); return nullptr; } QModelIndex MainPage::suggestedTimelineIndex() const { if (d_ptr->m_Invididual) - return PeersTimelineModel::instance().individualIndex( + return Session::instance()->peersTimelineModel()->individualIndex( qobject_cast(d_ptr->m_Invididual) ); return {}; } void MainPage::contextInserted() { QMetaObject::invokeMethod(d_ptr->m_pItem, "showNewContent"); } #include // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/src/desktopview/qml/dockbar.qml b/src/desktopview/qml/dockbar.qml index c4361434..d0796026 100644 --- a/src/desktopview/qml/dockbar.qml +++ b/src/desktopview/qml/dockbar.qml @@ -1,290 +1,291 @@ /*************************************************************************** * 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 QtQuick.Controls 2.0 as Controls import org.kde.kirigami 2.2 as Kirigami import QtQuick.Window 2.2 +import net.lvindustries.ringqtquick 1.0 as RingQtQuick import ContactView 1.0 import DesktopView 1.0 Item { id: topLevel width: Math.min(335, root.width-48) + dockBar.width property var newHolder: null function setCurrentIndex(index) { if (dockLoader.sourceComponent != timelineViewComponent) return dockLoader.item.setCurrentIndex(index) } signal timelineSelected() RowLayout { id: dockLayout spacing: 0 anchors.fill: parent Image { id: dockBar property string selectedItem: "timeline" source: "gui/icons/assets/toolbar_bg.png" fillMode: Image.Tile width: 48 height: parent.height Layout.fillHeight: true Layout.maximumWidth: width onSelectedItemChanged: { switch(selectedItem) { case "timeline": dockLoader.sourceComponent = timelineViewComponent break case "call" : dockLoader.sourceComponent = dialViewComponent break case "contact" : dockLoader.sourceComponent = contactViewComponent break case "bookmark": dockLoader.sourceComponent = bookmarkViewComponent break case "history" : dockLoader.sourceComponent = historyViewComponent break } } Behavior on width { NumberAnimation {duration: 200; easing.type: Easing.OutQuad } } DockModel { id: icons } FontMetrics { id: fontMetrics } Column { Item { width: dockBar.width height: Math.max( dockBar.width * 1.33, availableAccounts.count*(dockBar.width+5) + 6 ) ListView { y: 5 id: availableAccounts model: ProfileModel.availableProfileModel height: 200 spacing: 5 interactive: false delegate: MouseArea { width: dockBar.width height: dockBar.width ContactPhoto { anchors.centerIn: parent width: dockBar.width - 3 height: dockBar.width - 3 individual: object defaultColor: "#f2f2f2ff" //same as the SVG } onClicked: { var component = Qt.createComponent("PresenceSelector.qml") if (component.status == Component.Ready) { var window = component.createObject(applicationWindow().contentItem) window.individual = object window.open() } else console.log("ERROR", component.status, component.errorString()) } } } Loader { active: availableAccounts.count == 0 || !ProfileModel.hasAvailableProfiles anchors.centerIn: parent sourceComponent: Rectangle { height: 48 width: 48 border.width: 2 border.color: Kirigami.Theme.negativeTextColor color: "transparent" radius: 99 Image { anchors.fill: parent sourceSize.width: 48 sourceSize.height: 48 anchors.verticalCenter: parent.verticalCenter source: "image://SymbolicColorizer/:/sharedassets/outline/warning.svg" } } } Rectangle { height: 1 width: parent.width color: "#939393" anchors.bottom: parent.bottom } } Repeater { model: icons Rectangle { id: actionIcon color: dockBar.selectedItem == identifier ? "#111111" : "transparent" height: dockBar.width + 10 width: dockBar.width Image { anchors.verticalCenter: parent.verticalCenter source: decoration width: 48 height: 48 sourceSize.width: 48 sourceSize.height: 48 fillMode: Image.PreserveAspectFit } MouseArea { anchors.fill: parent onClicked: { dockBar.selectedItem = identifier } } Rectangle { color: "red" radius: 99 x: 2 y: 2 width: fontMetrics.height + 4 height: fontMetrics.height + 4 visible: activeCount > 0 Text { color: "white" anchors.centerIn: parent font.bold: true text: activeCount } } } } } } Component { id: timelineViewComponent PeersTimeline { anchors.fill: parent state: "" onIndividualSelected: { mainPage.setIndividual(ind) } } } Component { id: dialViewComponent DialView { anchors.fill: parent onSelectCall: { mainPage.showVideo(call) } } } Component { id: contactViewComponent ContactList { anchors.fill: parent onIndividualSelected: { mainPage.setIndividual(ind) } } } Component { id: bookmarkViewComponent BookmarkList { anchors.fill: parent onContactMethodSelected: { mainPage.setContactMethod(cm) } } } Component { id: historyViewComponent HistoryTimeline { anchors.fill: parent onContactMethodSelected: { mainPage.setContactMethod(cm) } } } ColumnLayout { id: dockHolder width: Math.min(335, root.width-48) Layout.fillHeight: true visible: true // This is a placeholder for the searchbox Item { Layout.fillWidth: true Layout.minimumHeight: 32 Layout.maximumHeight: 32 } Loader { id: dockLoader sourceComponent: timelineViewComponent Layout.fillWidth: true Layout.fillHeight: true onItemChanged: { if (item && sourceComponent == timelineViewComponent) timelineSelected() } } } } SearchOverlay { id: searchView source: parent anchors.fill: parent onContactMethodSelected: { mainPage.setContactMethod(cm) - setCurrentIndex(PeersTimelineModel.individualIndex(cm.individual)) + setCurrentIndex(RingSession.peersTimelineModel.individualIndex(cm.individual)) } } } diff --git a/src/ringapplication.cpp b/src/ringapplication.cpp index 678ecc51..91d3c8f7 100644 --- a/src/ringapplication.cpp +++ b/src/ringapplication.cpp @@ -1,625 +1,623 @@ /*************************************************************************** * Copyright (C) 2009-2015 by Savoir-Faire Linux * * Author : Emmanuel Lepage Valle * * * * 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 . * **************************************************************************/ //Parent #include "ringapplication.h" //Qt #include #include #include #include #include #include #include #include #include #include #include #include #include //KDE #include #include #include #include //LRC #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include