File indexing completed on 2023-09-24 05:08:01

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"