diff --git a/KTp/Declarative/conversation.cpp b/KTp/Declarative/conversation.cpp index e9d3198..80be403 100644 --- a/KTp/Declarative/conversation.cpp +++ b/KTp/Declarative/conversation.cpp @@ -1,318 +1,327 @@ /* Copyright (C) 2011 Lasath Fernando Copyright (C) 2016 Martin Klapetek 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" #include "messages-model.h" #include #include #include #include #include "debug.h" #include "channel-delegator.h" class Conversation::ConversationPrivate { public: ConversationPrivate() { messages = 0; delegated = false; valid = false; isGroupChat = false; } MessagesModel *messages; //stores if the conversation has been delegated to another client and we are only observing the channel //and not handling it. bool delegated; bool valid; Tp::AccountPtr account; QTimer *pausedStateTimer; KPeople::PersonData *personData; // May be null for group chats. KTp::ContactPtr targetContact; bool isGroupChat; }; Conversation::Conversation(const Tp::TextChannelPtr &channel, const Tp::AccountPtr &account, QObject *parent) : QObject(parent), d (new ConversationPrivate) { qCDebug(KTP_DECLARATIVE); d->valid = false; d->isGroupChat = false; d->account = account; connect(d->account.data(), SIGNAL(connectionChanged(Tp::ConnectionPtr)), SLOT(onAccountConnectionChanged(Tp::ConnectionPtr))); d->messages = new MessagesModel(account, this); connect(d->messages, &MessagesModel::unreadCountChanged, this, &Conversation::unreadMessagesChanged); connect(d->messages, &MessagesModel::lastMessageChanged, this, &Conversation::lastMessageChanged); setTextChannel(channel); d->delegated = false; d->pausedStateTimer = new QTimer(this); d->pausedStateTimer->setSingleShot(true); connect(d->pausedStateTimer, SIGNAL(timeout()), this, SLOT(onChatPausedTimerExpired())); } Conversation::Conversation(const QString &contactId, const Tp::AccountPtr &account, QObject *parent) : QObject(parent), d(new ConversationPrivate) { d->valid = true; d->isGroupChat = false; d->account = account; d->personData = new KPeople::PersonData(QStringLiteral("ktp://") + d->account->objectPath().mid(35) + QStringLiteral("?") + contactId); d->messages = new MessagesModel(account, this); connect(d->messages, &MessagesModel::unreadCountChanged, this, &Conversation::unreadMessagesChanged); connect(d->messages, &MessagesModel::lastMessageChanged, this, &Conversation::lastMessageChanged); d->messages->setContactData(contactId, d->personData->name()); Q_EMIT avatarChanged(); Q_EMIT titleChanged(); Q_EMIT presenceIconChanged(); Q_EMIT validityChanged(d->valid); } void Conversation::setTextChannel(const Tp::TextChannelPtr &channel) { if (d->messages->account().isNull()) { d->messages->setAccount(d->account); } 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))); connect(channel.data(), &Tp::TextChannel::chatStateChanged, this, &Conversation::contactTypingChanged); if (channel->targetContact().isNull()) { d->isGroupChat = true; } else { d->isGroupChat = false; d->targetContact = KTp::ContactPtr::qObjectCast(channel->targetContact()); d->personData = new KPeople::PersonData(QStringLiteral("ktp://") + d->account->objectPath().mid(35) + QStringLiteral("?") + d->targetContact->id()); 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(); Q_EMIT validityChanged(d->valid); } } Tp::TextChannelPtr Conversation::textChannel() const { return d->messages->textChannel(); } MessagesModel* Conversation::messages() const { return d->messages; } QString Conversation::title() const { if (d->isGroupChat) { QString roomName = textChannel()->targetId(); return roomName.left(roomName.indexOf(QLatin1Char('@'))); } else { return d->personData->name(); } } QIcon Conversation::presenceIcon() const { if (d->isGroupChat) { return KTp::Presence(Tp::Presence::available()).icon(); } else if (!d->targetContact.isNull()) { return KTp::Presence(d->targetContact->presence()).icon(); } return QIcon(); } QIcon Conversation::avatar() const { if (d->isGroupChat) { return QIcon(); } else { const QString path = d->targetContact->avatarData().fileName; QIcon icon; if (!path.isEmpty()) { icon = QIcon(path); } if (icon.availableSizes().isEmpty()) { icon = QIcon::fromTheme(QStringLiteral("im-user")); } return icon; } } KTp::ContactPtr Conversation::targetContact() const { if (d->isGroupChat) { return KTp::ContactPtr(); } else { return d->targetContact; } } Tp::AccountPtr Conversation::account() const { return d->account; } Tp::Account* Conversation::accountObject() const { if (!d->account.isNull()) { return d->account.data(); } return 0; } bool Conversation::isValid() const { return d->valid; } void Conversation::onChannelInvalidated(Tp::DBusProxy *proxy, const QString &errorName, const QString &errorMessage) { qCDebug(KTP_DECLARATIVE) << proxy << errorName << ":" << errorMessage; d->valid = false; Q_EMIT validityChanged(d->valid); } 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(op); Tp::TextChannelPtr textChannel = Tp::TextChannelPtr::dynamicCast(pendingChannelOp->channel()); if (textChannel) { setTextChannel(textChannel); } } void Conversation::delegateToProperClient() { ChannelDelegator::delegateChannel(d->account, d->messages->textChannel()); d->delegated = true; Q_EMIT conversationCloseRequested(); } void Conversation::requestClose() { qCDebug(KTP_DECLARATIVE); if (!d->messages->textChannel().isNull()) { d->messages->textChannel()->requestClose(); } } 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); } Conversation::~Conversation() { qCDebug(KTP_DECLARATIVE); //if we are not handling the channel do nothing. if (!d->delegated) { d->messages->textChannel()->requestClose(); } delete d; } bool Conversation::hasUnreadMessages() const { if (d->messages) { return d->messages->unreadCount() > 0; } return false; } KPeople::PersonData* Conversation::personData() const { return d->personData; } bool Conversation::isContactTyping() const { if (d->messages->textChannel()) { return d->messages->textChannel()->chatState(d->targetContact) == Tp::ChannelChatStateComposing; } return false; } + +bool Conversation::canSendMessages() const +{ + if (d->messages && d->messages->textChannel()) { + return true; + } + + return false; +} diff --git a/KTp/Declarative/conversation.h b/KTp/Declarative/conversation.h index 70e1225..cbaad99 100644 --- a/KTp/Declarative/conversation.h +++ b/KTp/Declarative/conversation.h @@ -1,110 +1,113 @@ /* Copyright (C) 2011 Lasath Fernando Copyright (C) 2016 Martin Klapetek 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 */ #ifndef CONVERSATION_H #define CONVERSATION_H #include #include #include #include #include #include "messages-model.h" class MessagesModel; class Conversation : public QObject { Q_OBJECT Q_PROPERTY(MessagesModel *messages READ messages CONSTANT) Q_PROPERTY(bool valid READ isValid NOTIFY validityChanged) Q_PROPERTY(QString title READ title NOTIFY titleChanged) Q_PROPERTY(QIcon presenceIcon READ presenceIcon NOTIFY presenceIconChanged) Q_PROPERTY(QIcon avatar READ avatar NOTIFY avatarChanged) Q_PROPERTY(Tp::Account *account READ accountObject CONSTANT) Q_PROPERTY(KTp::ContactPtr targetContact READ targetContact CONSTANT) Q_PROPERTY(KPeople::PersonData *personData READ personData CONSTANT) Q_PROPERTY(bool hasUnreadMessages READ hasUnreadMessages NOTIFY unreadMessagesChanged) Q_PROPERTY(bool isContactTyping READ isContactTyping NOTIFY contactTypingChanged) + Q_PROPERTY(bool canSendMessages READ canSendMessages NOTIFY canSendMessagesChanged) public: Conversation(const Tp::TextChannelPtr &channel, const Tp::AccountPtr &account, QObject *parent = 0); Conversation(const QString &contactId, const Tp::AccountPtr &account, QObject *parent = 0); ~Conversation() override; void setTextChannel(const Tp::TextChannelPtr &channel); Tp::TextChannelPtr textChannel() const; /** * Useful for offline history retrieving (as there's no text channel when offline) */ void setContactData(const QString &contactId, const QString &contactAlias); MessagesModel* messages() const; QString title() const; QIcon presenceIcon() const; QIcon avatar() const; /** * Target contact of this conversation. May be null if conversation is a group chat. */ KTp::ContactPtr targetContact() const; Tp::AccountPtr account() const; Tp::Account* accountObject() const; KPeople::PersonData* personData() const; bool isValid() const; bool hasUnreadMessages() const; bool isContactTyping() const; + bool canSendMessages() const; Q_SIGNALS: void validityChanged(bool isValid); void avatarChanged(); void titleChanged(); void presenceIconChanged(); void conversationCloseRequested(); void unreadMessagesChanged(); void lastMessageChanged(); void contactTypingChanged(); + void canSendMessagesChanged(); public Q_SLOTS: void delegateToProperClient(); void requestClose(); void updateTextChanged(const QString &message); private Q_SLOTS: void onChannelInvalidated(Tp::DBusProxy *proxy, const QString &errorName, const QString &errorMessage); void onAccountConnectionChanged(const Tp::ConnectionPtr &connection); void onCreateChannelFinished(Tp::PendingOperation *op); void onChatPausedTimerExpired(); private: class ConversationPrivate; ConversationPrivate *d; }; Q_DECLARE_METATYPE(Conversation*) #endif // CONVERSATION_H diff --git a/KTp/Declarative/messages-model.cpp b/KTp/Declarative/messages-model.cpp index a84f3f0..8477911 100644 --- a/KTp/Declarative/messages-model.cpp +++ b/KTp/Declarative/messages-model.cpp @@ -1,522 +1,527 @@ /* Copyright (C) 2011 Lasath Fernando Copyright (C) 2013 Lasath Fernando 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 "messages-model.h" #include #include "debug.h" #include #include #include #include #include #include #include #include class MessagePrivate { public: MessagePrivate(const KTp::Message &message); KTp::Message message; MessagesModel::DeliveryStatus deliveryStatus; QDateTime deliveryReportReceiveTime; }; MessagePrivate::MessagePrivate(const KTp::Message &message) : message(message), deliveryStatus(MessagesModel::DeliveryStatusUnknown) { } class MessagesModel::MessagesModelPrivate { public: Tp::TextChannelPtr textChannel; Tp::AccountPtr account; ScrollbackManager *logManager; QList messages; // For fast lookup of original messages upon receipt of a message delivery report. QHash messagesByMessageToken; bool visible; bool logsLoaded; }; MessagesModel::MessagesModel(const Tp::AccountPtr &account, QObject *parent) : QAbstractListModel(parent), d(new MessagesModelPrivate) { d->account = account; d->visible = false; d->logManager = new ScrollbackManager(this); d->logsLoaded = false; connect(d->logManager, SIGNAL(fetched(QList)), SLOT(onHistoryFetched(QList))); //Load configuration for number of message to show KConfig config(QLatin1String("ktelepathyrc")); KConfigGroup tabConfig = config.group("Behavior"); d->logManager->setScrollbackLength(tabConfig.readEntry("scrollbackLength", 10)); } QHash MessagesModel::roleNames() const { QHash roles = QAbstractListModel::roleNames(); roles[TextRole] = "text"; roles[TimeRole] = "time"; roles[TypeRole] = "type"; roles[SenderIdRole] = "senderId"; roles[SenderAliasRole] = "senderAlias"; roles[SenderAvatarRole] = "senderAvatar"; roles[DeliveryStatusRole] = "deliveryStatus"; roles[DeliveryReportReceiveTimeRole] = "deliveryReportReceiveTime"; roles[PreviousMessageTypeRole] = "previousMessageType"; roles[NextMessageTypeRole] = "nextMessageType"; return roles; } Tp::TextChannelPtr MessagesModel::textChannel() const { return d->textChannel; } bool MessagesModel::verifyPendingOperation(Tp::PendingOperation *op) { bool operationSucceeded = true; if (op->isError()) { qCWarning(KTP_DECLARATIVE) << op->errorName() << "+" << op->errorMessage(); operationSucceeded = false; } return operationSucceeded; } void MessagesModel::setupChannelSignals(const Tp::TextChannelPtr &channel) { connect(channel.data(), SIGNAL(messageReceived(Tp::ReceivedMessage)), SLOT(onMessageReceived(Tp::ReceivedMessage))); connect(channel.data(), SIGNAL(messageSent(Tp::Message,Tp::MessageSendingFlags,QString)), SLOT(onMessageSent(Tp::Message,Tp::MessageSendingFlags,QString))); connect(channel.data(), SIGNAL(pendingMessageRemoved(Tp::ReceivedMessage)), SLOT(onPendingMessageRemoved())); connect(channel.data(), &Tp::TextChannel::messageReceived, this, &MessagesModel::lastMessageChanged); connect(channel.data(), &Tp::TextChannel::messageSent, this, &MessagesModel::lastMessageChanged); connect(channel.data(), &Tp::TextChannel::pendingMessageRemoved, this, &MessagesModel::lastMessageChanged); } void MessagesModel::setTextChannel(const Tp::TextChannelPtr &channel) { Q_ASSERT(channel != d->textChannel); setupChannelSignals(channel); if (d->textChannel) { removeChannelSignals(d->textChannel); } d->textChannel = channel; d->logManager->setTextChannel(d->account, d->textChannel); //Load messages unless they have already been loaded if(!d->logsLoaded) { d->logManager->fetchScrollback(); } QList messageQueue = channel->messageQueue(); Q_FOREACH(const Tp::ReceivedMessage &message, messageQueue) { bool messageAlreadyInModel = false; Q_FOREACH(const MessagePrivate ¤t, d->messages) { //FIXME: docs say messageToken can return an empty string. What to do if that happens? //Tp::Message has an == operator. maybe I can use that? if (current.message.token() == message.messageToken()) { messageAlreadyInModel = true; break; } } if (!messageAlreadyInModel) { onMessageReceived(message); } } } Tp::AccountPtr MessagesModel::account() const { return d->account; } void MessagesModel::setAccount(const Tp::AccountPtr &account) { d->account = account; } void MessagesModel::setContactData(const QString &contactId, const QString &contactAlias) { d->logManager->setAccountAndContact(d->account, contactId, contactAlias); //Load messages unless they have already been loaded if (!d->logsLoaded) { qDebug() << "Fetching scrollback"; d->logManager->fetchScrollback(); } } void MessagesModel::onHistoryFetched(const QList &messages) { QList messagesToAdd; // Make sure we're not adding duplicated messages to the model if (!d->messages.isEmpty()) { int i = 0; for (i = 0; i < messages.size(); i++) { if (messages.at(i) == d->messages.at(0).message) { break; } } messagesToAdd = messages.mid(0, i); } else { messagesToAdd = messages; } if (!messagesToAdd.isEmpty()) { //Add all messages before the ones already present in the channel beginInsertRows(QModelIndex(), 0, messagesToAdd.count() - 1); for (int i = messagesToAdd.size() - 1; i >= 0; i--) { d->messages.prepend(messagesToAdd[i]); } endInsertRows(); } d->logsLoaded = true; // Emit changed for the first message after the prepended // logs, to make sure the bubble shape is updated // through PreviousMessageTypeRole QModelIndex index = createIndex(messagesToAdd.count(), 0); Q_EMIT dataChanged(index, index); Q_EMIT lastMessageChanged(); } void MessagesModel::onMessageReceived(const Tp::ReceivedMessage &message) { int unreadCount = d->textChannel->messageQueue().size(); if (message.isDeliveryReport()) { d->textChannel->acknowledge(QList() << message); Tp::ReceivedMessage::DeliveryDetails deliveryDetails = message.deliveryDetails(); if(!deliveryDetails.hasOriginalToken()) { qCWarning(KTP_DECLARATIVE) << "Delivery report without original message token received."; // Matching the delivery report to the original message is impossible without the token. return; } QPersistentModelIndex originalMessageIndex = d->messagesByMessageToken.value( deliveryDetails.originalToken()); if (!originalMessageIndex.isValid() || originalMessageIndex.row() >= d->messages.count()) { // The original message for this delivery report was not found. return; } MessagePrivate &originalMessage = d->messages[originalMessageIndex.row()]; originalMessage.deliveryReportReceiveTime = message.received(); switch(deliveryDetails.status()) { case Tp::DeliveryStatusPermanentlyFailed: case Tp::DeliveryStatusTemporarilyFailed: originalMessage.deliveryStatus = DeliveryStatusFailed; if (deliveryDetails.hasDebugMessage()) { qCDebug(KTP_DECLARATIVE) << "Delivery failure debug message:" << deliveryDetails.debugMessage(); } break; case Tp::DeliveryStatusDelivered: originalMessage.deliveryStatus = DeliveryStatusDelivered; break; case Tp::DeliveryStatusRead: originalMessage.deliveryStatus = DeliveryStatusRead; break; default: originalMessage.deliveryStatus = DeliveryStatusUnknown; break; } Q_EMIT dataChanged(originalMessageIndex, originalMessageIndex); } else { int newMessageIndex = 0; const QDateTime sentTimestamp = message.sent(); if (sentTimestamp.isValid()) { for (int i = d->messages.count() - 1; i >= 0; --i) { if (sentTimestamp > d->messages.at(i).message.time()) { newMessageIndex = i; break; } } } else { newMessageIndex = rowCount(); } beginInsertRows(QModelIndex(), newMessageIndex, newMessageIndex); d->messages.insert(newMessageIndex, KTp::MessageProcessor::instance()->processIncomingMessage( message, d->account, d->textChannel)); endInsertRows(); // Update the previous message in the view // This will redraw the part of the bubble to not // be bottom but middle one, if this is a consecutive // message to the previous one if (d->messages.count() > 1) { Q_EMIT dataChanged(createIndex(newMessageIndex - 1, 0), createIndex(newMessageIndex - 1, 0)); } if (d->visible) { acknowledgeAllMessages(); } else { Q_EMIT unreadCountChanged(unreadCount); } } } void MessagesModel::onMessageSent(const Tp::Message &message, Tp::MessageSendingFlags flags, const QString &messageToken) { Q_UNUSED(flags); int newMessageIndex = rowCount(); beginInsertRows(QModelIndex(), newMessageIndex, newMessageIndex); const KTp::Message &newMessage = KTp::MessageProcessor::instance()->processIncomingMessage( message, d->account, d->textChannel); d->messages.append(newMessage); if (!messageToken.isEmpty()) { // Insert the message into the lookup table for delivery reports. const QPersistentModelIndex modelIndex = createIndex(newMessageIndex, 0); d->messagesByMessageToken.insert(messageToken, modelIndex); } endInsertRows(); // Update the previous message in the view // This will redraw the part of the bubble to not // be bottom but middle one, if this is a consecutive // message to the previous one if (d->messages.count() > 1) { Q_EMIT dataChanged(createIndex(newMessageIndex - 1, 0), createIndex(newMessageIndex - 1, 0)); } } void MessagesModel::onPendingMessageRemoved() { Q_EMIT unreadCountChanged(unreadCount()); } QVariant MessagesModel::data(const QModelIndex &index, int role) const { QVariant result; if (index.isValid() && index.row() < rowCount(index.parent())) { const MessagePrivate m = d->messages[index.row()]; switch (role) { case TextRole: result = m.message.finalizedMessage(); break; case TypeRole: if (m.message.type() == Tp::ChannelTextMessageTypeAction) { result = MessageTypeAction; } else { if (m.message.direction() == KTp::Message::LocalToRemote) { result = MessageTypeOutgoing; } else { result = MessageTypeIncoming; } } break; case TimeRole: result = m.message.time(); break; case SenderIdRole: result = m.message.senderId(); break; case SenderAliasRole: result = m.message.senderAlias(); break; case SenderAvatarRole: if (m.message.sender()) { result = QVariant::fromValue(m.message.sender()->avatarPixmap()); } break; case DeliveryStatusRole: result = m.deliveryStatus; break; case DeliveryReportReceiveTimeRole: result = m.deliveryReportReceiveTime; break; case PreviousMessageTypeRole: if (index.row() > 0) { result = data(createIndex(index.row() - 1, 0), TypeRole); } break; case NextMessageTypeRole: if (index.row() < d->messages.size() - 1) { result = data(createIndex(index.row() + 1, 0), TypeRole); } break; }; } else { qWarning() << "Attempting to access data at invalid index (" << index << ")"; } return result; } int MessagesModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return d->messages.size(); } void MessagesModel::sendNewMessage(const QString &message) { if (message.isEmpty()) { qCWarning(KTP_DECLARATIVE) << "Attempting to send empty string, this is not supported"; } else { + if (!d->textChannel) { + qWarning(KTP_DECLARATIVE) << "Attempting to send a message without a channel, returning"; + return; + } + Tp::PendingOperation *op; QString modifiedMessage = message; if (d->textChannel->supportsMessageType(Tp::ChannelTextMessageTypeAction) && modifiedMessage.startsWith(QLatin1String("/me "))) { //remove "/me " from the start of the message modifiedMessage.remove(0,4); op = d->textChannel->send(modifiedMessage, Tp::ChannelTextMessageTypeAction); } else { op = d->textChannel->send(modifiedMessage); } connect(op, SIGNAL(finished(Tp::PendingOperation*)), SLOT(verifyPendingOperation(Tp::PendingOperation*))); } } void MessagesModel::removeChannelSignals(const Tp::TextChannelPtr &channel) { QObject::disconnect(channel.data(), SIGNAL(messageReceived(Tp::ReceivedMessage)), this, SLOT(onMessageReceived(Tp::ReceivedMessage)) ); QObject::disconnect(channel.data(), SIGNAL(messageSent(Tp::Message,Tp::MessageSendingFlags,QString)), this, SLOT(onMessageSent(Tp::Message,Tp::MessageSendingFlags,QString)) ); } int MessagesModel::unreadCount() const { if (d->textChannel) { return d->textChannel->messageQueue().size(); } return 0; } void MessagesModel::acknowledgeAllMessages() { if (d->textChannel.isNull()) { return; } QList queue = d->textChannel->messageQueue(); d->textChannel->acknowledge(queue); Q_EMIT unreadCountChanged(queue.size()); } void MessagesModel::setVisibleToUser(bool visible) { if (d->visible != visible) { d->visible = visible; Q_EMIT visibleToUserChanged(d->visible); } if (visible) { acknowledgeAllMessages(); } } bool MessagesModel::isVisibleToUser() const { return d->visible; } MessagesModel::~MessagesModel() { delete d; } bool MessagesModel::shouldStartOpened() const { return d->textChannel->isRequested(); } QString MessagesModel::lastMessage() const { const QModelIndex index = createIndex(rowCount() - 1, 0); if (!index.isValid()) { return QString(); } return data(index, MessagesModel::TextRole).toString().simplified(); } QDateTime MessagesModel::lastMessageDateTime() const { const QModelIndex index = createIndex(rowCount() - 1, 0); if (!index.isValid()) { return QDateTime(); } return data(index, MessagesModel::TimeRole).toDateTime(); } void MessagesModel::fetchMoreHistory() { if (d->messages.isEmpty() || !d->logsLoaded) { return; } d->logsLoaded = false; const KTp::Message message = d->messages.at(0).message; const QString token = message.token().isEmpty() ? message.time().toString(Qt::ISODate) + message.mainMessagePart() : message.token(); d->logManager->setScrollbackLength(10); d->logManager->fetchHistory(rowCount() + 10, token); }