From 43f3c1e4d861f95e9f156147323f9c1c3a095dae Mon Sep 17 00:00:00 2001 From: Edric Milaret Date: Thu, 16 Jul 2015 17:52:47 -0400 Subject: [PATCH] contact: implement add to contact feature - Add right click on history item - Copy number to clipboard - Add to new Contact - Add to existing contact Issue: #77711 Issue: #77862 Issue: #77859 Change-Id: Ia249707e51c5208abbd67eeb3b04e6ca835bcd75 --- RingWinClient.pro | 17 +- callwidget.cpp | 112 +++++++++++--- contactdelegate.cpp | 8 +- contactdialog.cpp | 45 ++++++ contactdialog.h | 46 ++++++ contactdialog.ui | 94 +++++++++++ contactpicker.cpp | 60 ++++++++ contactpicker.h | 50 ++++++ contactpicker.ui | 31 ++++ utils.cpp | 30 ++++ utils.h | 2 + windowscontactbackend.cpp | 317 +++++++++++++++++++++++++++++--------- windowscontactbackend.h | 5 + 13 files changed, 711 insertions(+), 106 deletions(-) create mode 100644 contactdialog.cpp create mode 100644 contactdialog.h create mode 100644 contactdialog.ui create mode 100644 contactpicker.cpp create mode 100644 contactpicker.h create mode 100644 contactpicker.ui diff --git a/RingWinClient.pro b/RingWinClient.pro index e5f5c63..42ef5bc 100644 --- a/RingWinClient.pro +++ b/RingWinClient.pro @@ -6,7 +6,7 @@ QT += core gui -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets svg +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets svg xml VERSION = 0.3.0 GIT_VERSION = $$system(git --git-dir $$PWD/.git --work-tree $$PWD describe --always --tags) @@ -47,7 +47,9 @@ SOURCES += main.cpp\ accountstatedelegate.cpp \ videoview.cpp \ videooverlay.cpp \ - imdelegate.cpp + imdelegate.cpp \ + contactdialog.cpp \ + contactpicker.cpp HEADERS += mainwindow.h \ callwidget.h \ @@ -70,7 +72,9 @@ HEADERS += mainwindow.h \ accountstatedelegate.h \ videoview.h \ videooverlay.h \ - imdelegate.h + imdelegate.h \ + contactdialog.h \ + contactpicker.h FORMS += mainwindow.ui \ callwidget.ui \ @@ -83,7 +87,9 @@ FORMS += mainwindow.ui \ wizarddialog.ui \ instantmessagingwidget.ui \ videoview.ui \ - videooverlay.ui + videooverlay.ui \ + contactdialog.ui \ + contactpicker.ui win32: LIBS += -lole32 -luuid -lshlwapi @@ -118,7 +124,8 @@ win32 { RUNTIME.path = $$OUT_PWD/release QTRUNTIME.files = $$RUNTIMEDIR/Qt5Core.dll $$RUNTIMEDIR/Qt5Widgets.dll \ - $$RUNTIMEDIR/Qt5Gui.dll $$RUNTIMEDIR/Qt5Svg.dll + $$RUNTIMEDIR/Qt5Gui.dll $$RUNTIMEDIR/Qt5Svg.dll \ + $$RUNTIMEDIR/Qt5Xml.dll QTRUNTIME.path = $$OUT_PWD/release QTDEPSRUNTIME.files = $$RUNTIMEDIR/zlib1.dll $$RUNTIMEDIR/iconv.dll \ diff --git a/callwidget.cpp b/callwidget.cpp index fa5eceb..670e632 100644 --- a/callwidget.cpp +++ b/callwidget.cpp @@ -19,10 +19,17 @@ #include "callwidget.h" #include "ui_callwidget.h" +#include + #include +//ERROR is defined in windows.h +#include "utils.h" +#undef ERROR + #include "audio/settings.h" #include "personmodel.h" +#include "person.h" #include "fallbackpersoncollection.h" #include "categorizedcontactmodel.h" #include "localhistorycollection.h" @@ -32,6 +39,8 @@ #include "wizarddialog.h" #include "windowscontactbackend.h" +#include "contactdialog.h" +#include "contactpicker.h" CallWidget::CallWidget(QWidget *parent) : NavWidget(Main ,parent), @@ -78,14 +87,17 @@ CallWidget::CallWidget(QWidget *parent) : ui->callList->setModel(callModel_); ui->callList->setSelectionModel(callModel_->selectionModel()); - CategorizedHistoryModel::instance()-> - addCollection(LoadOptions::FORCE_ENABLED); + auto personCollection = PersonModel::instance()-> + addCollection(LoadOptions::FORCE_ENABLED); - PersonModel::instance()-> - addCollection(LoadOptions::FORCE_ENABLED); + CategorizedContactModel::instance()->setSortAlphabetical(false); + CategorizedContactModel::instance()->setUnreachableHidden(true); + ui->contactView->setModel(CategorizedContactModel::instance()); + contactDelegate_ = new ContactDelegate(); + ui->contactView->setItemDelegate(contactDelegate_); - PersonModel::instance()-> - addCollection(LoadOptions::FORCE_ENABLED); + CategorizedHistoryModel::instance()-> + addCollection(LoadOptions::FORCE_ENABLED); ui->historyList->setModel(CategorizedHistoryModel::SortedProxy::instance()->model()); CategorizedHistoryModel::SortedProxy::instance()->model()->sort(0, Qt::DescendingOrder); @@ -99,14 +111,63 @@ CallWidget::CallWidget(QWidget *parent) : ui->historyList->setExpanded(idx, true); }); + ui->historyList->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->historyList, &QListView::customContextMenuRequested, [=](const QPoint& pos){ + if (ui->historyList->currentIndex().parent().isValid()) { + QPoint globalPos = ui->historyList->mapToGlobal(pos); + QMenu menu; + + ContactMethod* contactMethod = ui->historyList->currentIndex() + .data(static_cast(Call::Role::ContactMethod)).value(); + + auto copyAction = new QAction("Copy number", this); + menu.addAction(copyAction); + connect(copyAction, &QAction::triggered, [=]() { + QApplication::clipboard()->setText(contactMethod->uri()); + }); + + if (not contactMethod->contact()) { + auto addNew = new QAction("Add to new contact", this); + menu.addAction(addNew); + connect(addNew, &QAction::triggered, [=]() { + ContactDialog dialog(contactMethod->uri()); + auto ret = dialog.exec(); + if (!ret || dialog.getName().isEmpty()) + return; + auto *newPerson = new Person(); + newPerson->setFormattedName(dialog.getName()); + Person::ContactMethods cM; + cM.append(contactMethod); + newPerson->setContactMethods(cM); + newPerson->setUid(Utils::GenGUID().toLocal8Bit()); + PersonModel::instance()->addNewPerson(newPerson, personCollection); + }); + auto addExisting = new QAction("Add to existing contact", this); + menu.addAction(addExisting); + connect(addExisting, &QAction::triggered, [=]() { + /* Force LRC to update contact model as adding a number + to a contact without one didn't render him reachable */ + CategorizedContactModel::instance()->setUnreachableHidden(false); + + ContactPicker contactPicker; + contactPicker.move(globalPos.x(), globalPos.y() - (contactPicker.height()/2)); + auto ret = contactPicker.exec(); + if (!ret) + return; + auto p = contactPicker.getPersonSelected(); + Person::ContactMethods cM (p->phoneNumbers()); + cM.append(contactMethod); + p->setContactMethods(cM); + p->save(); + CategorizedContactModel::instance()->setUnreachableHidden(true); + }); + } + menu.exec(globalPos); + } + }); ui->sortComboBox->setModel(CategorizedHistoryModel::SortedProxy::instance()->categoryModel()); - CategorizedContactModel::instance()->setSortAlphabetical(false); - ui->contactView->setModel(CategorizedContactModel::instance()); - contactDelegate_ = new ContactDelegate(); - ui->contactView->setItemDelegate(contactDelegate_); - findRingAccount(); } catch (...) { @@ -250,7 +311,8 @@ CallWidget::on_refuseButton_clicked() } void -CallWidget::addedCall(Call* call, Call* parent) { +CallWidget::addedCall(Call* call, Call* parent) +{ Q_UNUSED(parent); if (call->direction() == Call::Direction::OUTGOING) { displaySpinner(true); @@ -300,28 +362,32 @@ CallWidget::on_callList_activated(const QModelIndex &index) } void -CallWidget::atExit() { +CallWidget::atExit() +{ } void CallWidget::on_contactView_doubleClicked(const QModelIndex &index) { - QString uri; + if (not index.isValid()) + return; + + ContactMethod* uri; auto var = index.child(0,0).data( static_cast(Person::Role::Object)); if (var.isValid()) { Person* person = var.value(); if (person->phoneNumbers().size() > 0) { - uri = person->phoneNumbers().at(0)->uri(); + uri = person->phoneNumbers().at(0); // FIXME: A person can have multiple contact method + if (uri) { + auto outCall = CallModel::instance()->dialingCall(person->formattedName()); + outCall->setDialNumber(uri); + outCall->performAction(Call::Action::ACCEPT); + } } } - if (not uri.isEmpty()) { - auto outCall = CallModel::instance()->dialingCall(uri); - outCall->setDialNumber(uri); - outCall->performAction(Call::Action::ACCEPT); - } } void @@ -330,9 +396,9 @@ CallWidget::on_historyList_doubleClicked(const QModelIndex &index) if (not index.isValid()) return; - QString number = index.model()->data(index, static_cast(Call::Role::Number)).toString(); - if (not number.isEmpty()) { - auto outCall = CallModel::instance()->dialingCall(number); + auto number = index.data(static_cast(Call::Role::ContactMethod)).value(); + if (number) { + auto outCall = CallModel::instance()->dialingCall(); outCall->setDialNumber(number); outCall->performAction(Call::Action::ACCEPT); } diff --git a/contactdelegate.cpp b/contactdelegate.cpp index e95b4fd..79c806f 100644 --- a/contactdelegate.cpp +++ b/contactdelegate.cpp @@ -35,7 +35,6 @@ ContactDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, initStyleOption(&opt, index); if (index.column() == 0) { - QString name = index.model()->data(index, Qt::DisplayRole).toString(); opt.text = ""; QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget); @@ -46,14 +45,13 @@ ContactDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, cg = QPalette::Inactive; painter->setPen(opt.palette.color(cg, QPalette::Text)); painter->setOpacity(1.0); - painter->drawText(QRect(rect.left()+sizeImage_+5, rect.top(), - rect.width(), rect.height()/2), - opt.displayAlignment, name); - QVariant var_c = index.child(0,0).data( static_cast(Person::Role::Object)); if (var_c.isValid()) { Person *c = var_c.value(); + painter->drawText(QRect(rect.left()+sizeImage_+5, rect.top(), + rect.width(), rect.height()/2), + opt.displayAlignment, c->formattedName()); QVariant var_p = c->photo(); painter->drawRect(QRect(rect.left(), rect.top(), sizeImage_+1, sizeImage_+1)); diff --git a/contactdialog.cpp b/contactdialog.cpp new file mode 100644 index 0000000..fd96c24 --- /dev/null +++ b/contactdialog.cpp @@ -0,0 +1,45 @@ +/*************************************************************************** + * Copyright (C) 2015 by Savoir-faire Linux * + * Author: Edric Ladent Milaret * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + **************************************************************************/ + +#include "contactdialog.h" +#include "ui_contactdialog.h" + +ContactDialog::ContactDialog(const QString &number, QWidget *parent) : + QDialog(parent), + ui(new Ui::ContactDialog) +{ + ui->setupUi(this); + ui->numberLineEdit->setText(number); +} + +ContactDialog::~ContactDialog() +{ + delete ui; +} + +const QString& +ContactDialog::getName() +{ + return contactName_; +} + +void +ContactDialog::on_nameLineEdit_textChanged(const QString &arg1) +{ + contactName_ = arg1; +} diff --git a/contactdialog.h b/contactdialog.h new file mode 100644 index 0000000..b97cdb2 --- /dev/null +++ b/contactdialog.h @@ -0,0 +1,46 @@ +/*************************************************************************** + * Copyright (C) 2015 by Savoir-faire Linux * + * Author: Edric Ladent Milaret * + * * + * 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 . * + **************************************************************************/ + +#ifndef CONTACTDIALOG_H +#define CONTACTDIALOG_H + +#include + +namespace Ui { +class ContactDialog; +} + +class ContactDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ContactDialog(const QString& number, QWidget *parent = 0); + ~ContactDialog(); + + const QString &getName(); + +private slots: + void on_nameLineEdit_textChanged(const QString &arg1); + +private: + Ui::ContactDialog *ui; + QString contactName_; +}; + +#endif // CONTACTDIALOG_H diff --git a/contactdialog.ui b/contactdialog.ui new file mode 100644 index 0000000..4d4e80f --- /dev/null +++ b/contactdialog.ui @@ -0,0 +1,94 @@ + + + ContactDialog + + + + 0 + 0 + 398 + 154 + + + + Dialog + + + + + + + 75 + true + + + + New Contact + + + + + + + Enter a name... + + + + + + + false + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + ContactDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ContactDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/contactpicker.cpp b/contactpicker.cpp new file mode 100644 index 0000000..417d30c --- /dev/null +++ b/contactpicker.cpp @@ -0,0 +1,60 @@ +/*************************************************************************** + * Copyright (C) 2015 by Savoir-faire Linux * + * Author: Edric Ladent Milaret * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + **************************************************************************/ + +#include "contactpicker.h" +#include "ui_contactpicker.h" + +#include "categorizedcontactmodel.h" + +ContactPicker::ContactPicker(QWidget *parent) : + QDialog(parent), + ui(new Ui::ContactPicker), + personSelected_(nullptr) +{ + ui->setupUi(this); + + this->setWindowFlags(Qt::CustomizeWindowHint); + this->setWindowFlags(Qt::FramelessWindowHint); + + auto personModel = PersonModel::instance(); + ui->contactView->setModel(personModel); +} + +ContactPicker::~ContactPicker() +{ + delete ui; +} + +void +ContactPicker::on_contactView_doubleClicked(const QModelIndex &index) +{ + personSelected_ = index.data(static_cast(Person::Role::Object)).value(); + this->accept(); +} + +Person* +ContactPicker::getPersonSelected() +{ + return personSelected_; +} + +void +ContactPicker::on_cancelButton_clicked() +{ + this->reject(); +} diff --git a/contactpicker.h b/contactpicker.h new file mode 100644 index 0000000..168d843 --- /dev/null +++ b/contactpicker.h @@ -0,0 +1,50 @@ +/*************************************************************************** + * Copyright (C) 2015 by Savoir-faire Linux * + * Author: Edric Ladent Milaret * + * * + * 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 . * + **************************************************************************/ + +#ifndef CONTACTPICKER_H +#define CONTACTPICKER_H + +#include + +#include "personmodel.h" + +namespace Ui { +class ContactPicker; +} + +class ContactPicker : public QDialog +{ + Q_OBJECT + +public: + explicit ContactPicker(QWidget *parent = 0); + ~ContactPicker(); + + Person *getPersonSelected(); + +//UI SLOTS +private slots: + void on_contactView_doubleClicked(const QModelIndex &index); + void on_cancelButton_clicked(); + +private: + Ui::ContactPicker *ui; + Person *personSelected_; +}; + +#endif // CONTACTPICKER_H diff --git a/contactpicker.ui b/contactpicker.ui new file mode 100644 index 0000000..b1cb6e3 --- /dev/null +++ b/contactpicker.ui @@ -0,0 +1,31 @@ + + + ContactPicker + + + + 0 + 0 + 249 + 438 + + + + Dialog + + + + + + + + + Cancel + + + + + + + + diff --git a/utils.cpp b/utils.cpp index 7e8a25b..18515cb 100644 --- a/utils.cpp +++ b/utils.cpp @@ -117,3 +117,33 @@ Utils::GetRingtonePath() { #endif } +QString +Utils::GenGUID() { +#ifdef Q_OS_WIN32 + GUID gidReference; + wchar_t *str; + HRESULT hCreateGuid = CoCreateGuid(&gidReference); + if (hCreateGuid == S_OK) { + StringFromCLSID(gidReference, &str); + auto gStr = QString::fromWCharArray(str); + return gStr.remove("{").remove("}").toLower(); + } + else + return QString(""); +#else + return QString(""); +#endif +} + +QString +Utils::GetISODate() { +#ifdef Q_OS_WIN32 + SYSTEMTIME lt; + GetSystemTime(<); + return QString("%1-%2-%3T%4:%5:%6Z").arg(lt.wYear).arg(lt.wMonth,2,10,QChar('0')).arg(lt.wDay,2,10,QChar('0')) + .arg(lt.wHour,2,10,QChar('0')).arg(lt.wMinute,2,10,QChar('0')).arg(lt.wSecond,2,10,QChar('0')); +#else + return QString(""); +#endif +} + diff --git a/utils.h b/utils.h index 46cf54f..519a46e 100644 --- a/utils.h +++ b/utils.h @@ -39,6 +39,8 @@ public: static bool CreateLink(LPCWSTR lpszPathObj, LPCWSTR lpszPathLink); static bool CheckStartupLink(); static QString GetRingtonePath(); + static QString GenGUID(); + static QString GetISODate(); }; diff --git a/windowscontactbackend.cpp b/windowscontactbackend.cpp index f8adb26..f246e94 100644 --- a/windowscontactbackend.cpp +++ b/windowscontactbackend.cpp @@ -18,6 +18,13 @@ #include "windowscontactbackend.h" +#include + +#include "personmodel.h" +#include "categorizedcontactmodel.h" + +#include "utils.h" + WindowsContactEditor::WindowsContactEditor(CollectionMediator *m , WindowsContactBackend *parent) : CollectionEditor(m),collection_(parent) @@ -33,15 +40,65 @@ WindowsContactEditor::~WindowsContactEditor() bool WindowsContactEditor::save(const Person *item) { - Q_UNUSED(item) - return false; + QFile file(QStandardPaths::writableLocation + (QStandardPaths::HomeLocation) + "/Contacts/" + + item->formattedName() + ".contact"); + if (!file.open(QIODevice::ReadWrite)) { + file.close(); + qDebug() << "Cannot open contact file"; + return false; + } + + QDomDocument doc; + doc.setContent(&file); + + auto root = doc.elementsByTagName("c:contact").at(0); + auto nodes = doc.elementsByTagName("c:PhoneNumberCollection"); + + //if PhoneNumberCollection already exists + QVector nodeNumberVector; + if (nodes.length()) { + auto phoneNumberCollection = nodes.at(0); + auto phoneNumbers = doc.elementsByTagName("c:Number"); + auto virtualPhoneNumber = item->phoneNumbers(); + for (int i = 0; i < phoneNumbers.length(); i++) { + auto node = phoneNumbers.at(i).toElement(); + nodeNumberVector.append(node.text()); + } + for (auto elem : virtualPhoneNumber) { + if (not nodeNumberVector.contains(elem->uri())) { + auto phoneNumber = doc.createElement("c:PhoneNumber"); + phoneNumberCollection.appendChild(phoneNumber); + phoneNumber.setAttribute("c:ElementID", Utils::GenGUID()); + auto numberNode = doc.createElement("c:Number"); + phoneNumber.appendChild(numberNode); + auto numberValue = doc.createTextNode(elem->uri()); + numberNode.appendChild(numberValue); + } + } + } else { + auto phoneNumberCollection = doc.createElement("c:PhoneNumberCollection"); + root.appendChild(phoneNumberCollection); + auto phoneNumber = doc.createElement("c:PhoneNumber"); + phoneNumberCollection.appendChild(phoneNumber); + phoneNumber.setAttribute("c:ElementID", Utils::GenGUID()); + auto numberNode = doc.createElement("c:Number"); + phoneNumber.appendChild(numberNode); + auto numberValue = doc.createTextNode(item->phoneNumbers().at(0)->uri()); + numberNode.appendChild(numberValue); + } + file.resize(0); + file.write(doc.toByteArray()); + file.close(); + return true; } bool WindowsContactEditor::remove(const Person *item) { - Q_UNUSED(item) - return false; + items_.removeOne(const_cast(item)); + mediator()->removeItem(item); + return true; } bool @@ -54,8 +111,82 @@ WindowsContactEditor::edit(Person *item) bool WindowsContactEditor::addNew(const Person *item) { - Q_UNUSED(item) - return false; + QDomDocument doc; + QFile file(QStandardPaths::writableLocation + (QStandardPaths::HomeLocation) + "/Contacts/" + + item->formattedName()+".contact"); + if (file.exists()) + return false; + if (!file.open(QIODevice::ReadWrite)) { + file.close(); + qDebug() << "Cannot create contact file"; + return false; + } + doc.appendChild( + doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"utf-8\"")); + + //Create root + auto root = doc.createElement("c:contact"); + root.setAttribute("c:Version", "1"); + root.setAttribute("xmlns:c", "http://schemas.microsoft.com/Contact"); + root.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); + root.setAttribute("xmlns:MSP2P","http://schemas.microsoft.com/Contact/Extended/MSP2P"); + doc.appendChild(root); + + auto date = Utils::GetISODate(); + + //Create creation date + auto creationDateNode = doc.createElement("c:CreationDate"); + auto creationDateValue = doc.createTextNode(date); + creationDateNode.appendChild(creationDateValue); + root.appendChild(creationDateNode); + + //Create extended node + auto extendedNode = doc.createElement("c:Extended"); + extendedNode.setAttribute("xsi:nil", "true"); + root.appendChild(extendedNode); + + //Create contactID collection + auto contactIDCol = doc.createElement("c:ContactIDCollection"); + root.appendChild(contactIDCol); + auto contactID = doc.createElement("c:ContactID"); + contactID.setAttribute("c:ElementID", Utils::GenGUID()); + auto contactValue = doc.createElement("c:Value"); + auto value = doc.createTextNode(item->uid()); + contactValue.appendChild(value); + contactID.appendChild(contactValue); + contactIDCol.appendChild(contactID); + + //Create NameCollection + auto nameCollection = doc.createElement("c:NameCollection"); + root.appendChild(nameCollection); + auto name = doc.createElement("c:Name"); + nameCollection.appendChild(name); + name.setAttribute("c:ElementID", Utils::GenGUID()); + auto formattedName = doc.createElement("c:FormattedName"); + name.appendChild(formattedName); + auto formattedNameValue = doc.createTextNode(item->formattedName()); + formattedName.appendChild(formattedNameValue); + + //Create PhoneNumberCollection + auto phoneNumberCollection = doc.createElement("c:PhoneNumberCollection"); + root.appendChild(phoneNumberCollection); + auto phoneNumber = doc.createElement("c:PhoneNumber"); + phoneNumberCollection.appendChild(phoneNumber); + phoneNumber.setAttribute("c:ElementID", Utils::GenGUID()); + auto numberNode = doc.createElement("c:Number"); + phoneNumber.appendChild(numberNode); + auto numberValue = doc.createTextNode(item->phoneNumbers().at(0)->uri()); + numberNode.appendChild(numberValue); + + //Write to file + file.write(doc.toByteArray()); + file.close(); + + //Add it to the collection + addExisting(item); + + return true; } bool @@ -75,7 +206,7 @@ WindowsContactEditor::items() const WindowsContactBackend::WindowsContactBackend(CollectionMediator* mediator, CollectionInterface* parent) : CollectionInterface(new WindowsContactEditor(mediator,this), parent) - , mediator_(mediator) + , mediator_(mediator), watcher_(new QFileSystemWatcher()) { } @@ -89,6 +220,12 @@ bool WindowsContactBackend::load() { QtConcurrent::run(this, &WindowsContactBackend::loadRun); + watcher_->addPath(QStandardPaths::writableLocation + (QStandardPaths::HomeLocation) + "/Contacts"); + QObject::connect(watcher_, &QFileSystemWatcher::directoryChanged, [=](QString path) { + Q_UNUSED(path) + QtConcurrent::run(this, &WindowsContactBackend::loadRun); + }); return true; } @@ -98,84 +235,115 @@ WindowsContactBackend::loadRun() QDir contactDir(QStandardPaths::writableLocation (QStandardPaths::HomeLocation) + "/Contacts"); QStringList filesList = contactDir.entryList(QStringList("*.contact")); - + auto ret = true; for(auto contactFileName : filesList) { - QString contactFilePath - (contactDir.absolutePath() + "/" + contactFileName); - QFile contactFile(contactFilePath); - if (contactFile.open(QIODevice::ReadOnly)) { - QXmlStreamReader reader; - Person *p = new Person(); - QVector contactMethod; - reader.setDevice(&contactFile); - while (!reader.atEnd()) { - reader.readNext(); - if (reader.isStartElement()) { - QString name = reader.name().toString(); - if (name == "FormattedName") - p->setFormattedName(reader.readElementText()); - else if (name == "NickName") - p->setNickName(reader.readElementText()); - else if (name == "GivenName") - p->setFirstName(reader.readElementText()); - else if (name == "FamilyName") - p->setFamilyName(reader.readElementText()); - else if (name == "Company") - p->setOrganization(reader.readElementText()); - else if (name == "Department") - p->setDepartment(reader.readElementText()); - else if (name == "Number") { - QString number = reader.readElementText(); - if (not number.isEmpty()) { - ContactMethod *contact = PhoneDirectoryModel::instance()->getNumber(number); - contactMethod.append(contact); - } + if (not getPersonFromContactFile(contactDir, contactFileName)) + ret = false; + } + return ret; +} + +bool +WindowsContactBackend::getPersonFromContactFile(const QDir& contactDir, + const QString &contactFileName) +{ + QString contactFilePath + (contactDir.absolutePath() + "/" + contactFileName); + QFile contactFile(contactFilePath); + if (contactFile.open(QIODevice::ReadOnly)) { + QXmlStreamReader reader; + Person *p = new Person(this); + QVector contactMethod; + reader.setDevice(&contactFile); + while (!reader.atEnd()) { + reader.readNext(); + if (reader.isStartElement()) { + QString name = reader.name().toString(); + if (name == "FormattedName") + p->setFormattedName(reader.readElementText()); + else if (name == "NickName") + p->setNickName(reader.readElementText()); + else if (name == "GivenName") + p->setFirstName(reader.readElementText()); + else if (name == "FamilyName") + p->setFamilyName(reader.readElementText()); + else if (name == "Company") + p->setOrganization(reader.readElementText()); + else if (name == "Department") + p->setDepartment(reader.readElementText()); + else if (name == "Number") { + QString number = reader.readElementText(); + if (not number.isEmpty()) { + ContactMethod *contact = + PhoneDirectoryModel::instance()->getNumber(number,p); + contactMethod.append(contact); } - else if (name == "Photo") { - //FIXME: It seems to be possible to have multiple photo... + } else if (name == "ContactID") { + while (reader.name().toString() != "Value") reader.readNext(); - if (reader.name().toString() == "Url") { - QString photoValue = reader.readElementText(); - QImage photo; - photo.load(photoValue); - p->setPhoto(photo.scaled(sizePhoto_,sizePhoto_, Qt::KeepAspectRatio, - Qt::SmoothTransformation)); - } + p->setUid(reader.readElementText().toUtf8()); + } + else if (name == "Photo") { + //FIXME: It seems to be possible to have multiple photo... + reader.readNext(); + if (reader.name().toString() == "Url") { + QString photoValue = reader.readElementText(); + QImage photo; + photo.load(photoValue); + p->setPhoto(photo.scaled(sizePhoto_,sizePhoto_, Qt::KeepAspectRatio, + Qt::SmoothTransformation)); } - else if (name == "EmailAddress") { - QString address; - bool isPreferred = false; - reader.readNext(); - while (reader.name().toString() != "EmailAddress" - && !reader.atEnd()) { - if (reader.isStartElement()) { - QString tag = reader.name().toString(); - if (tag == "Address") - address = reader.readElementText(); - else if (tag == "Label") - if (reader.readElementText() == "Preferred") - isPreferred = true; - } - reader.readNext(); + } + else if (name == "EmailAddress") { + QString address; + bool isPreferred = false; + reader.readNext(); + while (reader.name().toString() != "EmailAddress" + && !reader.atEnd()) { + if (reader.isStartElement()) { + QString tag = reader.name().toString(); + if (tag == "Address") + address = reader.readElementText(); + else if (tag == "Label") + if (reader.readElementText() == "Preferred") + isPreferred = true; } - if (isPreferred) - p->setPreferredEmail(address); + reader.readNext(); } + if (isPreferred) + p->setPreferredEmail(address); } } - if (reader.hasError()) { - qDebug() << reader.errorString(); - } else { + } + if (reader.hasError()) { + qDebug() << reader.errorString(); + contactFile.close(); + return false; + } else { + Person* existing = PersonModel::instance()->getPersonByUid(p->uid()); + if (existing) { if (contactMethod.size() > 0) - p->setContactMethods(contactMethod); + existing->setContactMethods ( contactMethod ); + existing->setNickName ( p->nickName() ); + existing->setFirstName ( p->firstName() ); + existing->setFamilyName ( p->secondName() ); + existing->setFormattedName ( p->formattedName() ); + existing->setOrganization ( p->organization() ); + existing->setPreferredEmail ( p->preferredEmail() ); + existing->setGroup ( p->group() ); + existing->setDepartment ( p->department() ); + existing->setPhoto ( p->photo() ); + delete p; + } else { + p->setContactMethods(contactMethod); editor()->addExisting(p); } - } else { - qDebug() << "Error Opening contact file : " << contactFileName; + return true; } + } else { + qDebug() << "Error Opening contact file : " << contactFileName; + return false; } - - return false; } bool @@ -218,6 +386,9 @@ FlagPack WindowsContactBackend::supporte { return ( CollectionInterface::SupportedFeatures::NONE | - CollectionInterface::SupportedFeatures::LOAD); + CollectionInterface::SupportedFeatures::LOAD | + CollectionInterface::SupportedFeatures::SAVE | + CollectionInterface::SupportedFeatures::REMOVE | + CollectionInterface::SupportedFeatures::ADD); } diff --git a/windowscontactbackend.h b/windowscontactbackend.h index c804a06..160b2ac 100644 --- a/windowscontactbackend.h +++ b/windowscontactbackend.h @@ -25,6 +25,7 @@ #include #include #include +#include #include "person.h" #include "collectioninterface.h" @@ -51,6 +52,10 @@ private: private: CollectionMediator* mediator_; constexpr static int sizePhoto_ = 50; + QFileSystemWatcher* watcher_; + +private: + bool getPersonFromContactFile(const QDir &contactDir, const QString& contactFileName); }; class WindowsContactEditor : public CollectionEditor -- GitLab