conversation.cpp 8.76 KB
Newer Older
1 2
/*
    Copyright (C) 2011  Lasath Fernando <kde@lasath.org>
Martin Klapetek's avatar
Martin Klapetek committed
3
    Copyright (C) 2016  Martin Klapetek <mklapetek@kde.org>
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/


#include "conversation.h"
22
#include "messages-model.h"
23

David Edmundson's avatar
David Edmundson committed
24
#include <TelepathyQt/TextChannel>
25 26 27 28
#include <TelepathyQt/Account>
#include <TelepathyQt/PendingChannelRequest>
#include <TelepathyQt/PendingChannel>

29
#include "debug.h"
30

31 32
#include "channel-delegator.h"

Lasath Fernando's avatar
Lasath Fernando committed
33 34
class Conversation::ConversationPrivate
{
35
  public:
36 37 38 39 40 41 42 43
      ConversationPrivate()
      {
          messages = 0;
          delegated = false;
          valid = false;
          isGroupChat = false;
      }

44
    MessagesModel *messages;
45 46 47
    //stores if the conversation has been delegated to another client and we are only observing the channel
    //and not handling it.
    bool delegated;
48
    bool valid;
49
    Tp::AccountPtr account;
50
    QTimer *pausedStateTimer;
51 52 53
    // May be null for group chats.
    KTp::ContactPtr targetContact;
    bool isGroupChat;
54 55
};

Martin Klapetek's avatar
Martin Klapetek committed
56 57
Conversation::Conversation(const Tp::TextChannelPtr &channel,
                           const Tp::AccountPtr &account,
58
                           QObject *parent) :
59
        QObject(parent),
60
        d (new ConversationPrivate)
61
{
62
    qCDebug(KTP_DECLARATIVE);
Lasath Fernando's avatar
Lasath Fernando committed
63

64
    d->account = account;
65
    connect(d->account.data(), SIGNAL(connectionChanged(Tp::ConnectionPtr)), SLOT(onAccountConnectionChanged(Tp::ConnectionPtr)));
66

67
    d->messages = new MessagesModel(account, this);
68
    connect(d->messages, &MessagesModel::unreadCountChanged, this, &Conversation::unreadMessagesChanged);
69
    connect(d->messages, &MessagesModel::lastMessageChanged, this, &Conversation::lastMessageChanged);
70
    setTextChannel(channel);
71

72 73
    d->delegated = false;

74 75 76
    d->pausedStateTimer = new QTimer(this);
    d->pausedStateTimer->setSingleShot(true);
    connect(d->pausedStateTimer, SIGNAL(timeout()), this, SLOT(onChatPausedTimerExpired()));
Lasath Fernando's avatar
Lasath Fernando committed
77 78
}

79 80 81
Conversation::Conversation(QObject *parent)
    : QObject(parent),
      d(new ConversationPrivate)
Lasath Fernando's avatar
Lasath Fernando committed
82
{
83 84
}

85
void Conversation::setTextChannel(const Tp::TextChannelPtr &channel)
86
{
87 88
    if (!d->messages) {
        d->messages = new MessagesModel(d->account, this);
89
        connect(d->messages, &MessagesModel::unreadCountChanged, this, &Conversation::unreadMessagesChanged);
90
        connect(d->messages, &MessagesModel::lastMessageChanged, this, &Conversation::lastMessageChanged);
91
    }
92 93 94 95 96
    if (d->messages->textChannel() != channel) {
        d->messages->setTextChannel(channel);
        d->valid = channel->isValid();
        connect(channel.data(), SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
                SLOT(onChannelInvalidated(Tp::DBusProxy*,QString,QString)));
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111

        if (channel->targetContact().isNull()) {
            d->isGroupChat = true;
        } else {
            d->isGroupChat = false;
            d->targetContact = KTp::ContactPtr::qObjectCast(channel->targetContact());

            connect(d->targetContact.constData(), SIGNAL(aliasChanged(QString)), SIGNAL(titleChanged()));
            connect(d->targetContact.constData(), SIGNAL(presenceChanged(Tp::Presence)), SIGNAL(presenceIconChanged()));
            connect(d->targetContact.constData(), SIGNAL(avatarDataChanged(Tp::AvatarData)), SIGNAL(avatarChanged()));
        }

        Q_EMIT avatarChanged();
        Q_EMIT titleChanged();
        Q_EMIT presenceIconChanged();
112
        Q_EMIT validityChanged(d->valid);
113
    }
114 115 116 117 118 119 120
}

Tp::TextChannelPtr Conversation::textChannel() const
{
    return d->messages->textChannel();
}

121
MessagesModel* Conversation::messages() const
122
{
123
    return d->messages;
124 125
}

126 127 128 129 130
QString Conversation::title() const
{
    if (d->isGroupChat) {
        QString roomName = textChannel()->targetId();
        return roomName.left(roomName.indexOf(QLatin1Char('@')));
131
    } else if (!d->targetContact.isNull()) {
132 133
        return d->targetContact->alias();
    }
134 135

    return QString();
136 137 138
}

QIcon Conversation::presenceIcon() const
139
{
140 141
    if (d->isGroupChat) {
        return KTp::Presence(Tp::Presence::available()).icon();
142
    } else if (!d->targetContact.isNull()) {
143 144
        return KTp::Presence(d->targetContact->presence()).icon();
    }
145 146

    return QIcon();
147 148 149 150 151 152 153
}

QIcon Conversation::avatar() const
{
    if (d->isGroupChat) {
        return QIcon();
    } else {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
154 155
        const QString path = d->targetContact->avatarData().fileName;
        QIcon icon;
156
        if (!path.isEmpty()) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
157
            icon = QIcon(path);
158 159 160 161
        }
        if (icon.availableSizes().isEmpty()) {
            icon = QIcon::fromTheme(QStringLiteral("im-user"));
        }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
162
        return icon;
163 164 165
    }
}

166 167
KTp::ContactPtr Conversation::targetContact() const
{
168 169 170 171 172 173 174
    if (d->isGroupChat) {
        return KTp::ContactPtr();
    } else {
        return d->targetContact;
    }
}

175 176
Tp::AccountPtr Conversation::account() const
{
177
    return d->account;
178 179
}

180 181 182 183 184
void Conversation::setAccount(const Tp::AccountPtr &account)
{
    d->account = account;
}

185
bool Conversation::isValid() const
186 187 188 189
{
    return d->valid;
}

Martin Klapetek's avatar
Martin Klapetek committed
190
void Conversation::onChannelInvalidated(Tp::DBusProxy *proxy, const QString &errorName, const QString &errorMessage)
191
{
192
    qCDebug(KTP_DECLARATIVE) << proxy << errorName << ":" << errorMessage;
193 194 195 196 197 198

    d->valid = false;

    Q_EMIT validityChanged(d->valid);
}

199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
void Conversation::onAccountConnectionChanged(const Tp::ConnectionPtr& connection)
{
    //if we have reconnected and we were handling the channel
    if (connection && ! d->delegated) {

        //general convention is to never use ensureAndHandle when we already have a client registrar
        //ensureAndHandle will implicity create a new temporary client registrar which is a waste
        //it's also more code to get the new channel

        //However, we cannot use use ensureChannel as normal because without being able to pass a preferredHandler
        //we need a preferredHandler so that this handler is the one that ends up with the channel if multi handlers are active
        //we do not know the name that this handler is currently registered with
        Tp::PendingChannel *pendingChannel = d->account->ensureAndHandleTextChat(textChannel()->targetId());
        connect(pendingChannel, SIGNAL(finished(Tp::PendingOperation*)), SLOT(onCreateChannelFinished(Tp::PendingOperation*)));
    }
}

void Conversation::onCreateChannelFinished(Tp::PendingOperation* op)
{
    Tp::PendingChannel *pendingChannelOp = qobject_cast<Tp::PendingChannel*>(op);
    Tp::TextChannelPtr textChannel = Tp::TextChannelPtr::dynamicCast(pendingChannelOp->channel());
    if (textChannel) {
        setTextChannel(textChannel);
    }
}

225 226 227
void Conversation::delegateToProperClient()
{
    ChannelDelegator::delegateChannel(d->account, d->messages->textChannel());
228
    d->delegated = true;
229
    Q_EMIT conversationCloseRequested();
230 231
}

232 233
void Conversation::requestClose()
{
234
    qCDebug(KTP_DECLARATIVE);
235 236 237

    //removing from the model will delete this object closing the channel
    Q_EMIT conversationCloseRequested();
238 239
}

240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
void Conversation::updateTextChanged(const QString &message)
{
    if (!message.isEmpty()) {
        //if the timer is active, it means the user is continuously typing
        if (d->pausedStateTimer->isActive()) {
            //just restart the timer and don't spam with chat state changes
            d->pausedStateTimer->start(5000);
        } else {
            //if the user has just typed some text, set state to Composing and start the timer
            d->messages->textChannel()->requestChatState(Tp::ChannelChatStateComposing);
            d->pausedStateTimer->start(5000);
        }
    } else {
        //if the user typed no text/cleared the input field, set Active and stop the timer
        d->messages->textChannel()->requestChatState(Tp::ChannelChatStateActive);
        d->pausedStateTimer->stop();
    }
}

void Conversation::onChatPausedTimerExpired()
{
    d->messages->textChannel()->requestChatState(Tp::ChannelChatStatePaused);
}

264 265
Conversation::~Conversation()
{
266
    qCDebug(KTP_DECLARATIVE);
267 268 269 270
    //if we are not handling the channel do nothing.
    if (!d->delegated) {
        d->messages->textChannel()->requestClose();
    }
271
    delete d;
272
}
273 274 275 276 277 278 279 280 281

bool Conversation::hasUnreadMessages() const
{
    if (d->messages) {
        return d->messages->unreadCount() > 0;
    }

    return false;
}