diff --git a/src/profilemodel.cpp b/src/profilemodel.cpp index 9024c6ec74f8d8f0de9745d2180f08e7df627ae5..70a10e67307b1a508075e909a5f11d290b94fd0e 100644 --- a/src/profilemodel.cpp +++ b/src/profilemodel.cpp @@ -61,19 +61,25 @@ public: virtual bool addNew ( Person* item ) override; virtual bool addExisting( const Person* item ) override; + //Attributes Node* getProfileById(const QByteArray& id); QList<Account*> getAccountsForProfile(const QString& id); QList<Node*> m_lProfiles; QHash<QByteArray,Node*> m_hProfileByAccountId; QHash<QByteArray,Node*> m_hAccountIdToNode; QVector<Person*> m_lProfilePersons; + QSet<Person*> m_bSaveBuffer; + + //Helpers + QString path(const Person* p) const; private: virtual QVector<Person*> items() const override; }; ///ProfileContentBackend: Implement a backend for Profiles -class ProfileContentBackend final : public QObject, public CollectionInterface { +class ProfileContentBackend final : public QObject, public CollectionInterface +{ Q_OBJECT public: template<typename T> @@ -96,25 +102,28 @@ public: bool m_needSaving; bool m_DelayedLoading {false}; - QList<Person*> m_bSaveBuffer; bool saveAll(); Node* m_pDefault; ProfileEditor* m_pEditor; //Helper - void setupDefaultProfile(); - void addAccount(Node* parent, Account* acc); + void setupDefaultProfile(); + void addAccount(Node* parent, Account* acc); + bool disableAndRemove(Person* p); public Q_SLOTS: - void contactChanged(); void save(); void loadProfiles(); }; +struct Node +{ + explicit Node(): account(nullptr),contact(nullptr),type(Node::Type::PROFILE), parent(nullptr),m_Index(0) {} -struct Node { - Node(): account(nullptr),contact(nullptr),type(Node::Type::PROFILE), parent(nullptr),m_Index(0) {} + virtual ~Node() { + QObject::disconnect(m_ChangedConn); + } enum class Type : bool { PROFILE, @@ -123,13 +132,52 @@ struct Node { Node* parent; QVector<Node*> children; - Type type; + Type type {Node::Type::ACCOUNT}; Account* account; Person* contact; int m_Index; uint m_ParentIndex {(uint)-1}; //INT_MAX + QMetaObject::Connection m_ChangedConn; }; +class ProfileModelPrivate final : public QObject +{ + Q_OBJECT +public: + ProfileModelPrivate(ProfileModel* parent); + ProfileContentBackend* m_pProfileBackend; + QStringList m_lMimes; + QItemSelectionModel* m_pSelectionModel {nullptr}; + QItemSelectionModel* m_pSortedProxySelectionModel {nullptr}; + QSortFilterProxyModel* m_pSortedProxyModel {nullptr}; + + //Helpers + void updateIndexes(); + void regenParentIndexes(); + inline QModelIndex addProfile(Person* person, const QString& name); + Node* insertProfile(Person* p); + +private Q_SLOTS: + void slotDataChanged(const QModelIndex& tl,const QModelIndex& br); + void slotLayoutchanged(); + void slotDelayedInit(); + void slotRowsRemoved (const QModelIndex& index, int first, int last); + void slotRowsInserted(const QModelIndex& index, int first, int last); + void slotRowsMoved (const QModelIndex& index, int first, int last, const QModelIndex& newPar, int newIdx); + +private: + ProfileModel* q_ptr; +}; + +QString ProfileEditor::path(const Person* p) const +{ + const QDir profilesDir = GlobalInstances::profilePersister().profilesDir(); + + return QString("%1/%2.vcf") + .arg(profilesDir.absolutePath()) + .arg(QString(p->uid())); +} + bool ProfileEditor::save(const Person* contact) { try { @@ -140,8 +188,7 @@ bool ProfileEditor::save(const Person* contact) QTimer::singleShot(0,[this,contact]() { const_cast<Person*>(contact)->setProperty("delayedSaving", false); #endif - const auto& profilesDir = GlobalInstances::profilePersister().profilesDir(); - const auto& filename = profilesDir.absolutePath() + '/' + contact->uid() + ".vcf"; + const auto& filename = path(contact); qDebug() << "Saving vcf in:" << filename; const auto& result = contact->toVCard(getAccountsForProfile(contact->uid())); @@ -171,7 +218,29 @@ ProfileEditor::~ProfileEditor() bool ProfileEditor::remove(const Person* item) { Q_UNUSED(item) - mediator()->removeItem(item); + + if (QFile::remove(path(item))) { + mediator()->removeItem(item); + + foreach (Node* n, m_lProfiles) { + if (n->contact == item) { + m_lProfiles.removeAll(n); + break; + } + } + + m_bSaveBuffer.remove(const_cast<Person*>(item)); + + const int idx = m_lProfilePersons.indexOf(const_cast<Person*>(item)); + + if (idx != -1) + m_lProfilePersons.removeAt(idx); + + return true; + } + else + qDebug() << "Failed to remove" << path(item); + return false; } @@ -187,6 +256,8 @@ bool ProfileEditor::addNew( Person* contact) m_lProfilePersons << contact; mediator()->addItem(contact); save(contact); + + ProfileModel::instance()->d_ptr->insertProfile(contact); // load(); //FIXME return true; } @@ -310,22 +381,14 @@ void ProfileContentBackend::setupDefaultProfile() orphans << i.key(); } } - qDebug() << "ORPHAN" << orphans.size() << m_pEditor << m_pEditor->m_lProfiles.size(); if (orphans.size() && (!m_pDefault)) { qDebug() << "No profile found, creating one"; - Person* profile = new Person(this,QString::number(QDateTime::currentDateTime().currentMSecsSinceEpoch()).toUtf8()); + Person* profile = new Person(this, QString::number(QDateTime::currentDateTime().currentMSecsSinceEpoch()).toUtf8()); m_pEditor->addNew(profile); profile->setFormattedName(tr("Default")); - m_pDefault = new Node ; - m_pDefault->type = Node::Type::PROFILE; - m_pDefault->contact = profile ; - m_pDefault->m_Index = m_pEditor->m_lProfiles.size() ; - - ProfileModel::instance()->beginInsertRows(QModelIndex(), m_pEditor->m_lProfiles.size(), m_pEditor->m_lProfiles.size()); - m_pEditor->m_lProfiles << m_pDefault; - ProfileModel::instance()->endInsertRows(); + m_pDefault = ProfileModel::instance()->d_ptr->insertProfile(profile); } foreach(Account* a, orphans) { @@ -333,6 +396,11 @@ void ProfileContentBackend::setupDefaultProfile() } } +bool ProfileContentBackend::disableAndRemove(Person* p) +{ + return remove(p); +} + void ProfileContentBackend::addAccount(Node* parent, Account* acc) { Node* account_pro = new Node; @@ -362,29 +430,21 @@ void ProfileContentBackend::loadProfiles() qDebug() << "Loading vcf from:" << profilesDir; - const QStringList entries = profilesDir.entryList({"*.vcf"}, QDir::Files); + const QStringList entries = profilesDir.entryList({QStringLiteral("*.vcf")}, QDir::Files); foreach (const QString& item , entries) { Person* profile = new Person(this); - Node* pro = new Node ; - pro->type = Node::Type::PROFILE; - pro->contact = profile ; - pro->m_Index = m_pEditor->m_lProfiles.size() ; - QList<Account*> accs; VCardUtils::mapToPerson(profile,QUrl(profilesDir.path()+'/'+item),&accs); - ProfileModel::instance()->beginInsertRows(QModelIndex(), m_pEditor->m_lProfiles.size(), m_pEditor->m_lProfiles.size()); - m_pEditor->m_lProfiles << pro; - ProfileModel::instance()->endInsertRows(); + Node* pro = ProfileModel::instance()->d_ptr->insertProfile(profile); /* must be done after inserting profile, or else we try to insert to non existing parent */ foreach(Account* a, accs) addAccount(pro,a); - connect(profile, SIGNAL(changed()), this, SLOT(contactChanged())); PersonModel::instance()->addPerson(profile); } @@ -412,7 +472,7 @@ bool ProfileContentBackend::reload() bool ProfileContentBackend::saveAll() { - for(Node* pro : m_pEditor->m_lProfiles) { + foreach (Node* pro, m_pEditor->m_lProfiles) { editor<Person>()->save(pro->contact); } return true; @@ -460,35 +520,21 @@ QList<Account*> ProfileEditor::getAccountsForProfile(const QString& id) Node* ProfileEditor::getProfileById(const QByteArray& id) { - for (Node* p : m_lProfiles) { + foreach(Node* p, m_lProfiles) { if(p->contact->uid() == id) return p; } return nullptr; } -void ProfileContentBackend::contactChanged() -{ - Person* c = qobject_cast<Person*>(sender()); - qDebug() << c->formattedName(); - qDebug() << "contactChanged!"; - - if(m_needSaving) { - m_bSaveBuffer << c; - QTimer::singleShot(0,this,SLOT(save())); - } - else - m_needSaving = true; -} - void ProfileContentBackend::save() { - for (Person* item : m_bSaveBuffer) { + foreach (Person* item, m_pEditor->m_bSaveBuffer) { qDebug() << "saving:" << item->formattedName(); editor<Person>()->save(item); } - m_bSaveBuffer.clear(); + m_pEditor->m_bSaveBuffer.clear(); m_needSaving = false; load(); } @@ -500,32 +546,6 @@ ProfileModel* ProfileModel::instance() return m_spInstance; } -class ProfileModelPrivate final : public QObject { - Q_OBJECT -public: - ProfileModelPrivate(ProfileModel* parent); - ProfileContentBackend* m_pProfileBackend; - QStringList m_lMimes; - QItemSelectionModel* m_pSelectionModel {nullptr}; - QItemSelectionModel* m_pSortedProxySelectionModel {nullptr}; - QSortFilterProxyModel* m_pSortedProxyModel {nullptr}; - - //Helpers - void updateIndexes(); - void regenParentIndexes(); - -private Q_SLOTS: - void slotDataChanged(const QModelIndex& tl,const QModelIndex& br); - void slotLayoutchanged(); - void slotDelayedInit(); - void slotRowsRemoved (const QModelIndex& index, int first, int last); - void slotRowsInserted(const QModelIndex& index, int first, int last); - void slotRowsMoved (const QModelIndex& index, int first, int last, const QModelIndex& newPar, int newIdx); - -private: - ProfileModel* q_ptr; -}; - ProfileModelPrivate::ProfileModelPrivate(ProfileModel* parent) : QObject(parent), q_ptr(parent),m_pProfileBackend(nullptr) { @@ -896,11 +916,136 @@ QVariant ProfileModel::headerData(int section, Qt::Orientation orientation, int return QVariant(); } -// bool ProfileModel::addNewProfile(Person* c, CollectionInterface* backend) -// { -// Q_UNUSED(backend); -// return d_ptr->m_pProfileBackend->addNew(c); -// } +/** + * Remove an unused profile + * + * @return If removing the profile has been successful + */ +bool ProfileModel::remove(const QModelIndex& idx) +{ + QModelIndex realIdx = idx; + + // Helper to unwind the proxies + while(realIdx.isValid() && realIdx.model() != this) { + if (qobject_cast<const QAbstractProxyModel*>(realIdx.model())) + realIdx = qobject_cast<const QAbstractProxyModel*>(realIdx.model())->mapToSource(realIdx); + else { + realIdx = QModelIndex(); + break; + } + } + + // Ensure it is a profile that can be removed + if (!realIdx.isValid()) { + qDebug() << "Failed to remove account: invalid index"; + return false; + } + + Node* n = static_cast<Node*>(realIdx.internalPointer()); + + if (n->type != Node::Type::PROFILE) { + qDebug() << "Failed to remove profile: It is not a profile" << (int)n->type << n->contact << realIdx.data(Qt::DisplayRole); + return false; + } + + if (n->children.size()) { + qDebug() << "Failed to remove profile: It is in use"; + return false; + } + + Q_ASSERT(n->contact); + + // Remove the profile + beginRemoveRows(QModelIndex(), n->m_Index, n->m_Index); + d_ptr->m_pProfileBackend->disableAndRemove(n->contact); + delete n; + endRemoveRows(); + + return true; +} + +QModelIndex ProfileModelPrivate::addProfile(Person* person, const QString& name) +{ + Person* p = nullptr; + + if (person && person->collection() == m_pProfileBackend) + return QModelIndex(); //Nothing to do + else if (person) { + p = new Person(*person); + m_pProfileBackend->editor<Person>()->addNew(p); + } + else { + p = new Person(m_pProfileBackend, QString::number(QDateTime::currentDateTime().currentMSecsSinceEpoch()).toUtf8()); + + QString profileName = name; + + if (profileName.isEmpty()) { + profileName = tr("New profile"); + } + + p->setFormattedName(profileName); + + m_pProfileBackend->editor<Person>()->addNew(p); + + } + + if (auto n = m_pProfileBackend->m_pEditor->m_hProfileByAccountId[p->uid()]) { + return q_ptr->index(n->m_Index, 0); + } + + return QModelIndex(); +} + +Node* ProfileModelPrivate::insertProfile(Person* p) +{ + Node* pro = new Node ; + pro->type = Node::Type::PROFILE; + pro->contact = p ; + pro->m_Index = m_pProfileBackend->m_pEditor->m_lProfiles.size(); + + m_pProfileBackend->m_pEditor->m_hProfileByAccountId[p->uid()] = pro; + + q_ptr->beginInsertRows({}, m_pProfileBackend->m_pEditor->m_lProfiles.size(), m_pProfileBackend->m_pEditor->m_lProfiles.size()); + m_pProfileBackend->m_pEditor->m_lProfiles << pro; + q_ptr->endInsertRows(); + + pro->m_ChangedConn = connect(p, &Person::changed, [this, pro]() { + if (pro->contact->isActive()) { + const QModelIndex idx = q_ptr->index(pro->m_Index, 0); + emit q_ptr->dataChanged(idx, idx); + + //TODO eventually replace with an edition state machine like other models + if(m_pProfileBackend->m_needSaving) + QTimer::singleShot(0,this,SLOT(m_pProfileBackend->save())); + else + m_pProfileBackend->m_needSaving = true; + + m_pProfileBackend->m_pEditor->m_bSaveBuffer << pro->contact; + } + }); + + return pro; +} + +/** + * Create a new profile + * + * @param person an optional person to use for the vCard template + */ +QModelIndex ProfileModel::add(Person* person) +{ + return d_ptr->addProfile(person, QString()); +} + +/** + * Create a new profile + * + * @param name The new profile name + */ +QModelIndex ProfileModel::add(const QString& name) +{ + return d_ptr->addProfile(nullptr, name); +} void ProfileModelPrivate::slotDataChanged(const QModelIndex& tl,const QModelIndex& br) { diff --git a/src/profilemodel.h b/src/profilemodel.h index 65b66d553d5d07e34a776c9528dbfd6a382a9952..000665d94327aa23c583db00f486df6131d98290 100644 --- a/src/profilemodel.h +++ b/src/profilemodel.h @@ -36,6 +36,7 @@ template<typename T> class CollectionMediator; class LIB_EXPORT ProfileModel : public QAbstractItemModel { Q_OBJECT friend class ProfileContentBackend; + friend class ProfileEditor; public: explicit ProfileModel(QObject* parent = nullptr); virtual ~ProfileModel(); @@ -72,7 +73,8 @@ private: static ProfileModel* m_spInstance; public Q_SLOTS: -// bool addNewProfile(Person* c, CollectionInterface* backend = nullptr); -// bool createProfile(const QString& name); + bool remove(const QModelIndex& idx); + QModelIndex add(Person* person = nullptr); + QModelIndex add(const QString& name); };