diff --git a/core/adapterFuctions.cpp b/core/adapterFuctions.cpp index 0693c34..0279533 100644 --- a/core/adapterFuctions.cpp +++ b/core/adapterFuctions.cpp @@ -218,6 +218,7 @@ void Core::initializeQXmppVCard(QXmppVCardIq& iq, const Shared::VCard& card) { itr = phones.emplace(mPh.number, QXmppVCardPhone()).first; } QXmppVCardPhone& phone = itr->second; + phone.setNumber(mPh.number); switch (mPh.type) { case Shared::VCard::Phone::fax: diff --git a/global.cpp b/global.cpp index e223a45..d054150 100644 --- a/global.cpp +++ b/global.cpp @@ -569,6 +569,7 @@ const std::deque & Shared::VCard::getPhones() const } const std::dequeShared::VCard::Contact::roleNames = {"Not specified", "Personal", "Business"}; +const std::dequeShared::VCard::Phone::typeNames = {"Fax", "Pager", "Voice", "Cell", "Video", "Modem", "Other"}; QIcon Shared::availabilityIcon(Shared::Availability av, bool big) { diff --git a/global.h b/global.h index 72c8406..ccb7317 100644 --- a/global.h +++ b/global.h @@ -248,6 +248,7 @@ public: modem, other }; + static const std::deque typeNames; Phone(const QString& number, Type p_type = voice, Role p_role = none, bool p_prefered = false); QString number; diff --git a/ui/widgets/vcard/CMakeLists.txt b/ui/widgets/vcard/CMakeLists.txt index 4af3d68..4d2ee15 100644 --- a/ui/widgets/vcard/CMakeLists.txt +++ b/ui/widgets/vcard/CMakeLists.txt @@ -12,6 +12,7 @@ find_package(Qt5Widgets CONFIG REQUIRED) set(vCardUI_SRC vcard.cpp emailsmodel.cpp + phonesmodel.cpp ) # Tell CMake to create the helloworld executable diff --git a/ui/widgets/vcard/emailsmodel.cpp b/ui/widgets/vcard/emailsmodel.cpp index 7e3a646..18838ee 100644 --- a/ui/widgets/vcard/emailsmodel.cpp +++ b/ui/widgets/vcard/emailsmodel.cpp @@ -198,3 +198,8 @@ void UI::VCard::EMailsModel::revertPreferred(int row) { setData(createIndex(row, 2), !isPreferred(row)); } + +QString UI::VCard::EMailsModel::getEmail(int row) const +{ + return deque[row].address; +} diff --git a/ui/widgets/vcard/emailsmodel.h b/ui/widgets/vcard/emailsmodel.h index 10a610f..358536f 100644 --- a/ui/widgets/vcard/emailsmodel.h +++ b/ui/widgets/vcard/emailsmodel.h @@ -45,6 +45,7 @@ public: void removeLines(int index, int count); void setEmails(const std::deque& emails); void getEmails(std::deque& emails) const; + QString getEmail(int row) const; public slots: QModelIndex addNewEmptyLine(); diff --git a/ui/widgets/vcard/phonesmodel.cpp b/ui/widgets/vcard/phonesmodel.cpp new file mode 100644 index 0000000..4371dff --- /dev/null +++ b/ui/widgets/vcard/phonesmodel.cpp @@ -0,0 +1,222 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * 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 "phonesmodel.h" + +UI::VCard::PhonesModel::PhonesModel(bool p_edit, QObject* parent): + QAbstractTableModel(parent), + edit(p_edit), + deque() +{ +} + +int UI::VCard::PhonesModel::columnCount(const QModelIndex& parent) const +{ + return 4; +} + +int UI::VCard::PhonesModel::rowCount(const QModelIndex& parent) const +{ + return deque.size(); +} + +QVariant UI::VCard::PhonesModel::data(const QModelIndex& index, int role) const +{ + if (index.isValid()) { + int col = index.column(); + switch (col) { + case 0: + switch (role) { + case Qt::DisplayRole: + case Qt::EditRole: + return deque[index.row()].number; + default: + return QVariant(); + } + break; + case 1: + switch (role) { + case Qt::DisplayRole: + return tr(Shared::VCard::Phone::roleNames[deque[index.row()].role].toStdString().c_str()); + case Qt::EditRole: + return deque[index.row()].role; + default: + return QVariant(); + } + break; + case 2: + switch (role) { + case Qt::DisplayRole: + return tr(Shared::VCard::Phone::typeNames[deque[index.row()].type].toStdString().c_str()); + case Qt::EditRole: + return deque[index.row()].type; + default: + return QVariant(); + } + break; + case 3: + switch (role) { + case Qt::DisplayRole: + return QVariant(); + case Qt::DecorationRole: + if (deque[index.row()].prefered) { + return Shared::icon("favorite", false); + } + return QVariant(); + default: + return QVariant(); + } + break; + default: + return QVariant(); + } + } + return QVariant(); +} + +QModelIndex UI::VCard::PhonesModel::addNewEmptyLine() +{ + beginInsertRows(QModelIndex(), deque.size(), deque.size()); + deque.emplace_back("", Shared::VCard::Phone::other); + endInsertRows(); + return createIndex(deque.size() - 1, 0, &(deque.back())); +} + +Qt::ItemFlags UI::VCard::PhonesModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags f = QAbstractTableModel::flags(index); + if (edit && index.column() != 3) { + f = Qt::ItemIsEditable | f; + } + return f; +} + +bool UI::VCard::PhonesModel::dropPrefered() +{ + bool dropped = false; + int i = 0; + for (Shared::VCard::Phone& phone : deque) { + if (phone.prefered) { + phone.prefered = false; + QModelIndex ci = createIndex(i, 2, &phone); + emit dataChanged(ci, ci); + dropped = true; + } + ++i; + } + return dropped; +} + +void UI::VCard::PhonesModel::getPhones(std::deque& phones) const +{ + for (const Shared::VCard::Phone& my : deque) { + phones.emplace_back(my); + } +} + +bool UI::VCard::PhonesModel::isPreferred(int row) const +{ + if (row < deque.size()) { + return deque[row].prefered; + } else { + return false; + } +} + +void UI::VCard::PhonesModel::removeLines(int index, int count) +{ + if (index < deque.size()) { + int maxCount = deque.size() - index; + if (count > maxCount) { + count = maxCount; + } + + if (count > 0) { + beginRemoveRows(QModelIndex(), index, index + count - 1); + std::deque::const_iterator itr = deque.begin() + index; + std::deque::const_iterator end = itr + count; + deque.erase(itr, end); + endRemoveRows(); + } + } +} + +void UI::VCard::PhonesModel::revertPreferred(int row) +{ + setData(createIndex(row, 3), !isPreferred(row)); +} + +bool UI::VCard::PhonesModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (role == Qt::EditRole && checkIndex(index)) { + Shared::VCard::Phone& item = deque[index.row()]; + switch (index.column()) { + case 0: + item.number = value.toString(); + return true; + case 1: { + quint8 newRole = value.toUInt(); + if (newRole > Shared::VCard::Phone::work) { + return false; + } + item.role = static_cast(newRole); + return true; + } + case 2: { + quint8 newType = value.toUInt(); + if (newType > Shared::VCard::Phone::other) { + return false; + } + item.type = static_cast(newType); + return true; + } + case 3: { + bool newDef = value.toBool(); + if (newDef != item.prefered) { + if (newDef) { + //dropPrefered(); + } + item.prefered = newDef; + return true; + } + } + } + return true; + } + return false; +} + +void UI::VCard::PhonesModel::setPhones(const std::deque& phones) +{ + if (deque.size() > 0) { + removeLines(0, deque.size()); + } + + if (phones.size() > 0) { + beginInsertRows(QModelIndex(), 0, phones.size() - 1); + for (const Shared::VCard::Phone& comming : phones) { + deque.emplace_back(comming); + } + endInsertRows(); + } +} + +QString UI::VCard::PhonesModel::getPhone(int row) const +{ + return deque[row].number; +} diff --git a/ui/widgets/vcard/phonesmodel.h b/ui/widgets/vcard/phonesmodel.h new file mode 100644 index 0000000..bf847d2 --- /dev/null +++ b/ui/widgets/vcard/phonesmodel.h @@ -0,0 +1,65 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * 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 UI_VCARD_PHONESMODEL_H +#define UI_VCARD_PHONESMODEL_H + +#include +#include + +#include "global.h" + +namespace UI { +namespace VCard { + +/** + * @todo write docs + */ +class PhonesModel : public QAbstractTableModel +{ + Q_OBJECT +public: + PhonesModel(bool edit = false, QObject *parent = nullptr); + + QVariant data(const QModelIndex& index, int role) const override; + int columnCount(const QModelIndex& parent) const override; + int rowCount(const QModelIndex& parent) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + bool isPreferred(int row) const; + + void removeLines(int index, int count); + void setPhones(const std::deque& phones); + void getPhones(std::deque& phones) const; + QString getPhone(int row) const; + +public slots: + QModelIndex addNewEmptyLine(); + void revertPreferred(int row); + +private: + bool edit; + std::deque deque; + +private: + bool dropPrefered(); +}; + +}} + +#endif // UI_VCARD_PHONESMODEL_H diff --git a/ui/widgets/vcard/vcard.cpp b/ui/widgets/vcard/vcard.cpp index 850830d..d66d6cd 100644 --- a/ui/widgets/vcard/vcard.cpp +++ b/ui/widgets/vcard/vcard.cpp @@ -38,7 +38,9 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent): overlay(new QWidget()), contextMenu(new QMenu()), emails(edit), - roleDelegate(new ComboboxDelegate()) + phones(edit), + roleDelegate(new ComboboxDelegate()), + phoneTypeDelegate(new ComboboxDelegate()) { m_ui->setupUi(this); m_ui->jabberID->setText(jid); @@ -57,13 +59,30 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent): roleDelegate->addEntry(tr(Shared::VCard::Email::roleNames[1].toStdString().c_str())); roleDelegate->addEntry(tr(Shared::VCard::Email::roleNames[2].toStdString().c_str())); + phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[0].toStdString().c_str())); + phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[1].toStdString().c_str())); + phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[2].toStdString().c_str())); + phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[3].toStdString().c_str())); + phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[4].toStdString().c_str())); + phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[5].toStdString().c_str())); + phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[6].toStdString().c_str())); + m_ui->emailsView->setContextMenuPolicy(Qt::CustomContextMenu); m_ui->emailsView->setModel(&emails); m_ui->emailsView->setItemDelegateForColumn(1, roleDelegate); - m_ui->emailsView->setColumnWidth(2, 30); + m_ui->emailsView->setColumnWidth(2, 25); m_ui->emailsView->horizontalHeader()->setStretchLastSection(false); m_ui->emailsView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + m_ui->phonesView->setContextMenuPolicy(Qt::CustomContextMenu); + m_ui->phonesView->setModel(&phones); + m_ui->phonesView->setItemDelegateForColumn(1, roleDelegate); + m_ui->phonesView->setItemDelegateForColumn(2, phoneTypeDelegate); + m_ui->phonesView->setColumnWidth(3, 25); + m_ui->phonesView->horizontalHeader()->setStretchLastSection(false); + m_ui->phonesView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + + connect(m_ui->phonesView, &QWidget::customContextMenuRequested, this, &VCard::onContextMenu); connect(m_ui->emailsView, &QWidget::customContextMenuRequested, this, &VCard::onContextMenu); if (edit) { @@ -121,6 +140,7 @@ VCard::~VCard() avatarMenu->deleteLater(); } + phoneTypeDelegate->deleteLater(); roleDelegate->deleteLater(); contextMenu->deleteLater(); } @@ -154,7 +174,9 @@ void VCard::setVCard(const Shared::VCard& card) updateAvatar(); const std::deque& ems = card.getEmails(); + const std::deque& phs = card.getPhones(); emails.setEmails(ems); + phones.setPhones(phs); } QString VCard::getJid() const @@ -181,6 +203,7 @@ void VCard::onButtonBoxAccepted() card.setAvatarType(currentAvatarType); emails.getEmails(card.getEmails()); + phones.getPhones(card.getPhones()); emit saveVCard(card); } @@ -282,8 +305,8 @@ void VCard::onContextMenu(const QPoint& point) bool hasMenu = false; QAbstractItemView* snd = static_cast(sender()); if (snd == m_ui->emailsView) { + hasMenu = true; if (editable) { - hasMenu = true; QAction* add = contextMenu->addAction(Shared::icon("list-add"), tr("Add email address")); connect(add, &QAction::triggered, this, &VCard::onAddEmail); @@ -306,6 +329,37 @@ void VCard::onContextMenu(const QPoint& point) connect(del, &QAction::triggered, this, &VCard::onRemoveEmail); } } + + QAction* cp = contextMenu->addAction(Shared::icon("copy"), tr("Copy selected emails to clipboard")); + connect(cp, &QAction::triggered, this, &VCard::onCopyEmail); + } else if (snd == m_ui->phonesView) { + hasMenu = true; + if (editable) { + QAction* add = contextMenu->addAction(Shared::icon("list-add"), tr("Add phone number")); + connect(add, &QAction::triggered, this, &VCard::onAddPhone); + + QItemSelectionModel* sm = m_ui->phonesView->selectionModel(); + int selectionSize = sm->selectedRows().size(); + + if (selectionSize > 0) { + if (selectionSize == 1) { + int row = sm->selectedRows().at(0).row(); + if (phones.isPreferred(row)) { + QAction* rev = contextMenu->addAction(Shared::icon("view-media-favorite"), tr("Unset this phone as preferred")); + connect(rev, &QAction::triggered, std::bind(&UI::VCard::PhonesModel::revertPreferred, &phones, row)); + } else { + QAction* rev = contextMenu->addAction(Shared::icon("favorite"), tr("Set this phone as preferred")); + connect(rev, &QAction::triggered, std::bind(&UI::VCard::PhonesModel::revertPreferred, &phones, row)); + } + } + + QAction* del = contextMenu->addAction(Shared::icon("remove"), tr("Remove selected phone numbers")); + connect(del, &QAction::triggered, this, &VCard::onRemovePhone); + } + } + + QAction* cp = contextMenu->addAction(Shared::icon("copy"), tr("Copy selected phones to clipboard")); + connect(cp, &QAction::triggered, this, &VCard::onCopyPhone); } if (hasMenu) { @@ -326,6 +380,9 @@ void VCard::onAddAddress() } void VCard::onAddPhone() { + QModelIndex index = phones.addNewEmptyLine(); + m_ui->phonesView->setCurrentIndex(index); + m_ui->phonesView->edit(index); } void VCard::onRemoveAddress() { @@ -353,4 +410,53 @@ void VCard::onRemoveEmail() void VCard::onRemovePhone() { + QItemSelection selection(m_ui->phonesView->selectionModel()->selection()); + + QList rows; + for (const QModelIndex& index : selection.indexes()) { + rows.append(index.row()); + } + + std::sort(rows.begin(), rows.end()); + + int prev = -1; + for (int i = rows.count() - 1; i >= 0; i -= 1) { + int current = rows[i]; + if (current != prev) { + phones.removeLines(current, 1); + prev = current; + } + } +} + +void VCard::onCopyEmail() +{ + QItemSelection selection(m_ui->emailsView->selectionModel()->selection()); + + QList addrs; + for (const QModelIndex& index : selection.indexes()) { + addrs.push_back(emails.getEmail(index.row())); + } + + QString list = addrs.join("\n"); + + qDebug() << list; + QClipboard* cb = QApplication::clipboard(); + cb->setText(list); +} + +void VCard::onCopyPhone() +{ + QItemSelection selection(m_ui->phonesView->selectionModel()->selection()); + + QList phs; + for (const QModelIndex& index : selection.indexes()) { + phs.push_back(phones.getPhone(index.row())); + } + + QString list = phs.join("\n"); + + qDebug() << list; + QClipboard* cb = QApplication::clipboard(); + cb->setText(list); } diff --git a/ui/widgets/vcard/vcard.h b/ui/widgets/vcard/vcard.h index e150ae9..8346fc3 100644 --- a/ui/widgets/vcard/vcard.h +++ b/ui/widgets/vcard/vcard.h @@ -31,11 +31,14 @@ #include #include #include +#include +#include #include #include "global.h" #include "emailsmodel.h" +#include "phonesmodel.h" #include "ui/utils/progress.h" #include "ui/utils/comboboxdelegate.h" @@ -71,8 +74,10 @@ private slots: void onAddAddress(); void onRemoveAddress(); void onAddEmail(); + void onCopyEmail(); void onRemoveEmail(); void onAddPhone(); + void onCopyPhone(); void onRemovePhone(); void onContextMenu(const QPoint& point); @@ -88,7 +93,9 @@ private: QWidget* overlay; QMenu* contextMenu; UI::VCard::EMailsModel emails; + UI::VCard::PhonesModel phones; ComboboxDelegate* roleDelegate; + ComboboxDelegate* phoneTypeDelegate; static const std::set supportedTypes;