File indexing completed on 2023-12-10 08:24:28
0001 /* 0002 Copyright (C) 2013 David Edmundson <davidedmundson@kde.org> 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 Street, Fifth Floor, Boston, MA 02110-1301 USA 0017 */ 0018 0019 0020 #include "kpeople-actions-plugin.h" 0021 0022 #include <QAction> 0023 0024 #include <QIcon> 0025 #include <KLocalizedString> 0026 #include <KPluginFactory> 0027 #include <QFileDialog> 0028 0029 #include "KTp/contact.h" 0030 #include "KTp/actions.h" 0031 #include "KTp/core.h" 0032 #include "KTp/global-contact-manager.h" 0033 0034 #include <TelepathyQt/Account> 0035 #include <TelepathyQt/ContactManager> 0036 #include <TelepathyQt/Constants> 0037 0038 #include <KPeople/PersonData> 0039 #include <kpeople/widgets/actions.h> // pretty headers are currently broken for this 0040 0041 enum IMActionType { 0042 TextChannel, 0043 AudioChannel, 0044 VideoChannel, 0045 FileTransfer, 0046 LogViewer, 0047 CollabEditing 0048 }; 0049 0050 class IMAction : public QAction { 0051 Q_OBJECT 0052 public: 0053 IMAction(const QString &text, const QIcon &icon, const KTp::ContactPtr &contact, 0054 const Tp::AccountPtr &account, IMActionType type, QObject *parent); 0055 IMAction(const QString &text, const QIcon &icon, const QUrl &uri, 0056 IMActionType type, QObject *parent); 0057 KTp::ContactPtr contact() const; 0058 Tp::AccountPtr account() const; 0059 IMActionType type() const; 0060 QUrl uri() const; 0061 private: 0062 void setActionType(); 0063 KTp::ContactPtr m_contact; 0064 Tp::AccountPtr m_account; 0065 QUrl m_uri; 0066 IMActionType m_type; 0067 }; 0068 0069 IMAction::IMAction(const QString &text, const QIcon &icon, const KTp::ContactPtr &contact, 0070 const Tp::AccountPtr &account, IMActionType type, QObject *parent): 0071 QAction(icon, text, parent), 0072 m_contact(contact), 0073 m_account(account), 0074 m_type(type) 0075 { 0076 setActionType(); 0077 } 0078 0079 IMAction::IMAction(const QString &text, const QIcon &icon, const QUrl &uri, 0080 IMActionType type, QObject *parent): 0081 QAction(icon, text, parent), 0082 m_uri(uri), 0083 m_type(type) 0084 { 0085 setActionType(); 0086 } 0087 0088 void IMAction::setActionType() 0089 { 0090 switch (m_type) { 0091 case TextChannel: 0092 setProperty("actionType", KPeople::TextChatAction); 0093 break; 0094 case AudioChannel: 0095 setProperty("actionType", KPeople::AudioCallAction); 0096 break; 0097 case VideoChannel: 0098 setProperty("actionType", KPeople::VideoCallAction); 0099 break; 0100 case FileTransfer: 0101 setProperty("actionType", KPeople::SendFileAction); 0102 break; 0103 default: 0104 setProperty("actionType", KPeople::OtherAction); 0105 break; 0106 } 0107 } 0108 0109 KTp::ContactPtr IMAction::contact() const 0110 { 0111 return m_contact; 0112 } 0113 0114 Tp::AccountPtr IMAction::account() const 0115 { 0116 return m_account; 0117 } 0118 0119 IMActionType IMAction::type() const 0120 { 0121 return m_type; 0122 } 0123 0124 QUrl IMAction::uri() const 0125 { 0126 return m_uri; 0127 } 0128 0129 KPeopleActionsPlugin::KPeopleActionsPlugin(QObject *parent, const QVariantList &args) 0130 : AbstractPersonAction(parent) 0131 { 0132 Q_UNUSED(args); 0133 } 0134 0135 QList<QAction*> KPeopleActionsPlugin::actionsForPerson(const KPeople::PersonData &person, 0136 QObject *parent) const 0137 { 0138 QList<QAction*> actions; 0139 0140 // Get the most online account path and contact id 0141 QString personAccountPath = person.contactCustomProperty(QLatin1String("telepathy-accountPath")).toString(); 0142 0143 // The property for "telepathy-accountPath" is in form "/org/freedesktop/Telepathy/Account/gabble/jabber/myjabberaccount" 0144 // which is needed for retrieving the account from Tp::AccountManager. However we can only retrieve the other 0145 // contact uri list, not any objects which would give us the "telepathy-accountPath" for the subcontact. 0146 // So the code below constructs the account path from the contact uri which is known to be for ktp contacts 0147 // in form of "ktp:// + short account path + ? + contact id". But in order to not have the same actions 0148 // twice in the menu (one for most online and one from the contactUris()), this turns these properties into regular 0149 // uri format and puts it into the first position in the list, making sure that they will appear at the beggining. 0150 // It's also easier to simply remove it here and not having to check each uri separately in the foreach below. 0151 // And it cannot take the person.personUri() because for metacontacts that is in form of "kpeople://num_id". 0152 personAccountPath = personAccountPath.right(personAccountPath.length() - TP_QT_ACCOUNT_OBJECT_PATH_BASE.size() - 1); 0153 QString personContactId = person.contactCustomProperty(QLatin1String("telepathy-contactId")).toString(); 0154 QString mostOnlineUri = QStringLiteral("ktp://") + personAccountPath + QLatin1Char('?') + personContactId; 0155 0156 QStringList uris{mostOnlineUri}; 0157 QStringList contactUris = person.contactUris(); 0158 0159 // Only append the child contacts if there is more than 1, otherwise 0160 // it means this contact has only itself as a subcontact. 0161 if (contactUris.size() > 1) { 0162 uris.append(contactUris); 0163 // Make sure we don't have duplicate uris in the list 0164 uris.removeDuplicates(); 0165 } 0166 0167 Q_FOREACH (const QString &uri, uris) { 0168 if (!uri.startsWith(QStringLiteral("ktp://"))) { 0169 continue; 0170 } 0171 0172 int delimiterIndex = uri.indexOf(QLatin1Char('?')); 0173 QString contactId = uri.right(uri.length() - delimiterIndex - 1); 0174 QString accountPath = uri.mid(6, delimiterIndex - 6); 0175 // Prepend the "/org/freedesktop/Telepathy" part so that Tp::AccountManager 0176 // returns valid account 0177 accountPath.prepend(TP_QT_ACCOUNT_OBJECT_PATH_BASE + QLatin1Char('/')); 0178 0179 0180 const Tp::AccountPtr account = KTp::contactManager()->accountForAccountPath(accountPath); 0181 if (!account) { 0182 continue; 0183 } 0184 0185 const KTp::ContactPtr contact = KTp::contactManager()->contactForContactId(accountPath, contactId); 0186 if (!contact || !contact->manager() || account->currentPresence().type() == Tp::ConnectionPresenceTypeOffline) { 0187 // if there is no valid contact or if the contact is online, 0188 // add a special action that will first connect the account 0189 // and then start a chat right away 0190 QAction *action = new IMAction(i18nc("Context menu action; means 'Bring your account online and then start a chat using %1 account'", 0191 "Connect and Start Chat Using %1...", account->displayName()), 0192 QIcon::fromTheme(QStringLiteral("text-x-generic")), 0193 QUrl(contactId), 0194 TextChannel, 0195 parent); 0196 action->setProperty("accountPath", accountPath); 0197 connect(action, SIGNAL(triggered(bool)), SLOT(onConnectAndActionTriggered())); 0198 actions << action; 0199 continue; 0200 } 0201 0202 if (contact->textChatCapability()) { 0203 QAction *action = new IMAction(i18n("Start Chat Using %1...", account->displayName()), 0204 QIcon::fromTheme(QStringLiteral("text-x-generic")), 0205 contact, 0206 account, 0207 TextChannel, 0208 parent); 0209 connect (action, SIGNAL(triggered(bool)), SLOT(onActionTriggered())); 0210 actions << action; 0211 } 0212 if (contact->audioCallCapability()) { 0213 QAction *action = new IMAction(i18n("Start Audio Call Using %1...", account->displayName()), 0214 QIcon::fromTheme(QStringLiteral("audio-headset")), 0215 contact, 0216 account, 0217 AudioChannel, 0218 parent); 0219 connect (action, SIGNAL(triggered(bool)), SLOT(onActionTriggered())); 0220 actions << action; 0221 } 0222 if (contact->videoCallCapability()) { 0223 QAction *action = new IMAction(i18n("Start Video Call Using %1...", account->displayName()), 0224 QIcon::fromTheme(QStringLiteral("camera-web")), 0225 contact, 0226 account, 0227 VideoChannel, 0228 parent); 0229 connect (action, SIGNAL(triggered(bool)), SLOT(onActionTriggered())); 0230 actions << action; 0231 } 0232 0233 if (contact->fileTransferCapability()) { 0234 QAction *action = new IMAction(i18n("Send Files Using %1...", account->displayName()), 0235 QIcon::fromTheme(QStringLiteral("mail-attachment")), 0236 contact, 0237 account, 0238 FileTransfer, 0239 parent); 0240 connect (action, SIGNAL(triggered(bool)), SLOT(onActionTriggered())); 0241 actions << action; 0242 } 0243 if (contact->collaborativeEditingCapability()) { 0244 QAction *action = new IMAction(i18n("Collaboratively edit a document Using %1...", account->displayName()), 0245 QIcon::fromTheme(QStringLiteral("document-edit")), 0246 contact, 0247 account, 0248 CollabEditing, 0249 parent); 0250 connect (action, SIGNAL(triggered(bool)), SLOT(onActionTriggered())); 0251 actions << action; 0252 } 0253 } 0254 0255 QAction *action = new IMAction(i18n("Previous Conversations..."), 0256 QIcon::fromTheme(QStringLiteral("documentation")), 0257 QUrl(person.personUri()), 0258 LogViewer, 0259 parent); 0260 connect(action, SIGNAL(triggered(bool)), SLOT(onActionTriggered())); 0261 actions << action; 0262 0263 return actions; 0264 } 0265 0266 void KPeopleActionsPlugin::onActionTriggered() 0267 { 0268 IMAction *action = qobject_cast<IMAction*>(sender()); 0269 KTp::ContactPtr contact = action->contact(); 0270 Tp::AccountPtr account = action->account(); 0271 IMActionType type = action->type(); 0272 0273 switch (type) { 0274 case TextChannel: 0275 KTp::Actions::startChat(account, contact); 0276 break; 0277 case AudioChannel: 0278 KTp::Actions::startAudioCall(account, contact); 0279 break; 0280 case VideoChannel: 0281 KTp::Actions::startAudioVideoCall(account, contact); 0282 break; 0283 case FileTransfer: { 0284 const QStringList fileNames = QFileDialog::getOpenFileNames(Q_NULLPTR, i18n("Choose files to send to %1", contact->alias()), 0285 QStringLiteral("kfiledialog:///FileTransferLastDirectory")); 0286 Q_FOREACH(const QString& file, fileNames) { 0287 KTp::Actions::startFileTransfer(account, contact, file); 0288 } 0289 break; 0290 } 0291 case LogViewer: 0292 KTp::Actions::openLogViewer(action->uri()); 0293 break; 0294 case CollabEditing: { 0295 const QUrl file = QUrl::fromLocalFile(QFileDialog::getOpenFileName(Q_NULLPTR, i18n("Choose a file to edit with %1", contact->alias()), 0296 QStringLiteral("kfiledialog:///CollabEditingLastDirectory"))); 0297 KTp::Actions::startCollaborativeEditing(account, contact, QList<QUrl>() << file, true); 0298 break; 0299 } 0300 } 0301 } 0302 0303 void KPeopleActionsPlugin::onConnectAndActionTriggered() 0304 { 0305 IMAction *action = qobject_cast<IMAction*>(sender()); 0306 0307 const Tp::AccountPtr account = KTp::contactManager()->accountForAccountPath(action->property("accountPath").toString()); 0308 account->setProperty("contactId", action->uri()); 0309 connect(account.data(), &Tp::Account::connectionStatusChanged, this, &KPeopleActionsPlugin::onAccountConnectionStatusChanged); 0310 account->setRequestedPresence(Tp::Presence::available()); 0311 } 0312 0313 void KPeopleActionsPlugin::onAccountConnectionStatusChanged(Tp::ConnectionStatus status) 0314 { 0315 Tp::AccountPtr account = Tp::AccountPtr(qobject_cast<Tp::Account*>(sender())); 0316 if (!account || status != Tp::ConnectionStatusConnected) { 0317 return; 0318 } 0319 0320 QString contactId = account->property("contactId").toString(); 0321 0322 if (contactId.isEmpty()) { 0323 return; 0324 } 0325 0326 account->ensureTextChat(contactId, 0327 QDateTime::currentDateTime(), 0328 QLatin1String("org.freedesktop.Telepathy.Client.KTp.TextUi")); 0329 0330 // avoid calling this slot repeatedly on account connection changes 0331 disconnect(account.data(), &Tp::Account::connectionStatusChanged, this, &KPeopleActionsPlugin::onAccountConnectionStatusChanged); 0332 } 0333 0334 K_PLUGIN_FACTORY_WITH_JSON( KPeopleActionsPluginFactory, "ktp_kpeople_plugin.json", registerPlugin<KPeopleActionsPlugin>(); ) 0335 K_EXPORT_PLUGIN( KPeopleActionsPluginFactory("ktp_kpeople_plugin", "ktp-common-internals") ) 0336 0337 #include "kpeople-actions-plugin.moc"