diff --git a/KTp/Declarative/conversation.cpp b/KTp/Declarative/conversation.cpp index fa10aab..f97d707 100644 --- a/KTp/Declarative/conversation.cpp +++ b/KTp/Declarative/conversation.cpp @@ -1,272 +1,274 @@ /* 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; // 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->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); setTextChannel(channel); d->delegated = false; d->pausedStateTimer = new QTimer(this); d->pausedStateTimer->setSingleShot(true); connect(d->pausedStateTimer, SIGNAL(timeout()), this, SLOT(onChatPausedTimerExpired())); } Conversation::Conversation(QObject *parent) : QObject(parent), d(new ConversationPrivate) { } -void Conversation::setTextChannel(const Tp::TextChannelPtr& channel) +void Conversation::setTextChannel(const Tp::TextChannelPtr &channel) { if (!d->messages) { d->messages = new MessagesModel(d->account, this); connect(d->messages, &MessagesModel::unreadCountChanged, this, &Conversation::unreadMessagesChanged); } 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))); 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(); 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 if (!d->targetContact.isNull()) { return d->targetContact->alias(); } return QString(); } 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 { +KTp::ContactPtr Conversation::targetContact() const +{ if (d->isGroupChat) { return KTp::ContactPtr(); } else { return d->targetContact; } } -Tp::AccountPtr Conversation::account() const { +Tp::AccountPtr Conversation::account() const +{ return d->account; } 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); //removing from the model will delete this object closing the channel Q_EMIT conversationCloseRequested(); } 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; } diff --git a/KTp/Declarative/conversations-model.cpp b/KTp/Declarative/conversations-model.cpp index a8ed75c..9cf8107 100644 --- a/KTp/Declarative/conversations-model.cpp +++ b/KTp/Declarative/conversations-model.cpp @@ -1,221 +1,220 @@ /* Copyright (C) 2011 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 "conversations-model.h" #include "conversation.h" #include "messages-model.h" #include "debug.h" #include #include #include static inline Tp::ChannelClassSpecList channelClassList() { return Tp::ChannelClassSpecList() << Tp::ChannelClassSpec::textChat(); } class ConversationsModel::ConversationsModelPrivate { public: QList conversations; int activeChatIndex; }; ConversationsModel::ConversationsModel(QObject *parent) : - QAbstractListModel(parent), - Tp::AbstractClientHandler(channelClassList()), - d(new ConversationsModelPrivate) + QAbstractListModel(parent), + Tp::AbstractClientHandler(channelClassList()), + d(new ConversationsModelPrivate) { d->activeChatIndex = -1; connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), SIGNAL(totalUnreadCountChanged())); connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)), SIGNAL(totalUnreadCountChanged())); } ConversationsModel::~ConversationsModel() { qDeleteAll(d->conversations); delete d; } QHash ConversationsModel::roleNames() const { QHash roles = QAbstractListModel::roleNames(); roles[ConversationRole] = "conversation"; return roles; } QVariant ConversationsModel::data(const QModelIndex &index, int role) const { QVariant result; - if (index.isValid()) { - if (role == ConversationRole) { - result = QVariant::fromValue(d->conversations[index.row()]); - } + if (index.isValid() && role == ConversationRole) { + result = QVariant::fromValue(d->conversations[index.row()]); } return result; } int ConversationsModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return d->conversations.count(); } void ConversationsModel::handleChannels(const Tp::MethodInvocationContextPtr<> &context, const Tp::AccountPtr &account, const Tp::ConnectionPtr &connection, const QList &channels, const QList &channelRequests, const QDateTime &userActionTime, const HandlerInfo &handlerInfo) { Q_UNUSED(connection); Q_UNUSED(handlerInfo); Q_UNUSED(userActionTime); bool handled = false; bool shouldDelegate = false; //check that the channel is of type text Tp::TextChannelPtr textChannel; - Q_FOREACH(const Tp::ChannelPtr &channel, channels) { + Q_FOREACH (const Tp::ChannelPtr &channel, channels) { textChannel = Tp::TextChannelPtr::dynamicCast(channel); if (textChannel) { break; } } Q_ASSERT(textChannel); //find the relevant channelRequest - Q_FOREACH(const Tp::ChannelRequestPtr channelRequest, channelRequests) { + Q_FOREACH (const Tp::ChannelRequestPtr channelRequest, channelRequests) { qCDebug(KTP_DECLARATIVE) << channelRequest->hints().allHints(); - shouldDelegate = channelRequest->hints().hint(QLatin1String("org.freedesktop.Telepathy.ChannelRequest"), QLatin1String("DelegateToPreferredHandler")).toBool(); + shouldDelegate = channelRequest->hints().hint(QLatin1String("org.freedesktop.Telepathy.ChannelRequest"), + QLatin1String("DelegateToPreferredHandler")).toBool(); } //loop through all conversations checking for matches //if we are handling and we're not told to delegate it, update the text channel //if we are handling but should delegate, call delegate channel int i = 0; - Q_FOREACH(Conversation *convo, d->conversations) { - if (convo->textChannel()->targetId() == textChannel->targetId() && - convo->textChannel()->targetHandleType() == textChannel->targetHandleType()) + Q_FOREACH (Conversation *conversation, d->conversations) { + if (conversation->textChannel()->targetId() == textChannel->targetId() + && conversation->textChannel()->targetHandleType() == textChannel->targetHandleType()) { if (!shouldDelegate) { - convo->setTextChannel(textChannel); + conversation->setTextChannel(textChannel); //Update the active chat index to this channel d->activeChatIndex = i; Q_EMIT activeChatIndexChanged(); } else { - if (convo->textChannel() == textChannel) { - convo->delegateToProperClient(); + if (conversation->textChannel() == textChannel) { + conversation->delegateToProperClient(); } } handled = true; break; } i++; } //if we are not handling channel already and should not delegate, add the conversation //if we not handling the channel but should delegate it, do nothing. if (!handled && !shouldDelegate) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); Conversation *newConvo = new Conversation(textChannel, account, this); connect(newConvo, SIGNAL(conversationCloseRequested()), SLOT(onConversationCloseRequested())); connect(newConvo->messages(), SIGNAL(unreadCountChanged(int)), SIGNAL(totalUnreadCountChanged())); d->conversations.append(newConvo); endInsertRows(); //If this is a locally generated request or there is no active chat, the index of the newly inserted conversation is saved as the active chat //The model is reset to load the newly created chat channel - if(textChannel->isRequested() || d->activeChatIndex == -1) { + if (textChannel->isRequested() || d->activeChatIndex == -1) { d->activeChatIndex = rowCount() - 1; Q_EMIT activeChatIndexChanged(); } context->setFinished(); } } bool ConversationsModel::bypassApproval() const { return true; } void ConversationsModel::onConversationCloseRequested() { removeConversation(qobject_cast(QObject::sender())); } void ConversationsModel::removeConversation(Conversation* conv) { int index = d->conversations.indexOf(conv); if (index != -1) { beginRemoveRows(QModelIndex(), index, index); d->conversations.removeAt(index); conv->deleteLater(); endRemoveRows(); } else { qWarning() << "attempting to delete non-existent conversation"; } } int ConversationsModel::nextActiveConversation(int fromRow) { - if(d->conversations.isEmpty()) { + if (d->conversations.isEmpty()) { return -1; } - Q_ASSERT(qBound(0, fromRow, d->conversations.count()-1) == fromRow); + Q_ASSERT(qBound(0, fromRow, d->conversations.count() - 1) == fromRow); bool first = true; //let first be checked on the first loop - for(int i = fromRow; i != fromRow || first; i = (i + 1) % d->conversations.count()) { - if(d->conversations[i]->messages()->unreadCount() > 0) { + for (int i = fromRow; i != fromRow || first; i = (i + 1) % d->conversations.count()) { + if (d->conversations[i]->messages()->unreadCount() > 0) { return i; } first = false; } return -1; } int ConversationsModel::totalUnreadCount() const { int ret = 0; Q_FOREACH(Conversation *c, d->conversations) { ret += c->messages()->unreadCount(); } return ret; } int ConversationsModel::activeChatIndex() const { return d->activeChatIndex; } void ConversationsModel::closeAllConversations() { if (!d->conversations.isEmpty()) { beginRemoveRows(QModelIndex(), 0, rowCount() - 1); d->conversations.clear(); endRemoveRows(); qDeleteAll(d->conversations); } } diff --git a/KTp/Declarative/conversations-model.h b/KTp/Declarative/conversations-model.h index 06e3b59..6ba159f 100644 --- a/KTp/Declarative/conversations-model.h +++ b/KTp/Declarative/conversations-model.h @@ -1,82 +1,82 @@ /* Copyright (C) 2011 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 */ #ifndef CONVERSATIONS_MODEL_H #define CONVERSATIONS_MODEL_H #include #include #include class Conversation; class ConversationsModel : public QAbstractListModel, public Tp::AbstractClientHandler { Q_OBJECT Q_PROPERTY(int totalUnreadCount READ totalUnreadCount NOTIFY totalUnreadCountChanged) Q_PROPERTY(int activeChatIndex READ activeChatIndex NOTIFY activeChatIndexChanged) - public: - explicit ConversationsModel(QObject *parent=0); +public: + explicit ConversationsModel(QObject *parent = 0); virtual ~ConversationsModel(); QHash roleNames() const Q_DECL_OVERRIDE; - virtual QVariant data ( const QModelIndex &index, int role = Qt::DisplayRole ) const Q_DECL_OVERRIDE; - virtual int rowCount ( const QModelIndex &parent = QModelIndex() ) const Q_DECL_OVERRIDE; + virtual QVariant data (const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; + virtual int rowCount (const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; /** @returns the sum of all unread messages among all conversations */ int totalUnreadCount() const; /** @returns the index of the active chat, ie one the user is interacting with */ int activeChatIndex() const; enum role { ConversationRole = Qt::UserRole }; Q_INVOKABLE void closeAllConversations(); void handleChannels(const Tp::MethodInvocationContextPtr<> &context, const Tp::AccountPtr &account, const Tp::ConnectionPtr &connection, const QList &channels, const QList &channelRequests, const QDateTime &userActionTime, const HandlerInfo &handlerInfo); bool bypassApproval() const; - public Q_SLOTS: +public Q_SLOTS: int nextActiveConversation(int first); - private: - void removeConversation(Conversation* conversation); +private: + void removeConversation(Conversation *conversation); class ConversationsModelPrivate; ConversationsModelPrivate *d; - private Q_SLOTS: +private Q_SLOTS: void onConversationCloseRequested(); - Q_SIGNALS: - void totalUnreadCountChanged(); - void activeChatIndexChanged(); +Q_SIGNALS: + void totalUnreadCountChanged(); + void activeChatIndexChanged(); }; #endif // CONVERSATIONS_MODEL_H diff --git a/KTp/Declarative/messages-model.cpp b/KTp/Declarative/messages-model.cpp index 4e7a25c..128a0b8 100644 --- a/KTp/Declarative/messages-model.cpp +++ b/KTp/Declarative/messages-model.cpp @@ -1,406 +1,406 @@ /* 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"; 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())); } 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); } } } void MessagesModel::onHistoryFetched(const QList &messages) { if (!messages.isEmpty()) { //Add all messages before the ones already present in the channel beginInsertRows(QModelIndex(), 0, messages.count() - 1); for(int i=messages.size()-1;i>=0;i--) { d->messages.prepend(messages[i]); } endInsertRows(); } d->logsLoaded = true; } 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; + 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(); 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 length = rowCount(); beginInsertRows(QModelIndex(), length, length); 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(length, 0); d->messagesByMessageToken.insert(messageToken, modelIndex); } endInsertRows(); } 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; }; } 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 { 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(); } diff --git a/KTp/Declarative/pinned-contacts-model.cpp b/KTp/Declarative/pinned-contacts-model.cpp index 19de777..918fa02 100644 --- a/KTp/Declarative/pinned-contacts-model.cpp +++ b/KTp/Declarative/pinned-contacts-model.cpp @@ -1,278 +1,277 @@ /* Copyright (C) 2012 Aleix Pol 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 "pinned-contacts-model.h" #include "conversations-model.h" #include "conversation.h" #include #include #include #include #include #include #include #include #include "debug.h" #include "KTp/presence.h" #include "KTp/contact.h" #include "KTp/persistent-contact.h" class PinnedContactsModelPrivate { public: PinnedContactsModelPrivate() { conversations = 0; } QList m_pins; ConversationsModel *conversations; QStringList pinsToString() const { QStringList ret; Q_FOREACH(const KTp::PersistentContactPtr &p, m_pins) { ret += p->accountId(); ret += p->contactId(); } return ret; } }; PinnedContactsModel::PinnedContactsModel(QObject *parent) : QAbstractListModel(parent) , d(new PinnedContactsModelPrivate) { connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), SIGNAL(countChanged())); connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)), SIGNAL(countChanged())); } PinnedContactsModel::~PinnedContactsModel() { delete d; } QHash PinnedContactsModel::roleNames() const { QHash roles = QAbstractListModel::roleNames(); roles[PresenceIconRole] = "presenceIcon"; roles[AvailabilityRole] = "available"; roles[ContactRole] = "contact"; roles[AccountRole] = "account"; roles[AlreadyChattingRole] = "alreadyChatting"; return roles; } QStringList PinnedContactsModel::state() const { return d->pinsToString(); } void PinnedContactsModel::setState(const QStringList &pins) { for (int i = 0; i < pins.count(); i += 2) { appendContactPin(KTp::PersistentContact::create(pins[0], pins[1])); } } QModelIndex PinnedContactsModel::indexForContact(const KTp::ContactPtr &contact) const { - for (int i=0; im_pins.size() && contact; i++) { + for (int i = 0; i < d->m_pins.size() && contact; i++) { if (d->m_pins[i]->contactId() == contact->id()) { return index(i); } } return QModelIndex(); } void PinnedContactsModel::setPinning(const Tp::AccountPtr &account, const KTp::ContactPtr &contact, bool newState) { QModelIndex idx = indexForContact(contact); bool found = idx.isValid(); if (newState && !found) { KTp::PersistentContactPtr p = KTp::PersistentContact::create(account->uniqueIdentifier(), contact->id()); appendContactPin(p); } else if (!newState && found) { removeContactPin(d->m_pins[idx.row()]); } } QVariant PinnedContactsModel::data(const QModelIndex &index, int role) const { if (index.isValid()) { KTp::PersistentContactPtr p = d->m_pins[index.row()]; switch(role) { case Qt::DisplayRole: if (p->contact()) { return p->contact()->alias(); } break; case PresenceIconRole: if (p->contact()) { return KTp::Presence(p->contact()->presence()).icon(); } else { return KTp::Presence(Tp::Presence::offline()).icon(); } break; case AvailabilityRole: if (!p->contact()) { return false; - } - else { - return p->contact()->presence().type()!=Tp::ConnectionPresenceTypeOffline - && p->contact()->presence().type()!=Tp::ConnectionPresenceTypeError - && p->contact()->presence().type()!=Tp::ConnectionPresenceTypeUnset - && p->contact()->presence().type()!=Tp::ConnectionPresenceTypeUnknown; + } else { + return p->contact()->presence().type() != Tp::ConnectionPresenceTypeOffline + && p->contact()->presence().type() != Tp::ConnectionPresenceTypeError + && p->contact()->presence().type() != Tp::ConnectionPresenceTypeUnset + && p->contact()->presence().type() != Tp::ConnectionPresenceTypeUnknown; } case ContactRole: return QVariant::fromValue(p->contact()); case AccountRole: return QVariant::fromValue(p->account()); case AlreadyChattingRole: { if (!p->contact() || !d->conversations) { return false; } bool found = false; for (int i = 0; !found && i < d->conversations->rowCount(); i++) { QModelIndex idx = d->conversations->index(i, 0); KTp::ContactPtr contact = idx.data(ConversationsModel::ConversationRole).value()->targetContact(); found |= contact->id() == p->contact()->id(); } return found; } case Qt::DecorationRole: { QIcon icon; if (p->contact()) { QString file = p->contact()->avatarData().fileName; if (!file.isEmpty()) { icon = QIcon::fromTheme(file); } } if (icon.isNull()) { icon = QIcon::fromTheme(QStringLiteral("im-user")); } return icon; } } } return QVariant(); } int PinnedContactsModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return d->m_pins.count(); } void PinnedContactsModel::removeContactPin(const KTp::PersistentContactPtr &pin) { int row = d->m_pins.indexOf(pin); - if (row>=0) { + if (row >= 0) { beginRemoveRows(QModelIndex(), row, row); d->m_pins.removeAt(row); endRemoveRows(); Q_EMIT stateChanged(); } else qWarning() << "trying to remove missing pin" << pin->contactId(); } void PinnedContactsModel::appendContactPin(const KTp::PersistentContactPtr &pin) { auto samePin = [pin](const KTp::PersistentContactPtr &p) -> bool { return p->contactId() == pin->contactId(); }; if (std::find_if(d->m_pins.constBegin(), d->m_pins.constEnd(), samePin) != d->m_pins.constEnd()) { return; } int s = d->m_pins.size(); beginInsertRows(QModelIndex(), s, s); d->m_pins += pin; endInsertRows(); if (pin->contact()) { contactChanged(pin->contact()); } connect(pin.data(), SIGNAL(contactChanged(KTp::ContactPtr)), SLOT(contactChanged(KTp::ContactPtr))); Q_EMIT stateChanged(); } void PinnedContactsModel::contactChanged(const KTp::ContactPtr &contact) { if (contact) { connect(contact.data(), SIGNAL(avatarDataChanged(Tp::AvatarData)), SLOT(contactDataChanged())); connect(contact.data(), SIGNAL(aliasChanged(QString)), SLOT(contactDataChanged())); connect(contact.data(), SIGNAL(presenceChanged(Tp::Presence)), SLOT(contactDataChanged())); } QModelIndex index = indexForContact(contact); Q_EMIT dataChanged(index, index); } void PinnedContactsModel::contactDataChanged() { KTp::Contact *c = qobject_cast(sender()); QModelIndex index = indexForContact(KTp::ContactPtr(c)); Q_EMIT dataChanged(index, index); } void PinnedContactsModel::setConversationsModel(ConversationsModel *model) { beginResetModel(); if (d->conversations) { disconnect(d->conversations, &QAbstractItemModel::rowsAboutToBeRemoved, this, &PinnedContactsModel::conversationsStateChanged); disconnect(d->conversations, &QAbstractItemModel::rowsInserted, this, &PinnedContactsModel::conversationsStateChanged); } d->conversations = model; if (model) { connect(d->conversations, &QAbstractItemModel::rowsAboutToBeRemoved, this, &PinnedContactsModel::conversationsStateChanged); connect(d->conversations, &QAbstractItemModel::rowsInserted, this, &PinnedContactsModel::conversationsStateChanged); } endResetModel(); } void PinnedContactsModel::conversationsStateChanged(const QModelIndex &parent, int start, int end) { for (int i = start; i <= end; i++) { QModelIndex idx = d->conversations->index(i, 0, parent); - Conversation* conv = idx.data(ConversationsModel::ConversationRole).value(); + Conversation *conv = idx.data(ConversationsModel::ConversationRole).value(); QString contactId = conv->targetContact()->id(); - Q_FOREACH(const KTp::PersistentContactPtr &p, d->m_pins) { + Q_FOREACH (const KTp::PersistentContactPtr &p, d->m_pins) { if (p->contactId() == contactId) { QModelIndex contactIdx = indexForContact(p->contact()); //We need to delay the dataChanged until the next event loop, when endRowsRemoved has been called QMetaObject::invokeMethod(this, "dataChanged", Qt::QueuedConnection, Q_ARG(QModelIndex, contactIdx), Q_ARG(QModelIndex, contactIdx)); } } } } ConversationsModel* PinnedContactsModel::conversationsModel() const { return d->conversations; } diff --git a/KTp/Declarative/pinned-contacts-model.h b/KTp/Declarative/pinned-contacts-model.h index 27835b1..82cf36d 100644 --- a/KTp/Declarative/pinned-contacts-model.h +++ b/KTp/Declarative/pinned-contacts-model.h @@ -1,80 +1,80 @@ /* Copyright (C) 2012 Aleix Pol 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 PINNEDCONTACTSMODEL_H #define PINNEDCONTACTSMODEL_H #include #include #include #include class ConversationsModel; class PinnedContactsModelPrivate; class PinnedContactsModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(ConversationsModel *conversations READ conversationsModel WRITE setConversationsModel) Q_PROPERTY(QStringList state READ state WRITE setState NOTIFY stateChanged) Q_PROPERTY(int count READ rowCount NOTIFY countChanged) public: explicit PinnedContactsModel(QObject *parent = 0); virtual ~PinnedContactsModel(); enum role { PresenceIconRole = Qt::UserRole + 1, AvailabilityRole, ContactRole, AccountRole, AlreadyChattingRole }; QHash roleNames() const Q_DECL_OVERRIDE; virtual QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; Q_SLOT void setPinning(const Tp::AccountPtr &account, const KTp::ContactPtr &contact, bool newState); QModelIndex indexForContact(const KTp::ContactPtr &contact) const; ConversationsModel* conversationsModel() const; void setConversationsModel(ConversationsModel *model); QStringList state() const; - void setState(const QStringList &s); + void setState(const QStringList &state); private Q_SLOTS: void contactDataChanged(); void contactChanged(const KTp::ContactPtr &contact); void conversationsStateChanged(const QModelIndex &parent, int start, int end); Q_SIGNALS: void countChanged(); void stateChanged(); private: void appendContactPin(const KTp::PersistentContactPtr &pin); void removeContactPin(const KTp::PersistentContactPtr &pin); PinnedContactsModelPrivate * const d; }; #endif // PINNEDCONTACTSMODEL_H