File indexing completed on 2023-11-26 08:16:46

0001 /*
0002  * Copyright (C) 2013 Dan Vrátil <dvratil@redhat.com>
0003  *
0004  * This library is free software; you can redistribute it and/or
0005  * modify it under the terms of the GNU Lesser General Public
0006  * License as published by the Free Software Foundation; either
0007  * version 2.1 of the License, or (at your option) any later version.
0008  *
0009  * This library is distributed in the hope that it will be useful,
0010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012  * Lesser General Public License for more details.
0013  *
0014  * You should have received a copy of the GNU Lesser General Public
0015  * License along with this library; if not, write to the Free Software
0016  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
0017  */
0018 
0019 #include "contact-info-dialog.h"
0020 #include "contact.h"
0021 
0022 #include <QGridLayout>
0023 #include <QPicture>
0024 #include <QLabel>
0025 #include <QVBoxLayout>
0026 #include <QFormLayout>
0027 
0028 #include <TelepathyQt/Contact>
0029 #include <TelepathyQt/PendingContactInfo>
0030 #include <TelepathyQt/AvatarData>
0031 #include <TelepathyQt/Presence>
0032 #include <TelepathyQt/SharedPtr>
0033 #include <TelepathyQt/ContactManager>
0034 #include <TelepathyQt/Connection>
0035 #include <TelepathyQt/Account>
0036 #include <TelepathyQt/PendingContacts>
0037 #include <TelepathyQt/PendingReady>
0038 
0039 #include <QDebug>
0040 #include <QPushButton>
0041 #include <QLineEdit>
0042 #include <QFileDialog>
0043 #include <QMimeType>
0044 #include <QMimeDatabase>
0045 #include <QDialogButtonBox>
0046 
0047 #include <KMessageBox>
0048 #include <KTitleWidget>
0049 #include <KLocalizedString>
0050 #include <KDateComboBox>
0051 #include <KImageFilePreview>
0052 #include <KIconLoader>
0053 
0054 namespace KTp {
0055 
0056 enum InfoRowIndex {
0057     FullName = 0,
0058     Nickname,
0059     Email,
0060     Phone,
0061     Homepage,
0062     Birthday,
0063     Organization,
0064     _InfoRowCount
0065 };
0066 
0067 static struct InfoRow {
0068     const InfoRowIndex index;
0069     const QString fieldName;
0070     const char* title;
0071 } InfoRows[] = {                                        // Don't use i18n in global static vars
0072     { FullName,         QLatin1String("fn"),            I18N_NOOP("Full name:") },
0073     { Nickname,         QLatin1String("nickname"),      I18N_NOOP("Nickname:") },
0074     { Email,            QLatin1String("email"),         I18N_NOOP("Email:") },
0075     { Phone,            QLatin1String("tel"),           I18N_NOOP("Phone:") },
0076     { Homepage,         QLatin1String("url"),           I18N_NOOP("Homepage:") },
0077     { Birthday,         QLatin1String("bday"),          I18N_NOOP("Birthday:") },
0078     { Organization,     QLatin1String("org"),           I18N_NOOP("Organization:") }
0079 };
0080 
0081 class ContactInfoDialog::Private
0082 {
0083   public:
0084     Private(ContactInfoDialog *parent):
0085         editable(false),
0086         infoDataChanged(false),
0087         avatarChanged(false),
0088         columnsLayout(nullptr),
0089         infoLayout(nullptr),
0090         stateLayout(nullptr),
0091         changeAvatarButton(nullptr),
0092         clearAvatarButton(nullptr),
0093         avatarLabel(nullptr),
0094         q(parent)
0095     {}
0096 
0097     void onContactUpgraded(Tp::PendingOperation *op);
0098     void onContactInfoReceived(Tp::PendingOperation *op);
0099     void onChangeAvatarButtonClicked();
0100     void onClearAvatarButtonClicked();
0101     void onInfoDataChanged();
0102     void onFeatureRosterReady(Tp::PendingOperation *op);
0103 
0104     void addInfoRow(InfoRowIndex index, const QString &value);
0105     void addStateRow(const QString &description, Tp::Contact::PresenceState state);
0106     void loadStateRows();
0107 
0108     Tp::AccountPtr account;
0109     KTp::ContactPtr contact;
0110     bool editable;
0111 
0112     bool infoDataChanged;
0113     bool avatarChanged;
0114     QString newAvatarFile;
0115 
0116     QMap<InfoRowIndex,QWidget*> infoValueWidgets;
0117 
0118     QHBoxLayout *columnsLayout;
0119     QFormLayout *infoLayout;
0120     QFormLayout *stateLayout;
0121     QPushButton *changeAvatarButton;
0122     QPushButton *clearAvatarButton;
0123     QLabel *avatarLabel;
0124     QDialogButtonBox *buttonBox;
0125 
0126   private:
0127     ContactInfoDialog *q;
0128 };
0129 
0130 void ContactInfoDialog::Private::onContactUpgraded(Tp::PendingOperation* op)
0131 {
0132     Tp::PendingContacts *contacts = qobject_cast<Tp::PendingContacts*>(op);
0133     if (op->isError()) {
0134         return;
0135     }
0136 
0137     Q_ASSERT(contacts->contacts().count() == 1);
0138 
0139     contact = KTp::ContactPtr::qObjectCast(contacts->contacts().first());
0140 
0141     /* Show avatar immediatelly */
0142     if (contacts->features().contains(Tp::Contact::FeatureAvatarData)) {
0143         QVBoxLayout *avatarLayout = new QVBoxLayout();
0144         avatarLayout->setSpacing(5);
0145         avatarLayout->setAlignment(Qt::AlignHCenter);
0146         columnsLayout->addLayout(avatarLayout);
0147 
0148         avatarLabel = new QLabel(q);
0149         avatarLabel->setMaximumSize(150, 150);
0150         avatarLayout->addWidget(avatarLabel, 0, Qt::AlignTop);
0151 
0152         if (editable) {
0153             changeAvatarButton = new QPushButton(i18n("Change Avatar"), q);
0154             connect(changeAvatarButton, SIGNAL(clicked(bool)),
0155                     q, SLOT(onChangeAvatarButtonClicked()));
0156             avatarLayout->addWidget(changeAvatarButton);
0157 
0158             clearAvatarButton = new QPushButton(i18n("Clear Avatar"), q);
0159             connect(clearAvatarButton, SIGNAL(clicked(bool)),
0160                     q, SLOT(onClearAvatarButtonClicked()));
0161             avatarLayout->addWidget(clearAvatarButton);
0162 
0163             avatarLayout->addStretch(1);
0164         }
0165 
0166         QPixmap avatar(contact->avatarPixmap());
0167         avatarLabel->setPixmap(avatar.scaled(avatarLabel->maximumSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
0168     }
0169 
0170     /* Request detailed contact info */
0171     if (contacts->features().contains(Tp::Contact::FeatureInfo)) {
0172         infoLayout = new QFormLayout();
0173         infoLayout->setSpacing(10);
0174         columnsLayout->addLayout(infoLayout);
0175 
0176         Tp::PendingContactInfo *op = contact->requestInfo();
0177         connect(op, SIGNAL(finished(Tp::PendingOperation*)),
0178                 q, SLOT(onContactInfoReceived(Tp::PendingOperation*)));
0179     }
0180 }
0181 
0182 void ContactInfoDialog::Private::onFeatureRosterReady(Tp::PendingOperation *op)
0183 {
0184     loadStateRows();
0185 }
0186 
0187 void ContactInfoDialog::Private::onContactInfoReceived(Tp::PendingOperation* op)
0188 {
0189     Tp::PendingContactInfo *ci = qobject_cast<Tp::PendingContactInfo*>(op);
0190     const Tp::ContactInfoFieldList fieldList = ci->infoFields().allFields();
0191 
0192     for (InfoRowIndex index = (InfoRowIndex) 0; index < _InfoRowCount; index = (InfoRowIndex)(index + 1)) {
0193         QString value;
0194 
0195         Q_FOREACH(const Tp::ContactInfoField &field, fieldList) {
0196             if (field.fieldValue.count() == 0) {
0197                 continue;
0198             }
0199 
0200             if (field.fieldName == InfoRows[index].fieldName) {
0201                 value = field.fieldValue.first();
0202                 break;
0203             }
0204         }
0205 
0206         /* Show edits for all values when in editable mode */
0207         if (!editable && value.isEmpty()) {
0208             continue;
0209         }
0210 
0211         addInfoRow(index, value);
0212     }
0213 }
0214 
0215 void ContactInfoDialog::Private::onChangeAvatarButtonClicked()
0216 {
0217     QPointer<QFileDialog> fileDialog = new QFileDialog(q);
0218 //     fileDialog->setPreviewWidget(new KImageFilePreview(fileDialog)); //TODO KF5 - is there a replacement?
0219     fileDialog->setMimeTypeFilters(QStringList() << QStringLiteral("image/*"));
0220     fileDialog->setFileMode(QFileDialog::ExistingFile);
0221 
0222     int c = fileDialog->exec();
0223     if (fileDialog && c && !fileDialog->selectedFiles().isEmpty()) {
0224         newAvatarFile = fileDialog->selectedFiles().first();
0225 
0226         QPixmap avatar(newAvatarFile);
0227         if (avatar.isNull()) {
0228             KMessageBox::error(q, i18n("Failed to load the new avatar image"));
0229             newAvatarFile.clear();
0230             delete fileDialog;
0231             return;
0232         }
0233         avatarLabel->setPixmap(avatar.scaled(avatarLabel->maximumSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
0234         avatarChanged = true;
0235         clearAvatarButton->setEnabled(true);
0236     }
0237 
0238     delete fileDialog;
0239 }
0240 
0241 void ContactInfoDialog::Private::onClearAvatarButtonClicked()
0242 {
0243     QPixmap avatar;
0244     avatar = KIconLoader::global()->loadIcon(QLatin1String("im-user"), KIconLoader::Desktop, 128);
0245 
0246     newAvatarFile.clear();
0247     avatarChanged = true;
0248 }
0249 
0250 void ContactInfoDialog::Private::onInfoDataChanged()
0251 {
0252     infoDataChanged = true;
0253 }
0254 
0255 void ContactInfoDialog::Private::addInfoRow(InfoRowIndex index, const QString &value)
0256 {
0257     InfoRow *row = &InfoRows[index];
0258 
0259     // I18N_NOOP only marks the string for translation, the actual lookup of
0260     // translated row->title happens here
0261     QLabel *descriptionLabel = new QLabel(i18n(row->title), q);
0262     QFont font = descriptionLabel->font();
0263     font.setBold(true);
0264     descriptionLabel->setFont(font);
0265 
0266     if (editable) {
0267         if (index == Birthday) {
0268             KDateComboBox *combo = new KDateComboBox(q);
0269             combo->setOptions(KDateComboBox::EditDate | KDateComboBox::SelectDate | KDateComboBox::DatePicker);
0270             combo->setMinimumWidth(200);
0271             combo->setDate(QDate::fromString(value));
0272             connect(combo, SIGNAL(dateChanged(QDate)), q, SLOT(onInfoDataChanged()));
0273 
0274             infoValueWidgets.insert(index, combo);
0275         } else {
0276             QLineEdit *edit = new QLineEdit(q);
0277             edit->setMinimumWidth(200);
0278             edit->setText(value);
0279             connect(edit, SIGNAL(textChanged(QString)), q, SLOT(onInfoDataChanged()));
0280 
0281             infoValueWidgets.insert(index, edit);
0282         }
0283     } else {
0284         QLabel *label = new QLabel(q);
0285         label->setOpenExternalLinks(true);
0286         label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
0287         if (index == Email) {
0288             label->setText(QString::fromLatin1("<a href=\"mailto:%1\">%1</a>").arg(value));
0289         } else if (index == Homepage) {
0290             QString format;
0291             if (!value.startsWith(QLatin1String("http"), Qt::CaseInsensitive)) {
0292                 format = QLatin1String("<a href=\"http://%1\">%1</a>");
0293             } else {
0294                 format = QLatin1String("<a href=\"%1\">%1</a>");
0295             }
0296             label->setText(format.arg(value));
0297         } else {
0298             label->setText(value);
0299         }
0300 
0301         infoValueWidgets.insert(index, label);
0302     }
0303 
0304     infoLayout->addRow(descriptionLabel, infoValueWidgets.value(index));
0305 }
0306 
0307 void ContactInfoDialog::Private::addStateRow(const QString& description, Tp::Contact::PresenceState state)
0308 {
0309     QLabel *descriptionLabel = new QLabel(description, q);
0310 
0311     QIcon icon;
0312     switch (state) {
0313         case Tp::Contact::PresenceStateYes:
0314             icon = QIcon::fromTheme(QStringLiteral("task-complete"));
0315             break;
0316         case Tp::Contact::PresenceStateNo:
0317             icon = QIcon::fromTheme(QStringLiteral("task-reject"));
0318             break;
0319         case Tp::Contact::PresenceStateAsk:
0320         default:
0321             icon = QIcon::fromTheme(QStringLiteral("task-attempt"));
0322             break;
0323     }
0324 
0325     QLabel *stateLabel = new QLabel(q);
0326     stateLabel->setPixmap(icon.pixmap(16));
0327 
0328     stateLayout->addRow(descriptionLabel, stateLabel);
0329 }
0330 
0331 void ContactInfoDialog::Private::loadStateRows()
0332 {
0333     if(stateLayout) {
0334         addStateRow(i18n("Contact can see when you are online:"), contact->publishState());
0335         addStateRow(i18n("You can see when the contact is online:"), contact->subscriptionState());
0336         addStateRow(i18n("Contact is blocked:"), contact->isBlocked() ? Tp::Contact::PresenceStateYes : Tp::Contact::PresenceStateNo);
0337     }
0338 }
0339 
0340 ContactInfoDialog::ContactInfoDialog(const Tp::AccountPtr &account, const Tp::ContactPtr &contact, QWidget *parent)
0341     : QDialog(parent)
0342     , d(new Private(this))
0343 {
0344 #if 0   // Editing contacts is not yet supported in TpQt
0345     /* Whether contact is the user himself */
0346     d->editable = (contact == account->connection()->selfContact());
0347 #endif
0348     d->editable = false;
0349     d->account = account;
0350     d->contact = KTp::ContactPtr::qObjectCast(contact);
0351 
0352     d->buttonBox = new QDialogButtonBox(this);
0353 
0354 
0355     if (d->editable) {
0356         d->buttonBox->setStandardButtons(QDialogButtonBox::Save | QDialogButtonBox::Close);
0357     } else {
0358         d->buttonBox->setStandardButtons(QDialogButtonBox::Close);
0359     }
0360 
0361     connect(d->buttonBox, &QDialogButtonBox::clicked, this, &ContactInfoDialog::slotButtonClicked);
0362 
0363     setMaximumSize(sizeHint());
0364 
0365     QVBoxLayout *layout = new QVBoxLayout(this);
0366     layout->setSpacing(30);
0367 
0368     /* Title - presence icon, alias, id */
0369     KTitleWidget *titleWidget = new KTitleWidget(this);
0370     KTp::Presence presence(contact->presence());
0371     titleWidget->setPixmap(presence.icon().pixmap(32, 32), KTitleWidget::ImageLeft);
0372     titleWidget->setText(contact->alias());
0373     titleWidget->setComment(contact->id());
0374     layout->addWidget(titleWidget);
0375 
0376     /* 1st column: avatar; 2nd column: details */
0377     d->columnsLayout = new QHBoxLayout();
0378     d->columnsLayout->setSpacing(30);
0379     layout->addLayout(d->columnsLayout);
0380 
0381     /* Make sure the contact has all neccessary features ready */
0382     Tp::PendingContacts *op = contact->manager()->upgradeContacts(
0383             QList<Tp::ContactPtr>() << contact,
0384             Tp::Features() << Tp::Contact::FeatureAvatarData
0385                            << Tp::Contact::FeatureInfo);
0386     connect(op, SIGNAL(finished(Tp::PendingOperation*)), SLOT(onContactUpgraded(Tp::PendingOperation*)));
0387 
0388     /* State Info - there is no point showing this information when it's about ourselves */
0389     if (!d->editable) {
0390         d->stateLayout = new QFormLayout();
0391         d->stateLayout->setSpacing(10);
0392         layout->addLayout(d->stateLayout);
0393 
0394         // Fetch roster feature, if it is supported, but not loaded
0395         Tp::ConnectionPtr conn = contact->manager()->connection();
0396         if(!conn->actualFeatures().contains(Tp::Connection::FeatureRoster) && !conn->missingFeatures().contains(Tp::Connection::FeatureRoster)) {
0397             Tp::PendingReady *pr = conn->becomeReady(Tp::Features() << Tp::Connection::FeatureRoster);
0398 
0399             connect(pr, SIGNAL(finished(Tp::PendingOperation*)),
0400                     SLOT(onFeatureRosterReady(Tp::PendingOperation*)));
0401         } else {
0402             d->loadStateRows();
0403         }
0404     }
0405 
0406     layout->addWidget(d->buttonBox);
0407 }
0408 
0409 ContactInfoDialog::~ContactInfoDialog()
0410 {
0411     delete d;
0412 }
0413 
0414 void ContactInfoDialog::slotButtonClicked(QAbstractButton *button)
0415 {
0416     if (button == d->buttonBox->button(QDialogButtonBox::Save)) {
0417         if (d->avatarChanged) {
0418             Tp::Avatar avatar;
0419             if (!d->newAvatarFile.isEmpty()) {
0420                 QFile file(d->newAvatarFile);
0421                 file.open(QIODevice::ReadOnly);
0422 
0423                 QFileInfo fi(file);
0424 
0425                 avatar.avatarData = file.readAll();
0426                 file.seek(0); // reset before passing to KMimeType
0427 
0428                 QMimeDatabase db;
0429                 avatar.MIMEType = db.mimeTypeForFileNameAndData(d->newAvatarFile, &file).name();
0430             }
0431 
0432             d->account->setAvatar(avatar);
0433         }
0434 
0435         if (d->infoDataChanged) {
0436             Tp::ContactInfoFieldList fieldList;
0437 
0438             for (InfoRowIndex index = (InfoRowIndex) 0; index < _InfoRowCount; index = (InfoRowIndex) (index + 1)) {
0439                 InfoRow *row = &InfoRows[index];
0440 
0441                 Tp::ContactInfoField field;
0442                 field.fieldName = row->fieldName;
0443 
0444                 if (index == Birthday) {
0445                     KDateComboBox *combo = qobject_cast<KDateComboBox*>(d->infoValueWidgets.value(index));
0446                     field.fieldValue << combo->date().toString();
0447                 } else {
0448                     QLineEdit *lineEdit = qobject_cast<QLineEdit*>(d->infoValueWidgets.value(index));
0449                     field.fieldValue << lineEdit->text();
0450                 }
0451 
0452                 fieldList << field;
0453             }
0454 
0455 #if 0   // This method does not exist in TpQt (yet)
0456             d->account->connection()->setContactInfo(fieldList);
0457 #endif
0458         }
0459 
0460         accept();
0461         return;
0462     }
0463 }
0464 
0465 
0466 } /* namespace KTp */
0467 
0468 #include "moc_contact-info-dialog.cpp"