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"