diff --git a/Linphone/core/chat/ChatCore.cpp b/Linphone/core/chat/ChatCore.cpp index bd1b38545..ee4bdb58d 100644 --- a/Linphone/core/chat/ChatCore.cpp +++ b/Linphone/core/chat/ChatCore.cpp @@ -23,6 +23,7 @@ #include "core/chat/message/content/ChatMessageContentGui.hpp" #include "core/friend/FriendCore.hpp" #include "core/setting/SettingsCore.hpp" +#include "model/chat/message/EventLogModel.hpp" #include "model/core/CoreModel.hpp" #include "model/friend/FriendModel.hpp" #include "model/tool/ToolModel.hpp" @@ -604,6 +605,10 @@ std::shared_ptr ChatCore::getModel() const { return mChatModel; } +QSharedPointer> ChatCore::getChatModelConnection() const { + return mChatModelConnection; +} + bool ChatCore::isMuted() const { return mIsMuted; } diff --git a/Linphone/core/chat/ChatCore.hpp b/Linphone/core/chat/ChatCore.hpp index c6c2b1fa7..fede2ce68 100644 --- a/Linphone/core/chat/ChatCore.hpp +++ b/Linphone/core/chat/ChatCore.hpp @@ -135,6 +135,7 @@ public: void setComposingAddress(QString composingAddress); std::shared_ptr getModel() const; + QSharedPointer> getChatModelConnection() const; QList> buildParticipants(const std::shared_ptr &chatRoom) const; QList> getParticipants() const; diff --git a/Linphone/core/chat/message/EventLogCore.cpp b/Linphone/core/chat/message/EventLogCore.cpp index 30fee03bc..57fad0fd8 100644 --- a/Linphone/core/chat/message/EventLogCore.cpp +++ b/Linphone/core/chat/message/EventLogCore.cpp @@ -21,6 +21,7 @@ #include "EventLogCore.hpp" #include "core/App.hpp" #include "core/chat/ChatCore.hpp" +#include "model/chat/message/EventLogModel.hpp" #include "model/tool/ToolModel.hpp" DEFINE_ABSTRACT_OBJECT(EventLogCore) @@ -36,6 +37,7 @@ EventLogCore::EventLogCore(const std::shared_ptr &even mustBeInLinphoneThread(getClassName()); App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); mEventLogType = LinphoneEnums::fromLinphone(eventLog->getType()); + mEventLogModel = Utils::makeQObject_ptr(eventLog); mTimestamp = QDateTime::fromMSecsSinceEpoch(eventLog->getCreationTime() * 1000); auto chatmessage = eventLog->getChatMessage(); if (chatmessage) { @@ -86,6 +88,10 @@ QDateTime EventLogCore::getTimestamp() const { return mTimestamp; } +std::shared_ptr EventLogCore::getModel() const { + return mEventLogModel; +} + // Events (other than ChatMessage and CallLog which are handled in their respective Core) void EventLogCore::computeEvent(const std::shared_ptr &eventLog) { diff --git a/Linphone/core/chat/message/EventLogCore.hpp b/Linphone/core/chat/message/EventLogCore.hpp index 600c71ea3..fe0b4c550 100644 --- a/Linphone/core/chat/message/EventLogCore.hpp +++ b/Linphone/core/chat/message/EventLogCore.hpp @@ -36,6 +36,7 @@ class ChatMessageCore; class ChatMessageGui; +class EventLogModel; class EventLogCore : public QObject, public AbstractObject { Q_OBJECT @@ -68,6 +69,8 @@ public: return mEphemeralRelated; } + std::shared_ptr getModel() const; + private: DECLARE_ABSTRACT_OBJECT QString mEventId; @@ -83,6 +86,7 @@ private: ChatMessageCore *getChatMessageCorePointer(); CallHistoryCore *getCallHistoryCorePointer(); + std::shared_ptr mEventLogModel; void computeEvent(const std::shared_ptr &eventLog); }; diff --git a/Linphone/core/chat/message/EventLogList.cpp b/Linphone/core/chat/message/EventLogList.cpp index 4dc787989..5c0dd5650 100644 --- a/Linphone/core/chat/message/EventLogList.cpp +++ b/Linphone/core/chat/message/EventLogList.cpp @@ -26,6 +26,7 @@ #include "core/call-history/CallHistoryGui.hpp" #include "core/chat/ChatCore.hpp" #include "core/chat/ChatGui.hpp" +#include "model/chat/message/EventLogModel.hpp" #include #include @@ -75,8 +76,11 @@ void EventLogList::connectItem(const QSharedPointer item) { void EventLogList::setChatCore(QSharedPointer core) { if (mChatCore != core) { - if (mChatCore) disconnect(mChatCore.get(), &ChatCore::eventListChanged, this, nullptr); - if (mChatCore) disconnect(mChatCore.get(), &ChatCore::eventsInserted, this, nullptr); + if (mChatCore) { + disconnect(mChatCore.get(), &ChatCore::eventListChanged, this, nullptr); + disconnect(mChatCore.get(), &ChatCore::eventsInserted, this, nullptr); + mModelConnection->disconnect(); + } mChatCore = core; if (mChatCore) { connect(mChatCore.get(), &ChatCore::eventListChanged, this, &EventLogList::lUpdate); @@ -93,6 +97,7 @@ void EventLogList::setChatCore(QSharedPointer core) { } } }); + mModelConnection = SafeConnection::create(mChatCore, mChatCore->getModel()); } emit eventChanged(); lUpdate(); @@ -112,6 +117,39 @@ int EventLogList::findFirstUnreadIndex() { return it == eventList.end() ? -1 : std::distance(eventList.begin(), it); } +void EventLogList::findChatMessageWithFilter(QString filter, + QSharedPointer startEvent, + bool forward, + bool isFirstResearch) { + if (mChatCore) { + auto modelConnection = mChatCore->getChatModelConnection(); + auto chatModel = mChatCore->getModel(); + auto startEventModel = startEvent ? startEvent->getModel() : nullptr; + modelConnection->invokeToModel( + [this, chatModel, startEventModel, filter, forward, modelConnection, isFirstResearch] { + auto linStartEvent = startEventModel ? startEventModel->getEventLog() : nullptr; + auto eventLog = chatModel->searchMessageByText(filter, linStartEvent, forward); + // If it is the first research and event was not found from the start event, search in the + // entire history + if (!eventLog && isFirstResearch) { + auto firstEvent = getAt(0); + auto linFirst = firstEvent ? firstEvent->getModel()->getEventLog() : nullptr; + eventLog = chatModel->searchMessageByText(filter, nullptr, forward); + } + int index = -1; + if (eventLog) { + auto eventList = getSharedList(); + auto it = std::find_if(eventList.begin(), eventList.end(), + [eventLog](const QSharedPointer item) { + return item->getModel()->getEventLog() == eventLog; + }); + index = it == eventList.end() ? -1 : std::distance(eventList.begin(), it); + } + modelConnection->invokeToCore([this, index] { emit messageWithFilterFound(index); }); + }); + } +} + void EventLogList::setSelf(QSharedPointer me) { connect(this, &EventLogList::lUpdate, this, [this]() { for (auto &event : getSharedList()) { diff --git a/Linphone/core/chat/message/EventLogList.hpp b/Linphone/core/chat/message/EventLogList.hpp index dd9d5d1ed..d0e65b108 100644 --- a/Linphone/core/chat/message/EventLogList.hpp +++ b/Linphone/core/chat/message/EventLogList.hpp @@ -30,6 +30,7 @@ class EventLogGui; class EventLogCore; class ChatCore; class ChatGui; +class ChatModel; // ============================================================================= class EventLogList : public ListProxy, public AbstractObject { @@ -48,6 +49,11 @@ public: int findFirstUnreadIndex(); + void findChatMessageWithFilter(QString filter, + QSharedPointer startEvent, + bool forward = true, + bool isFirstResearch = true); + void setSelf(QSharedPointer me); virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QHash roleNames() const override; @@ -57,10 +63,12 @@ signals: void filterChanged(QString filter); void eventChanged(); void eventInserted(int index, EventLogGui *message); + void messageWithFilterFound(int index); private: QString mFilter; QSharedPointer mChatCore; + QSharedPointer> mModelConnection; DECLARE_ABSTRACT_OBJECT }; diff --git a/Linphone/core/chat/message/EventLogProxy.cpp b/Linphone/core/chat/message/EventLogProxy.cpp index 8d8fb96ab..b34a5a066 100644 --- a/Linphone/core/chat/message/EventLogProxy.cpp +++ b/Linphone/core/chat/message/EventLogProxy.cpp @@ -51,8 +51,20 @@ void EventLogProxy::setSourceModel(QAbstractItemModel *model) { .row(); if (mMaxDisplayItems <= index) setMaxDisplayItems(index + mDisplayItemsStep); } + loadUntil(index); emit eventInserted(index, event); }); + connect(newEventLogList, &EventLogList::messageWithFilterFound, this, [this, newEventLogList](int index) { + if (index != -1) { + auto model = getListModel(); + mLastSearchStart = model->getAt(index); + index = dynamic_cast(sourceModel()) + ->mapFromSource(newEventLogList->index(index, 0)) + .row(); + loadUntil(index); + } + emit indexWithFilterFound(index); + }); } setSourceModels(new SortFilterList(model, Qt::DescendingOrder)); sort(0); @@ -69,14 +81,17 @@ void EventLogProxy::setChatGui(ChatGui *chat) { } EventLogGui *EventLogProxy::getEventAtIndex(int i) { - auto model = getListModel(); - auto sourceIndex = mapToSource(index(i, 0)).row(); - if (model) { - auto event = model->getAt(sourceIndex); - if (event) return new EventLogGui(event); - else return nullptr; - } - return nullptr; + auto eventCore = getEventCoreAtIndex(i); + return eventCore == nullptr ? nullptr : new EventLogGui(eventCore); +} + +QSharedPointer EventLogProxy::getEventCoreAtIndex(int i) { + return getItemAt(i); +} + +void EventLogProxy::loadUntil(int index) { + auto confInfoList = getListModel(); + if (mMaxDisplayItems < index) setMaxDisplayItems(index + mDisplayItemsStep); } int EventLogProxy::findFirstUnreadIndex() { @@ -100,21 +115,17 @@ void EventLogProxy::markIndexAsRead(int proxyIndex) { if (event && event->getChatMessageCore()) event->getChatMessageCore()->lMarkAsRead(); } -int EventLogProxy::findIndexCorrespondingToFilter(int startIndex, bool goingBackward) { +void EventLogProxy::findIndexCorrespondingToFilter(int startIndex, bool forward, bool isFirstResearch) { auto filter = getFilterText(); - if (filter.isEmpty()) return startIndex; - int endIndex = goingBackward ? 0 : getCount() - 1; - startIndex = goingBackward ? startIndex - 1 : startIndex + 1; - for (int i = startIndex; (goingBackward ? i >= 0 : i < getCount() - 1); (goingBackward ? --i : ++i)) { - auto eventLog = getItemAt(i); - if (!eventLog) continue; - if (auto message = eventLog->getChatMessageCore()) { - auto text = message->getText(); - int regexIndex = text.indexOf(filter, 0, Qt::CaseInsensitive); - if (regexIndex != -1) return i; + if (filter.isEmpty()) return; + auto eventLogList = getListModel(); + if (eventLogList) { + auto startEvent = mLastSearchStart; + if (!startEvent) { + startEvent = getItemAt(startIndex); } + eventLogList->findChatMessageWithFilter(filter, startEvent, forward, isFirstResearch); } - return -1; } bool EventLogProxy::SortFilterList::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { diff --git a/Linphone/core/chat/message/EventLogProxy.hpp b/Linphone/core/chat/message/EventLogProxy.hpp index 316efc7c0..fc117e4bf 100644 --- a/Linphone/core/chat/message/EventLogProxy.hpp +++ b/Linphone/core/chat/message/EventLogProxy.hpp @@ -44,17 +44,21 @@ public: void setSourceModel(QAbstractItemModel *sourceModel) override; - Q_INVOKABLE EventLogGui *getEventAtIndex(int index); + Q_INVOKABLE void loadUntil(int index); + Q_INVOKABLE EventLogGui *getEventAtIndex(int i); + QSharedPointer getEventCoreAtIndex(int i); Q_INVOKABLE int findFirstUnreadIndex(); Q_INVOKABLE void markIndexAsRead(int proxyIndex); - Q_INVOKABLE int findIndexCorrespondingToFilter(int startIndex, bool goingBackward = false); + Q_INVOKABLE void findIndexCorrespondingToFilter(int startIndex, bool forward = true, bool isFirstResearch = true); signals: void eventChanged(); void eventInserted(int index, EventLogGui *message); + void indexWithFilterFound(int index); protected: QSharedPointer mList; + QSharedPointer mLastSearchStart; ChatGui *mChatGui = nullptr; DECLARE_ABSTRACT_OBJECT }; diff --git a/Linphone/data/image/arrow-square-out.svg b/Linphone/data/image/arrow-square-out.svg new file mode 100644 index 000000000..471fbeb3d --- /dev/null +++ b/Linphone/data/image/arrow-square-out.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Linphone/data/languages/de.ts b/Linphone/data/languages/de.ts index fe95b0e3c..db2676323 100644 --- a/Linphone/data/languages/de.ts +++ b/Linphone/data/languages/de.ts @@ -1748,13 +1748,13 @@ ChatCore - + info_toast_deleted_title Deleted - + info_toast_deleted_message_history Message history has been deleted @@ -2072,45 +2072,45 @@ Error ChatMessagesListView - - + + popup_info_find_message_title Find message - + info_popup_no_result_message No result found - + info_popup_first_result_message First result reached - + info_popup_last_result_message Last result reached - + chat_message_list_encrypted_header_title End to end encrypted chat - + chat_message_list_encrypted_header_message Les messages de cette conversation sont chiffrés de bout en bout. Seul votre correspondant peut les déchiffrer. - + chat_message_is_writing_info %1 is writing… @@ -2825,142 +2825,143 @@ Error ConversationInfos - + one_one_infos_call "Appel" Anrufen - + one_one_infos_unmute "Sourdine" Unmute - + one_one_infos_mute Stummschalten - one_one_infos_search "Rechercher" - Suchen + Suchen - + group_infos_participants Participants (%1) - + group_infos_media_docs Medias & documents Medien & Dokumente - + group_infos_shared_medias Shared medias - + group_infos_shared_docs Shared documents Geteilte Dokumente - + group_infos_other_actions Other actions Weitere Aktionen - + group_infos_ephemerals Ephemeral messages : - + group_infos_enable_ephemerals Flüchtige Nachrichten aktivieren - + group_infos_meeting Schedule a meeting Meeting - + group_infos_leave_room Leave chat room - + group_infos_leave_room_toast_title Leave Chat Room ? Chatraum verlassen? - + group_infos_leave_room_toast_message All the messages will be removed from the chat room. Do you want to continue ? Alle Nachrichten werden aus dem Chat entfernt. Möchten Sie fortfahren? - + group_infos_delete_history Delete history Verlauf löschen - + group_infos_delete_history_toast_title Delete history ? Verlauf löschen? - + group_infos_delete_history_toast_message All the messages will be removed from the chat room. Do you want to continue ? Alle Nachrichten werden aus dem Chat entfernt. Möchten Sie fortfahren? - + one_one_infos_open_contact + Show contact Kontakt öffnen - + one_one_infos_create_contact + Create contact Kontakt erstellen - + one_one_infos_ephemerals Ephemeral messages : - + one_one_infos_enable_ephemerals Flüchtige Nachrichten aktivieren - + one_one_infos_delete_history Verlauf löschen - + one_one_infos_delete_history_toast_title Delete history ? Verlauf löschen? - + one_one_infos_delete_history_toast_message All the messages will be removed from the chat room. Do you want to continue ? Alle Nachrichten werden aus dem Chat entfernt. Möchten Sie fortfahren? @@ -3198,58 +3199,58 @@ Error EventLogCore - + conference_created_event - + conference_created_terminated - + conference_participant_added_event - + conference_participant_removed_event - - + + conference_security_event - + conference_ephemeral_message_enabled_event - + conference_ephemeral_message_lifetime_changed_event - + conference_ephemeral_message_disabled_event - + conference_subject_changed_event - + conference_participant_unset_admin_event - + conference_participant_set_admin_event @@ -4982,36 +4983,36 @@ Pour les activer dans un projet commercial, merci de nous contacter. SelectedChatView - + chat_view_group_call_toast_message Start a group call ? - + reply_to_label Reply to %1 - + shared_medias_title Shared medias - + shared_documents_title Shared documents - + forward_to_title Forward to… - + conversations_title Conversations diff --git a/Linphone/data/languages/en.ts b/Linphone/data/languages/en.ts index 4b8222903..44f462833 100644 --- a/Linphone/data/languages/en.ts +++ b/Linphone/data/languages/en.ts @@ -1710,13 +1710,13 @@ ChatCore - + info_toast_deleted_title Deleted Deleted - + info_toast_deleted_message_history Message history has been deleted Message history has been deleted @@ -2034,38 +2034,38 @@ Error ChatMessagesListView - - + + popup_info_find_message_title Find message Find message - + info_popup_no_result_message No result found No result found - + info_popup_first_result_message First result reached First result reached - + info_popup_last_result_message Last result reached Last result reached - + chat_message_list_encrypted_header_title End to end encrypted chat End to end encrypted chat - + chat_message_list_encrypted_header_message Les messages de cette conversation sont chiffrés de bout en bout. Seul votre correspondant peut les déchiffrer. @@ -2073,7 +2073,7 @@ Error Only your correspondent can decrypt them. - + chat_message_is_writing_info %1 is writing… %1 is writing… @@ -2748,142 +2748,138 @@ Only your correspondent can decrypt them. ConversationInfos - + one_one_infos_call "Appel" Call - + one_one_infos_unmute "Sourdine" Unmute - + one_one_infos_mute Mute - - one_one_infos_search - "Rechercher" - Search - - - + group_infos_participants Participants (%1) - + group_infos_media_docs Medias & documents Medias & documents - + group_infos_shared_medias Shared medias Shared medias - + group_infos_shared_docs Shared documents Shared documents - + group_infos_other_actions Other actions Other actions - + group_infos_ephemerals Ephemeral messages : - + group_infos_enable_ephemerals Enable ephemeral messages - + group_infos_meeting Schedule a meeting Schedule a meeting - + group_infos_leave_room Leave chat room Leave Chat Room - + group_infos_leave_room_toast_title Leave Chat Room ? Leave Chat Room ? - + group_infos_leave_room_toast_message All the messages will be removed from the chat room. Do you want to continue ? All the messages will be removed from the chat room. Do you want to continue ? - + group_infos_delete_history Delete history Delete history - + group_infos_delete_history_toast_title Delete history ? Delete history ? - + group_infos_delete_history_toast_message All the messages will be removed from the chat room. Do you want to continue ? All the messages will be removed from the chat room. Do you want to continue ? - + one_one_infos_open_contact + Show contact Show contact - + one_one_infos_create_contact + Create contact Create contact - + one_one_infos_ephemerals Ephemeral messages : - + one_one_infos_enable_ephemerals Enable ephemeral messages - + one_one_infos_delete_history Delete history - + one_one_infos_delete_history_toast_title Delete history ? Delete history ? - + one_one_infos_delete_history_toast_message All the messages will be removed from the chat room. Do you want to continue ? All the messages will be removed from the chat room. Do you want to continue ? @@ -3121,59 +3117,59 @@ Only your correspondent can decrypt them. EventLogCore - + conference_created_event You have joined the group - + conference_created_terminated You have left the group - + conference_participant_added_event %1 has joined - + conference_participant_removed_event %1 is no longer in the conversation - + conference_participant_set_admin_event %1 is now an admin - + conference_participant_unset_admin_event %1 is no longer an admin - - + + conference_security_event Security level degraded by %1 - + conference_ephemeral_message_enabled_event Ephemeral messages enabled Expiration : %1 - + conference_ephemeral_message_disabled_event Ephemeral messages disabled - + conference_subject_changed_event New subject: %1 - + conference_ephemeral_message_lifetime_changed_event Ephemeral messages updated Expiration : %1 @@ -4881,36 +4877,36 @@ To enable them in a commercial project, please contact us. SelectedChatView - + chat_view_group_call_toast_message Start a group call ? - + reply_to_label Reply to %1 Reply to %1 - + shared_medias_title Shared medias Shared medias - + shared_documents_title Shared documents Shared documents - + forward_to_title Forward to… Froward to… - + conversations_title Conversations Conversations diff --git a/Linphone/data/languages/fr_FR.ts b/Linphone/data/languages/fr_FR.ts index a7160862b..303d2f4a9 100644 --- a/Linphone/data/languages/fr_FR.ts +++ b/Linphone/data/languages/fr_FR.ts @@ -1710,13 +1710,13 @@ ChatCore - + info_toast_deleted_title Deleted Supprimé - + info_toast_deleted_message_history Message history has been deleted L'historique des messages a été supprimé @@ -2034,38 +2034,38 @@ Error ChatMessagesListView - - + + popup_info_find_message_title Find message Trouver un message - + info_popup_no_result_message No result found Aucun résultat trouvé - + info_popup_first_result_message First result reached Premier résultat atteint - + info_popup_last_result_message Last result reached Dernier résultat atteint - + chat_message_list_encrypted_header_title End to end encrypted chat Conversation chiffrée de bout en bout - + chat_message_list_encrypted_header_message Les messages de cette conversation sont chiffrés de bout en bout. Seul votre correspondant peut les déchiffrer. @@ -2073,7 +2073,7 @@ Error en bout. Seul votre correspondant peut les déchiffrer. - + chat_message_is_writing_info %1 is writing… %1 est en train d'écrire… @@ -2748,142 +2748,138 @@ en bout. Seul votre correspondant peut les déchiffrer. ConversationInfos - + one_one_infos_call "Appel" Appel - + one_one_infos_unmute "Sourdine" Réactiver les notifications - + one_one_infos_mute Sourdine - - one_one_infos_search - "Rechercher" - Rechercher - - - + group_infos_participants Participants (%1) - + group_infos_media_docs Medias & documents Medias & documents - + group_infos_shared_medias Shared medias Médias partagés - + group_infos_shared_docs Shared documents Documents partagés - + group_infos_other_actions Other actions Autres actions - + group_infos_ephemerals Messages éphémères : - + group_infos_enable_ephemerals Activer les messages éphémères - + group_infos_meeting Schedule a meeting Programmer une réunion - + group_infos_leave_room Leave chat room Quitter la conversation - + group_infos_leave_room_toast_title Leave Chat Room ? Quitter la conversation ? - + group_infos_leave_room_toast_message All the messages will be removed from the chat room. Do you want to continue ? Vous ne recevrez ni pourrez envoyer des messages dans cette conversation, quitter ? - + group_infos_delete_history Delete history Supprimer l'historique - + group_infos_delete_history_toast_title Delete history ? Supprimer l'historique ? - + group_infos_delete_history_toast_message All the messages will be removed from the chat room. Do you want to continue ? Tous les messages seront supprimés. Souhaitez-vous continuer ? - + one_one_infos_open_contact + Show contact Voir le contact - + one_one_infos_create_contact + Create contact Créer un contact - + one_one_infos_ephemerals Messages éphémères : - + one_one_infos_enable_ephemerals Activer les messages éphémères - + one_one_infos_delete_history Supprimer l'historique - + one_one_infos_delete_history_toast_title Delete history ? Supprimer l'historique ? - + one_one_infos_delete_history_toast_message All the messages will be removed from the chat room. Do you want to continue ? Tous les messages seront supprimés. Souhaitez-vous continuer ? @@ -3121,60 +3117,60 @@ en bout. Seul votre correspondant peut les déchiffrer. EventLogCore - + conference_created_event Vous avez rejoint le groupe - + conference_created_terminated Vous avez quitté le groupe - + conference_participant_added_event %1 a rejoint le groupe - + conference_participant_removed_event %1 ne fait plus partie du groupe - - + + conference_security_event Niveau de sécurité dégradé par %1 - + conference_ephemeral_message_enabled_event Messages éphémères activés Expiration : %1 - + conference_ephemeral_message_lifetime_changed_event Messages éphémères mis à jour Expiration : %1 - + conference_ephemeral_message_disabled_event Messages éphémères désactivés - + conference_subject_changed_event Nouveau sujet : %1 - + conference_participant_unset_admin_event %1 n'est plus admin - + conference_participant_set_admin_event %1 est maintenant admin @@ -4881,36 +4877,36 @@ Pour les activer dans un projet commercial, merci de nous contacter. SelectedChatView - + chat_view_group_call_toast_message Démarrer un appel de groupe ? - + reply_to_label Reply to %1 Réponse à %1 - + shared_medias_title Shared medias Médias partagés - + shared_documents_title Shared documents Documents partagés - + forward_to_title Forward to… Transférer à… - + conversations_title Conversations Conversations diff --git a/Linphone/model/CMakeLists.txt b/Linphone/model/CMakeLists.txt index 63e437cc5..3680900c2 100644 --- a/Linphone/model/CMakeLists.txt +++ b/Linphone/model/CMakeLists.txt @@ -13,6 +13,7 @@ list(APPEND _LINPHONEAPP_SOURCES model/chat/ChatModel.cpp model/chat/message/ChatMessageModel.cpp + model/chat/message/EventLogModel.cpp model/chat/message/content/ChatMessageContentModel.cpp model/conference/ConferenceInfoModel.cpp diff --git a/Linphone/model/chat/ChatModel.cpp b/Linphone/model/chat/ChatModel.cpp index c96cca920..8187c8e00 100644 --- a/Linphone/model/chat/ChatModel.cpp +++ b/Linphone/model/chat/ChatModel.cpp @@ -175,6 +175,12 @@ ChatModel::createMessage(QString text, QList +ChatModel::searchMessageByText(QString text, std::shared_ptr from, bool forward) { + return mMonitor->searchChatMessageByText(Utils::appStringToCoreString(text), from, + forward ? linphone::SearchDirection::Down : linphone::SearchDirection::Up); +} + void ChatModel::compose() { mMonitor->compose(); } diff --git a/Linphone/model/chat/ChatModel.hpp b/Linphone/model/chat/ChatModel.hpp index 61f9042b4..96bacf187 100644 --- a/Linphone/model/chat/ChatModel.hpp +++ b/Linphone/model/chat/ChatModel.hpp @@ -61,6 +61,10 @@ public: std::shared_ptr createTextMessageFromText(QString text); std::shared_ptr createMessage(QString text, QList> filesContent); + + std::shared_ptr + searchMessageByText(QString text, std::shared_ptr = nullptr, bool forward = true); + void compose(); linphone::ChatRoom::State getState() const; void setMuted(bool muted); diff --git a/Linphone/model/chat/message/EventLogModel.cpp b/Linphone/model/chat/message/EventLogModel.cpp new file mode 100644 index 000000000..0aa97c3a3 --- /dev/null +++ b/Linphone/model/chat/message/EventLogModel.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "EventLogModel.hpp" + +#include + +#include "core/path/Paths.hpp" +#include "model/core/CoreModel.hpp" +#include "model/setting/SettingsModel.hpp" +#include "model/tool/ToolModel.hpp" +#include "tool/Utils.hpp" + +DEFINE_ABSTRACT_OBJECT(EventLogModel) + +EventLogModel::EventLogModel(const std::shared_ptr &eventLog, QObject *parent) + : mEventLog(eventLog) { + // lDebug() << "[EventLogModel] new" << this << " / SDKModel=" << eventLog.get(); + mustBeInLinphoneThread(getClassName()); +} + +EventLogModel::~EventLogModel() { + mustBeInLinphoneThread("~" + getClassName()); +} + +std::shared_ptr EventLogModel::getEventLog() const { + return mEventLog; +} diff --git a/Linphone/model/chat/message/EventLogModel.hpp b/Linphone/model/chat/message/EventLogModel.hpp new file mode 100644 index 000000000..966fd62be --- /dev/null +++ b/Linphone/model/chat/message/EventLogModel.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2010-2024 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EVENT_LOG_MODEL_H_ +#define EVENT_LOG_MODEL_H_ + +#include "model/listener/Listener.hpp" +#include "tool/AbstractObject.hpp" +#include "tool/LinphoneEnums.hpp" + +#include +#include +#include + +class EventLogModel : public QObject, public AbstractObject { + Q_OBJECT +public: + EventLogModel(const std::shared_ptr &eventLog, QObject *parent = nullptr); + ~EventLogModel(); + + std::shared_ptr getEventLog() const; + +private: + std::shared_ptr mEventLog; + DECLARE_ABSTRACT_OBJECT +}; + +#endif \ No newline at end of file diff --git a/Linphone/view/Control/Button/LabelButton.qml b/Linphone/view/Control/Button/LabelButton.qml index 544303662..997509242 100644 --- a/Linphone/view/Control/Button/LabelButton.qml +++ b/Linphone/view/Control/Button/LabelButton.qml @@ -28,6 +28,7 @@ ColumnLayout { Text { id: text Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true text: labelButton.label font { pixelSize: Typography.p1.pixelSize diff --git a/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml b/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml index a6116d3cc..e49194d90 100644 --- a/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml +++ b/Linphone/view/Control/Display/Chat/ChatMessagesListView.qml @@ -13,6 +13,7 @@ ListView { property ChatGui chat property color backgroundColor property bool lastItemVisible: false + property int lastIndexFoundWithFilter: -1 verticalLayoutDirection: ListView.BottomToTop signal showReactionsForMessageRequested(ChatMessageGui chatMessage) signal showImdnStatusForMessageRequested(ChatMessageGui chatMessage) @@ -20,39 +21,21 @@ ListView { signal forwardMessageRequested(ChatMessageGui chatMessage) signal requestHighlight(int indexToHighlight) signal requestAutoPlayVoiceRecording(int indexToPlay) + currentIndex: -1 property string filterText onFilterTextChanged: { + lastIndexFoundWithFilter = -1 if (filterText === "") return eventLogProxy.filterText = filterText var indexVisible = indexAt(contentX, contentY) - var found = eventLogProxy.findIndexCorrespondingToFilter(indexVisible) - if (found !== -1) { - currentIndex = found - positionViewAtIndex(found, ListView.Center) - requestHighlight(found) - } else { - //: Find message - UtilsCpp.showInformationPopup(qsTr("popup_info_find_message_title"), - //: No result found - qsTr("info_popup_no_result_message"), false) - } + eventLogProxy.findIndexCorrespondingToFilter(indexVisible, true, true) } - signal findIndexWithFilter(bool goingBackward) - onFindIndexWithFilter: (goingBackward) => { - var nextIndex = eventLogProxy.findIndexCorrespondingToFilter(currentIndex, goingBackward) - if (nextIndex !== -1 && nextIndex !== currentIndex) { - currentIndex = nextIndex - positionViewAtIndex(nextIndex, ListView.Center) - requestHighlight(nextIndex) - } else if (currentIndex !== -1) { - //: Find message - UtilsCpp.showInformationPopup(qsTr("popup_info_find_message_title"), - //: First result reached - goingBackward ? qsTr("info_popup_first_result_message") - //: Last result reached - : qsTr("info_popup_last_result_message"), false) - } + signal findIndexWithFilter(bool forward) + property bool searchForward: true + onFindIndexWithFilter: (forward) => { + searchForward = forward + eventLogProxy.findIndexCorrespondingToFilter(currentIndex, forward, false) } Component.onCompleted: { @@ -62,10 +45,7 @@ ListView { eventLogProxy.markIndexAsRead(index) }) } - - onCountChanged: if (atYEnd) { - positionViewAtEnd() - } + onChatChanged: lastItemVisible = false Button { @@ -109,6 +89,32 @@ ListView { positionViewAtIndex(index, ListView.Beginning) eventLogProxy.markIndexAsRead(index) }) + onIndexWithFilterFound: (index) => { + if (index !== -1) { + currentIndex = index + mainItem.positionViewAtIndex(index, ListView.Center) + mainItem.requestHighlight(index) + mainItem.lastIndexFoundWithFilter = index + } else { + if (mainItem.lastIndexFoundWithFilter !== index) { + //: Find message + UtilsCpp.showInformationPopup(qsTr("popup_info_find_message_title"), + mainItem.searchForward + //: Last result reached + ? qsTr("info_popup_last_result_message") + //: First result reached + : qsTr("info_popup_first_result_message"), false) + mainItem.positionViewAtIndex(mainItem.lastIndexFoundWithFilter, ListView.Center) + mainItem.requestHighlight(mainItem.lastIndexFoundWithFilter) + } + else { + //: Find message + UtilsCpp.showInformationPopup(qsTr("popup_info_find_message_title"), + //: No result found + qsTr("info_popup_no_result_message"), false) + } + } + } } footer: Item { diff --git a/Linphone/view/Page/Form/Chat/SelectedChatView.qml b/Linphone/view/Page/Form/Chat/SelectedChatView.qml index 1d10f2711..b444c28fe 100644 --- a/Linphone/view/Page/Form/Chat/SelectedChatView.qml +++ b/Linphone/view/Page/Form/Chat/SelectedChatView.qml @@ -19,7 +19,6 @@ RowLayout { property CallGui call property alias callHeaderContent: splitPanel.headerContentItem property bool replyingToMessage: false - property bool showSearchBar: false spacing: 0 enum PanelType { MessageReactions, SharedFiles, Medias, ImdnStatus, ForwardToList, ManageParticipants, EphemeralSettings, None} @@ -51,6 +50,10 @@ RowLayout { }) } + Keys.onPressed: (event) => { + if (event.modifiers & Qt.ControlModifier && event.key === Qt.Key_F) searchBarLayout.visible = true + } + //onEventChanged: { // TODO : call when all messages read after scroll to unread feature available // if (chat) chat.core.lMarkAsRead() @@ -65,7 +68,7 @@ RowLayout { header.leftPadding: Math.round(32 * DefaultStyle.dp) header.rightPadding: Math.round(32 * DefaultStyle.dp) header.topPadding: Math.round(6 * DefaultStyle.dp) - header.bottomPadding: mainItem.showSearchBar ? Math.round(3 * DefaultStyle.dp) : Math.round(6 * DefaultStyle.dp) + header.bottomPadding: searchBarLayout.visible ? Math.round(3 * DefaultStyle.dp) : Math.round(6 * DefaultStyle.dp) headerContentItem: ColumnLayout { anchors.left: parent?.left @@ -119,6 +122,18 @@ RowLayout { } } } + RoundButton { + id: searchInHistoryButton + style: ButtonStyle.noBackground + icon.source: AppIcons.search + checkable: true + checkedImageColor: DefaultStyle.main1_500_main + onCheckedChanged: searchBarLayout.visible = checked + Connections { + target: searchBarLayout + function onVisibleChanged() {searchInHistoryButton.checked = searchBarLayout.visible} + } + } RoundButton { style: ButtonStyle.noBackground icon.source: AppIcons.videoCamera @@ -140,7 +155,7 @@ RowLayout { } RowLayout { id: searchBarLayout - visible: mainItem.showSearchBar + visible: searchInHistoryButton.checked onVisibleChanged: { if(!visible) chatMessagesSearchBar.clearText() else chatMessagesSearchBar.forceActiveFocus() @@ -151,18 +166,24 @@ RowLayout { id: chatMessagesSearchBar Layout.fillWidth: true Layout.rightMargin: Math.round(10 * DefaultStyle.dp) + property ChatMessageGui messageFound delaySearch: false Keys.onPressed: (event) => { + event.accepted = false if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { if (chatMessagesListView.filterText !== text) { chatMessagesListView.filterText = text } else { if (event.modifiers & Qt.ShiftModifier) { - chatMessagesListView.findIndexWithFilter(true) - } else { chatMessagesListView.findIndexWithFilter(false) + } else { + chatMessagesListView.findIndexWithFilter(true) } } + event.accepted = true + } else if (event.key === Qt.Key_Escape) { + searchBarLayout.visible = false + event.accepted = true } } } @@ -171,18 +192,21 @@ RowLayout { RoundButton { icon.source: AppIcons.upArrow style: ButtonStyle.noBackground - onClicked: chatMessagesListView.findIndexWithFilter(true) + onClicked: chatMessagesListView.findIndexWithFilter(false) } RoundButton { icon.source: AppIcons.downArrow style: ButtonStyle.noBackground - onClicked: chatMessagesListView.findIndexWithFilter(false) + onClicked: chatMessagesListView.findIndexWithFilter(true) } } RoundButton { icon.source: AppIcons.closeX Layout.rightMargin: Math.round(20 * DefaultStyle.dp) - onClicked: mainItem.showSearchBar = false + onClicked: { + chatMessagesListView.filterText = "" + searchBarLayout.visible = false + } style: ButtonStyle.noBackground } } @@ -204,7 +228,7 @@ RowLayout { ChatMessagesListView { id: chatMessagesListView clip: true - height: contentHeight + height: implicitHeight backgroundColor: splitPanel.panelColor width: parent.width - anchors.leftMargin - anchors.rightMargin chat: mainItem.chat @@ -502,6 +526,7 @@ RowLayout { // anchors.top: parent.top anchors.fill: parent anchors.topMargin: Math.round(39 * DefaultStyle.dp) + anchors.rightMargin: Math.round(15 * DefaultStyle.dp) sourceComponent: panelType === SelectedChatView.PanelType.EphemeralSettings ? ephemeralSettingsComponent : panelType === SelectedChatView.PanelType.MessageReactions @@ -531,10 +556,6 @@ RowLayout { onEphemeralSettingsRequested: contentLoader.panelType = SelectedChatView.PanelType.EphemeralSettings onShowSharedFilesRequested: (showMedias) => { contentLoader.panelType = showMedias ? SelectedChatView.PanelType.Medias : SelectedChatView.PanelType.SharedFiles - } - onSearchInHistoryRequested: { - mainItem.showSearchBar = true - detailsPanel.visible = false } onManageParticipantsRequested: contentLoader.panelType = SelectedChatView.PanelType.ManageParticipants } diff --git a/Linphone/view/Page/Layout/Chat/ConversationInfos.qml b/Linphone/view/Page/Layout/Chat/ConversationInfos.qml index ec559fb6e..3852a6262 100644 --- a/Linphone/view/Page/Layout/Chat/ConversationInfos.qml +++ b/Linphone/view/Page/Layout/Chat/ConversationInfos.qml @@ -19,7 +19,6 @@ ColumnLayout { spacing: 0 signal ephemeralSettingsRequested() signal showSharedFilesRequested(bool showMedias) - signal searchInHistoryRequested() signal manageParticipantsRequested() Avatar { @@ -202,6 +201,7 @@ ColumnLayout { } } LabelButton { + visible: !mainItem.isGroup || !SettingsCpp.disableMeetingsFeature text.Layout.fillWidth: true text.horizontalAlignment: Text.AlignHCenter text.wrapMode: Text.Wrap @@ -210,11 +210,28 @@ ColumnLayout { Layout.maximumWidth: Math.round(130 * DefaultStyle.dp) button.icon.width: Math.round(24 * DefaultStyle.dp) button.icon.height: Math.round(24 * DefaultStyle.dp) - button.icon.source: AppIcons.search - //: "Rechercher" - label: qsTr("one_one_infos_search") + button.icon.source: mainItem.isGroup + ? AppIcons.videoconference + : contactObj.value + ? AppIcons.adressBook + : AppIcons.plusCircle + label: mainItem.isGroup + //: Schedule a meeting + ? qsTr("group_infos_meeting") + : contactObj.value + //: Show contact + ? qsTr("one_one_infos_open_contact") + //: Create contact + : qsTr("one_one_infos_create_contact") button.onClicked: { - mainItem.searchInHistoryRequested() + if (mainItem.isGroup) + UtilsCpp.getMainWindow().scheduleMeeting(mainItem.chatCore.title, mainItem.chatCore.participantsAddresses) + else { + if (contactObj.value) + mainWindow.displayContactPage(contactObj.value.core.defaultAddress) + else + mainWindow.displayCreateContactPage("",mainItem.chatCore.peerAddress) + } } } } @@ -226,7 +243,6 @@ ColumnLayout { Layout.topMargin: Math.round(30 * DefaultStyle.dp) clip: true Layout.leftMargin: Math.round(15 * DefaultStyle.dp) - Layout.rightMargin: Math.round(15 * DefaultStyle.dp) ColumnLayout { spacing: 0 @@ -298,17 +314,6 @@ ColumnLayout { mainItem.ephemeralSettingsRequested() } }, - { - visible: !SettingsCpp.disableMeetingsFeature, - icon: AppIcons.videoconference, - color: DefaultStyle.main2_600, - showRightArrow: false, - //: Schedule a meeting - text: qsTr("group_infos_meeting"), - action: function() { - UtilsCpp.getMainWindow().scheduleMeeting(mainItem.chatCore.title, mainItem.chatCore.participantsAddresses) - } - }, { icon: AppIcons.signOut, visible: !mainItem.chatCore.isReadOnly, @@ -351,20 +356,6 @@ ColumnLayout { } ] : [ - { - icon: contactObj.value ? AppIcons.adressBook : AppIcons.plusCircle, - visible: true, - text: contactObj.value ? qsTr("one_one_infos_open_contact") : qsTr("one_one_infos_create_contact"), - color: DefaultStyle.main2_600, - showRightArrow: false, - action: function() { - // contactObj.value = friendGui - if (contactObj.value) - mainWindow.displayContactPage(contactObj.value.core.defaultAddress) - else - mainWindow.displayCreateContactPage("",mainItem.chatCore.peerAddress) - } - }, { icon: AppIcons.clockCountDown, visible: !mainItem.chatCore.isReadOnly,