summarylogtreecommitdiffstats
diff options
context:
space:
mode:
authorPierre Choffet2016-01-07 16:27:18 -0500
committerPierre Choffet2016-01-07 16:27:18 -0500
commitf15bbef4ee4813f179d4c69854f17b736e24bb00 (patch)
tree7a5186b5405fa8313c52bfbd14c7b275e140817e
parentc4bfdd13c3f4c395c87bf87abf9e9b171b4a32a6 (diff)
downloadaur-f15bbef4ee4813f179d4c69854f17b736e24bb00.tar.gz
Fix upstream broken build
Upstream ring-kde is broken since libringclient commit 6ea7e56b9f9034bff817451fbebbaf8d1478be3c. Some patches were ready in the next branch but still not in the master. This commit adds them in the package building process. The following commits have been backported: f3f593378d7b26fb8373b2d927156ad7a6f69e19 8cc3ebf5fb9ab3af4d3f991ab93aa94be4203d7a 6926e7714306c5c06d24473954993ee7290c37a5 4a7efcb2fb8cf0bb35f9dac6cc626206ad8116c2 79680670d4376f8f7e0334a0a2d2900b2980f58f Plus some minor modifications have been written in src/CMakeFiles.txt.
-rw-r--r--0001-textmessages-Vastly-revamp-and-fix-the-text-messages.patch886
-rw-r--r--0002-contact-Enable-Akonadi-support-again.patch1565
-rw-r--r--0003-history-De-duplicate-identical-calls.patch685
-rw-r--r--0004-akonadi-Make-it-optional.patch234
-rw-r--r--0005-Fix-CMakeLists.txt.patch25
-rw-r--r--0006-contact-Add-new-basic-contact-selection-dialog.patch461
-rw-r--r--ChangeLog7
-rw-r--r--PKGBUILD29
8 files changed, 3888 insertions, 4 deletions
diff --git a/0001-textmessages-Vastly-revamp-and-fix-the-text-messages.patch b/0001-textmessages-Vastly-revamp-and-fix-the-text-messages.patch
new file mode 100644
index 000000000000..57ff096f7d8b
--- /dev/null
+++ b/0001-textmessages-Vastly-revamp-and-fix-the-text-messages.patch
@@ -0,0 +1,886 @@
+From 8f430e63c139efbd6df69f846963db925ecf1699 Mon Sep 17 00:00:00 2001
+From: Emmanuel Lepage Vallee <elv1313@gmail.com>
+Date: Sun, 13 Dec 2015 06:49:01 -0500
+Subject: [PATCH 1/6] textmessages: Vastly revamp and fix the text messages
+
+They are always neglected because nobody (I know of) use them, but
+many of the issues and regressions have been fixed. A short sumary:
+
+ * New notification for text messages
+ * Show only "readable" text messages
+ * Show the history when selecting the entry widget
+ * Show the entry box when relevant
+ * Set a red color for missed messages tabs
+ * Scroll down to the end automatically
+---
+ src/CMakeLists.txt | 1 +
+ src/delegates/imdelegate.cpp | 23 +++-
+ src/delegates/imdelegate.h | 5 +-
+ src/eventmanager.cpp | 23 ++--
+ src/eventmanager.h | 11 +-
+ src/mainwindow.cpp | 3 -
+ src/notification.cpp | 261 +++++++++++++++++++++++++++++++++++++++++++
+ src/notification.h | 56 ++++++++++
+ src/view.cpp | 64 ++++++++++-
+ src/view.h | 2 +
+ src/widgets/immanager.cpp | 129 +++++++++++++++++++--
+ src/widgets/immanager.h | 15 ++-
+ 12 files changed, 562 insertions(+), 31 deletions(-)
+ create mode 100644 src/notification.cpp
+ create mode 100644 src/notification.h
+
+diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
+index 1b461f9..201f107 100755
+--- a/src/CMakeLists.txt
++++ b/src/CMakeLists.txt
+@@ -173,6 +173,7 @@ SET(
+ delegates/toolbardelegate.cpp
+ delegates/kdepixmapmanipulation.cpp
+ proxies/simplerotateproxy.cpp
++ #proxies/valuefilterproxy.cpp
+ )
+
+ # Configuration pages
+diff --git a/src/delegates/imdelegate.cpp b/src/delegates/imdelegate.cpp
+index 6467676..bb4a40b 100644
+--- a/src/delegates/imdelegate.cpp
++++ b/src/delegates/imdelegate.cpp
+@@ -20,8 +20,10 @@
+ #include "call.h"
+ #include <media/textrecording.h>
+ #include <QtGui/QPainter>
+-#include <QIcon>
++#include <QtGui/QIcon>
++#include <QtCore/QTimer>
+ #include <QtGui/QFont>
++#include <QtWidgets/QScrollBar>
+
+ ///Delegate contructor
+ ImDelegates::ImDelegates(IMTab* parent) : QStyledItemDelegate(parent),m_pParent(parent)
+@@ -70,7 +72,7 @@ void ImDelegates::paint(QPainter* painter, const QStyleOptionViewItem& option, c
+ }
+
+ ///Constructor
+-IMTab::IMTab(QAbstractListModel* model,QWidget* parent) : QListView(parent)
++IMTab::IMTab(QAbstractItemModel* model,QWidget* parent) : QListView(parent)
+ {
+ setModel(model);
+ setAlternatingRowColors(true);
+@@ -78,7 +80,14 @@ IMTab::IMTab(QAbstractListModel* model,QWidget* parent) : QListView(parent)
+ setUniformItemSizes(false);
+ setItemDelegate(new ImDelegates(this));
+ setVerticalScrollMode(ScrollPerPixel);
++
++ scrollTo(model->index(model->rowCount()-1,0));
++
++ if (verticalScrollBar())
++ verticalScrollBar()->setValue(verticalScrollBar()->maximum());
++
+ connect(model,SIGNAL(dataChanged(QModelIndex,QModelIndex)),this,SLOT(scrollBottom()));
++ connect(model, &QAbstractItemModel::rowsInserted, this, &IMTab::updateScrollBar);
+ }
+
+ ///Scroll to last message
+@@ -86,3 +95,13 @@ void IMTab::scrollBottom()
+ {
+ scrollTo(model()->index(model()->rowCount()-1,0));
+ }
++
++void IMTab::updateScrollBar()
++{
++ if (verticalScrollBar() && verticalScrollBar()->value()
++ == verticalScrollBar()->maximum()) {
++ QTimer::singleShot(0,[this]() {
++ verticalScrollBar()->setValue(verticalScrollBar()->maximum());
++ });
++ }
++}
+diff --git a/src/delegates/imdelegate.h b/src/delegates/imdelegate.h
+index d809e67..4692de8 100644
+--- a/src/delegates/imdelegate.h
++++ b/src/delegates/imdelegate.h
+@@ -22,7 +22,7 @@
+ #include <QtWidgets/QListView>
+ #include <QtWidgets/QStyledItemDelegate>
+
+-class QAbstractListModel;
++class QAbstractItemModel;
+ class IMTab;
+
+ class ImDelegates : public QStyledItemDelegate
+@@ -41,9 +41,10 @@ class IMTab : public QListView
+ {
+ Q_OBJECT
+ public:
+- explicit IMTab(QAbstractListModel* model,QWidget* parent = nullptr);
++ explicit IMTab(QAbstractItemModel* model,QWidget* parent = nullptr);
+ private Q_SLOTS:
+ void scrollBottom();
++ void updateScrollBar();
+ };
+
+ #endif // IM_MANAGER
+diff --git a/src/eventmanager.cpp b/src/eventmanager.cpp
+index 39bb3be..7e22c4d 100644
+--- a/src/eventmanager.cpp
++++ b/src/eventmanager.cpp
+@@ -47,6 +47,8 @@
+ #include "widgets/callviewoverlay.h"
+ #include "widgets/autocompletion.h"
+
++bool EventManager::m_HasFocus = false;
++
+ //This code detect if the window is active, innactive or minimzed
+ class MainWindowEvent : public QObject {
+ Q_OBJECT
+@@ -57,13 +59,7 @@ public:
+ protected:
+ virtual bool eventFilter(QObject *obj, QEvent *event) override {
+ Q_UNUSED(obj)
+- if (event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut) {
+- QFocusEvent* e = static_cast<QFocusEvent*>(event);
+- if (e->reason() == Qt::ActiveWindowFocusReason) {
+- qDebug() << "ACTIVE WINDOW EVENT";
+- }
+- }
+- else if (event->type() == QEvent::WindowStateChange) {
++ if (event->type() == QEvent::WindowStateChange) {
+ QWindowStateChangeEvent* e = static_cast<QWindowStateChangeEvent*>(event);
+ switch (MainWindow::app()->windowState()) {
+ case Qt::WindowMinimized:
+@@ -81,6 +77,12 @@ protected:
+ else if (event->type() == QEvent::KeyPress) {
+ m_pParent->viewKeyEvent(static_cast<QKeyEvent*>(event));
+ }
++ else if (event->type() == QEvent::WindowDeactivate) {
++ m_pParent->m_HasFocus = false;
++ }
++ else if (event->type() == QEvent::WindowActivate) {
++ m_pParent->m_HasFocus = true;
++ }
+ return false;
+ }
+
+@@ -495,7 +497,7 @@ void EventManager::enter()
+ ///Macros needs to be executed at high level so the animations kicks in
+ void EventManager::addDTMF(const QString& sequence)
+ {
+- if (sequence == "\n")
++ if (sequence == QLatin1String("\n"))
+ enter();
+ else
+ typeString(sequence);
+@@ -613,4 +615,9 @@ void EventManager::slotNetworkDown()
+ m_pParent->m_pCanvasManager->newEvent(CanvasObjectManager::CanvasEvent::NETWORK_ERROR);
+ }
+
++bool EventManager::mayHaveFocus()
++{
++ return m_HasFocus;
++}
++
+ #include "eventmanager.moc"
+diff --git a/src/eventmanager.h b/src/eventmanager.h
+index 3d8febd..22e9202 100644
+--- a/src/eventmanager.h
++++ b/src/eventmanager.h
+@@ -56,14 +56,21 @@ public:
+ //Implement macro key listener
+ virtual void addDTMF(const QString& sequence) override;
+
++ /**
++ * An unreliable way to track the application focus
++ *
++ * It is better than nothing
++ */
++ static bool mayHaveFocus();
+
+ protected:
+ virtual bool eventFilter(QObject *obj, QEvent *event) override;
+
+ private:
+ //Attributes
+- View* m_pParent ;
+- MainWindowEvent* m_pMainWindowEv ;
++ View* m_pParent ;
++ MainWindowEvent* m_pMainWindowEv ;
++ static bool m_HasFocus ;
+
+ //Methods
+ bool viewKeyEvent ( QKeyEvent* e);
+diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
+index 0f430fb..d8f3993 100755
+--- a/src/mainwindow.cpp
++++ b/src/mainwindow.cpp
+@@ -94,9 +94,6 @@ MainWindow* MainWindow::m_sApp = nullptr;
+
+ static void loadNumberCategories()
+ {
+-// QList<int> list = ConfigurationSkeleton::phoneTypeList();
+-// const bool isEmpty = !list.size();
+-// #define IS_ENABLED(name) (list.indexOf(name) != -1) || isEmpty
+ auto& model = NumberCategoryModel::instance();
+ #define ICN(name) QPixmap(QString(":/mini/icons/miniicons/%1.png").arg(name))
+ model.addCategory(i18n("Home") ,ICN("home") , 1 /*KABC::PhoneNumber::Home */);
+diff --git a/src/notification.cpp b/src/notification.cpp
+new file mode 100644
+index 0000000..5e4e2f4
+--- /dev/null
++++ b/src/notification.cpp
+@@ -0,0 +1,261 @@
++/***************************************************************************
++ * Copyright (C) 2015 by Emmanuel Lepage Vallee *
++ * Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com>*
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ **************************************************************************/
++#include "notification.h"
++
++
++// Qt
++#include <QtWidgets/QApplication>
++
++// KDE
++#include <KNotification>
++#include <KLocalizedString>
++
++// LRC
++#include <callmodel.h>
++#include <accountmodel.h>
++#include <call.h>
++#include <person.h>
++#include <contactmethod.h>
++#include <numbercategory.h>
++#include <useractionmodel.h>
++#include <media/textrecording.h>
++#include <media/recordingmodel.h>
++
++// Ring-KDE
++#include <mainwindow.h>
++#include <view.h>
++#include <eventmanager.h>
++
++#define REGISTER_ACTION(list, var, name) list << name;var = list.size();
++
++
++/*
++ * IncomingCallNotification
++ */
++
++class IncomingCallNotification : public KNotification
++{
++ Q_OBJECT
++
++public:
++ explicit IncomingCallNotification(Call* c);
++
++private:
++ uint m_Answer {99};
++ uint m_AnswerVideo {99};
++ uint m_HangUp {99};
++
++ Call* m_pCall;
++
++private Q_SLOTS:
++ void stateChanged(Call::LifeCycleState newState, Call::LifeCycleState previousState);
++ void actionPerformed(uint actionId);
++};
++
++IncomingCallNotification::IncomingCallNotification(Call* call) : KNotification(
++ QStringLiteral("incomingCall"), MainWindow::view(), NotificationFlag::Persistent ),
++ m_pCall(call)
++{
++
++ const Person* contact = call->peerContactMethod()->contact();
++ if (contact) {
++
++ const QPixmap px = (contact->photo()).type() == QVariant::Pixmap ? (contact->photo()).value<QPixmap>():QPixmap();
++ setPixmap(px);
++ }
++
++ setTitle(i18n("New incoming call"));
++
++ setText(i18n("New call from <b>%1</b>\n<i>(%2)</i>"
++ ,call->formattedName()
++ ,call->peerContactMethod()->uri()
++ ));
++
++ QStringList actions;
++
++ REGISTER_ACTION(actions, m_Answer, i18n( "Answer" ))
++ REGISTER_ACTION(actions, m_HangUp, i18n( "Hang up" ))
++
++ //TODO support answer without video
++
++ setActions(actions);
++
++ addContext(QStringLiteral("contact"), call->peerContactMethod()->category()->name());
++
++ connect(call, &Call::lifeCycleStateChanged, this, &IncomingCallNotification::stateChanged);
++
++ connect(this, SIGNAL(activated(uint)), this , SLOT(actionPerformed(uint)));
++}
++
++void IncomingCallNotification::stateChanged(Call::LifeCycleState newState, Call::LifeCycleState previousState)
++{
++ Q_UNUSED(previousState)
++
++ if (newState != Call::LifeCycleState::INITIALIZATION) {
++
++ close();
++
++ deleteLater();
++ }
++}
++
++void IncomingCallNotification::actionPerformed(uint actionId)
++{
++ if (actionId == m_Answer) {
++ m_pCall << Call::Action::ACCEPT;
++ }
++ else if (actionId == m_HangUp) {
++ m_pCall << Call::Action::REFUSE;
++ }
++}
++
++
++/*
++ * Text message notification
++ */
++class IncomingTextNotification : public KNotification
++{
++ Q_OBJECT
++public:
++ explicit IncomingTextNotification(ContactMethod* cm, Media::TextRecording* t);
++ void actionPerformed(uint actionId);
++};
++
++IncomingTextNotification::IncomingTextNotification(ContactMethod* cm, Media::TextRecording* t) : KNotification(
++ QStringLiteral("incomingText"), MainWindow::view())
++{
++ setTitle(i18n("Message from %1", cm->primaryName()));
++
++ if (auto contact = cm->contact()) {
++
++ const QPixmap px = (contact->photo()).type() == QVariant::Pixmap ? (contact->photo()).value<QPixmap>():QPixmap();
++ setPixmap(px);
++ }
++
++ setText(t->instantTextMessagingModel()->index(
++ t->instantTextMessagingModel()->rowCount()-1, 0
++ ).data().toString());
++}
++
++void IncomingTextNotification::actionPerformed(uint actionId)
++{
++ Q_UNUSED(actionId)
++}
++
++/*
++ * CreateContactNotification
++ */
++
++class CreateContactNotification : public KNotification
++{
++ Q_OBJECT
++
++public:
++ explicit CreateContactNotification(ContactMethod* cm);
++
++private:
++ uint m_Yes {99};
++ uint m_No {99};
++ uint m_Never {99};
++
++private Q_SLOTS:
++ void actionPerformed(uint actionId);
++};
++
++CreateContactNotification::CreateContactNotification(ContactMethod* cm) :KNotification(
++ QStringLiteral("incomingCall"), MainWindow::view())
++{
++ setTitle(i18n("Add %1 to contacts?", cm->uri()));
++
++ setText(i18n("Do you wish to add %1 to your addressbook?", cm->primaryName()));
++
++ QStringList actions;
++
++ REGISTER_ACTION(actions, m_Yes , i18n( "Yes" ))
++ REGISTER_ACTION(actions, m_No , i18n( "No" ))
++ REGISTER_ACTION(actions, m_Never, i18n( "Do not ask again" ))
++
++ setActions(actions);
++
++ connect(this, SIGNAL(activated(uint)), this , SLOT(actionPerformed(uint)));
++}
++
++void CreateContactNotification::actionPerformed(uint actionId)
++{
++ if (actionId == m_Yes) {
++ //TODO do something
++ }
++ else if (actionId == m_No) {
++ //Nothing to do
++ }
++ else if (actionId == m_Never) {
++ //TODO edit the config to disable this notification
++ }
++
++ deleteLater();
++}
++
++
++
++
++Notification::Notification(QObject* parent) : QObject(parent)
++{
++ connect(&CallModel::instance(), &CallModel::incomingCall, this, &Notification::incomingCall);
++ connect(&AccountModel::instance(), &AccountModel::accountStateChanged, this, &Notification::accountStatus);
++ connect(&Media::RecordingModel::instance(), &Media::RecordingModel::newTextMessage, this, &Notification::incomingText);
++}
++
++Notification* Notification::instance()
++{
++ static auto i = new Notification(QApplication::instance());
++
++ return i;
++}
++
++void Notification::contactOnline()
++{
++ //TODO fix the presence model
++}
++
++void Notification::accountStatus(Account* a, const Account::RegistrationState state)
++{
++ Q_UNUSED(a)
++ Q_UNUSED(state)
++ //TODO
++}
++
++void Notification::incomingCall(Call* call)
++{
++ if (call)
++ (new IncomingCallNotification(call))->sendEvent();
++}
++
++void Notification::incomingText(Media::TextRecording* t, ContactMethod* cm)
++{
++ if (t && !EventManager::mayHaveFocus())
++ (new IncomingTextNotification(cm, t))->sendEvent();
++}
++
++void Notification::createContact()
++{
++ //TODO
++}
++
++#undef REGISTER_ACTION
++
++#include <notification.moc>
+diff --git a/src/notification.h b/src/notification.h
+new file mode 100644
+index 0000000..73d2d34
+--- /dev/null
++++ b/src/notification.h
+@@ -0,0 +1,56 @@
++/***************************************************************************
++ * Copyright (C) 2015 by Emmanuel Lepage Vallee *
++ * Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com>*
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ **************************************************************************/
++#ifndef NOTIFICATION_H
++#define NOTIFICATION_H
++
++#include <QtCore/QObject>
++
++#include <account.h>
++class Call;
++class ContactMethod;
++namespace Media {
++ class TextRecording;
++}
++
++/**
++ * This singleton class watch and reatch on events to provide system
++ * notifications.
++ */
++class Notification : public QObject
++{
++ Q_OBJECT
++
++public:
++ explicit Notification(QObject* parent = nullptr);
++
++ void contactOnline();
++
++ void accountStatus(Account* a, const Account::RegistrationState state);
++
++
++ void incomingText(Media::TextRecording* t, ContactMethod* cm);
++
++ void createContact();
++
++ static Notification* instance();
++
++private Q_SLOTS:
++ void incomingCall(Call* c);
++};
++
++#endif
+\ No newline at end of file
+diff --git a/src/view.cpp b/src/view.cpp
+index 1188887..31ab04d 100644
+--- a/src/view.cpp
++++ b/src/view.cpp
+@@ -58,6 +58,30 @@
+ #include <tip/tipmanager.h>
+ #include "implementation.h"
+
++class FocusWatcher : public QObject
++{
++ Q_OBJECT
++public:
++ explicit FocusWatcher(QObject* parent = nullptr) : QObject(parent)
++ {
++ if (parent)
++ parent->installEventFilter(this);
++ }
++ virtual bool eventFilter(QObject *obj, QEvent *event) override
++ {
++ Q_UNUSED(obj)
++ if (event->type() == QEvent::FocusIn)
++ emit focusChanged(true);
++ else if (event->type() == QEvent::FocusOut)
++ emit focusChanged(false);
++
++ return false;
++ }
++
++Q_SIGNALS:
++ void focusChanged(bool in);
++};
++
+ ///Constructor
+ View::View(QWidget *parent)
+ : QWidget(parent),m_pTransferOverlay(nullptr),m_pAutoCompletion(nullptr)
+@@ -80,7 +104,9 @@ View::View(QWidget *parent)
+
+ //Create a call already, the app is useless without one anyway
+ m_pView->selectionModel()->setCurrentIndex(
+- CallModel::instance().getIndex(CallModel::instance().dialingCall()),
++ CallModel::instance().rowCount() ?
++ CallModel::instance().index(0, 0)
++ : CallModel::instance().getIndex(CallModel::instance().dialingCall()),
+ QItemSelectionModel::SelectCurrent
+ );
+
+@@ -107,7 +133,13 @@ View::View(QWidget *parent)
+
+ GlobalInstances::setInterface<ColorDelegate>(pal);
+
+- m_pMessageBoxW->setVisible(false);
++ const QModelIndex currentIndex = CallModel::instance().selectionModel()->currentIndex();
++
++ m_pMessageBoxW->setVisible(
++ qvariant_cast<Call::LifeCycleState>(currentIndex.data((int)Call::Role::LifeCycleState))
++ == Call::LifeCycleState::PROGRESS
++ );
++ connect(new FocusWatcher(m_pSendMessageLE), &FocusWatcher::focusChanged, this, &View::displayHistory);
+
+ //Setup volume
+ toolButton_recVol->setDefaultAction(ActionCollection::instance()->muteCaptureAction ());
+@@ -125,6 +157,8 @@ View::View(QWidget *parent)
+ /**/connect(widget_dialpad , &Dialpad::typed , m_pEventManager, &EventManager::typeString );
+ /* */
+
++ connect(m_pView->selectionModel(), &QItemSelectionModel::currentChanged, this, &View::updateTextBoxStatus);
++
+ //Auto completion
+ loadAutoCompletion();
+
+@@ -137,7 +171,6 @@ View::View(QWidget *parent)
+
+ setFocus(Qt::OtherFocusReason);
+
+- loadAutoCompletion ();
+ widget_dialpad->setVisible(ConfigurationSkeleton::displayDialpad());
+ Audio::Settings::instance().setEnableRoomTone(ConfigurationSkeleton::enableRoomTone());
+ }
+@@ -278,4 +311,29 @@ void View::slotAutoCompleteClicked(ContactMethod* n) //TODO use the new LRC API
+ }
+ }
+
++void View::updateTextBoxStatus()
++{
++ static bool display = ConfigurationSkeleton::displayMessageBox();
++ if (display) {
++ Call* call = CallModel::instance().selectedCall();
++ m_pMessageBoxW->setVisible(call
++ && (call->lifeCycleState() == Call::LifeCycleState::PROGRESS)
++ );
++ }
++}
++
++void View::displayHistory(bool in)
++{
++ if (!in)
++ return;
++
++ Call* call = CallModel::instance().selectedCall();
++
++ if (call->lifeCycleState() == Call::LifeCycleState::PROGRESS) {
++ m_pMessageTabBox->showConversation(call->peerContactMethod());
++ }
++
++ m_pMessageTabBox->clearColor();
++
++}
+ #include "view.moc"
+diff --git a/src/view.h b/src/view.h
+index d67f63b..f61c3fa 100644
+--- a/src/view.h
++++ b/src/view.h
+@@ -77,6 +77,8 @@ private Q_SLOTS:
+ void sendMessage ();
+ void slotAutoCompleteClicked(ContactMethod* n);
+ void loadAutoCompletion ();
++ void updateTextBoxStatus ();
++ void displayHistory (bool);
+
+ public Q_SLOTS:
+ void updateVolumeControls ();
+diff --git a/src/widgets/immanager.cpp b/src/widgets/immanager.cpp
+index 4afd45a..d486fbd 100644
+--- a/src/widgets/immanager.cpp
++++ b/src/widgets/immanager.cpp
+@@ -19,37 +19,139 @@
+ #include "call.h"
+ #include "callmodel.h"
+ #include "contactmethod.h"
++#include "person.h"
+ #include <media/text.h>
+ #include <media/textrecording.h>
++#include <media/recordingmodel.h>
+ #include "../delegates/imdelegate.h"
++#include <eventmanager.h>
++
++//KDE
++#include <KColorScheme>
+ #include <klocalizedstring.h>
+
++// Qt
++#include <QtWidgets/QScrollBar>
++#include <QtGui/QPixmap>
++#include <QtCore/QTimer>
++
+ ///Constructor
+ IMManager::IMManager(QWidget* parent) : QTabWidget(parent)
+ {
+ setVisible(false);
+ setTabsClosable(true);
+
+- connect(&CallModel::instance(), &CallModel::mediaAdded,[this](Call* c, Media::Media* m) {
+- if (m->type() == Media::Media::Type::TEXT) {
++ connect(&CallModel::instance(), &CallModel::mediaAdded, this, &IMManager::addMedia);
+
+- Media::Text* media = static_cast<Media::Text*>(m);
++ connect(this,SIGNAL(tabCloseRequested(int)),this,SLOT(closeRequest(int)));
+
+- newConversation(c->peerContactMethod(),media->recording()->instantMessagingModel());
+- }
+- });
++ connect(&Media::RecordingModel::instance(), &Media::RecordingModel::newTextMessage, this, &IMManager::newMessageInserted);
++
++ foreach(Call* c, CallModel::instance().getActiveCalls()) {
++ if (c->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::IN))
++ addMedia(c, c->firstMedia<Media::Text>(Media::Media::Direction::IN));
++ else if (c->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::OUT))
++ addMedia(c, c->firstMedia<Media::Text>(Media::Media::Direction::OUT));
++ }
++
++ connect(this, &IMManager::currentChanged, this, &IMManager::clearColor);
++}
++
++void IMManager::newMessageInserted(Media::TextRecording* r, ContactMethod* cm)
++{
++ if (!r->instantMessagingModel()->index(
++ r->instantMessagingModel()->rowCount()-1, 0
++ ).data((int)Media::TextRecording::Role::HasText).toBool()
++ )
++ return;
++
++ IMTab* tab = nullptr;
++
++ if (!(tab = m_lTabs[cm]))
++ tab = newConversation(cm, r->instantTextMessagingModel());
++
++ if (currentWidget() != tab
++ || (!EventManager::mayHaveFocus())
++ || tab->verticalScrollBar()->value() != tab->verticalScrollBar()->maximum()) {
++ static QColor awayBrush = KStatefulBrush( KColorScheme::Window, KColorScheme::NegativeText ).brush(QPalette::Normal).color();
++
++ tabBar()->setTabTextColor(indexOf(tab), awayBrush);
++ }
+
+- connect(this,SIGNAL(tabCloseRequested(int)),this,SLOT(closeRequest(int)));
++}
++
++void IMManager::addMedia(Call* c, Media::Media* m)
++{
++ if (m->type() == Media::Media::Type::TEXT) {
++
++ Media::Text* media = static_cast<Media::Text*>(m);
++
++ bool isRelevant = media->hasMimeType(QStringLiteral("text/plain"))
++ || media->hasMimeType(QStringLiteral("text/html"));
++
++ auto cm = c->peerContactMethod();
++
++ if (!isRelevant) {
++ connect(media, &Media::Text::mimeTypesChanged, [this, media, cm]() {
++ bool isRelevant = media->hasMimeType(QStringLiteral("text/plain"))
++ || media->hasMimeType(QStringLiteral("text/html"));
++
++ if (isRelevant)
++ newConversation(cm, media->recording()->instantTextMessagingModel());
++
++ });
++ }
++ }
+ }
+
+ ///Destructor
+-void IMManager::newConversation(ContactMethod* cm, QAbstractListModel* model)
++IMTab* IMManager::newConversation(ContactMethod* cm, QAbstractItemModel* model)
+ {
++ if (auto tab = m_lTabs[cm]) {
++ setCurrentWidget(m_lTabs[cm]);
++ setVisible(true);
++ return tab;
++ }
++
+ IMTab* newTab = new IMTab(model,this);
+ m_lTabs[cm] = newTab;
+ setVisible(true);
+ const QString name = cm->primaryName();
+- addTab(newTab,name);
++
++ if (cm->contact())
++ setCurrentIndex(addTab(newTab,qvariant_cast<QPixmap>(cm->contact()->photo()), name));
++ else
++ setCurrentIndex(addTab(newTab,name));
++
++
++ // If the IMManager widget is not in the view yet, the scrollbar will be wrong
++ QTimer::singleShot(0,[this, model, newTab] {
++ newTab->scrollTo(model->index(model->rowCount()-1,0));
++
++ if (newTab->verticalScrollBar())
++ newTab->verticalScrollBar()->setValue(newTab->verticalScrollBar()->maximum());
++ });
++
++ return newTab;
++}
++
++///Display a conversation from history
++bool IMManager::showConversation(ContactMethod* cm)
++{
++ if (!cm)
++ return false;
++
++ auto textRec = cm->textRecording();
++
++ if (!textRec)
++ return false;
++
++ QAbstractItemModel* model = textRec->instantTextMessagingModel();
++
++ if (model->rowCount())
++ newConversation(cm, model);
++
++ return true;
+ }
+
+ ///Close a tab
+@@ -64,3 +166,12 @@ void IMManager::closeRequest(int index)
+ setVisible(false);
+ }
+ }
++
++void IMManager::clearColor(int idx)
++{
++ if (idx == -1)
++ for(int i =0; i < count(); i++)
++ clearColor(i);
++ else
++ tabBar()->setTabTextColor(idx, QColor());
++}
+diff --git a/src/widgets/immanager.h b/src/widgets/immanager.h
+index 37e7c7a..21476df 100644
+--- a/src/widgets/immanager.h
++++ b/src/widgets/immanager.h
+@@ -20,7 +20,7 @@
+
+ //Qt
+ #include <QtCore/QHash>
+-class QAbstractListModel;
++class QAbstractItemModel;
+
+ //KDE
+ #include <QtWidgets/QTabWidget>
+@@ -28,6 +28,11 @@ class QAbstractListModel;
+ //Ring
+ class IMTab;
+ class ContactMethod;
++class Call;
++namespace Media {
++ class Media;
++ class TextRecording;
++}
+
+ class IMManager : public QTabWidget
+ {
+@@ -40,9 +45,15 @@ private:
+ //Attrubutes
+ QHash<ContactMethod*,IMTab*> m_lTabs;
+
++public Q_SLOTS:
++ void clearColor(int idx = -1);
++ bool showConversation(ContactMethod* cm);
++
+ private Q_SLOTS:
+- void newConversation(ContactMethod* cm, QAbstractListModel* model);
++ IMTab* newConversation(ContactMethod* cm, QAbstractItemModel* model);
+ void closeRequest(int index);
++ void addMedia(Call* c, Media::Media* m);
++ void newMessageInserted(Media::TextRecording* r, ContactMethod* cm);
+ };
+
+ #endif // IM_MANAGER
+--
+2.7.0
+
diff --git a/0002-contact-Enable-Akonadi-support-again.patch b/0002-contact-Enable-Akonadi-support-again.patch
new file mode 100644
index 000000000000..72b0f5a0949c
--- /dev/null
+++ b/0002-contact-Enable-Akonadi-support-again.patch
@@ -0,0 +1,1565 @@
+From 21755b923acdcfaac8130c52b204cb46d49f5bb5 Mon Sep 17 00:00:00 2001
+From: Emmanuel Lepage Vallee <elv1313@gmail.com>
+Date: Wed, 16 Dec 2015 23:32:26 -0500
+Subject: [PATCH 2/6] contact: Enable Akonadi support again
+
+Most features from the previous releases seem to work again
+with the KF5 version of Akonadi.
+---
+ src/conf/dlgdisplay.cpp | 6 +
+ src/conf/dlgdisplaybase.ui | 10 +
+ src/dock.cpp | 189 ++++++++---
+ src/klib/CMakeLists.txt | 14 +
+ src/klib/akonadibackend.cpp | 492 ++++++++++++++++-------------
+ src/klib/akonadibackend.h | 145 +++------
+ src/klib/akonadicontactcollectionmodel.cpp | 141 ---------
+ src/klib/akonadicontactcollectionmodel.h | 70 ----
+ src/klib/itemmodelserialization.cpp | 17 +
+ src/klib/itemmodelserialization.h | 6 +-
+ src/klib/ring-kde.kcfg | 6 +
+ src/mainwindow.cpp | 5 +-
+ 12 files changed, 514 insertions(+), 587 deletions(-)
+ delete mode 100644 src/klib/akonadicontactcollectionmodel.cpp
+ delete mode 100644 src/klib/akonadicontactcollectionmodel.h
+
+diff --git a/src/conf/dlgdisplay.cpp b/src/conf/dlgdisplay.cpp
+index 32fac93..c83d43f 100644
+--- a/src/conf/dlgdisplay.cpp
++++ b/src/conf/dlgdisplay.cpp
+@@ -24,6 +24,9 @@
+ #include <KConfigDialog>
+ #include <klocalizedstring.h>
+
++//LRC
++#include <categorizedcontactmodel.h>
++
+ //Ring
+ #include "mainwindow.h"
+
+@@ -97,6 +100,9 @@ void DlgDisplay::updateSettings()
+ ConfigurationSkeleton::setAutoStartOverride(true);
+ }
+
++ if (ConfigurationSkeleton::hideUnreachable() != kcfg_hideUnreachable->isChecked())
++ CategorizedContactModel::instance().setUnreachableHidden(kcfg_hideUnreachable->isChecked());
++
+ MainWindow::app()->setAutoStart(kcfg_autoStart->isChecked());
+
+ m_HasChanged = false;
+diff --git a/src/conf/dlgdisplaybase.ui b/src/conf/dlgdisplaybase.ui
+index b7e424a..cea3098 100644
+--- a/src/conf/dlgdisplaybase.ui
++++ b/src/conf/dlgdisplaybase.ui
+@@ -102,6 +102,16 @@
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
++ <widget class="QCheckBox" name="kcfg_hideUnreachable">
++ <property name="toolTip">
++ <string>Hide a contact when there is no enabled account that can reach him/her</string>
++ </property>
++ <property name="text">
++ <string>Hide unreachable contacts</string>
++ </property>
++ </widget>
++ </item>
++ <item>
+ <widget class="QCheckBox" name="kcfg_hidePersonWithoutPhone">
+ <property name="text">
+ <string>Hide contacts without phone numbers</string>
+diff --git a/src/dock.cpp b/src/dock.cpp
+index 91ba8e2..4b1f88e 100644
+--- a/src/dock.cpp
++++ b/src/dock.cpp
+@@ -21,6 +21,10 @@
+ //Qt
+ #include <QtWidgets/QMainWindow>
+ #include <QtCore/QSortFilterProxyModel>
++#include <QtCore/QTimer>
++
++//KDE
++#include <KColorScheme>
+
+ //Delegates
+ #include <conf/account/delegates/categorizeddelegate.h>
+@@ -28,11 +32,6 @@
+ #include "delegates/phonenumberdelegate.h"
+ #include "delegates/historydelegate.h"
+
+-//Menu
+-#include "menu/person.h"
+-#include "menu/call.h"
+-#include "menu/contactmethod.h"
+-
+ //Widgets
+ #include "widgets/dockbase.h"
+
+@@ -43,8 +42,11 @@
+
+ //Ring
+ #include "mainwindow.h"
++#include "view.h"
+ #include "actioncollection.h"
+ #include "klib/kcfg_settings.h"
++#include <proxies/deduplicateproxy.h>
++#include <proxies/roletransformationproxy.h>
+
+ class BookmarkSortFilterProxyModel : public QSortFilterProxyModel
+ {
+@@ -76,7 +78,7 @@ protected:
+ Dock::Dock(QMainWindow* w) : QObject(w)
+ {
+ //Contact dock
+- m_pContactCD = new DockBase ( w );
++ m_pContactCD = new DockBase ( nullptr );
+ m_pContactCD->setObjectName("contactDock");
+ m_pContactCD->setWindowTitle(i18nc("Contact tab","Contact"));
+ auto m_pCategoryDelegate = new CategorizedDelegate(m_pContactCD->view());
+@@ -88,65 +90,112 @@ Dock::Dock(QMainWindow* w) : QObject(w)
+ m_pCategoryDelegate->setChildChildDelegate(m_pContactMethodDelegate);
+ m_pContactCD->setDelegate(m_pCategoryDelegate);
+
+- CategorizedContactModel::instance().setUnreachableHidden(ConfigurationSkeleton::hidePersonWithoutPhone());
+- QSortFilterProxyModel* proxy = CategorizedContactModel::SortedProxy::instance().model();
+- m_pContactCD->setProxyModel(proxy);
+- m_pContactCD->setSortingModel(
+- CategorizedContactModel::SortedProxy::instance().categoryModel(),
+- CategorizedContactModel::SortedProxy::instance().categorySelectionModel()
+- );
+-
+- CategorizedContactModel::SortedProxy::instance().categorySelectionModel()->setCurrentIndex(
+- CategorizedContactModel::SortedProxy::instance().categoryModel()->index(
+- ConfigurationSkeleton::contactSortMode() , 0
+- ), QItemSelectionModel::ClearAndSelect
+- );
+-
+- connect(CategorizedContactModel::SortedProxy::instance().categorySelectionModel(), & QItemSelectionModel::currentChanged,[](const QModelIndex& idx) {
+- if (idx.isValid())
+- ConfigurationSkeleton::setContactSortMode(idx.row());
+- });
+-
+- m_pContactCD->setMenuConstructor([]() {
+- return new Menu::Person();
++ // Load later to speed up the process (avoid showing while inserting items)
++ QTimer::singleShot(10, [this]() {
++ CategorizedContactModel::instance().setUnreachableHidden(ConfigurationSkeleton::hideUnreachable());
++ auto proxy = CategorizedContactModel::SortedProxy::instance().model();
++ m_pContactCD->setProxyModel(proxy, proxy);
++ m_pContactCD->setSortingModel(
++ CategorizedContactModel::SortedProxy::instance().categoryModel(),
++ CategorizedContactModel::SortedProxy::instance().categorySelectionModel()
++ );
++
++ CategorizedContactModel::SortedProxy::instance().categorySelectionModel()->setCurrentIndex(
++ CategorizedContactModel::SortedProxy::instance().categoryModel()->index(
++ ConfigurationSkeleton::contactSortMode() , 0
++ ), QItemSelectionModel::ClearAndSelect
++ );
++
++ connect(CategorizedContactModel::SortedProxy::instance().categorySelectionModel(), & QItemSelectionModel::currentChanged,[](const QModelIndex& idx) {
++ if (idx.isValid())
++ ConfigurationSkeleton::setContactSortMode(idx.row());
++ });
+ });
+
+ //History dock
+- m_pHistoryDW = new DockBase ( w );
++ m_pHistoryDW = new DockBase ( nullptr );
+ m_pHistoryDW->setObjectName("historyDock");
+ m_pHistoryDW->setWindowTitle(i18nc("History tab","History"));
+ CategorizedDelegate* delegate = new CategorizedDelegate(m_pHistoryDW->view());
+ delegate->setChildDelegate(new HistoryDelegate(m_pHistoryDW->view()));
+ m_pHistoryDW->setDelegate(delegate);
+- m_pHistoryDW->setMenuConstructor([]() {
+- return new Menu::Call();
+- });
+- proxy = CategorizedHistoryModel::SortedProxy::instance().model();
+- m_pHistoryDW->setProxyModel(proxy);
+- m_pHistoryDW->setSortingModel(
+- CategorizedHistoryModel::SortedProxy::instance().categoryModel (),
+- CategorizedHistoryModel::SortedProxy::instance().categorySelectionModel()
+- );
+-
+- CategorizedHistoryModel::SortedProxy::instance().categorySelectionModel()->setCurrentIndex(
+- CategorizedHistoryModel::SortedProxy::instance().categoryModel()->index(
+- ConfigurationSkeleton::historySortMode() , 0
+- ), QItemSelectionModel::ClearAndSelect
+- );
+-
+- connect(CategorizedHistoryModel::SortedProxy::instance().categorySelectionModel(), & QItemSelectionModel::currentChanged,[](const QModelIndex& idx) {
+- if (idx.isValid())
+- ConfigurationSkeleton::setHistorySortMode(idx.row());
++
++
++ QTimer::singleShot(1000, [this]() {
++ // De-duplicate by name and date
++ auto proxy = CategorizedHistoryModel::SortedProxy::instance().model();
++ RoleTransformationProxy* highlight = nullptr;
++ auto dedup = ConfigurationSkeleton::mergeSameDayPeer() ? new DeduplicateProxy(proxy) : nullptr;
++
++ if (dedup)
++ dedup->addFilterRole(static_cast<int>(Call::Role::DateOnly));
++
++ // Highlight missed calls
++ static const bool highlightMissedIn = ConfigurationSkeleton::highlightMissedIncomingCalls();
++ static const bool highlightMissedOut = ConfigurationSkeleton::highlightMissedOutgoingCalls();
++
++ if (highlightMissedOut || highlightMissedIn) {
++ static QColor awayBrush = KStatefulBrush( KColorScheme::Window, KColorScheme::NegativeText ).brush(QPalette::Normal).color();
++ awayBrush.setAlpha(30);
++ static QVariant missedBg(awayBrush);
++
++ highlight = new RoleTransformationProxy(dedup ? dedup : proxy);
++
++ highlight->setRole(Qt::BackgroundRole, [](const QModelIndex& idx) {
++ if (idx.data((int)Call::Role::Missed).toBool()) {
++ const Call::Direction d = qvariant_cast<Call::Direction>(
++ idx.data((int)Call::Role::Direction)
++ );
++
++ if ((highlightMissedIn && d == Call::Direction::INCOMING)
++ || (highlightMissedOut && d == Call::Direction::OUTGOING))
++ return missedBg;
++ }
++
++ return QVariant();
++ });
++
++ highlight->setSourceModel(proxy);
++
++ if (dedup)
++ dedup->setSourceModel(highlight);
++ }
++ else if (dedup)
++ dedup->setSourceModel(proxy);
++
++ if (dedup)
++ m_pHistoryDW->setProxyModel(dedup , proxy );
++ else if (highlight)
++ m_pHistoryDW->setProxyModel(highlight, proxy );
++ else
++ m_pHistoryDW->setProxyModel(proxy , proxy );
++
++ m_pHistoryDW->setSortingModel(
++ CategorizedHistoryModel::SortedProxy::instance().categoryModel (),
++ CategorizedHistoryModel::SortedProxy::instance().categorySelectionModel()
++ );
++
++ CategorizedHistoryModel::SortedProxy::instance().categorySelectionModel()->setCurrentIndex(
++ CategorizedHistoryModel::SortedProxy::instance().categoryModel()->index(
++ ConfigurationSkeleton::historySortMode() , 0
++ ), QItemSelectionModel::ClearAndSelect
++ );
++
++ connect(CategorizedHistoryModel::SortedProxy::instance().categorySelectionModel(), & QItemSelectionModel::currentChanged,[](const QModelIndex& idx) {
++ if (idx.isValid())
++ ConfigurationSkeleton::setHistorySortMode(idx.row());
++ });
+ });
+
+ //Bookmark dock
+- m_pBookmarkDW = new DockBase ( w );
++ m_pBookmarkDW = new DockBase ( nullptr );
+ m_pBookmarkDW->setObjectName("bookmarkDock");
+ m_pBookmarkDW->setWindowTitle(i18nc("Bookmark tab","Bookmark"));
+ CategorizedDelegate* delegate2 = new CategorizedDelegate(m_pBookmarkDW->view());
+ delegate2->setChildDelegate(new HistoryDelegate(m_pHistoryDW->view()));
+ m_pBookmarkDW->setDelegate(delegate2);
+- m_pBookmarkDW->setProxyModel(new BookmarkSortFilterProxyModel(this));
++ auto m = new BookmarkSortFilterProxyModel(this);
++ m_pBookmarkDW->setProxyModel(m, m);
+
+
+ //GUI
+@@ -178,6 +227,10 @@ Dock::Dock(QMainWindow* w) : QObject(w)
+ connect(ActionCollection::instance()->showHistoryDockAction(), SIGNAL(toggled(bool)),m_pHistoryDW, SLOT(setVisible(bool)));
+ connect(ActionCollection::instance()->showBookmarkDockAction(),SIGNAL(toggled(bool)),m_pBookmarkDW,SLOT(setVisible(bool)));
+
++ connect( ActionCollection::instance()->focusHistory (), &QAction::triggered, this, &Dock::focusHistory );
++ connect( ActionCollection::instance()->focusContact (), &QAction::triggered, this, &Dock::focusContact );
++ connect( ActionCollection::instance()->focusCall (), &QAction::triggered, this, &Dock::focusCall );
++ connect( ActionCollection::instance()->focusBookmark(), &QAction::triggered, this, &Dock::focusBookmark );
+ }
+
+ Dock::~Dock()
+@@ -185,9 +238,14 @@ Dock::~Dock()
+ m_pContactCD ->setDelegate (nullptr);
+ m_pHistoryDW ->setDelegate (nullptr);
+ m_pBookmarkDW->setDelegate (nullptr);
+- m_pContactCD ->setProxyModel(nullptr);
+- m_pHistoryDW ->setProxyModel(nullptr);
+- m_pBookmarkDW->setProxyModel(nullptr);
++
++ m_pContactCD ->setProxyModel(nullptr, nullptr);
++ m_pHistoryDW ->setProxyModel(nullptr, nullptr);
++ m_pBookmarkDW->setProxyModel(nullptr, nullptr);
++
++ m_pContactCD ->deleteLater();
++ m_pHistoryDW ->deleteLater();
++ m_pBookmarkDW->deleteLater();
+
+ if (!MainWindow::app()->isHidden()) {
+ ConfigurationSkeleton::setDisplayContactDock ( m_pContactCD->isVisible() );
+@@ -240,4 +298,31 @@ void Dock::updateTabIcons()
+ }
+ } //updateTabIcons
+
++void Dock::focusHistory()
++{
++ m_pHistoryDW->raise();
++ ActionCollection::instance()->raiseClient(false);
++ m_pHistoryDW->m_pFilterLE->setFocus(Qt::OtherFocusReason);
++}
++
++void Dock::focusContact()
++{
++ m_pContactCD->raise();
++ ActionCollection::instance()->raiseClient(false);
++ m_pContactCD->m_pFilterLE->setFocus(Qt::OtherFocusReason);
++}
++
++void Dock::focusCall()
++{
++ MainWindow::view()->raise();
++ ActionCollection::instance()->raiseClient(true);
++}
++
++void Dock::focusBookmark()
++{
++ m_pBookmarkDW->raise();
++ ActionCollection::instance()->raiseClient(false);
++ m_pBookmarkDW->m_pFilterLE->setFocus(Qt::OtherFocusReason);
++}
++
+ #include <dock.moc>
+diff --git a/src/klib/CMakeLists.txt b/src/klib/CMakeLists.txt
+index a361d9d..be24937 100644
+--- a/src/klib/CMakeLists.txt
++++ b/src/klib/CMakeLists.txt
+@@ -28,6 +28,12 @@ FIND_PACKAGE(KF5 REQUIRED COMPONENTS
+ XmlGui
+ )
+
++FIND_PACKAGE(KF5 COMPONENTS
++ Akonadi
++ AkonadiContact
++ Contacts
++)
++
+ INCLUDE_DIRECTORIES(${Qt5Widgets_INCLUDES} ${Qt5Core_INCLUDES} ${LIB_RING_CLIENT_INCLUDE_DIR})
+
+ ADD_DEFINITIONS(${Qt5Core_DEFINITIONS})
+@@ -45,6 +51,11 @@ SET( libkring_LIB_SRCS
+ itemmodelserialization.cpp
+ )
+
++SET(libkring_LIB_SRCS
++ ${libkring_LIB_SRCS}
++ akonadibackend.cpp
++)
++
+ KCONFIG_ADD_KCFG_FILES (libkring_LIB_SRCS kcfg_settings.kcfgc)
+
+ ADD_LIBRARY( libkring STATIC ${libkring_LIB_SRCS} )
+@@ -58,6 +69,9 @@ target_link_libraries( libkring
+ KF5::WidgetsAddons
+ KF5::ConfigCore
+ KF5::ConfigGui
++ KF5::AkonadiCore
++ KF5::AkonadiContact
++ KF5::Contacts
+ )
+
+ SET( libkring_LIB_HDRS
+diff --git a/src/klib/akonadibackend.cpp b/src/klib/akonadibackend.cpp
+index 8afc1c4..be05b06 100644
+--- a/src/klib/akonadibackend.cpp
++++ b/src/klib/akonadibackend.cpp
+@@ -20,29 +20,33 @@
+ #include "akonadibackend.h"
+
+ //Qt
+-#include <QPointer>
++#include <QtCore/QPointer>
++#include <QtWidgets/QVBoxLayout>
++#include <QtWidgets/QDialog>
++#include <QtWidgets/QDialogButtonBox>
++#include <QtWidgets/QPushButton>
+
+ //KDE
+-#include <QDebug>
+ #include <KJob>
+-#include <QDialog>
+-#include <akonadi/control.h>
+-#include <akonadi/collectionfilterproxymodel.h>
+-#include <akonadi/kmime/messagemodel.h>
+-#include <akonadi/recursiveitemfetchjob.h>
+-#include <akonadi/itemfetchjob.h>
+-#include <akonadi/itemfetchscope.h>
+-#include <akonadi/collectionfetchjob.h>
+-#include <akonadi/collectionfetchscope.h>
+-#include <akonadi/contact/contacteditor.h>
+-#include <akonadi/contact/contacteditordialog.h>
+-#include <akonadi/session.h>
+-#include <akonadi/monitor.h>
+-#include <akonadi/itemdeletejob.h>
+-#include <akonadi/entitydisplayattribute.h>
+-#include <kabc/addressee.h>
+-#include <kabc/addresseelist.h>
+-#include <kabc/contactgroup.h>
++#include <AkonadiCore/Control>
++#include <AkonadiCore/CollectionFilterProxyModel>
++// #include <AkonadiCore/Kmime/MessageModel>
++#include <AkonadiCore/RecursiveItemFetchJob>
++#include <AkonadiCore/ItemFetchJob>
++#include <AkonadiCore/ItemFetchScope>
++#include <AkonadiCore/CollectionFetchJob>
++#include <AkonadiCore/CollectionFetchScope>
++#include <Akonadi/Contact/ContactEditor>
++#include <Akonadi/Contact/ContactEditorDialog>
++#include <AkonadiCore/Session>
++#include <AkonadiCore/Monitor>
++#include <AkonadiCore/ChangeRecorder>
++#include <AkonadiCore/ItemDeleteJob>
++#include <AkonadiCore/EntityDisplayAttribute>
++#include <AkonadiCore/EntityTreeModel>
++#include <kcontacts/addressee.h>
++#include <kcontacts/addresseelist.h>
++#include <kcontacts/contactgroup.h>
+
+ //Ring library
+ #include "person.h"
+@@ -56,12 +60,97 @@
+ #include "collectioninterface.h"
+ #include "numbercategory.h"
+ #include "personmodel.h"
++#include "interfaces/itemmodelstateserializeri.h"
++#include "globalinstances.h"
++
++// Ring-KDE
+ #include "kcfg_settings.h"
+
+-Akonadi::Session* AkonadiBackend::m_pSession = nullptr;
++Akonadi::Session* AkonadiBackend::m_spSession = nullptr;
++Akonadi::EntityTreeModel* AkonadiBackend::m_spModel = nullptr;
+ QHash<Akonadi::Collection::Id, AkonadiBackend*> AkonadiBackend::m_hParentLookup;
+
++class AkonadiEditor : public CollectionEditor<Person>
++{
++public:
++ AkonadiEditor(CollectionMediator<Person>* m) : CollectionEditor<Person>(m) {}
++ ~AkonadiEditor() {
++ m_lBackendPersons.clear();
++ m_ItemHash.clear();
++ m_AddrHash.clear();
++ }
++ virtual bool save ( const Person* item ) override;
++ virtual bool addExisting( const Person* item ) override;
++ virtual bool remove ( const Person* item ) override;
++ virtual bool edit ( Person* item ) override;
++ virtual bool addNew ( Person* item ) override;
++
++ QHash<QString,KContacts::Addressee> m_AddrHash ;
++ QHash<QString,Akonadi::Item> m_ItemHash ;
++ QVector<Person*> m_lBackendPersons;
++private:
++ virtual QVector<Person*> items() const override;
++};
++
++void AkonadiBackend::digg(QAbstractItemModel* model, const QModelIndex& idx)
++{
++ if (!model)
++ return;
++
++ if (idx.isValid()) {
++ auto col = idx.data( Akonadi::EntityTreeModel::CollectionRole ).value<Akonadi::Collection>();
++
++ if (col.isValid()) {
++ auto col2 = PersonModel::instance().addCollection<AkonadiBackend,Akonadi::Collection*>(&col);
++ if (col2 && col2->isEnabled())
++ col2->load();
++ }
++ }
++
++ for (int i = 0;i < model->rowCount(idx);i++) {
++ QModelIndex current = model->index(i,0,idx);
++ digg(model, current);
++ }
++}
++
++void AkonadiBackend::slotRowsInserted(const QModelIndex& parent, int start, int end)
++{
++ for (int i = start; i <= end; i++) {
++ digg(m_spModel, m_spModel->index(i,0, parent));
++ }
++}
++
++// Watch and digg the EntityTreeModel to get the collection list
++void AkonadiBackend::initCollections()
++{
++ if (!m_spSession)
++ m_spSession = new Akonadi::Session("ring-kde");
++
++ auto changeRecorder = new Akonadi::ChangeRecorder();
+
++ changeRecorder->setCollectionMonitored( Akonadi::Collection::root() );
++ changeRecorder->setMimeTypeMonitored( "text/directory" );
++ changeRecorder->setSession( m_spSession );
++
++ m_spModel = new Akonadi::EntityTreeModel( changeRecorder, changeRecorder );
++ m_spModel->setItemPopulationStrategy( Akonadi::EntityTreeModel::NoItemPopulation );
++
++ QObject::connect(m_spModel, &QAbstractItemModel::rowsInserted, &slotRowsInserted);
++
++ digg(m_spModel, QModelIndex());
++}
++
++///Constructor
++AkonadiBackend::AkonadiBackend(CollectionMediator<Person>* mediator, Akonadi::Collection* parentCol) : QObject(mediator->model()),
++ CollectionInterface(new AkonadiEditor(mediator),m_hParentLookup[parentCol->parentCollection().id()]),m_pJob{},m_pMediator(mediator),
++ m_pMonitor(nullptr),m_isEnabled(false),m_wasEnabled(false)
++{
++ if (!m_spSession)
++ m_spSession = new Akonadi::Session( "Ring::instance" );
++ setObjectName(parentCol->name());
++ m_Coll = *parentCol;
++ m_hParentLookup[m_Coll.id()] = this;
++} //AkonadiBackend
+
+
+
+@@ -80,38 +169,91 @@ bool AkonadiEditor::save(const Person* item)
+ return false;
+ }
+
+-bool AkonadiEditor::append(const Person* item)
++bool AkonadiEditor::addExisting(const Person* item)
+ {
+ Q_UNUSED(item)
+ return false;
+ }
+
+-bool AkonadiEditor::remove(Person* c)
++bool AkonadiEditor::remove(const Person* c)
+ {
+ if (!c)
+ return false;
+ Akonadi::Item item = m_ItemHash[c->uid()];
+ Akonadi::ItemDeleteJob *job = new Akonadi::ItemDeleteJob( item );
+ job->exec();
+- c->setActive(false);
+ return true;
+ }
+
+-bool AkonadiEditor::edit( Person* item)
++void applyNativeEdit()
+ {
+- Q_UNUSED(item)
+- return false;
++
+ }
+
+-bool AkonadiEditor::addNew( Person* item)
++bool AkonadiEditor::edit( Person* contact)
+ {
+- Q_UNUSED(item)
++ Akonadi::Item item = m_ItemHash[contact->uid()];
++ if (!(item.hasPayload<KContacts::Addressee>() && item.payload<KContacts::Addressee>().uid() == contact->uid())) {
++ qDebug() << "Person not found";
++ return false ;
++ }
++
++ if ( item.isValid() ) {
++ QPointer<Akonadi::ContactEditorDialog> editor = new Akonadi::ContactEditorDialog( Akonadi::ContactEditorDialog::EditMode );
++ editor->editor()->loadContact(item);
++
++ if ( editor->exec() == QDialog::Accepted ) {
++ if ( !contact->save() ) {
++ delete editor;
++ qDebug() << "Unable to save new contact to storage";
++ return false;
++ }
++ }
++ delete editor;
++ return true;
++ }
+ return false;
+ }
+
++bool AkonadiEditor::addNew( Person* contact)
++{
++ KContacts::Addressee newPerson;
++ newPerson.setNickName ( contact->nickName () );
++ newPerson.setFormattedName ( contact->formattedName() );
++ newPerson.setGivenName ( contact->firstName () );
++ newPerson.setFamilyName ( contact->secondName () );
++ newPerson.setOrganization ( contact->organization () );
++ newPerson.setDepartment ( contact->department () );
++// newPerson.setPreferredEmail ( contact->preferredEmail() );//TODO
++
++ foreach (ContactMethod* nb, contact->phoneNumbers()) {
++ KContacts::PhoneNumber pn;
++ pn.setType(AkonadiBackend::nameToType(nb->category()->name()));
++
++ pn.setNumber(nb->uri());
++ newPerson.insertPhoneNumber(pn);
++ }
++
++ //aPerson->setContactMethods (newNumbers );//TODO
++
++ QPointer<Akonadi::ContactEditorDialog> editor = new Akonadi::ContactEditorDialog( Akonadi::ContactEditorDialog::CreateMode );
++
++ editor->editor()->setContactTemplate(newPerson);
++
++ if ( editor->exec() == QDialog::Accepted ) {
++ if ( !contact->save() ) {
++ delete editor;
++ qDebug() << "Unable to save new contact to storage";
++ return false;
++ }
++ }
++ delete editor;
++ return true;
++}
++
+ QVector<Person*> AkonadiEditor::items() const
+ {
+- return QVector<Person*>();
++ return m_lBackendPersons;
+ }
+
+ QString AkonadiBackend::name () const
+@@ -138,7 +280,11 @@ QVariant AkonadiBackend::icon() const
+
+ bool AkonadiBackend::isEnabled() const
+ {
+- return m_isEnabled;
++ try {
++ return GlobalInstances::itemModelStateSerializer().isChecked(this);
++ } catch (...) {
++ return true;
++ }
+ }
+
+ bool AkonadiBackend::load()
+@@ -160,9 +306,9 @@ bool AkonadiBackend::load()
+ connect(m_pMonitor,SIGNAL(itemChanged(Akonadi::Item,QSet<QByteArray>)),this,SLOT(slotItemChanged(Akonadi::Item,QSet<QByteArray>)));
+ connect(m_pMonitor,SIGNAL(itemRemoved(Akonadi::Item)),this,SLOT(slotItemRemoved(Akonadi::Item)));
+
+-
+ m_pMonitor->setCollectionMonitored(m_Coll,true);
+ m_isEnabled = true; //FIXME does it make sense to merge loaded and enabled?
++ m_wasEnabled = false;
+ return true;
+ }
+
+@@ -172,15 +318,17 @@ bool AkonadiBackend::enable (bool enable)
+ return load();
+ }
+ else if (m_wasEnabled && enable) {
+- foreach(Person* contact, static_cast<AkonadiEditor*>(editor<Person>())->m_lBackendPersons) {
+- contact->setActive(true);
++ foreach(Person* contact, items<Person>()) {
++ activate(contact);
++ emit contact->changed();
+ }
+ m_wasEnabled = false;
+ m_isEnabled = true;
+ }
+- else if (isEnabled()) {
+- foreach(Person* contact, static_cast<AkonadiEditor*>(editor<Person>())->m_lBackendPersons) {
+- contact->setActive(false);
++ else if (m_isEnabled && !enable) {
++ foreach(Person* contact, items<Person>()) {
++ deactivate(contact);
++ emit contact->changed();
+ }
+ m_isEnabled = false;
+ m_wasEnabled = true;
+@@ -199,9 +347,9 @@ QByteArray AkonadiBackend::id() const
+ return QString::number(m_Coll.id()).toLatin1();
+ }
+
+-CollectionInterface::SupportedFeatures AkonadiBackend::supportedFeatures() const
++FlagPack<CollectionInterface::SupportedFeatures> AkonadiBackend::supportedFeatures() const
+ {
+- return (CollectionInterface::SupportedFeatures) (
++ return (
+ CollectionInterface::SupportedFeatures::NONE |
+ CollectionInterface::SupportedFeatures::LOAD |
+ CollectionInterface::SupportedFeatures::SAVE |
+@@ -220,59 +368,63 @@ CollectionInterface::SupportedFeatures AkonadiBackend::supportedFeatures() const
+ * *
+ ****************************************************************************/
+
+-///Convert string to akonadi KABC::PhoneNumber
+-KABC::PhoneNumber::Type AkonadiBackend::nameToType(const QString& name)
+-{
+- if (name == "Home" ) return KABC::PhoneNumber::Home ;
+- else if (name == "Work" ) return KABC::PhoneNumber::Work ;
+- else if (name == "Msg" ) return KABC::PhoneNumber::Msg ;
+- else if (name == "Pref" ) return KABC::PhoneNumber::Pref ;
+- else if (name == "Voice" ) return KABC::PhoneNumber::Voice;
+- else if (name == "Fax" ) return KABC::PhoneNumber::Fax ;
+- else if (name == "Cell" ) return KABC::PhoneNumber::Cell ;
+- else if (name == "Video" ) return KABC::PhoneNumber::Video;
+- else if (name == "Bbs" ) return KABC::PhoneNumber::Bbs ;
+- else if (name == "Modem" ) return KABC::PhoneNumber::Modem;
+- else if (name == "Car" ) return KABC::PhoneNumber::Car ;
+- else if (name == "Isdn" ) return KABC::PhoneNumber::Isdn ;
+- else if (name == "Pcs" ) return KABC::PhoneNumber::Pcs ;
+- else if (name == "Pager" ) return KABC::PhoneNumber::Pager;
+- return KABC::PhoneNumber::Home;
++///Convert string to akonadi KContacts::PhoneNumber
++KContacts::PhoneNumber::Type AkonadiBackend::nameToType(const QString& name)
++{
++ if (name == "Home" ) return KContacts::PhoneNumber::Home ;
++ else if (name == "Work" ) return KContacts::PhoneNumber::Work ;
++ else if (name == "Msg" ) return KContacts::PhoneNumber::Msg ;
++ else if (name == "Pref" ) return KContacts::PhoneNumber::Pref ;
++ else if (name == "Voice" ) return KContacts::PhoneNumber::Voice;
++ else if (name == "Fax" ) return KContacts::PhoneNumber::Fax ;
++ else if (name == "Cell" ) return KContacts::PhoneNumber::Cell ;
++ else if (name == "Video" ) return KContacts::PhoneNumber::Video;
++ else if (name == "Bbs" ) return KContacts::PhoneNumber::Bbs ;
++ else if (name == "Modem" ) return KContacts::PhoneNumber::Modem;
++ else if (name == "Car" ) return KContacts::PhoneNumber::Car ;
++ else if (name == "Isdn" ) return KContacts::PhoneNumber::Isdn ;
++ else if (name == "Pcs" ) return KContacts::PhoneNumber::Pcs ;
++ else if (name == "Pager" ) return KContacts::PhoneNumber::Pager;
++ return KContacts::PhoneNumber::Home;
+ }
+
+-void AkonadiBackend::fillPerson(Person* c, const KABC::Addressee& addr) const
+-{
+- c->setNickName (addr.nickName() );
+- c->setFormattedName (addr.formattedName() );
+- c->setFirstName (addr.givenName() );
+- c->setFamilyName (addr.familyName() );
+- c->setOrganization (addr.organization() );
+- c->setPreferredEmail (addr.preferredEmail() );
+- c->setDepartment (addr.department() );
+- c->setUid (addr.uid().toUtf8() );
+-
+- const KABC::PhoneNumber::List numbers = addr.phoneNumbers();
+- Person::ContactMethods newNumbers(c);
+- foreach (const KABC::PhoneNumber& number, numbers) {
+- newNumbers << PhoneDirectoryModel::instance()->getNumber(number.number(),c,nullptr,number.typeLabel());
+- QString number2 = number.number();
+- if (number2.left (5) == "<sip:")
+- number2 = number2.remove(0,5);
+- if (number2.right(1) == ">" )
+- number2 = number2.remove(number2.size()-2,1);
++void AkonadiBackend::fillPerson(Person* c, const KContacts::Addressee& addr)
++{
++ if (!c) {
++ qDebug() << "Contact not found";
++ return;
+ }
+- c->setContactMethods (newNumbers );
++
++ c->setNickName ( addr.nickName () );
++ c->setFormattedName ( addr.formattedName () );
++ c->setFirstName ( addr.givenName () );
++ c->setFamilyName ( addr.familyName () );
++ c->setOrganization ( addr.organization () );
++ c->setPreferredEmail ( addr.preferredEmail() );
++ c->setDepartment ( addr.department () );
++ c->setUid ( addr.uid().toUtf8 () );
++
++ const KContacts::PhoneNumber::List numbers = addr.phoneNumbers();
++ QVector<ContactMethod*> newNumbers;
++ foreach (const KContacts::PhoneNumber& number, numbers) {
++ ContactMethod* cm = PhoneDirectoryModel::instance().getNumber(number.number(),c,nullptr,number.typeLabel());
++
++ newNumbers << cm;
++ }
++
++ c->setContactMethods ( newNumbers );
+ }
+
+ Person* AkonadiBackend::addItem(Akonadi::Item item, bool ignoreEmpty)
+ {
++ Q_UNUSED(ignoreEmpty)
+ Person* aPerson = nullptr;
+- if ( item.hasPayload<KABC::Addressee>() ) {
++ if ( item.hasPayload<KContacts::Addressee>() ) {
+ m_pMonitor->setItemMonitored(item,true);
+- KABC::Addressee tmp = item.payload<KABC::Addressee>();
+- const KABC::PhoneNumber::List numbers = tmp.phoneNumbers();
++ KContacts::Addressee tmp = item.payload<KContacts::Addressee>();
++ const KContacts::PhoneNumber::List numbers = tmp.phoneNumbers();
+ const QString uid = tmp.uid();
+- if (numbers.size() || !ignoreEmpty) {
++ if (numbers.size() ) {
+ aPerson = new Person(this);
+
+ //This need to be done first because of the phone numbers indexes
+@@ -308,7 +460,7 @@ void AkonadiBackend::slotJobCompleted(KJob* job)
+ const Akonadi::Item::List items = akojob->items();
+ foreach ( const Akonadi::Item &item, items ) {
+ Person* c = addItem(item,onlyWithNumber);
+- PersonModel::instance()->addPerson(c);
++ PersonModel::instance().addPerson(c);
+ }
+ }
+ }
+@@ -321,66 +473,34 @@ void AkonadiBackend::update(const Akonadi::Collection& collection)
+ return;
+ }
+
+- Akonadi::RecursiveItemFetchJob *job = new Akonadi::RecursiveItemFetchJob( collection, QStringList() << KABC::Addressee::mimeType() << KABC::ContactGroup::mimeType());
++ Akonadi::RecursiveItemFetchJob *job = new Akonadi::RecursiveItemFetchJob( collection, QStringList() << KContacts::Addressee::mimeType() << KContacts::ContactGroup::mimeType());
+ job->fetchScope().fetchFullPayload();
+ connect(job, SIGNAL(result(KJob*)), this, SLOT(slotJobCompleted(KJob*)));
+ job->start();
+-// return m_PersonByUid.values();
+-} //update
+-
+-// bool AkonadiBackend::remove(Person* c)
+-// {
+-//
+-// }
+
+-///Edit backend value using an updated frontend contact
+-bool AkonadiBackend::edit(Person* contact,QWidget* parent)
+-{
+- Akonadi::Item item = static_cast<AkonadiEditor*>(editor<Person>())->m_ItemHash[contact->uid()];
+- if (!(item.hasPayload<KABC::Addressee>() && item.payload<KABC::Addressee>().uid() == contact->uid())) {
+- qDebug() << "Person not found";
+- return false ;
+- }
+-
+- if ( item.isValid() ) {
+- QPointer<Akonadi::ContactEditor> editor = new Akonadi::ContactEditor( Akonadi::ContactEditor::EditMode, parent );
+- editor->loadContact(item);
+- QPointer<QDialog> dlg = new QDialog(parent);
+- QVBoxLayout *mainLayout = new QVBoxLayout;
+- dlg->setLayout(mainLayout);
+- mainLayout->addWidget(editor);
+- if ( dlg->exec() == QDialog::Accepted ) {
+- if ( !editor->saveContact() ) {
+- delete dlg;
+- qDebug() << "Unable to save new contact to storage";
+- return false;
+- }
+- }
+- delete editor;
+- delete dlg ;
+- return true;
+- }
+- return false;
+-} //editPerson
++} //update
+
+ ///Save a contact
+ bool AkonadiBackend::save(const Person* contact)
+ {
+ Akonadi::Item item = static_cast<AkonadiEditor*>(editor<Person>())->m_ItemHash[contact->uid()];
+- if (!(item.hasPayload<KABC::Addressee>() && item.payload<KABC::Addressee>().uid() == contact->uid())) {
++
++ if (!(item.hasPayload<KContacts::Addressee>() && item.payload<KContacts::Addressee>().uid() == contact->uid())) {
+ qDebug() << "Person not found";
+ return false;
+ }
+- KABC::Addressee payload = item.payload<KABC::Addressee>();
+- payload.setNickName ( contact->nickName() );
+- payload.setFormattedName ( contact->formattedName() );
+- payload.setGivenName ( contact->firstName() );
+- payload.setFamilyName ( contact->secondName() );
+- payload.setOrganization ( contact->organization() );
+- payload.setDepartment ( contact->department() );
++
++ auto payload = item.payload<KContacts::Addressee> ();
++
++ payload.setNickName ( contact->nickName () );
++ payload.setFormattedName ( contact->formattedName() );
++ payload.setGivenName ( contact->firstName () );
++ payload.setFamilyName ( contact->secondName () );
++ payload.setOrganization ( contact->organization () );
++ payload.setDepartment ( contact->department () );
+
+ foreach (ContactMethod* nb, contact->phoneNumbers()) {
+- KABC::PhoneNumber pn;
++ KContacts::PhoneNumber pn;
+ pn.setType(nameToType(nb->category()->name()));
+
+ pn.setNumber(nb->uri());
+@@ -390,93 +510,29 @@ bool AkonadiBackend::save(const Person* contact)
+ return false;
+ }
+
+-
+-// bool AkonadiBackend::append(const Person* item)
+-// {
+-// Q_UNUSED(item)
+-// return false;
+-// }
+-
+-///Add a new contact
+-bool AkonadiBackend::addNewPerson(Person* contact,QWidget* parent)
+-{
+- KABC::Addressee newPerson;
+- newPerson.setNickName ( contact->nickName() );
+- newPerson.setFormattedName ( contact->formattedName() );
+- newPerson.setGivenName ( contact->firstName() );
+- newPerson.setFamilyName ( contact->secondName() );
+- newPerson.setOrganization ( contact->organization() );
+- newPerson.setDepartment ( contact->department() );
+- //newPerson.setPreferredEmail ( contact->getPreferredEmail() );//TODO
+-
+- foreach (ContactMethod* nb, contact->phoneNumbers()) {
+- KABC::PhoneNumber pn;
+- pn.setType(nameToType(nb->category()->name()));
+-
+- pn.setNumber(nb->uri());
+- newPerson.insertPhoneNumber(pn);
+- }
+-
+- //aPerson->setContactMethods (newNumbers );//TODO
+-
+- QPointer<Akonadi::ContactEditor> editor = new Akonadi::ContactEditor( Akonadi::ContactEditor::CreateMode, parent );
+-
+- editor->setContactTemplate(newPerson);
+-
+- QPointer<QDialog> dlg = new QDialog(parent);
+- QVBoxLayout *mainLayout = new QVBoxLayout;
+- dlg->setLayout(mainLayout);
+- mainLayout->addWidget(editor);
+- if ( dlg->exec() == QDialog::Accepted ) {
+- if ( !editor->saveContact() ) {
+- delete dlg;
+- qDebug() << "Unable to save new contact to storage";
+- return false;
+- }
+- }
+- delete dlg;
+- return true;
+-} //addNewPerson
+-
+-///Implement virtual pure method
+-// bool AkonadiBackend::edit(Person* contact)
+-// {
+-// return edit(contact,nullptr);
+-// }
+-
+-///Implement virtual pure method
+-// bool AkonadiBackend::addNew(Person* contact)
+-// {
+-// return addNewPerson(contact,nullptr);
+-// }
+-
+ ///Add a new phone number to an existing contact
+ bool AkonadiBackend::addContactMethod(Person* contact, ContactMethod* number)
+ {
+ Akonadi::Item item = static_cast<AkonadiEditor*>(editor<Person>())->m_ItemHash[contact->uid()];
+- if (!(item.hasPayload<KABC::Addressee>() && item.payload<KABC::Addressee>().uid() == contact->uid())) {
++ if (!(item.hasPayload<KContacts::Addressee>() && item.payload<KContacts::Addressee>().uid() == contact->uid())) {
+ qDebug() << "Person not found";
+ return false;
+ }
+ if ( item.isValid() ) {
+- KABC::Addressee payload = item.payload<KABC::Addressee>();
+- payload.insertPhoneNumber(KABC::PhoneNumber(number->uri(),nameToType(number->category()->name())));
+- item.setPayload<KABC::Addressee>(payload);
+- QPointer<Akonadi::ContactEditor> editor = new Akonadi::ContactEditor( Akonadi::ContactEditor::EditMode, (QWidget*)nullptr );
+- editor->loadContact(item);
+-
+- QPointer<QDialog> dlg = new QDialog(nullptr);
+- QVBoxLayout *mainLayout = new QVBoxLayout;
+- dlg->setLayout(mainLayout);
+- mainLayout->addWidget(editor);
+- if ( dlg->exec() == QDialog::Accepted ) {
+- if ( !editor->saveContact() ) {
+- delete dlg;
++ KContacts::Addressee payload = item.payload<KContacts::Addressee>();
++ payload.insertPhoneNumber(KContacts::PhoneNumber(number->uri(),nameToType(number->category()->name())));
++ item.setPayload<KContacts::Addressee>(payload);
++ QPointer<Akonadi::ContactEditorDialog> editor = new Akonadi::ContactEditorDialog( Akonadi::ContactEditorDialog::EditMode, (QWidget*)nullptr );
++ editor->editor()->loadContact(item);
++
++ if ( editor->exec() == QDialog::Accepted ) {
++ if ( !contact->save() ) {
++ delete editor;
+ qDebug() << "Unable to save new contact to storage";
+ return false;
+ }
+ }
+- delete dlg ;
++
+ delete editor;
+ return true;
+ }
+@@ -496,12 +552,7 @@ bool AkonadiBackend::addContactMethod(Person* contact, ContactMethod* number)
+ ///Called when a new collection is added
+ void AkonadiBackend::itemsReceived( const Akonadi::Item::List& list)
+ {
+-// QList<int> disabledColl = ConfigurationSkeleton::disabledCollectionList();
+ foreach (const Akonadi::Item& item, list) {
+-// if (disabledColl.indexOf(coll.id()) == -1) {
+-// update(coll);
+-// emit reloaded();
+-// }
+ slotItemAdded(item,m_Coll);
+ }
+ }
+@@ -520,9 +571,9 @@ void AkonadiBackend::slotItemAdded(const Akonadi::Item& item,const Akonadi::Coll
+ void AkonadiBackend::slotItemChanged(const Akonadi::Item &item, const QSet< QByteArray > &part)
+ {
+ Q_UNUSED(part)
+- if (item.hasPayload<KABC::Addressee>()) {
+- KABC::Addressee tmp = item.payload<KABC::Addressee>();
+- Person* c = PersonModel::instance()->getPersonByUid(tmp.uid().toUtf8());
++ if (item.hasPayload<KContacts::Addressee>()) {
++ KContacts::Addressee tmp = item.payload<KContacts::Addressee>();
++ Person* c = PersonModel::instance().getPersonByUid(tmp.uid().toUtf8());
+ if (c)
+ fillPerson(c,tmp);
+ }
+@@ -531,16 +582,11 @@ void AkonadiBackend::slotItemChanged(const Akonadi::Item &item, const QSet< QByt
+ ///Callback when a contact is removed
+ void AkonadiBackend::slotItemRemoved(const Akonadi::Item &item)
+ {
+- Person* c = PersonModel::instance()->getPersonByUid(item.remoteId().toUtf8());
+- PersonModel::instance()->disablePerson(c);
+-}
++ Person* c = PersonModel::instance().getPersonByUid(item.remoteId().toUtf8());
+
+-Akonadi::Collection AkonadiBackend::collection() const
+-{
+- return m_Coll;
++ if (c)
++ deactivate(c);
++ else
++ qDebug() << "A contact was deleted, but Ring-KDE can't find it";
++ //PersonModel::instance().disablePerson(c);
+ }
+-
+-// QList<Person*> AkonadiBackend::items() const
+-// {
+-// return m_lBackendPersons;
+-// }
+diff --git a/src/klib/akonadibackend.h b/src/klib/akonadibackend.h
+index b7ba169..58adb51 100644
+--- a/src/klib/akonadibackend.h
++++ b/src/klib/akonadibackend.h
+@@ -20,30 +20,30 @@
+ #ifndef AKONADI_BACKEND_H
+ #define AKONADI_BACKEND_H
+
+-#include <QPointer>
++#include <QtCore/QPointer>
+
+ #include <collectioninterface.h>
+ #include <collectioneditor.h>
+ #include <collectionmediator.h>
+ #include "typedefs.h"
+-#include <akonadi/collectionmodel.h>
+-#include <kabc/phonenumber.h>
+-#include <akonadi/item.h>
++#include <kcontacts/phonenumber.h>
++#include <AkonadiCore/Item>
+
+ //Qt
+ class QObject;
+
+ //KDE
+-namespace KABC {
++namespace KContacts {
+ class Addressee ;
+ class ContactMethod ;
+ }
+
+ namespace Akonadi {
+- class Session ;
+- class Collection ;
+- class ItemFetchJob;
+- class Monitor ;
++ class Session ;
++ class Collection ;
++ class ItemFetchJob ;
++ class Monitor ;
++ class EntityTreeModel;
+ }
+ class KJob;
+
+@@ -58,50 +58,47 @@ class LIB_EXPORT AkonadiBackend : public QObject, public CollectionInterface
+ {
+ Q_OBJECT
+ public:
+- template<typename T>
+- explicit AkonadiBackend(CollectionMediator<T>* mediator, const Akonadi::Collection& parentCol);
+-// Person* getPersonByPhone ( const QString& phoneNumber ,bool resolveDNS = false, Account* a=nullptr);
+- bool edit ( Person* contact , QWidget* parent = 0 );
+- bool addNewPerson ( Person* contact , QWidget* parent = 0 );
+- virtual bool addContactMethod( Person* contact , ContactMethod* number );
+-
+- virtual QString name () const override;
+- virtual QString category () const override;
+- virtual QVariant icon() const override;
+- virtual bool isEnabled() const override;
+- virtual bool enable (bool enable) override;
+- virtual QByteArray id() const;
+-
+-// virtual bool edit ( Person* contact ) override;
+-// virtual bool addNew ( Person* contact ) override;
+-// virtual bool remove ( Person* c ) override;
+-// virtual bool append(const Person* item);
+- virtual ~AkonadiBackend ( );
+-
+- virtual bool load();
+- virtual bool reload();
+- virtual bool save(const Person* contact);
+-
+- SupportedFeatures supportedFeatures() const;
+-
+-// virtual QList<Person*> items() const override;
+-
+- Akonadi::Collection collection() const;
++ // Constructor
++ explicit AkonadiBackend(CollectionMediator<Person>* mediator, Akonadi::Collection* parentCol);
++ virtual ~AkonadiBackend( );
++
++ // Mutator
++ bool addContactMethod( Person* contact , ContactMethod* number );
++
++ // CollectionInterface override
++ virtual QString name ( ) const override;
++ virtual QString category ( ) const override;
++ virtual QVariant icon ( ) const override;
++ virtual bool isEnabled ( ) const override;
++ virtual bool enable ( bool enable ) override;
++ virtual QByteArray id ( ) const override;
++ virtual bool load ( ) override;
++ virtual bool reload ( ) override;
++ virtual bool save ( const Person* contact );
++ virtual FlagPack<SupportedFeatures> supportedFeatures() const override;
++
++ //Helper
++ static KContacts::PhoneNumber::Type nameToType(const QString& name);
++ static void initCollections();
++
+ private:
+
+ //Attributes
+- static Akonadi::Session* m_pSession ;
+- Akonadi::Monitor* m_pMonitor ;
+- Akonadi::Collection m_Coll ;
+- QPointer<Akonadi::ItemFetchJob> m_pJob;
+- bool m_isEnabled;
+- bool m_wasEnabled;
+- CollectionMediator<Person>* m_pMediator;
++ static Akonadi::Session* m_spSession ;
++ static Akonadi::EntityTreeModel* m_spModel ;
++
++ Akonadi::Monitor* m_pMonitor ;
++ Akonadi::Collection m_Coll ;
++ QPointer<Akonadi::ItemFetchJob> m_pJob ;
++ bool m_isEnabled ;
++ bool m_wasEnabled ;
++ CollectionMediator<Person>* m_pMediator ;
+
+ //Helper
+- KABC::PhoneNumber::Type nameToType(const QString& name);
+- Person* addItem(Akonadi::Item item, bool ignoreEmpty = false);
+- void fillPerson(Person* c, const KABC::Addressee& addr) const;
++ Person* addItem (Akonadi::Item item , bool ignoreEmpty = false );
++ static void fillPerson (Person* c , const KContacts::Addressee& addr);
++ static void digg (QAbstractItemModel* model, const QModelIndex& idx );
++ static void slotRowsInserted(const QModelIndex& parent, int start , int end);
+
+ //Parent locator
+ static QHash<Akonadi::Collection::Id, AkonadiBackend*> m_hParentLookup;
+@@ -109,6 +106,7 @@ private:
+ public Q_SLOTS:
+ void update(const Akonadi::Collection& collection);
+ void itemsReceived( const Akonadi::Item::List& );
++
+ private Q_SLOTS:
+ void slotItemAdded(const Akonadi::Item& item, const Akonadi::Collection& coll);
+ void slotItemChanged (const Akonadi::Item &item, const QSet< QByteArray > &partIdentifiers);
+@@ -116,55 +114,4 @@ private Q_SLOTS:
+ void slotJobCompleted(KJob* job);
+ };
+
+-class AkonadiEditor : public CollectionEditor<Person>
+-{
+-public:
+- AkonadiEditor(CollectionMediator<Person>* m) : CollectionEditor<Person>(m) {}
+- ~AkonadiEditor() {
+- m_lBackendPersons.clear();
+- m_ItemHash.clear();
+- m_AddrHash.clear();
+- }
+- virtual bool save ( const Person* item ) override;
+- virtual bool append ( const Person* item ) override;
+- virtual bool remove ( Person* item ) override;
+- virtual bool edit ( Person* item ) override;
+- virtual bool addNew ( Person* item ) override;
+-
+- QHash<QString,KABC::Addressee> m_AddrHash ;
+- QHash<QString,Akonadi::Item> m_ItemHash ;
+- QList<Person*> m_lBackendPersons;
+-private:
+- virtual QVector<Person*> items() const override;
+-};
+-#include <akonadi/control.h>
+-#include <akonadi/collectionfilterproxymodel.h>
+-#include <akonadi/kmime/messagemodel.h>
+-#include <akonadi/recursiveitemfetchjob.h>
+-#include <akonadi/itemfetchjob.h>
+-#include <akonadi/itemfetchscope.h>
+-#include <akonadi/collectionfetchjob.h>
+-#include <akonadi/collectionfetchscope.h>
+-#include <akonadi/contact/contacteditor.h>
+-#include <akonadi/contact/contacteditordialog.h>
+-#include <akonadi/session.h>
+-#include <akonadi/monitor.h>
+-#include <akonadi/itemdeletejob.h>
+-#include <akonadi/entitydisplayattribute.h>
+-#include <kabc/addressee.h>
+-#include <kabc/addresseelist.h>
+-#include <kabc/contactgroup.h>
+-///Constructor
+-template<typename T>
+-AkonadiBackend::AkonadiBackend(CollectionMediator<T>* mediator, const Akonadi::Collection& parentCol) : QObject(mediator->model()),
+- CollectionInterface(new AkonadiEditor(mediator),m_hParentLookup[parentCol.parent()]),m_pJob(nullptr),m_pMediator(mediator),
+- m_pMonitor(nullptr),m_isEnabled(false),m_wasEnabled(false)
+-{
+- if (!m_pSession)
+- m_pSession = new Akonadi::Session( "Ring::instance" );
+- setObjectName(parentCol.name());
+- m_Coll = parentCol;
+- m_hParentLookup[m_Coll.id()] = this;
+-} //AkonadiBackend
+-
+ #endif
+diff --git a/src/klib/akonadicontactcollectionmodel.cpp b/src/klib/akonadicontactcollectionmodel.cpp
+deleted file mode 100644
+index 6d4cc9c..0000000
+--- a/src/klib/akonadicontactcollectionmodel.cpp
++++ /dev/null
+@@ -1,141 +0,0 @@
+-/****************************************************************************
+- * Copyright (C) 2014-2015 by Savoir-Faire Linux *
+- * Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> *
+- * *
+- * 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 GNU General Public License *
+- * along with this program. If not, see <http://www.gnu.org/licenses/>. *
+- ***************************************************************************/
+-#include "akonadicontactcollectionmodel.h"
+-
+-//Qt
+-#include <QCoreApplication>
+-
+-// KDE
+-#include <akonadi/collectionmodel.h>
+-
+-// Ring
+-#include "personmodel.h"
+-#include "akonadibackend.h"
+-#include "kcfg_settings.h"
+-
+-AkonadiPersonCollectionModel* AkonadiPersonCollectionModel::m_spInstance = nullptr;
+-
+-
+-AkonadiPersonCollectionModel* AkonadiPersonCollectionModel::instance()
+-{
+- if (!m_spInstance)
+- m_spInstance = new AkonadiPersonCollectionModel(QCoreApplication::instance());
+- return m_spInstance;
+-}
+-
+-AkonadiPersonCollectionModel::AkonadiPersonCollectionModel(QObject* parent) : QSortFilterProxyModel(parent) {
+- m_pParentModel = new Akonadi::CollectionModel(this);
+- setSourceModel(m_pParentModel);
+- setDynamicSortFilter(true);
+- reload();
+- connect(this,SIGNAL(rowsInserted(QModelIndex,int,int)),this,SLOT(slotInsertCollection(QModelIndex,int,int)));
+- connect(this,SIGNAL(rowsRemoved(QModelIndex,int,int)),this,SLOT(slotRemoveCollection(QModelIndex,int,int)));
+-}
+-
+-AkonadiPersonCollectionModel::~AkonadiPersonCollectionModel()
+-{
+- setSourceModel(nullptr);
+- delete m_pParentModel;
+-}
+-
+-bool AkonadiPersonCollectionModel::filterAcceptsRow ( int source_row, const QModelIndex & source_parent ) const
+-{
+- const QModelIndex idx = sourceModel()->index(source_row,0,source_parent);
+- Akonadi::Collection col = qvariant_cast<Akonadi::Collection>(idx.data(Akonadi::CollectionModel::Roles::CollectionRole));
+- return col.contentMimeTypes().indexOf("text/directory") != -1;
+-}
+-
+-Qt::ItemFlags AkonadiPersonCollectionModel::flags ( const QModelIndex& index ) const
+-{
+- return QSortFilterProxyModel::flags(index) | Qt::ItemIsUserCheckable;
+-}
+-
+-QVariant AkonadiPersonCollectionModel::data( const QModelIndex& index, int role ) const
+-{
+- if (role == Qt::CheckStateRole) {
+- const int id = index.data(Akonadi::CollectionModel::Roles::CollectionIdRole).toInt();
+- return m_hChecked[id]?Qt::Unchecked:Qt::Checked;
+- }
+- return QSortFilterProxyModel::data(index,role);
+-}
+-
+-bool AkonadiPersonCollectionModel::setData( const QModelIndex& index, const QVariant &value, int role)
+-{
+- if (role == Qt::CheckStateRole) {
+- const int id = index.data(Akonadi::CollectionModel::Roles::CollectionIdRole).toInt();
+- m_hChecked[id] = !value.toBool();
+- emit dataChanged(index,index);
+- emit changed();
+- return false;
+- }
+- else
+- return QSortFilterProxyModel::setData(index,value,role);
+-}
+-
+-
+-void AkonadiPersonCollectionModel::reload()
+-{
+-// m_hChecked.clear();
+-// const QList<int> disabled = ConfigurationSkeleton::disabledCollectionList();
+-// foreach(const int str, disabled) {
+-// m_hChecked[str] = true; //Disabled == true, enabled == false
+-// }
+-}
+-
+-void AkonadiPersonCollectionModel::digg(const QModelIndex& idx)
+-{
+- for (int i = 0;i < rowCount(idx);i++) {
+- QModelIndex current = index(i,0,idx);
+- if (!m_hLoaded[current.data(Akonadi::CollectionModel::Roles::CollectionIdRole).toInt()]) {
+- PersonModel::instance()->addBackend<AkonadiBackend,Akonadi::Collection>(qvariant_cast<Akonadi::Collection>(index(i,0,idx).data(Akonadi::CollectionModel::Roles::CollectionRole)));
+- }
+- digg(current);
+- }
+-}
+-
+-void AkonadiPersonCollectionModel::save()
+-{
+-// QList<int> ret;
+-// for (QHash<int,bool>::iterator i = m_hChecked.begin(); i != m_hChecked.end(); ++i) {
+-// if (i.value())
+-// ret << i.key();
+-// }
+- digg(QModelIndex());
+-// ConfigurationSkeleton::setDisabledCollectionList(ret);
+-}
+-
+-
+-void AkonadiPersonCollectionModel::slotInsertCollection(const QModelIndex& parentIdx, int start, int end)
+-{
+- for (int i =start; i <= end;i++) {
+- Akonadi::Collection col = qvariant_cast<Akonadi::Collection>(index(i,0,parentIdx).data(Akonadi::CollectionModel::Roles::CollectionRole));
+-
+- PersonModel::instance()->addBackend<AkonadiBackend,Akonadi::Collection&>(col);
+- m_hLoaded[col.id()] = !m_hChecked[col.id()];
+- }
+-}
+-
+-void AkonadiPersonCollectionModel::slotRemoveCollection(const QModelIndex& index , int start, int end)
+-{
+- Q_UNUSED(index)
+- Q_UNUSED(start)
+- Q_UNUSED(end)
+- for (int i =start; i <= end;i++) {
+- //TODO
+- }
+-}
+diff --git a/src/klib/akonadicontactcollectionmodel.h b/src/klib/akonadicontactcollectionmodel.h
+deleted file mode 100644
+index 8bdb50c..0000000
+--- a/src/klib/akonadicontactcollectionmodel.h
++++ /dev/null
+@@ -1,70 +0,0 @@
+-/****************************************************************************
+- * Copyright (C) 2014-2015 by Savoir-Faire Linux *
+- * Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> *
+- * *
+- * 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 GNU General Public License *
+- * along with this program. If not, see <http://www.gnu.org/licenses/>. *
+- ***************************************************************************/
+-#ifndef AKONADI_CONTACT_COLLECTION_MODEL_H
+-#define AKONADI_CONTACT_COLLECTION_MODEL_H
+-
+-#include <QtCore/QSortFilterProxyModel>
+-#include "typedefs.h"
+-
+-namespace Akonadi {
+- class CollectionModel;
+-}
+-
+-///Filter out notes and emails collections
+-class LIB_EXPORT AkonadiPersonCollectionModel : public QSortFilterProxyModel
+-{
+- Q_OBJECT
+-public:
+- virtual ~AkonadiPersonCollectionModel();
+-
+-public:
+- virtual QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const;
+- virtual Qt::ItemFlags flags ( const QModelIndex& index ) const;
+- virtual bool setData ( const QModelIndex& index, const QVariant &value, int role );
+-
+- //Mutator
+- void reload();
+- void save();
+-
+- //Singleton
+- static AkonadiPersonCollectionModel* instance();
+-
+-protected:
+- virtual bool filterAcceptsRow( int source_row, const QModelIndex & source_parent ) const;
+-
+-private:
+- explicit AkonadiPersonCollectionModel(QObject* parent);
+- QHash<int,bool> m_hChecked;
+- QHash<int,bool> m_hLoaded ;
+- Akonadi::CollectionModel* m_pParentModel;
+-
+- //Singleton
+- static AkonadiPersonCollectionModel* m_spInstance;
+-
+- //Helper
+- void digg(const QModelIndex& idx);
+-
+-private Q_SLOTS:
+- void slotInsertCollection(const QModelIndex&,int,int);
+- void slotRemoveCollection(const QModelIndex&,int,int);
+-
+-Q_SIGNALS:
+- void changed();
+-};
+-
+-#endif
+diff --git a/src/klib/itemmodelserialization.cpp b/src/klib/itemmodelserialization.cpp
+index 1a0d4cd..6da8cc1 100644
+--- a/src/klib/itemmodelserialization.cpp
++++ b/src/klib/itemmodelserialization.cpp
+@@ -17,6 +17,10 @@
+ ***************************************************************************/
+ #include "itemmodelserialization.h"
+
++#include <personmodel.h>
++
++#include "akonadibackend.h"
++
+ #include "collectioninterface.h"
+ #include "kcfg_settings.h"
+
+@@ -59,3 +63,16 @@ bool ItemModelStateSerialization::setChecked(const CollectionInterface* backend,
+ m_hChecked[backend->id()] = ! enabled;
+ return true;
+ }
++
++CollectionInterface* ItemModelStateSerialization::preferredCollection(CollectionManagerInterfaceBase* manager, FlagPack<CollectionInterface::SupportedFeatures> features, FlagPack<Interfaces::ItemModelStateSerializerI::Hints> hints )
++{
++ Q_UNUSED(hints)
++ if (manager == &PersonModel::instance()) {
++ foreach(CollectionInterface* i, PersonModel::instance().collections(features)) {
++ if (dynamic_cast<AkonadiBackend*>(i)) //TODO use something better
++ return i;
++ }
++ }
++
++ return nullptr;
++}
+diff --git a/src/klib/itemmodelserialization.h b/src/klib/itemmodelserialization.h
+index 3242e09..53e164d 100644
+--- a/src/klib/itemmodelserialization.h
++++ b/src/klib/itemmodelserialization.h
+@@ -25,7 +25,6 @@
+ class Account;
+ class CollectionInterface;
+
+-///Ringlib Qt does not link to QtGui, and does not need to, this allow to add runtime Gui support
+ class LIB_EXPORT ItemModelStateSerialization : public Interfaces::ItemModelStateSerializerI
+ {
+ public:
+@@ -35,6 +34,11 @@ public:
+
+ //Getter
+ virtual bool isChecked(const CollectionInterface* backend) const override;
++ virtual CollectionInterface* preferredCollection(
++ CollectionManagerInterfaceBase* manager,
++ FlagPack<CollectionInterface::SupportedFeatures> features,
++ FlagPack<Interfaces::ItemModelStateSerializerI::Hints> hints
++ ) override;
+
+ //Setter
+ virtual bool setChecked(const CollectionInterface* backend, bool enabled) override;
+diff --git a/src/klib/ring-kde.kcfg b/src/klib/ring-kde.kcfg
+index 79def64..85b539d 100644
+--- a/src/klib/ring-kde.kcfg
++++ b/src/klib/ring-kde.kcfg
+@@ -213,6 +213,12 @@
+ <default> false </default>
+ </entry>
+
++ <entry name="hideUnreachable" type="Bool">
++ <label>Hide unreachable contacts</label>
++ <tooltip>Hide a contact if there is no enabled accounts that can reach him/her</tooltip>
++ <default> true </default>
++ </entry>
++
+ <entry name="defaultAccountId" type="String">
+ <label>Default account used for contact lookup if only partial (only extension) contacts info is available</label>
+ <default>IP2IP</default>
+diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
+index d8f3993..1d0553d 100755
+--- a/src/mainwindow.cpp
++++ b/src/mainwindow.cpp
+@@ -86,6 +86,7 @@
+ #include <video/renderer.h>
+ #include "ringapplication.h"
+ #include "widgets/dockbase.h"
++#include "klib/akonadibackend.h"
+ #ifdef ENABLE_VIDEO
+ #include "widgets/videodock.h"
+ #endif
+@@ -174,6 +175,9 @@ MainWindow::MainWindow(QWidget* parent)
+ PersonModel::instance().addCollection<FallbackPersonCollection>(LoadOptions::FORCE_ENABLED);
+
+ GlobalInstances::setInterface<ItemModelStateSerialization>();
++ GlobalInstances::itemModelStateSerializer().load();
++
++ AkonadiBackend::initCollections();
+ // PersonModel::instance().backendModel()->load();
+ // AccountModel::instance().setDefaultAccount(AccountModel::instance().getAccountById(ConfigurationSkeleton::defaultAccountId()));
+
+@@ -353,7 +357,6 @@ MainWindow::MainWindow(QWidget* parent)
+ if (!ConfigurationSkeleton::autoStartOverride())
+ setAutoStart(true);
+
+-// RecentModel::instance();
+
+ } //Ring
+
+--
+2.7.0
+
diff --git a/0003-history-De-duplicate-identical-calls.patch b/0003-history-De-duplicate-identical-calls.patch
new file mode 100644
index 000000000000..8e2975c7afdd
--- /dev/null
+++ b/0003-history-De-duplicate-identical-calls.patch
@@ -0,0 +1,685 @@
+From f2a5aaa2b668ab5d87284dec9dddeb3b805b5ec4 Mon Sep 17 00:00:00 2001
+From: Emmanuel Lepage Vallee <elv1313@gmail.com>
+Date: Wed, 9 Dec 2015 08:49:10 -0500
+Subject: [PATCH 3/6] history: De-duplicate identical calls
+
+Add a new generic proxy model that can be used to
+filter out duplicate items from a model.
+---
+ src/CMakeLists.txt | 41 +++----
+ src/dock.cpp | 100 ++---------------
+ src/proxies/deduplicateproxy.cpp | 219 ++++++++++++++++++++++++++++++++++++++
+ src/proxies/deduplicateproxy.h | 97 +++++++++++++++++
+ src/proxies/simplerotateproxy.cpp | 9 +-
+ src/proxies/simplerotateproxy.h | 9 ++
+ 6 files changed, 365 insertions(+), 110 deletions(-)
+ create mode 100644 src/proxies/deduplicateproxy.cpp
+ create mode 100644 src/proxies/deduplicateproxy.h
+
+diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
+index 201f107..b49dcd0 100755
+--- a/src/CMakeLists.txt
++++ b/src/CMakeLists.txt
+@@ -6,6 +6,10 @@ IF(POLICY CMP0022)
+ CMAKE_POLICY(SET CMP0022 NEW)
+ ENDIF(POLICY CMP0022)
+
++IF(POLICY CMP0063)
++ CMAKE_POLICY(SET CMP0063 NEW)
++ENDIF(POLICY CMP0063)
++
+ # set(CMAKE_AUTOMOC ON)
+
+ SET(KF5_DEP_VERSION "5.6.0")
+@@ -146,8 +150,6 @@ INCLUDE_DIRECTORIES(SYSTEM
+ #Build KDE specific files
+ ADD_SUBDIRECTORY( klib )
+
+-MESSAGE("CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}")
+-
+ IF(${CMAKE_BUILD_TYPE} MATCHES Release)
+ MESSAGE("NO DEBUG OUTPUT")
+ ADD_DEFINITIONS( -DQT_NO_DEBUG_OUTPUT)
+@@ -173,7 +175,7 @@ SET(
+ delegates/toolbardelegate.cpp
+ delegates/kdepixmapmanipulation.cpp
+ proxies/simplerotateproxy.cpp
+- #proxies/valuefilterproxy.cpp
++ proxies/deduplicateproxy.cpp
+ )
+
+ # Configuration pages
+@@ -195,6 +197,8 @@ SET(
+ # Widgets
+ SET(
+ ring_kde_WIDGETS
++ widgets/personselector.cpp
++ widgets/contactmethodselector.cpp
+ widgets/systray.cpp
+ widgets/dialpad.cpp
+ widgets/dockbase.cpp
+@@ -223,14 +227,6 @@ SET(
+ widgets/wizard.cpp
+ )
+
+-# Asset contextual menus
+-SET(
+- ring_kde_MENUS
+- menu/person.cpp
+- menu/call.cpp
+- menu/contactmethod.cpp
+-)
+-
+ # Video Widgets
+ IF(NOT (${ENABLE_VIDEO} MATCHES false))
+ SET(
+@@ -252,6 +248,8 @@ SET(
+ mainwindow.cpp
+ cmd.cpp
+ dock.cpp
++ notification.cpp
++ notification.cpp
+ ringapplication.cpp
+ errormessage.cpp
+ kspeechinterfacesingleton.cpp
+@@ -264,7 +262,6 @@ SET(
+ ${ring_kde_CONF}
+ ${ring_kde_WIDGETS}
+ ${ring_kde_VIDEO}
+- ${ring_kde_MENUS}
+ )
+
+ # generate rules for building source files from the resources
+@@ -287,6 +284,8 @@ SET(
+ conf/dlgaudiorecording.ui
+ conf/dlgpresence.ui
+ conf/dlgfallbackperson.ui
++ widgets/ui/personselector.ui
++ widgets/ui/contactmethodselector.ui
+ widgets/ui/player.ui
+ widgets/ui/playeroverlay.ui
+ widgets/ui/contactdock.ui
+@@ -302,7 +301,6 @@ SET(
+ # add_subdirectory( test ) #Enable again some day, it cause compile problems for some users
+
+ IF(NOT (${ENABLE_VIDEO} MATCHES false))
+- MESSAGE("VIDEO enabled")
+ SET(ENABLE_VIDEO 1 CACHE BOOLEAN "Enable video")
+ ADD_DEFINITIONS( -DENABLE_VIDEO=true )
+ SET (
+@@ -355,12 +353,15 @@ TARGET_LINK_LIBRARIES(ring-kde
+ ${LIB_RING_CLIENT_LIBRARY}
+ ${OPENGL_link}
+
+- ${Qt5Widgets_LIBRARIES}
+- ${Qt5Core_LIBRARIES}
+- ${Qt5Gui_LIBRARIES}
+- ${Qt5Svg_LIBRARIES}
+- ${Qt5OpenGL_LIBRARIES}
+- ${Qt5PrintSupport_LIBRARIES}
++ # Qt5
++ Qt5::Widgets
++ Qt5::Core
++ Qt5::Gui
++ Qt5::Svg
++ Qt5::OpenGL
++ Qt5::PrintSupport
++
++ # KF5
+ KF5::I18n
+ KF5::WidgetsAddons
+ KF5::ConfigCore
+@@ -372,6 +373,8 @@ TARGET_LINK_LIBRARIES(ring-kde
+ KF5::KIOWidgets
+ KF5::Completion
+ KF5::Crash
++ KF5::NotifyConfig
++ KF5::GlobalAccel
+ # ${KDEPIMLIBS_AKONADI_KMIME_LIBS}
+ # ${KDEPIMLIBS_AKONADI_LIBS}
+ # ${KDEPIMLIBS_AKONADI_CONTACT_LIBS}
+diff --git a/src/dock.cpp b/src/dock.cpp
+index 4b1f88e..1d41470 100644
+--- a/src/dock.cpp
++++ b/src/dock.cpp
+@@ -23,9 +23,6 @@
+ #include <QtCore/QSortFilterProxyModel>
+ #include <QtCore/QTimer>
+
+-//KDE
+-#include <KColorScheme>
+-
+ //Delegates
+ #include <conf/account/delegates/categorizeddelegate.h>
+ #include "delegates/contactdelegate.h"
+@@ -42,11 +39,9 @@
+
+ //Ring
+ #include "mainwindow.h"
+-#include "view.h"
+ #include "actioncollection.h"
+ #include "klib/kcfg_settings.h"
+ #include <proxies/deduplicateproxy.h>
+-#include <proxies/roletransformationproxy.h>
+
+ class BookmarkSortFilterProxyModel : public QSortFilterProxyModel
+ {
+@@ -92,9 +87,9 @@ Dock::Dock(QMainWindow* w) : QObject(w)
+
+ // Load later to speed up the process (avoid showing while inserting items)
+ QTimer::singleShot(10, [this]() {
+- CategorizedContactModel::instance().setUnreachableHidden(ConfigurationSkeleton::hideUnreachable());
++ CategorizedContactModel::instance().setUnreachableHidden(ConfigurationSkeleton::hidePersonWithoutPhone());
+ auto proxy = CategorizedContactModel::SortedProxy::instance().model();
+- m_pContactCD->setProxyModel(proxy, proxy);
++ m_pContactCD->setProxyModel(proxy);
+ m_pContactCD->setSortingModel(
+ CategorizedContactModel::SortedProxy::instance().categoryModel(),
+ CategorizedContactModel::SortedProxy::instance().categorySelectionModel()
+@@ -122,54 +117,13 @@ Dock::Dock(QMainWindow* w) : QObject(w)
+
+
+ QTimer::singleShot(1000, [this]() {
+- // De-duplicate by name and date
+ auto proxy = CategorizedHistoryModel::SortedProxy::instance().model();
+- RoleTransformationProxy* highlight = nullptr;
+- auto dedup = ConfigurationSkeleton::mergeSameDayPeer() ? new DeduplicateProxy(proxy) : nullptr;
+-
+- if (dedup)
+- dedup->addFilterRole(static_cast<int>(Call::Role::DateOnly));
+-
+- // Highlight missed calls
+- static const bool highlightMissedIn = ConfigurationSkeleton::highlightMissedIncomingCalls();
+- static const bool highlightMissedOut = ConfigurationSkeleton::highlightMissedOutgoingCalls();
+-
+- if (highlightMissedOut || highlightMissedIn) {
+- static QColor awayBrush = KStatefulBrush( KColorScheme::Window, KColorScheme::NegativeText ).brush(QPalette::Normal).color();
+- awayBrush.setAlpha(30);
+- static QVariant missedBg(awayBrush);
+-
+- highlight = new RoleTransformationProxy(dedup ? dedup : proxy);
+-
+- highlight->setRole(Qt::BackgroundRole, [](const QModelIndex& idx) {
+- if (idx.data((int)Call::Role::Missed).toBool()) {
+- const Call::Direction d = qvariant_cast<Call::Direction>(
+- idx.data((int)Call::Role::Direction)
+- );
+-
+- if ((highlightMissedIn && d == Call::Direction::INCOMING)
+- || (highlightMissedOut && d == Call::Direction::OUTGOING))
+- return missedBg;
+- }
+-
+- return QVariant();
+- });
+-
+- highlight->setSourceModel(proxy);
+-
+- if (dedup)
+- dedup->setSourceModel(highlight);
+- }
+- else if (dedup)
+- dedup->setSourceModel(proxy);
+-
+- if (dedup)
+- m_pHistoryDW->setProxyModel(dedup , proxy );
+- else if (highlight)
+- m_pHistoryDW->setProxyModel(highlight, proxy );
+- else
+- m_pHistoryDW->setProxyModel(proxy , proxy );
++ auto dedup = new DeduplicateProxy(proxy);
++ // De-duplicate by name and date
++ dedup->addFilterRole(static_cast<int>(Call::Role::DateOnly));
++ dedup->setSourceModel(proxy);
+
++ m_pHistoryDW->setProxyModel(dedup);
+ m_pHistoryDW->setSortingModel(
+ CategorizedHistoryModel::SortedProxy::instance().categoryModel (),
+ CategorizedHistoryModel::SortedProxy::instance().categorySelectionModel()
+@@ -194,8 +148,7 @@ Dock::Dock(QMainWindow* w) : QObject(w)
+ CategorizedDelegate* delegate2 = new CategorizedDelegate(m_pBookmarkDW->view());
+ delegate2->setChildDelegate(new HistoryDelegate(m_pHistoryDW->view()));
+ m_pBookmarkDW->setDelegate(delegate2);
+- auto m = new BookmarkSortFilterProxyModel(this);
+- m_pBookmarkDW->setProxyModel(m, m);
++ m_pBookmarkDW->setProxyModel(new BookmarkSortFilterProxyModel(this));
+
+
+ //GUI
+@@ -227,10 +180,6 @@ Dock::Dock(QMainWindow* w) : QObject(w)
+ connect(ActionCollection::instance()->showHistoryDockAction(), SIGNAL(toggled(bool)),m_pHistoryDW, SLOT(setVisible(bool)));
+ connect(ActionCollection::instance()->showBookmarkDockAction(),SIGNAL(toggled(bool)),m_pBookmarkDW,SLOT(setVisible(bool)));
+
+- connect( ActionCollection::instance()->focusHistory (), &QAction::triggered, this, &Dock::focusHistory );
+- connect( ActionCollection::instance()->focusContact (), &QAction::triggered, this, &Dock::focusContact );
+- connect( ActionCollection::instance()->focusCall (), &QAction::triggered, this, &Dock::focusCall );
+- connect( ActionCollection::instance()->focusBookmark(), &QAction::triggered, this, &Dock::focusBookmark );
+ }
+
+ Dock::~Dock()
+@@ -239,9 +188,9 @@ Dock::~Dock()
+ m_pHistoryDW ->setDelegate (nullptr);
+ m_pBookmarkDW->setDelegate (nullptr);
+
+- m_pContactCD ->setProxyModel(nullptr, nullptr);
+- m_pHistoryDW ->setProxyModel(nullptr, nullptr);
+- m_pBookmarkDW->setProxyModel(nullptr, nullptr);
++ m_pContactCD ->setProxyModel(nullptr);
++ m_pHistoryDW ->setProxyModel(nullptr);
++ m_pBookmarkDW->setProxyModel(nullptr);
+
+ m_pContactCD ->deleteLater();
+ m_pHistoryDW ->deleteLater();
+@@ -298,31 +247,4 @@ void Dock::updateTabIcons()
+ }
+ } //updateTabIcons
+
+-void Dock::focusHistory()
+-{
+- m_pHistoryDW->raise();
+- ActionCollection::instance()->raiseClient(false);
+- m_pHistoryDW->m_pFilterLE->setFocus(Qt::OtherFocusReason);
+-}
+-
+-void Dock::focusContact()
+-{
+- m_pContactCD->raise();
+- ActionCollection::instance()->raiseClient(false);
+- m_pContactCD->m_pFilterLE->setFocus(Qt::OtherFocusReason);
+-}
+-
+-void Dock::focusCall()
+-{
+- MainWindow::view()->raise();
+- ActionCollection::instance()->raiseClient(true);
+-}
+-
+-void Dock::focusBookmark()
+-{
+- m_pBookmarkDW->raise();
+- ActionCollection::instance()->raiseClient(false);
+- m_pBookmarkDW->m_pFilterLE->setFocus(Qt::OtherFocusReason);
+-}
+-
+ #include <dock.moc>
+diff --git a/src/proxies/deduplicateproxy.cpp b/src/proxies/deduplicateproxy.cpp
+new file mode 100644
+index 0000000..ccde71a
+--- /dev/null
++++ b/src/proxies/deduplicateproxy.cpp
+@@ -0,0 +1,219 @@
++/****************************************************************************
++ * Copyright (C) 2015 by Emmanuel Lepage Vallee *
++ * Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> *
++ * *
++ * 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 GNU General Public License *
++ * along with this program. If not, see <http://www.gnu.org/licenses/>. *
++ ***************************************************************************/
++#include "deduplicateproxy.h"
++
++#include <QtCore/QDebug>
++
++typedef std::function<bool(const QModelIndex&, const QModelIndex&)> Comparator;
++
++class DeduplicateProxyPrivate final
++{
++public:
++ // Attributes
++ bool m_AddChildren { false };
++ Comparator m_fCompare { };
++ DeduplicateProxy::Mode m_Mode { DeduplicateProxy::Mode::ROLE };
++ bool m_isSrcProxy { false };
++ QVector<int> m_lOtherRoles { };
++ int m_HiddenRoleId { -1 };
++ bool m_isHiddenRoleSet{ false };
++ QString m_HiddenRoleName { };
++};
++
++DeduplicateProxy::DeduplicateProxy(QObject* parent) : QSortFilterProxyModel(parent),
++d_ptr(new DeduplicateProxyPrivate())
++{
++}
++
++void DeduplicateProxy::setComparator(const Comparator& f)
++{
++ d_ptr->m_Mode = DeduplicateProxy::Mode::FUNCTION;
++ d_ptr->m_fCompare = f;
++
++ invalidateFilter();
++}
++
++/**
++ * Useful when there is a main filter role, but some items need to be kept.
++ *
++ * If any role added to this list is different, the index wont be hidden
++ *
++ * For example using this proxy to create instant messaging thread if they
++ * come from the same person, but also create different threads if the date
++ * differ.
++ */
++void DeduplicateProxy::addFilterRole(int role)
++{
++ d_ptr->m_lOtherRoles << role;
++
++ invalidateFilter();
++}
++
++QVector<int> DeduplicateProxy::filterRoles() const
++{
++ return d_ptr->m_lOtherRoles;// + filterRole();
++}
++
++/**
++ * Add rejected items as children of the non-rejected matching item
++ *
++ * @warning This is ignored for indexes that already have children
++ */
++void DeduplicateProxy::setAddChildren(bool add)
++{
++ d_ptr->m_AddChildren = add;
++}
++
++bool DeduplicateProxy::areChildrenAdded()
++{
++ return d_ptr->m_AddChildren;
++}
++
++Comparator DeduplicateProxy::comparator()
++{
++ return d_ptr->m_fCompare;
++}
++
++/**
++ * Check if the source_row index is identical to the previous index
++ */
++bool DeduplicateProxy::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
++{
++ // Get both source QModelIndex and compare them
++ if (source_row > 0) {
++ const QModelIndex idx = sourceModel()->index(source_row, filterKeyColumn(), source_parent);
++
++ // It is correct to assume sourceModelIndex - 1 will be part of the same group
++ if (idx.row() > 0) {
++ const QModelIndex sibling = sourceModel()->index(idx.row() - 1, idx.column(), idx.parent());
++
++ bool accept = false;
++
++ // Allow to filter with more than one role, apply only if they exist
++ foreach(const int r, d_ptr->m_lOtherRoles) {
++ const QVariant v1 = idx.data (r);
++ const QVariant v2 = sibling.data(r);
++
++ // Yes, this is an "=", not "=="
++ if ((accept = !(v1.isValid() && v2.isValid() && (v1 == v2))))
++ break;
++ }
++
++ return accept || idx.data(filterRole()) != sibling.data(filterRole());
++ }
++ }
++
++ return true;
++}
++
++//TODO support for columns too
++
++///Return the number of hidden siblings of idx
++int DeduplicateProxy::hiddenSiblingCount(const QModelIndex& idx) const
++{
++ if ((!sourceModel()) || (!idx.isValid()) || idx.model() != this)
++ return 0;
++
++ const QModelIndex nextIdx = index(idx.row()+1, idx.column(), idx.parent());
++
++ const QModelIndex srcIdx = mapToSource(idx);
++
++ if (!nextIdx.isValid())
++ return sourceModel()->rowCount(srcIdx.parent()) - srcIdx.row();
++
++ const QModelIndex srcNextIdx = mapToSource(nextIdx);
++
++ return srcNextIdx.row() - srcIdx.row();
++}
++
++/**
++ * Return the list of source model QModelIndex that have been hidden
++ */
++QList<QModelIndex> DeduplicateProxy::hiddenSiblings(const QModelIndex& idx) const
++{
++ const int count = hiddenSiblingCount(idx);
++
++ if (!count)
++ return {};
++
++ QList<QModelIndex> ret;
++
++ const int parentRow = mapToSource(idx).row();
++ const QModelIndex srcParentIdx = mapToSource(idx).parent();
++
++ for (int i = 0; i < count; i++)
++ ret << sourceModel()->index(parentRow + count, idx.column(), srcParentIdx);
++
++ return ret;
++}
++
++/**
++ * Add a model data role to report the number of hidden siblings
++ */
++void DeduplicateProxy::setHiddenCountRole(int role, const QString& name)
++{
++ d_ptr->m_isHiddenRoleSet = true;
++ d_ptr->m_HiddenRoleId = role;
++ d_ptr->m_HiddenRoleName = name;
++}
++
++///This proxy doesn't sort anything
++void DeduplicateProxy::sort ( int column, Qt::SortOrder order)
++{
++ sourceModel()->sort(column, order);
++}
++
++void DeduplicateProxy::setSourceModel ( QAbstractItemModel * sourceModel )
++{
++ QSortFilterProxyModel::setSourceModel(sourceModel);
++
++ d_ptr->m_isSrcProxy = sourceModel && qobject_cast<QAbstractProxyModel*>(sourceModel);
++
++ //TODO find a way to catch setFilterString to forward it as this proxy
++ // is likely in front of another QSortFilterProxyModel re-implement ::invalidateFilter?
++}
++
++int DeduplicateProxy::rowCount(const QModelIndex& parent) const
++{
++ if (!d_ptr->m_AddChildren)
++ return QSortFilterProxyModel::rowCount(parent);
++
++
++ //TODO ::index, ::mapToSource and ::mapFromSource still need to be implemented
++ /*int hidden = 0;
++ // Handle when the rejected items need to be added as children
++ if (d_ptr->m_AddChildren && (hidden = hiddenSiblingCount(parent))) {
++ const QModelIndex src = mapToSource(parent);
++
++ // Only add the children if there is none
++ int parentRowCount = sourceModel()->rowCount(src);
++
++ if (!parentRowCount)
++ return hidden;
++ }*/
++
++ return QSortFilterProxyModel::rowCount(parent); //TODO
++}
++
++QVariant DeduplicateProxy::data( const QModelIndex& index, int role ) const
++{
++ if (d_ptr->m_isHiddenRoleSet && role == d_ptr->m_HiddenRoleId)
++ return hiddenSiblingCount(index);
++
++ return QSortFilterProxyModel::data(index, role);
++}
+diff --git a/src/proxies/deduplicateproxy.h b/src/proxies/deduplicateproxy.h
+new file mode 100644
+index 0000000..d2cee28
+--- /dev/null
++++ b/src/proxies/deduplicateproxy.h
+@@ -0,0 +1,97 @@
++/****************************************************************************
++ * Copyright (C) 2015 by Emmanuel Lepage Vallee *
++ * Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> *
++ * *
++ * 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 GNU General Public License *
++ * along with this program. If not, see <http://www.gnu.org/licenses/>. *
++ ***************************************************************************/
++#ifndef DEDUPLICATEPROXY_H
++#define DEDUPLICATEPROXY_H
++
++#include <functional>
++
++#include <QtCore/QSortFilterProxyModel>
++
++class DeduplicateProxyPrivate;
++
++/**
++ * This proxy take a role and remove additional rows matching the operator==
++ * of the role. It only deduplication indexes without child.
++ *
++ * Further QIdentityProxyModels can be created to extract the number of
++ *
++ * Example:
++ * ==Without addChildren== ==With addChildren==
++ * Foo Foo Foo
++ * |-> Bar |-> Bar |-> Bar
++ * |-> Bar |-> Baz |-> Bar
++ * |-> Bar |-> Bar |-> Bar
++ * |-> Baz Bar |-> Baz
++ * |-> Bar |-> Foo |-> Bar
++ * Bar ====== > |-> Baz Bar
++ * |-> Foo Bar |-> Foo
++ * |-> Baz |-> Foo |-> Baz
++ * Bar Bar
++ * |-> Foo |-> Foo
++ * |-> Foo |-> Foo
++ *
++ * @warning The default role comparaison only work for QVariant basic types \
++ * for custom types, it is better to use the function comparaison
++ *
++ * @note The filter string and sort role will be forwarded down the proxy chain\
++ * so this model can be used as a drop in replacment in existing code TODO
++ */
++class DeduplicateProxy : public QSortFilterProxyModel
++{
++ Q_OBJECT
++
++public:
++ enum class Mode {
++ ROLE,
++ FUNCTION //TODO
++ };
++
++ explicit DeduplicateProxy(QObject* parent = nullptr);
++
++ void addFilterRole(int role);
++
++ QVector<int> filterRoles() const;
++
++ void setComparator(const std::function<bool(const QModelIndex&, const QModelIndex&)>& f); //TODO
++
++ void setHiddenCountRole(int role, const QString& name = QString());
++
++ QList<QModelIndex> hiddenSiblings(const QModelIndex& idx) const;
++
++ void setAddChildren(bool add); //TODO
++
++ int hiddenSiblingCount(const QModelIndex& idx) const;
++
++ bool areChildrenAdded(); //TODO
++ std::function<bool(const QModelIndex&, const QModelIndex&)> comparator(); //TODO
++
++ // Model override
++ virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override;
++ virtual void sort ( int column, Qt::SortOrder order) override;
++ virtual void setSourceModel ( QAbstractItemModel * sourceModel ) override;
++ virtual int rowCount(const QModelIndex& parent) const override;
++ virtual QVariant data( const QModelIndex& index, int role ) const override;
++
++
++
++private:
++ DeduplicateProxyPrivate* d_ptr;
++ Q_DECLARE_PRIVATE(DeduplicateProxy)
++};
++
++#endif
+\ No newline at end of file
+diff --git a/src/proxies/simplerotateproxy.cpp b/src/proxies/simplerotateproxy.cpp
+index ab3fe47..03a9fb1 100644
+--- a/src/proxies/simplerotateproxy.cpp
++++ b/src/proxies/simplerotateproxy.cpp
+@@ -19,6 +19,7 @@
+
+ SimpleRotateProxy::SimpleRotateProxy(QObject * parent) : QAbstractProxyModel(parent)
+ {
++ //TODO implement it correctly, this is the lazy way
+ connect(this, &SimpleRotateProxy::sourceModelChanged, [this]() {
+ connect(this->sourceModel(),&QAbstractItemModel::dataChanged,[this](const QModelIndex&, const QModelIndex&) {
+ emit this->layoutChanged();
+@@ -77,10 +78,14 @@ QModelIndex SimpleRotateProxy::parent(const QModelIndex& ) const
+
+ int SimpleRotateProxy::rowCount(const QModelIndex& idx) const
+ {
+- return sourceModel()->columnCount(idx);
++ return idx.parent().isValid() ?
++ sourceModel()->rowCount (idx) :
++ sourceModel()->columnCount(idx);
+ }
+
+ int SimpleRotateProxy::columnCount(const QModelIndex& idx) const
+ {
+- return sourceModel()->rowCount(idx);
++ return idx.parent().isValid() ?
++ sourceModel()->columnCount(idx) :
++ sourceModel()->rowCount (idx);
+ }
+diff --git a/src/proxies/simplerotateproxy.h b/src/proxies/simplerotateproxy.h
+index 255ba1c..ce61a03 100644
+--- a/src/proxies/simplerotateproxy.h
++++ b/src/proxies/simplerotateproxy.h
+@@ -36,6 +36,15 @@
+ * displaying them horizontally create a better visual separation.
+ *
+ * 3) The source model is external and display data in inverted order
++ *
++ * Example:
++ *
++ * |-> Foo
++ * |-> Bar -----------------
++ * |-> Baz ====> | | |
++ * |-> Foobar Foo Bar Baz
++ * |-> Foobaz |-> Foobar
++ * |-> Foobaz
+ */
+ class SimpleRotateProxy : public QAbstractProxyModel
+ {
+--
+2.7.0
+
diff --git a/0004-akonadi-Make-it-optional.patch b/0004-akonadi-Make-it-optional.patch
new file mode 100644
index 000000000000..8fb5b7bd640f
--- /dev/null
+++ b/0004-akonadi-Make-it-optional.patch
@@ -0,0 +1,234 @@
+From db0bdfdc7473d60a694a14813512acca48066613 Mon Sep 17 00:00:00 2001
+From: Emmanuel Lepage Vallee <elv1313@gmail.com>
+Date: Sat, 19 Dec 2015 23:08:50 -0500
+Subject: [PATCH 4/6] akonadi: Make it optional
+
+Not all distributions ship with a KF5 version of Akonadi yet.
+---
+ CMakeLists.txt | 16 ++++++++++----
+ src/CMakeLists.txt | 4 ----
+ src/klib/CMakeLists.txt | 44 ++++++++++++++++++++++++++++---------
+ src/klib/itemmodelserialization.cpp | 11 +++++++++-
+ src/mainwindow.cpp | 16 +++++++++-----
+ 5 files changed, 67 insertions(+), 24 deletions(-)
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+index 8372310..23eb4bd 100755
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -15,8 +15,9 @@ ENDIF(POLICY CMP0028)
+ SET(PROJECT_VERSION "2.2.0")
+ PROJECT(ring-kde)
+
+-SET(QT_MIN_VERSION "5.2.0")
+-SET(KF5_DEP_VERSION "5.6.0")
++SET(QT_MIN_VERSION "5.2.0" )
++SET(KF5_DEP_VERSION "5.6.0" )
++SET(AKO_DEP_VERSION "4.89.0")
+
+ SET(LOCAL_CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/" )
+ SET(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH} ${LOCAL_CMAKE_MODULE_PATH}" )
+@@ -69,8 +70,15 @@ FIND_PACKAGE(KF5 "${KF5_DEP_VERSION}" REQUIRED COMPONENTS
+ Crash
+ )
+
+-# SET(AKONADI_MIN_VERSION 1.0)
+-# FIND_PACKAGE(Akonadi QUIET NO_MODULE ${AKONADI_MIN_VERSION})
++FIND_PACKAGE(KF5 "${AKO_DEP_VERSION}" COMPONENTS
++ Akonadi
++ AkonadiContact
++ Contacts
++)
++
++IF ( KF5_AKONADI_FOUND AND KF5_AKONADICONTACT_FOUND AND KF5_CONTACTS_FOUND)
++ ADD_DEFINITIONS("-DENABLE_AKONADI=1")
++ENDIF()
+
+ # INCLUDE ( KDE4Defaults )
+
+diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
+index b49dcd0..67c4e2b 100755
+--- a/src/CMakeLists.txt
++++ b/src/CMakeLists.txt
+@@ -375,10 +375,6 @@ TARGET_LINK_LIBRARIES(ring-kde
+ KF5::Crash
+ KF5::NotifyConfig
+ KF5::GlobalAccel
+-# ${KDEPIMLIBS_AKONADI_KMIME_LIBS}
+-# ${KDEPIMLIBS_AKONADI_LIBS}
+-# ${KDEPIMLIBS_AKONADI_CONTACT_LIBS}
+-# ${X11_LIBRARIES}
+ )
+
+ IF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+diff --git a/src/klib/CMakeLists.txt b/src/klib/CMakeLists.txt
+index be24937..03c07b6 100644
+--- a/src/klib/CMakeLists.txt
++++ b/src/klib/CMakeLists.txt
+@@ -4,6 +4,18 @@ ADD_DEFINITIONS("-std=c++0x")
+
+ PROJECT(libkring)
+
++IF(POLICY CMP0048)
++ CMAKE_POLICY(SET CMP0048 NEW)
++ENDIF(POLICY CMP0048)
++
++IF(POLICY CMP0017)
++ CMAKE_POLICY(SET CMP0017 NEW)
++ENDIF(POLICY CMP0017)
++
++IF(POLICY CMP0028)
++ CMAKE_POLICY(SET CMP0028 NEW)
++ENDIF(POLICY CMP0028)
++
+ FIND_PACKAGE(ECM 1.1.0 REQUIRED NO_MODULE)
+ SET (CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
+
+@@ -34,6 +46,11 @@ FIND_PACKAGE(KF5 COMPONENTS
+ Contacts
+ )
+
++IF ( KF5_AKONADI_FOUND AND KF5_AKONADICONTACT_FOUND AND KF5_CONTACTS_FOUND)
++ SET(AKONADI_FOUND 1)
++ENDIF()
++
++
+ INCLUDE_DIRECTORIES(${Qt5Widgets_INCLUDES} ${Qt5Core_INCLUDES} ${LIB_RING_CLIENT_INCLUDE_DIR})
+
+ ADD_DEFINITIONS(${Qt5Core_DEFINITIONS})
+@@ -45,16 +62,17 @@ SET(GENERIC_LIB_VERSION "2.0.0")
+ #File to compile
+ SET( libkring_LIB_SRCS
+ helperfunctions.cpp
+-# akonadibackend.cpp
+-# akonadicontactcollectionmodel.cpp
+ kdeprofilepersistor.cpp
+ itemmodelserialization.cpp
+ )
+
+-SET(libkring_LIB_SRCS
+- ${libkring_LIB_SRCS}
+- akonadibackend.cpp
+-)
++
++IF ( ${AKONADI_FOUND} )
++ SET(libkring_LIB_SRCS
++ ${libkring_LIB_SRCS}
++ akonadibackend.cpp
++ )
++ENDIF()
+
+ KCONFIG_ADD_KCFG_FILES (libkring_LIB_SRCS kcfg_settings.kcfgc)
+
+@@ -69,13 +87,19 @@ target_link_libraries( libkring
+ KF5::WidgetsAddons
+ KF5::ConfigCore
+ KF5::ConfigGui
+- KF5::AkonadiCore
+- KF5::AkonadiContact
+- KF5::Contacts
+ )
+
++# Akonadi support is optional
++IF ( ${AKONADI_FOUND} )
++ target_link_libraries( libkring
++ KF5::AkonadiCore
++ KF5::AkonadiContact
++ KF5::Contacts
++ )
++ENDIF()
++
+ SET( libkring_LIB_HDRS
+-# akonadibackend.h
++ akonadibackend.h
+ helperfunctions.h
+ itemmodelserialization.h
+ )
+diff --git a/src/klib/itemmodelserialization.cpp b/src/klib/itemmodelserialization.cpp
+index 6da8cc1..8152d12 100644
+--- a/src/klib/itemmodelserialization.cpp
++++ b/src/klib/itemmodelserialization.cpp
+@@ -19,7 +19,10 @@
+
+ #include <personmodel.h>
+
+-#include "akonadibackend.h"
++
++#ifdef ENABLE_AKONADI
++ #include "akonadibackend.h"
++#endif
+
+ #include "collectioninterface.h"
+ #include "kcfg_settings.h"
+@@ -67,12 +70,18 @@ bool ItemModelStateSerialization::setChecked(const CollectionInterface* backend,
+ CollectionInterface* ItemModelStateSerialization::preferredCollection(CollectionManagerInterfaceBase* manager, FlagPack<CollectionInterface::SupportedFeatures> features, FlagPack<Interfaces::ItemModelStateSerializerI::Hints> hints )
+ {
+ Q_UNUSED(hints)
++
++#ifdef ENABLE_AKONADI
+ if (manager == &PersonModel::instance()) {
+ foreach(CollectionInterface* i, PersonModel::instance().collections(features)) {
+ if (dynamic_cast<AkonadiBackend*>(i)) //TODO use something better
+ return i;
+ }
+ }
++#else
++ Q_UNUSED(manager )
++ Q_UNUSED(features)
++#endif
+
+ return nullptr;
+ }
+diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
+index 1d0553d..c8cf752 100755
+--- a/src/mainwindow.cpp
++++ b/src/mainwindow.cpp
+@@ -86,9 +86,13 @@
+ #include <video/renderer.h>
+ #include "ringapplication.h"
+ #include "widgets/dockbase.h"
+-#include "klib/akonadibackend.h"
++
++#ifdef ENABLE_AKONADI
++ #include "klib/akonadibackend.h"
++#endif
++
+ #ifdef ENABLE_VIDEO
+-#include "widgets/videodock.h"
++ #include "widgets/videodock.h"
+ #endif
+
+ MainWindow* MainWindow::m_sApp = nullptr;
+@@ -177,9 +181,9 @@ MainWindow::MainWindow(QWidget* parent)
+ GlobalInstances::setInterface<ItemModelStateSerialization>();
+ GlobalInstances::itemModelStateSerializer().load();
+
++#ifdef ENABLE_AKONADI
+ AkonadiBackend::initCollections();
+-// PersonModel::instance().backendModel()->load();
+-// AccountModel::instance().setDefaultAccount(AccountModel::instance().getAccountById(ConfigurationSkeleton::defaultAccountId()));
++#endif
+
+ init = true;
+
+@@ -223,7 +227,6 @@ MainWindow::MainWindow(QWidget* parent)
+ m_pCentralDW->setTitleBarWidget(new QWidget());
+ m_pCentralDW->setContentsMargins(0,0,0,0);
+ m_pView->setContentsMargins (0,0,0,0);
+- addDockWidget( Qt::BottomDockWidgetArea, m_pCentralDW );
+ m_pCentralDW->setObjectName( "callDock" );
+ m_pCentralDW->show();
+
+@@ -233,6 +236,9 @@ MainWindow::MainWindow(QWidget* parent)
+ m_pTrayIcon = new SysTray ( this->windowIcon(), this );
+
+ m_pDock = new Dock(this);
++
++ addDockWidget( Qt::BottomDockWidgetArea, m_pCentralDW );
++
+ connect(m_pCentralDW ,SIGNAL(visibilityChanged(bool)),m_pDock,SLOT(updateTabIcons()));
+
+ selectCallTab();
+--
+2.7.0
+
diff --git a/0005-Fix-CMakeLists.txt.patch b/0005-Fix-CMakeLists.txt.patch
new file mode 100644
index 000000000000..cd015aa0dc87
--- /dev/null
+++ b/0005-Fix-CMakeLists.txt.patch
@@ -0,0 +1,25 @@
+From b6e20f613203d98e549d0d1e0924ef273513e9e2 Mon Sep 17 00:00:00 2001
+From: Pierre Choffet <peuc@wanadoo.fr>
+Date: Thu, 7 Jan 2016 15:44:22 -0500
+Subject: [PATCH 5/6] Fix CMakeLists.txt
+
+---
+ src/CMakeLists.txt | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
+index 67c4e2b..01fd97c 100755
+--- a/src/CMakeLists.txt
++++ b/src/CMakeLists.txt
+@@ -15,7 +15,7 @@ ENDIF(POLICY CMP0063)
+ SET(KF5_DEP_VERSION "5.6.0")
+ SET(QT_MIN_VERSION "5.2.0")
+
+-FIND_PACKAGE ( KF5 REQUIRED )
++FIND_PACKAGE ( KF5 REQUIRED NotifyConfig GlobalAccel)
+
+ find_package (ECM 1.1.0 REQUIRED NO_MODULE)
+ set (CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
+--
+2.7.0
+
diff --git a/0006-contact-Add-new-basic-contact-selection-dialog.patch b/0006-contact-Add-new-basic-contact-selection-dialog.patch
new file mode 100644
index 000000000000..9d143615133a
--- /dev/null
+++ b/0006-contact-Add-new-basic-contact-selection-dialog.patch
@@ -0,0 +1,461 @@
+From 962788b7c0956953c688170cbe5c234e0a6c1876 Mon Sep 17 00:00:00 2001
+From: Emmanuel Lepage Vallee <elv1313@gmail.com>
+Date: Sat, 5 Dec 2015 21:20:59 -0500
+Subject: [PATCH 6/6] contact: Add new (basic) contact selection dialog
+
+This will be expanded in further commits to improve assigning contacts
+to unassigned contact methods.
+---
+ src/widgets/contactmethodselector.cpp | 40 ++++++++++
+ src/widgets/contactmethodselector.h | 42 +++++++++++
+ src/widgets/personselector.cpp | 47 ++++++++++++
+ src/widgets/personselector.h | 42 +++++++++++
+ src/widgets/ui/contactmethodselector.ui | 126 ++++++++++++++++++++++++++++++++
+ src/widgets/ui/personselector.ui | 100 +++++++++++++++++++++++++
+ 6 files changed, 397 insertions(+)
+ create mode 100644 src/widgets/contactmethodselector.cpp
+ create mode 100644 src/widgets/contactmethodselector.h
+ create mode 100644 src/widgets/personselector.cpp
+ create mode 100644 src/widgets/personselector.h
+ create mode 100644 src/widgets/ui/contactmethodselector.ui
+ create mode 100644 src/widgets/ui/personselector.ui
+
+diff --git a/src/widgets/contactmethodselector.cpp b/src/widgets/contactmethodselector.cpp
+new file mode 100644
+index 0000000..d2a0728
+--- /dev/null
++++ b/src/widgets/contactmethodselector.cpp
+@@ -0,0 +1,40 @@
++/***************************************************************************
++ * Copyright (C) 2015 by Emmanuel Lepage Vallee *
++ * Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com>*
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ **************************************************************************/
++#include "contactmethodselector.h"
++
++// Qt
++#include <QtCore/QSortFilterProxyModel>
++
++#include <phonedirectorymodel.h>
++#include <contactmethod.h>
++
++ContactMethodSelector::ContactMethodSelector(QWidget* parent) : QDialog(parent)
++{
++ setupUi(this);
++
++ m_pSortedContacts = new QSortFilterProxyModel(this);
++ m_pSortedContacts->setSourceModel ( &PhoneDirectoryModel::instance() );
++ m_pSortedContacts->setSortRole ( Qt::DisplayRole );
++ m_pSortedContacts->setFilterCaseSensitivity( Qt::CaseInsensitive );
++ m_pSortedContacts->setSortCaseSensitivity ( Qt::CaseInsensitive );
++
++
++ connect(m_pFilterLE ,SIGNAL(filterStringChanged(QString)), m_pSortedContacts, SLOT(setFilterRegExp(QString)) );
++
++ listView->setModel(m_pSortedContacts);
++}
+diff --git a/src/widgets/contactmethodselector.h b/src/widgets/contactmethodselector.h
+new file mode 100644
+index 0000000..7819856
+--- /dev/null
++++ b/src/widgets/contactmethodselector.h
+@@ -0,0 +1,42 @@
++/***************************************************************************
++ * Copyright (C) 2015 by Emmanuel Lepage Vallee *
++ * Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com>*
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ **************************************************************************/
++#ifndef CONTACTMETHODSELECTOR_H
++#define CONTACTMETHODSELECTOR_H
++
++#include "ui_contactmethodselector.h"
++
++#include <QtWidgets/QDialog>
++
++class QSortFilterProxyModel;
++
++class ContactMethod;
++
++class ContactMethodSelector : public QDialog, public Ui_ContactMethodSelector
++{
++ Q_OBJECT
++public:
++ explicit ContactMethodSelector(QWidget* parent = nullptr);
++ virtual ~ContactMethodSelector() = default;
++
++private:
++ QSortFilterProxyModel* m_pSortedContacts;
++ QSortFilterProxyModel* m_pNearMatchContact;
++
++};
++
++#endif
+\ No newline at end of file
+diff --git a/src/widgets/personselector.cpp b/src/widgets/personselector.cpp
+new file mode 100644
+index 0000000..97e8af9
+--- /dev/null
++++ b/src/widgets/personselector.cpp
+@@ -0,0 +1,47 @@
++/***************************************************************************
++ * Copyright (C) 2015 by Emmanuel Lepage Vallee *
++ * Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com>*
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ **************************************************************************/
++#include "personselector.h"
++
++// Qt
++#include <QtCore/QSortFilterProxyModel>
++
++#include <personmodel.h>
++#include <contactmethod.h>
++
++PersonSelector::PersonSelector(QWidget* parent, const ContactMethod* cm) : QDialog(parent)
++{
++ setupUi(this);
++
++ m_pSortedContacts = new QSortFilterProxyModel(this);
++ m_pSortedContacts->setSourceModel ( &PersonModel::instance() );
++ m_pSortedContacts->setSortRole ( Qt::DisplayRole );
++ m_pSortedContacts->setFilterCaseSensitivity( Qt::CaseInsensitive );
++ m_pSortedContacts->setSortCaseSensitivity ( Qt::CaseInsensitive );
++
++ if (cm) {
++ m_pNearMatchContact = new QSortFilterProxyModel(this);
++ m_pNearMatchContact->setSourceModel ( &PersonModel::instance() );
++ m_pNearMatchContact->setFilterFixedString( cm->primaryName() );
++ }
++
++ nearMatch->setVisible(cm != nullptr && m_pNearMatchContact->rowCount());
++
++ connect(m_pFilterLE ,SIGNAL(filterStringChanged(QString)), m_pSortedContacts, SLOT(setFilterRegExp(QString)) );
++
++ listView->setModel(m_pSortedContacts);
++}
+\ No newline at end of file
+diff --git a/src/widgets/personselector.h b/src/widgets/personselector.h
+new file mode 100644
+index 0000000..09b6332
+--- /dev/null
++++ b/src/widgets/personselector.h
+@@ -0,0 +1,42 @@
++/***************************************************************************
++ * Copyright (C) 2015 by Emmanuel Lepage Vallee *
++ * Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com>*
++ * *
++ * 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 <http://www.gnu.org/licenses/>. *
++ **************************************************************************/
++#ifndef PERSONSELECTOR_H
++#define PERSONSELECTOR_H
++
++#include "ui_personselector.h"
++
++#include <QtWidgets/QDialog>
++
++class QSortFilterProxyModel;
++
++class ContactMethod;
++
++class PersonSelector : public QDialog, public Ui_PersonSelector
++{
++ Q_OBJECT
++public:
++ explicit PersonSelector(QWidget* parent = nullptr, const ContactMethod* cm = nullptr);
++ virtual ~PersonSelector() = default;
++
++private:
++ QSortFilterProxyModel* m_pSortedContacts;
++ QSortFilterProxyModel* m_pNearMatchContact;
++
++};
++
++#endif
+\ No newline at end of file
+diff --git a/src/widgets/ui/contactmethodselector.ui b/src/widgets/ui/contactmethodselector.ui
+new file mode 100644
+index 0000000..dd69b93
+--- /dev/null
++++ b/src/widgets/ui/contactmethodselector.ui
+@@ -0,0 +1,126 @@
++<?xml version="1.0" encoding="UTF-8"?>
++<ui version="4.0">
++ <class>ContactMethodSelector</class>
++ <widget class="QDialog" name="ContactMethodSelector">
++ <property name="geometry">
++ <rect>
++ <x>0</x>
++ <y>0</y>
++ <width>400</width>
++ <height>300</height>
++ </rect>
++ </property>
++ <property name="windowTitle">
++ <string>Dialog</string>
++ </property>
++ <layout class="QVBoxLayout" name="verticalLayout_2">
++ <item>
++ <widget class="QGroupBox" name="groupBox">
++ <property name="title">
++ <string>Existing</string>
++ </property>
++ <layout class="QVBoxLayout" name="verticalLayout">
++ <item>
++ <widget class="QListView" name="listView"/>
++ </item>
++ <item>
++ <widget class="FilterLineEdit" name="m_pFilterLE">
++ <property name="placeholderText">
++ <string>Search</string>
++ </property>
++ </widget>
++ </item>
++ </layout>
++ </widget>
++ </item>
++ <item>
++ <widget class="QGroupBox" name="groupBox_2">
++ <property name="title">
++ <string>New</string>
++ </property>
++ <layout class="QFormLayout" name="formLayout_2">
++ <item row="0" column="0">
++ <widget class="QLabel" name="label_2">
++ <property name="text">
++ <string>Phone number</string>
++ </property>
++ </widget>
++ </item>
++ <item row="0" column="1">
++ <widget class="QLineEdit" name="lineEdit"/>
++ </item>
++ <item row="1" column="0">
++ <widget class="QLabel" name="label">
++ <property name="text">
++ <string>Account</string>
++ </property>
++ </widget>
++ </item>
++ <item row="1" column="1">
++ <widget class="QComboBox" name="comboBox"/>
++ </item>
++ </layout>
++ </widget>
++ </item>
++ <item>
++ <widget class="QDialogButtonBox" name="buttonBox">
++ <property name="orientation">
++ <enum>Qt::Horizontal</enum>
++ </property>
++ <property name="standardButtons">
++ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
++ </property>
++ </widget>
++ </item>
++ </layout>
++ </widget>
++ <customwidgets>
++ <customwidget>
++ <class>FilterLineEdit</class>
++ <extends>QLineEdit</extends>
++ <header>widgets/filterlineedit.h</header>
++ </customwidget>
++ </customwidgets>
++ <tabstops>
++ <tabstop>m_pFilterLE</tabstop>
++ <tabstop>listView</tabstop>
++ <tabstop>lineEdit</tabstop>
++ <tabstop>comboBox</tabstop>
++ <tabstop>buttonBox</tabstop>
++ </tabstops>
++ <resources/>
++ <connections>
++ <connection>
++ <sender>buttonBox</sender>
++ <signal>accepted()</signal>
++ <receiver>ContactMethodSelector</receiver>
++ <slot>accept()</slot>
++ <hints>
++ <hint type="sourcelabel">
++ <x>248</x>
++ <y>254</y>
++ </hint>
++ <hint type="destinationlabel">
++ <x>157</x>
++ <y>274</y>
++ </hint>
++ </hints>
++ </connection>
++ <connection>
++ <sender>buttonBox</sender>
++ <signal>rejected()</signal>
++ <receiver>ContactMethodSelector</receiver>
++ <slot>reject()</slot>
++ <hints>
++ <hint type="sourcelabel">
++ <x>316</x>
++ <y>260</y>
++ </hint>
++ <hint type="destinationlabel">
++ <x>286</x>
++ <y>274</y>
++ </hint>
++ </hints>
++ </connection>
++ </connections>
++</ui>
+diff --git a/src/widgets/ui/personselector.ui b/src/widgets/ui/personselector.ui
+new file mode 100644
+index 0000000..c415c37
+--- /dev/null
++++ b/src/widgets/ui/personselector.ui
+@@ -0,0 +1,100 @@
++<?xml version="1.0" encoding="UTF-8"?>
++<ui version="4.0">
++ <class>PersonSelector</class>
++ <widget class="QDialog" name="PersonSelector">
++ <property name="geometry">
++ <rect>
++ <x>0</x>
++ <y>0</y>
++ <width>400</width>
++ <height>300</height>
++ </rect>
++ </property>
++ <property name="windowTitle">
++ <string>Dialog</string>
++ </property>
++ <layout class="QVBoxLayout" name="verticalLayout">
++ <item>
++ <widget class="QSplitter" name="splitter">
++ <property name="orientation">
++ <enum>Qt::Vertical</enum>
++ </property>
++ <widget class="QListView" name="nearMatch">
++ <property name="sizePolicy">
++ <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
++ <horstretch>0</horstretch>
++ <verstretch>0</verstretch>
++ </sizepolicy>
++ </property>
++ </widget>
++ <widget class="QListView" name="listView"/>
++ </widget>
++ </item>
++ <item>
++ <widget class="FilterLineEdit" name="m_pFilterLE">
++ <property name="placeholderText">
++ <string>Search</string>
++ </property>
++ </widget>
++ </item>
++ <item>
++ <widget class="QDialogButtonBox" name="buttonBox">
++ <property name="orientation">
++ <enum>Qt::Horizontal</enum>
++ </property>
++ <property name="standardButtons">
++ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
++ </property>
++ </widget>
++ </item>
++ </layout>
++ </widget>
++ <customwidgets>
++ <customwidget>
++ <class>FilterLineEdit</class>
++ <extends>QLineEdit</extends>
++ <header>widgets/filterlineedit.h</header>
++ </customwidget>
++ </customwidgets>
++ <tabstops>
++ <tabstop>m_pFilterLE</tabstop>
++ <tabstop>listView</tabstop>
++ <tabstop>nearMatch</tabstop>
++ <tabstop>buttonBox</tabstop>
++ </tabstops>
++ <resources/>
++ <connections>
++ <connection>
++ <sender>buttonBox</sender>
++ <signal>accepted()</signal>
++ <receiver>PersonSelector</receiver>
++ <slot>accept()</slot>
++ <hints>
++ <hint type="sourcelabel">
++ <x>248</x>
++ <y>254</y>
++ </hint>
++ <hint type="destinationlabel">
++ <x>157</x>
++ <y>274</y>
++ </hint>
++ </hints>
++ </connection>
++ <connection>
++ <sender>buttonBox</sender>
++ <signal>rejected()</signal>
++ <receiver>PersonSelector</receiver>
++ <slot>reject()</slot>
++ <hints>
++ <hint type="sourcelabel">
++ <x>316</x>
++ <y>260</y>
++ </hint>
++ <hint type="destinationlabel">
++ <x>286</x>
++ <y>274</y>
++ </hint>
++ </hints>
++ </connection>
++ </connections>
++</ui>
+--
+2.7.0
+
diff --git a/ChangeLog b/ChangeLog
index e023f7cd1226..10241a5360ce 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2016-01-07 Pierre Choffet <peuc@wanadoo.fr>
+ * 2.0.0.r138.g18ff797-2 :
+ Fix upstream which is broken after libringclient update. Add 5 backported
+ patches.
+
2015-12-16 Pierre Choffet <peuc@wanadoo.fr>
- * 2.0.0.r138.g18ff797 :
+ * 2.0.0.r138.g18ff797-1 :
Initial PKGBUILD.
diff --git a/PKGBUILD b/PKGBUILD
index f79243730c59..b49e9bb8b70d 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -7,7 +7,7 @@
pkgname=ring-kde-git
pkgver=2.0.0.r138.g18ff797
-pkgrel=1
+pkgrel=2
pkgdesc="KDE client for Ring"
arch=("i686" "x86_64")
url="http://ring.cx/"
@@ -20,8 +20,20 @@ depends=("libringclient-git" "ring-daemon-git" "qt5-base" "qt5-svg"
makedepends=("git" "cmake" "extra-cmake-modules")
provides=("ring-kde")
changelog="ChangeLog"
-source=("git://anongit.kde.org/ring-kde")
-sha256sums=('SKIP')
+source=("git://anongit.kde.org/ring-kde"
+ "0001-textmessages-Vastly-revamp-and-fix-the-text-messages.patch"
+ "0002-contact-Enable-Akonadi-support-again.patch"
+ "0003-history-De-duplicate-identical-calls.patch"
+ "0004-akonadi-Make-it-optional.patch"
+ "0005-Fix-CMakeLists.txt.patch"
+ "0006-contact-Add-new-basic-contact-selection-dialog.patch")
+sha256sums=('SKIP'
+ 'fe1a4a7ba6df920117adf5d57a8fcfaa784986369a50c91bd3ae5b0f69ea2c59'
+ '2fce01531bcdf001b1f8cc34522c5951dcbb853ea597ffa4421cdd78661148d2'
+ '1c1ff7d6244982cbf56720eeda731b5fb09933178e02d0b3d24614a906d91e29'
+ 'b50755d6653b417046734a29d25cdb4ff2cadcc900e119b309e4fbb0d8614894'
+ 'd1215e1ba920f7b359a4319caa6ea286784b32079fc647c2fe7b66a9dde4e44c'
+ 'c22e0d8518575d00dc6b660e5c8979659678e403947526465ceaf17cdca7c517')
pkgver() {
cd "ring-kde"
@@ -29,6 +41,17 @@ pkgver() {
git describe --long | sed 's/\([^-]*-g\)/r\1/;s/-/./g'
}
+prepare() {
+ cd "ring-kde"
+
+ git am < '../0001-textmessages-Vastly-revamp-and-fix-the-text-messages.patch'
+ git am < '../0002-contact-Enable-Akonadi-support-again.patch'
+ git am < '../0003-history-De-duplicate-identical-calls.patch'
+ git am < '../0004-akonadi-Make-it-optional.patch'
+ git am < '../0005-Fix-CMakeLists.txt.patch'
+ git am < '../0006-contact-Add-new-basic-contact-selection-dialog.patch'
+}
+
build() {
cd "ring-kde"