mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
Merge pull request #6374
Anthony Bilinski (20): revert(chatlog): "fix stick to bottom behavior" revert(chatlog): partially revert "prevent invalid history access" revert(chatlog): "add comments for functions that load history" revert(chatlog): "scroll bar stuck to bottom (fix #5755)" revert(chatlog): "update workerStb" revert(chatlog): "optimize load messages during the search" revert(chatlog): "feat: save selected search text after scrolling up" revert(chatlog): "feat: check chat status before start a search" revert(chatlog): "fix: data validation during the search" revert(chatlog): "fix a crash when there are no messages to load" revert(chatlog): "prohibition to remove messages in group chat" revert(chatlog): "edit load history when scrolling" revert(chatlog): "simple edit code" revert(chatlog): "remove part messages from chat" revert(chatlog): "edit position chat after load history" revert(chatlog): "add action "Go to current date"" revert(chatlog): "edit load history in search" revert(chatlog): "edit function "Load chat history"" revert(chatlog): "load messages from the database after date" chore(review): Copyright notice cleanup Mick Sayson (8): fix(history): Fix qt deprecation warning revert(chatlog): "feat: load messages from the database before date" revert(chatlog): Revert cleanup refactor(chatlog): Move rendering of messages from GenericChatForm -> ChatLog refactor(chatlog): Store ChatLine::Ptr in messages instead of ChatMessage feat(chatlog): Re-implement sliding window ChatLog view refactor(chatlog): Remove unused getRow functions from ChatLine refactor(chatlog): Rename ChatLog -> ChatWidget
This commit is contained in:
commit
6a10abf1b3
|
@ -201,8 +201,10 @@ set(${PROJECT_NAME}_SOURCES
|
|||
src/chatlog/chatlinecontentproxy.h
|
||||
src/chatlog/chatline.cpp
|
||||
src/chatlog/chatline.h
|
||||
src/chatlog/chatlog.cpp
|
||||
src/chatlog/chatlog.h
|
||||
src/chatlog/chatlinestorage.cpp
|
||||
src/chatlog/chatlinestorage.h
|
||||
src/chatlog/chatwidget.cpp
|
||||
src/chatlog/chatwidget.h
|
||||
src/chatlog/chatmessage.cpp
|
||||
src/chatlog/chatmessage.h
|
||||
src/chatlog/content/filetransferwidget.cpp
|
||||
|
|
|
@ -45,6 +45,7 @@ auto_test(core toxid "")
|
|||
auto_test(core toxstring "")
|
||||
auto_test(chatlog textformatter "")
|
||||
auto_test(net bsu "${${PROJECT_NAME}_RESOURCES}") # needs nodes list
|
||||
auto_test(chatlog chatlinestorage "")
|
||||
auto_test(persistence paths "")
|
||||
auto_test(persistence dbschema "")
|
||||
auto_test(persistence offlinemsgengine "")
|
||||
|
|
|
@ -37,14 +37,6 @@ ChatLine::~ChatLine()
|
|||
}
|
||||
}
|
||||
|
||||
void ChatLine::setRow(int idx)
|
||||
{
|
||||
row = idx;
|
||||
|
||||
for (int c = 0; c < static_cast<int>(content.size()); ++c)
|
||||
content[c]->setIndex(row, c);
|
||||
}
|
||||
|
||||
void ChatLine::visibilityChanged(bool visible)
|
||||
{
|
||||
if (isVisible != visible) {
|
||||
|
@ -55,11 +47,6 @@ void ChatLine::visibilityChanged(bool visible)
|
|||
isVisible = visible;
|
||||
}
|
||||
|
||||
int ChatLine::getRow() const
|
||||
{
|
||||
return row;
|
||||
}
|
||||
|
||||
ChatLineContent* ChatLine::getContent(int col) const
|
||||
{
|
||||
if (col < static_cast<int>(content.size()) && col >= 0)
|
||||
|
@ -153,6 +140,7 @@ void ChatLine::addColumn(ChatLineContent* item, ColumnFormat fmt)
|
|||
|
||||
format.push_back(fmt);
|
||||
content.push_back(item);
|
||||
item->setIndex(0, content.size() -1 );
|
||||
}
|
||||
|
||||
void ChatLine::replaceContent(int col, ChatLineContent* lineContent)
|
||||
|
@ -262,8 +250,3 @@ bool ChatLine::lessThanBSRectBottom(const ChatLine::Ptr& lhs, const qreal& rhs)
|
|||
{
|
||||
return lhs->sceneBoundingRect().bottom() < rhs;
|
||||
}
|
||||
|
||||
bool ChatLine::lessThanRowIndex(const ChatLine::Ptr& lhs, const ChatLine::Ptr& rhs)
|
||||
{
|
||||
return lhs->getRow() < rhs->getRow();
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
#include <QVector>
|
||||
#include <memory>
|
||||
|
||||
class ChatLog;
|
||||
class ChatWidget;
|
||||
class ChatLineContent;
|
||||
class QGraphicsScene;
|
||||
class QStyleOptionGraphicsItem;
|
||||
|
@ -84,7 +84,6 @@ public:
|
|||
void reloadTheme();
|
||||
|
||||
int getColumnCount();
|
||||
int getRow() const;
|
||||
|
||||
ChatLineContent* getContent(int col) const;
|
||||
ChatLineContent* getContent(QPointF scenePos) const;
|
||||
|
@ -94,16 +93,14 @@ public:
|
|||
// comparators
|
||||
static bool lessThanBSRectTop(const ChatLine::Ptr& lhs, const qreal& rhs);
|
||||
static bool lessThanBSRectBottom(const ChatLine::Ptr& lhs, const qreal& rhs);
|
||||
static bool lessThanRowIndex(const ChatLine::Ptr& lhs, const ChatLine::Ptr& rhs);
|
||||
|
||||
protected:
|
||||
friend class ChatLog;
|
||||
friend class ChatWidget;
|
||||
|
||||
QPointF mapToContent(ChatLineContent* c, QPointF pos);
|
||||
|
||||
void addColumn(ChatLineContent* item, ColumnFormat fmt);
|
||||
void updateBBox();
|
||||
void setRow(int idx);
|
||||
void visibilityChanged(bool visible);
|
||||
|
||||
private:
|
||||
|
|
|
@ -30,11 +30,6 @@ int ChatLineContent::getColumn() const
|
|||
return col;
|
||||
}
|
||||
|
||||
int ChatLineContent::getRow() const
|
||||
{
|
||||
return row;
|
||||
}
|
||||
|
||||
int ChatLineContent::type() const
|
||||
{
|
||||
return GraphicsItemType::ChatLineContentType;
|
||||
|
|
|
@ -35,7 +35,6 @@ public:
|
|||
};
|
||||
|
||||
int getColumn() const;
|
||||
int getRow() const;
|
||||
|
||||
virtual void setWidth(qreal width) = 0;
|
||||
int type() const final;
|
||||
|
|
222
src/chatlog/chatlinestorage.cpp
Normal file
222
src/chatlog/chatlinestorage.cpp
Normal file
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
Copyright © 2020-2021 by The qTox Project Contributors
|
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||
|
||||
qTox is libre 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.
|
||||
|
||||
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "chatlinestorage.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
ChatLineStorage::iterator ChatLineStorage::insertChatMessage(ChatLogIdx idx, QDateTime timestamp, ChatLine::Ptr line)
|
||||
{
|
||||
if (idxInfoMap.find(idx) != idxInfoMap.end()) {
|
||||
qWarning() << "Index is already rendered, not updating";
|
||||
return lines.end();
|
||||
}
|
||||
|
||||
auto linePosIncrementIt = infoIteratorForIdx(idx);
|
||||
|
||||
auto insertionPoint = equivalentLineIterator(linePosIncrementIt);
|
||||
insertionPoint = adjustItForDate(insertionPoint, timestamp);
|
||||
|
||||
insertionPoint = lines.insert(insertionPoint, line);
|
||||
|
||||
// All indexes after the insertion have to be incremented by one
|
||||
incrementLinePosAfter(linePosIncrementIt);
|
||||
|
||||
// Newly inserted index is insertinPoint - start
|
||||
IdxInfo info;
|
||||
info.linePos = std::distance(lines.begin(), insertionPoint);
|
||||
info.timestamp = timestamp;
|
||||
idxInfoMap[idx] = info;
|
||||
|
||||
return insertionPoint;
|
||||
}
|
||||
|
||||
ChatLineStorage::iterator ChatLineStorage::insertDateLine(QDateTime timestamp, ChatLine::Ptr line)
|
||||
{
|
||||
// Assume we only need to render one date line per date. I.e. this does
|
||||
// not handle the case of
|
||||
// * Message inserted Jan 3
|
||||
// * Message inserted Jan 4
|
||||
// * Message inserted Jan 3
|
||||
// In this case the second "Jan 3" message will appear to have been sent
|
||||
// on Jan 4
|
||||
// As of right now this should not be a problem since all items should
|
||||
// be sent/received in order. If we ever implement sender timestamps and
|
||||
// the sender screws us by changing their time we may need to revisit this
|
||||
auto idxMapIt = std::find_if(idxInfoMap.begin(), idxInfoMap.end(), [&] (const IdxInfoMap_t::value_type& v) {
|
||||
return timestamp <= v.second.timestamp;
|
||||
});
|
||||
|
||||
auto insertionPoint = equivalentLineIterator(idxMapIt);
|
||||
insertionPoint = adjustItForDate(insertionPoint, timestamp);
|
||||
|
||||
insertionPoint = lines.insert(insertionPoint, line);
|
||||
|
||||
// All indexes after the insertion have to be incremented by one
|
||||
incrementLinePosAfter(idxMapIt);
|
||||
|
||||
dateMap[line] = timestamp;
|
||||
|
||||
return insertionPoint;
|
||||
}
|
||||
|
||||
bool ChatLineStorage::contains(QDateTime timestamp) const
|
||||
{
|
||||
auto it = std::find_if(dateMap.begin(), dateMap.end(), [&] (DateLineMap_t::value_type v) {
|
||||
return v.second == timestamp;
|
||||
});
|
||||
|
||||
return it != dateMap.end();
|
||||
}
|
||||
|
||||
ChatLineStorage::iterator ChatLineStorage::find(ChatLogIdx idx)
|
||||
{
|
||||
auto infoIt = infoIteratorForIdx(idx);
|
||||
if (infoIt == idxInfoMap.end()) {
|
||||
return lines.end();
|
||||
}
|
||||
|
||||
return lines.begin() + infoIt->second.linePos;
|
||||
|
||||
}
|
||||
|
||||
ChatLineStorage::iterator ChatLineStorage::find(ChatLine::Ptr line)
|
||||
{
|
||||
return std::find(lines.begin(), lines.end(), line);
|
||||
}
|
||||
|
||||
void ChatLineStorage::erase(ChatLogIdx idx)
|
||||
{
|
||||
auto linePosDecrementIt = infoIteratorForIdx(idx);
|
||||
auto lineIt = equivalentLineIterator(linePosDecrementIt);
|
||||
|
||||
erase(lineIt);
|
||||
}
|
||||
|
||||
ChatLineStorage::iterator ChatLineStorage::erase(iterator it)
|
||||
{
|
||||
iterator prevIt = it;
|
||||
|
||||
do {
|
||||
it = prevIt;
|
||||
|
||||
auto infoIterator = equivalentInfoIterator(it);
|
||||
auto dateMapIt = dateMap.find(*it);
|
||||
|
||||
if (dateMapIt != dateMap.end()) {
|
||||
dateMap.erase(dateMapIt);
|
||||
}
|
||||
|
||||
if (infoIterator != idxInfoMap.end()) {
|
||||
infoIterator = idxInfoMap.erase(infoIterator);
|
||||
decrementLinePosAfter(infoIterator);
|
||||
}
|
||||
|
||||
it = lines.erase(it);
|
||||
|
||||
if (it > lines.begin()) {
|
||||
prevIt = std::prev(it);
|
||||
} else {
|
||||
prevIt = lines.end();
|
||||
}
|
||||
} while (shouldRemovePreviousLine(prevIt, it));
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
ChatLineStorage::iterator ChatLineStorage::equivalentLineIterator(IdxInfoMap_t::iterator it)
|
||||
{
|
||||
if (it == idxInfoMap.end()) {
|
||||
return lines.end();
|
||||
}
|
||||
|
||||
return std::next(lines.begin(), it->second.linePos);
|
||||
}
|
||||
|
||||
ChatLineStorage::IdxInfoMap_t::iterator ChatLineStorage::equivalentInfoIterator(iterator it)
|
||||
{
|
||||
auto idx = static_cast<size_t>(std::distance(lines.begin(), it));
|
||||
auto equivalentIt = std::find_if(idxInfoMap.begin(), idxInfoMap.end(), [&](const IdxInfoMap_t::value_type& v) {
|
||||
return v.second.linePos >= idx;
|
||||
});
|
||||
|
||||
return equivalentIt;
|
||||
}
|
||||
|
||||
ChatLineStorage::IdxInfoMap_t::iterator ChatLineStorage::infoIteratorForIdx(ChatLogIdx idx)
|
||||
{
|
||||
// If lower_bound proves to be expensive for appending we can try
|
||||
// special casing when idx > idxToLineMap.rbegin()->first
|
||||
|
||||
// If we find an exact match we return that index, otherwise we return
|
||||
// the first item after it. It's up to the caller to check if there's an
|
||||
// exact match first
|
||||
auto it = std::lower_bound(idxInfoMap.begin(), idxInfoMap.end(), idx, [](const IdxInfoMap_t::value_type& v, ChatLogIdx idx) {
|
||||
return v.first < idx;
|
||||
});
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
ChatLineStorage::iterator ChatLineStorage::adjustItForDate(iterator it, QDateTime timestamp)
|
||||
{
|
||||
// Continuously move back until either
|
||||
// 1. The dateline found is earlier than our timestamp
|
||||
// 2. There are no more datelines
|
||||
while (it > lines.begin()) {
|
||||
auto possibleDateIt = it - 1;
|
||||
auto dateIt = dateMap.find(*possibleDateIt);
|
||||
if (dateIt == dateMap.end()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (dateIt->second > timestamp) {
|
||||
it = possibleDateIt;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
void ChatLineStorage::incrementLinePosAfter(IdxInfoMap_t::iterator inputIt)
|
||||
{
|
||||
for (auto it = inputIt; it != idxInfoMap.end(); ++it) {
|
||||
it->second.linePos++;
|
||||
}
|
||||
}
|
||||
|
||||
void ChatLineStorage::decrementLinePosAfter(IdxInfoMap_t::iterator inputIt)
|
||||
{
|
||||
// All indexes after the insertion have to be incremented by one
|
||||
for (auto it = inputIt; it != idxInfoMap.end(); ++it) {
|
||||
it->second.linePos--;
|
||||
}
|
||||
}
|
||||
|
||||
bool ChatLineStorage::shouldRemovePreviousLine(iterator prevIt, iterator it)
|
||||
{
|
||||
return prevIt != lines.end() && // Previous iterator is valid
|
||||
dateMap.find(*prevIt) != dateMap.end() && // Previous iterator is a date line
|
||||
(
|
||||
it == lines.end() || // Previous iterator is the last line
|
||||
dateMap.find(*it) != dateMap.end() // Adjacent date lines
|
||||
);
|
||||
}
|
131
src/chatlog/chatlinestorage.h
Normal file
131
src/chatlog/chatlinestorage.h
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
Copyright © 2021 by The qTox Project Contributors
|
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||
|
||||
qTox is libre 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.
|
||||
|
||||
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "src/chatlog/chatline.h"
|
||||
#include "src/model/ichatlog.h"
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
/**
|
||||
* Helper class to keep track of what we're currently rendering and in what order
|
||||
* Some constraints that may not be obvious
|
||||
* * Rendered views are not always contiguous. When we clear the chatlog
|
||||
* ongoing file transfers are not removed or else we would have no way to stop
|
||||
* them. If history is loaded after this point then we could actually be
|
||||
* inserting elements both before and in the middle of our existing rendered
|
||||
* items
|
||||
* * We need to be able to go from ChatLogIdx to rendered row index. E.g. if
|
||||
* an SQL query is made for search, we need to map the result back to the
|
||||
* displayed row to send it
|
||||
* * We need to be able to map rows back to ChatLogIdx in order to decide where
|
||||
* to insert newly added messages
|
||||
* * Not all rendered lines will have an associated ChatLogIdx, date lines for
|
||||
* example are not clearly at any ChatLogIdx
|
||||
* * Need to track date messages to ensure that if messages are inserted above
|
||||
* the current position the date line is moved appropriately
|
||||
*
|
||||
* The class is designed to be used like a vector over the currently rendered
|
||||
* items, but with some tweaks for ensuring items tied to the current view are
|
||||
* moved correctly (selection indexes, removal of associated date lines,
|
||||
* mappings of ChatLogIdx -> ChatLine::Ptr, etc.)
|
||||
*/
|
||||
class ChatLineStorage
|
||||
{
|
||||
|
||||
struct IdxInfo
|
||||
{
|
||||
size_t linePos;
|
||||
QDateTime timestamp;
|
||||
};
|
||||
using Lines_t = std::vector<ChatLine::Ptr>;
|
||||
using DateLineMap_t = std::map<ChatLine::Ptr, QDateTime>;
|
||||
using IdxInfoMap_t = std::map<ChatLogIdx, IdxInfo>;
|
||||
|
||||
public:
|
||||
// Types to conform with other containers
|
||||
using size_type = Lines_t::size_type;
|
||||
using reference = Lines_t::reference;
|
||||
using const_reference = Lines_t::const_reference;
|
||||
using const_iterator = Lines_t::const_iterator;
|
||||
using iterator = Lines_t::iterator;
|
||||
|
||||
|
||||
public:
|
||||
iterator insertChatMessage(ChatLogIdx idx, QDateTime timestamp, ChatLine::Ptr line);
|
||||
iterator insertDateLine(QDateTime timestamp, ChatLine::Ptr line);
|
||||
|
||||
ChatLogIdx firstIdx() const { return idxInfoMap.begin()->first; }
|
||||
|
||||
ChatLogIdx lastIdx() const { return idxInfoMap.rbegin()->first; }
|
||||
|
||||
bool contains(ChatLogIdx idx) const { return idxInfoMap.find(idx) != idxInfoMap.end(); }
|
||||
|
||||
bool contains(QDateTime timestamp) const;
|
||||
|
||||
iterator find(ChatLogIdx idx);
|
||||
iterator find(ChatLine::Ptr line);
|
||||
|
||||
const_reference operator[](size_type idx) const { return lines[idx]; }
|
||||
|
||||
const_reference operator[](ChatLogIdx idx) const { return lines[idxInfoMap.at(idx).linePos]; }
|
||||
|
||||
size_type size() const { return lines.size(); }
|
||||
|
||||
iterator begin() { return lines.begin(); }
|
||||
iterator end() { return lines.end(); }
|
||||
|
||||
bool empty() const { return lines.empty(); }
|
||||
|
||||
bool hasIndexedMessage() const { return !idxInfoMap.empty(); }
|
||||
|
||||
void clear()
|
||||
{
|
||||
idxInfoMap.clear();
|
||||
dateMap.clear();
|
||||
return lines.clear();
|
||||
}
|
||||
|
||||
reference front() { return lines.front(); }
|
||||
reference back() { return lines.back(); }
|
||||
|
||||
void erase(ChatLogIdx idx);
|
||||
iterator erase(iterator it);
|
||||
|
||||
private:
|
||||
iterator equivalentLineIterator(IdxInfoMap_t::iterator it);
|
||||
|
||||
IdxInfoMap_t::iterator equivalentInfoIterator(iterator it);
|
||||
|
||||
IdxInfoMap_t::iterator infoIteratorForIdx(ChatLogIdx idx);
|
||||
|
||||
iterator adjustItForDate(iterator it, QDateTime timestamp);
|
||||
|
||||
void incrementLinePosAfter(IdxInfoMap_t::iterator it);
|
||||
void decrementLinePosAfter(IdxInfoMap_t::iterator it);
|
||||
bool shouldRemovePreviousLine(iterator prevIt, iterator it);
|
||||
|
||||
std::vector<ChatLine::Ptr> lines;
|
||||
std::map<ChatLine::Ptr, QDateTime> dateMap;
|
||||
IdxInfoMap_t idxInfoMap;
|
||||
};
|
File diff suppressed because it is too large
Load Diff
1543
src/chatlog/chatwidget.cpp
Normal file
1543
src/chatlog/chatwidget.cpp
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -26,6 +26,7 @@
|
|||
#include "chatline.h"
|
||||
#include "chatmessage.h"
|
||||
#include "src/widget/style.h"
|
||||
#include "src/model/ichatlog.h"
|
||||
|
||||
class QGraphicsScene;
|
||||
class QGraphicsRectItem;
|
||||
|
@ -34,32 +35,25 @@ class QTimer;
|
|||
class ChatLineContent;
|
||||
struct ToxFile;
|
||||
|
||||
static const size_t DEF_NUM_MSG_TO_LOAD = 100;
|
||||
class ChatLineStorage;
|
||||
|
||||
class ChatLog : public QGraphicsView
|
||||
static const size_t DEF_NUM_MSG_TO_LOAD = 100;
|
||||
class ChatWidget : public QGraphicsView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ChatLog(QWidget* parent = nullptr);
|
||||
virtual ~ChatLog();
|
||||
explicit ChatWidget(IChatLog& chatLog, const Core& core, QWidget* parent = nullptr);
|
||||
virtual ~ChatWidget();
|
||||
|
||||
void insertChatlineAtBottom(ChatLine::Ptr l);
|
||||
void insertChatlineAtBottom(const QList<ChatLine::Ptr>& newLines);
|
||||
void insertChatlineOnTop(ChatLine::Ptr l);
|
||||
void insertChatlinesOnTop(const QList<ChatLine::Ptr>& newLines);
|
||||
void insertChatlines(std::map<ChatLogIdx, ChatLine::Ptr> chatLines);
|
||||
void clearSelection();
|
||||
void clear();
|
||||
void copySelectedText(bool toSelectionBuffer = false) const;
|
||||
void setBusyNotification(ChatLine::Ptr notification);
|
||||
void setTypingNotificationVisible(bool visible);
|
||||
void setTypingNotificationName(const QString& displayName);
|
||||
void scrollToLine(ChatLine::Ptr line);
|
||||
void selectAll();
|
||||
void fontChanged(const QFont& font);
|
||||
void removeFirsts(const int num);
|
||||
void removeLasts(const int num);
|
||||
void setScroll(const bool scroll);
|
||||
int getNumRemove() const;
|
||||
|
||||
QString getSelectedText() const;
|
||||
|
||||
|
@ -68,22 +62,40 @@ public:
|
|||
ChatLineContent* getContentFromGlobalPos(QPoint pos) const;
|
||||
const uint repNameAfter = 5 * 60;
|
||||
|
||||
void setColorizedNames(bool enable) { colorizeNames = enable; };
|
||||
void jumpToDate(QDate date);
|
||||
void jumpToIdx(ChatLogIdx idx);
|
||||
|
||||
signals:
|
||||
void selectionChanged();
|
||||
void workerTimeoutFinished();
|
||||
void firstVisibleLineChanged(const ChatLine::Ptr& prevLine, const ChatLine::Ptr& firstLine);
|
||||
void loadHistoryLower();
|
||||
void loadHistoryUpper();
|
||||
|
||||
void messageNotFoundShow(SearchDirection direction);
|
||||
void renderFinished();
|
||||
public slots:
|
||||
void forceRelayout();
|
||||
void reloadTheme();
|
||||
|
||||
void startSearch(const QString& phrase, const ParameterSearch& parameter);
|
||||
void onSearchUp(const QString& phrase, const ParameterSearch& parameter);
|
||||
void onSearchDown(const QString& phrase, const ParameterSearch& parameter);
|
||||
void handleSearchResult(SearchResult result, SearchDirection direction);
|
||||
void removeSearchPhrase();
|
||||
|
||||
private slots:
|
||||
void onSelectionTimerTimeout();
|
||||
void onWorkerTimeout();
|
||||
void onMultiClickTimeout();
|
||||
|
||||
void onMessageUpdated(ChatLogIdx idx);
|
||||
void renderMessage(ChatLogIdx idx);
|
||||
void renderMessages(ChatLogIdx begin, ChatLogIdx end);
|
||||
|
||||
void setRenderedWindowStart(ChatLogIdx start);
|
||||
void setRenderedWindowEnd(ChatLogIdx end);
|
||||
|
||||
void onRenderFinished();
|
||||
void onScrollValueChanged(int value);
|
||||
protected:
|
||||
QRectF calculateSceneRect() const;
|
||||
QRect getVisibleRect() const;
|
||||
|
@ -95,11 +107,10 @@ protected:
|
|||
|
||||
qreal useableWidth() const;
|
||||
|
||||
void reposition(int start, int end, qreal deltaY);
|
||||
void updateSceneRect();
|
||||
void checkVisibility(bool causedWheelEvent = false);
|
||||
void checkVisibility();
|
||||
void scrollToBottom();
|
||||
void startResizeWorker(bool stick, ChatLine::Ptr anchorLine = nullptr);
|
||||
void startResizeWorker();
|
||||
|
||||
void mouseDoubleClickEvent(QMouseEvent* ev) final;
|
||||
void mousePressEvent(QMouseEvent* ev) final;
|
||||
|
@ -108,6 +119,7 @@ protected:
|
|||
void scrollContentsBy(int dx, int dy) final;
|
||||
void resizeEvent(QResizeEvent* ev) final;
|
||||
void showEvent(QShowEvent*) final;
|
||||
void hideEvent(QHideEvent* event) final;
|
||||
void focusInEvent(QFocusEvent* ev) final;
|
||||
void focusOutEvent(QFocusEvent* ev) final;
|
||||
void wheelEvent(QWheelEvent *event) final;
|
||||
|
@ -118,6 +130,8 @@ protected:
|
|||
|
||||
ChatLine::Ptr findLineByPosY(qreal yPos) const;
|
||||
|
||||
void removeLines(ChatLogIdx being, ChatLogIdx end);
|
||||
|
||||
private:
|
||||
void retranslateUi();
|
||||
bool isActiveFileTransfer(ChatLine::Ptr l);
|
||||
|
@ -130,6 +144,11 @@ private:
|
|||
void moveMultiSelectionDown(int offset);
|
||||
void setTypingNotification();
|
||||
|
||||
void renderItem(const ChatLogItem &item, bool hideName, bool colorizeNames, ChatLine::Ptr &chatMessage);
|
||||
void renderFile(QString displayName, ToxFile file, bool isSelf, QDateTime timestamp, ChatLine::Ptr &chatMessage);
|
||||
bool needsToHideName(ChatLogIdx idx, bool prevIdxRendered) const;
|
||||
bool shouldRenderMessage(ChatLogIdx idx) const;
|
||||
void disableSearchText();
|
||||
private:
|
||||
enum class SelectionMode
|
||||
{
|
||||
|
@ -149,16 +168,22 @@ private:
|
|||
QAction* selectAllAction = nullptr;
|
||||
QGraphicsScene* scene = nullptr;
|
||||
QGraphicsScene* busyScene = nullptr;
|
||||
QVector<ChatLine::Ptr> lines;
|
||||
QList<ChatLine::Ptr> visibleLines;
|
||||
ChatLine::Ptr typingNotification;
|
||||
ChatLine::Ptr busyNotification;
|
||||
|
||||
// selection
|
||||
int selClickedRow = -1; // These 4 are only valid while selectionMode != None
|
||||
|
||||
// For the time being we store these selection indexes as ChatLine::Ptrs. In
|
||||
// order to do multi-selection we do an O(n) search in the chatline storage
|
||||
// to determine the index. This is inefficient but correct with the moving
|
||||
// window of storage. If this proves to cause performance issues we can move
|
||||
// this responsibility into ChatlineStorage and have it coordinate the
|
||||
// shifting of indexes
|
||||
ChatLine::Ptr selClickedRow; // These 4 are only valid while selectionMode != None
|
||||
int selClickedCol = -1;
|
||||
int selFirstRow = -1;
|
||||
int selLastRow = -1;
|
||||
ChatLine::Ptr selFirstRow;
|
||||
ChatLine::Ptr selLastRow;
|
||||
QColor selectionRectColor = Style::getColor(Style::SelectText);
|
||||
SelectionMode selectionMode = SelectionMode::None;
|
||||
QPointF clickPos;
|
||||
|
@ -170,10 +195,9 @@ private:
|
|||
int clickCount = 0;
|
||||
QPoint lastClickPos;
|
||||
Qt::MouseButton lastClickButton;
|
||||
bool isScroll{true};
|
||||
|
||||
// worker vars
|
||||
int workerLastIndex = 0;
|
||||
size_t workerLastIndex = 0;
|
||||
bool workerStb = false;
|
||||
ChatLine::Ptr workerAnchorLine;
|
||||
|
||||
|
@ -181,6 +205,13 @@ private:
|
|||
QMargins margins = QMargins(10, 10, 10, 10);
|
||||
qreal lineSpacing = 5.0f;
|
||||
|
||||
int numRemove{0};
|
||||
const int maxMessages{300};
|
||||
IChatLog& chatLog;
|
||||
bool colorizeNames = false;
|
||||
SearchPos searchPos;
|
||||
const Core& core;
|
||||
bool scrollMonitoringEnabled = true;
|
||||
|
||||
std::unique_ptr<ChatLineStorage> chatLineStorage;
|
||||
|
||||
std::vector<std::function<void(void)>> renderCompletionFns;
|
||||
};
|
|
@ -66,11 +66,9 @@ void Text::selectText(const QString& txt, const std::pair<int, int>& point)
|
|||
return;
|
||||
}
|
||||
|
||||
selectCursor = doc->find(txt, point.first);
|
||||
selectPoint = point;
|
||||
auto cursor = doc->find(txt, point.first);
|
||||
|
||||
regenerate();
|
||||
update();
|
||||
selectText(cursor, point);
|
||||
}
|
||||
|
||||
void Text::selectText(const QRegularExpression &exp, const std::pair<int, int>& point)
|
||||
|
@ -81,20 +79,14 @@ void Text::selectText(const QRegularExpression &exp, const std::pair<int, int>&
|
|||
return;
|
||||
}
|
||||
|
||||
selectCursor = doc->find(exp, point.first);
|
||||
selectPoint = point;
|
||||
auto cursor = doc->find(exp, point.first);
|
||||
|
||||
regenerate();
|
||||
update();
|
||||
selectText(cursor, point);
|
||||
}
|
||||
|
||||
void Text::deselectText()
|
||||
{
|
||||
dirty = true;
|
||||
|
||||
selectCursor = QTextCursor();
|
||||
selectPoint = {0, 0};
|
||||
|
||||
regenerate();
|
||||
update();
|
||||
}
|
||||
|
@ -363,10 +355,6 @@ void Text::regenerate()
|
|||
dirty = false;
|
||||
}
|
||||
|
||||
if (!selectCursor.isNull()) {
|
||||
selectText(selectCursor, selectPoint);
|
||||
}
|
||||
|
||||
// if we are not visible -> free mem
|
||||
if (!keepInMemory)
|
||||
freeResources();
|
||||
|
@ -474,6 +462,9 @@ void Text::selectText(QTextCursor& cursor, const std::pair<int, int>& point)
|
|||
QTextCharFormat format;
|
||||
format.setBackground(QBrush(Style::getColor(Style::SearchHighlighted)));
|
||||
cursor.mergeCharFormat(format);
|
||||
|
||||
regenerate();
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
#include "src/widget/style.h"
|
||||
|
||||
#include <QFont>
|
||||
#include <QTextCursor>
|
||||
|
||||
class QTextDocument;
|
||||
|
||||
|
@ -110,7 +109,4 @@ private:
|
|||
TextType textType;
|
||||
QColor color;
|
||||
QColor customColor;
|
||||
|
||||
QTextCursor selectCursor;
|
||||
std::pair<int, int> selectPoint{0, 0};
|
||||
};
|
||||
|
|
|
@ -160,19 +160,13 @@ SearchResult ChatHistory::searchBackward(SearchPos startIdx, const QString& phra
|
|||
history->getDateWhereFindPhrase(f.getPublicKey(), earliestMessageDate, phrase,
|
||||
parameter);
|
||||
|
||||
if (dateWherePhraseFound.isValid()) {
|
||||
auto loadIdx = history->getNumMessagesForFriendBeforeDate(f.getPublicKey(), dateWherePhraseFound);
|
||||
loadHistoryIntoSessionChatLog(ChatLogIdx(loadIdx));
|
||||
auto loadIdx = history->getNumMessagesForFriendBeforeDate(f.getPublicKey(), dateWherePhraseFound);
|
||||
loadHistoryIntoSessionChatLog(ChatLogIdx(loadIdx));
|
||||
|
||||
// Reset search pos to the message we just loaded to avoid a double search
|
||||
startIdx.logIdx = ChatLogIdx(loadIdx);
|
||||
startIdx.numMatches = 0;
|
||||
return sessionChatLog.searchBackward(startIdx, phrase, parameter);
|
||||
}
|
||||
|
||||
SearchResult ret;
|
||||
ret.found = false;
|
||||
return ret;
|
||||
// Reset search pos to the message we just loaded to avoid a double search
|
||||
startIdx.logIdx = ChatLogIdx(loadIdx);
|
||||
startIdx.numMatches = 0;
|
||||
return sessionChatLog.searchBackward(startIdx, phrase, parameter);
|
||||
}
|
||||
|
||||
ChatLogIdx ChatHistory::getFirstIdx() const
|
||||
|
|
|
@ -36,8 +36,6 @@ public:
|
|||
virtual void setEventFlag(bool flag) = 0;
|
||||
virtual bool getEventFlag() const = 0;
|
||||
|
||||
virtual bool useHistory() const = 0; // TODO: remove after added history in group chat
|
||||
|
||||
signals:
|
||||
void displayedNameChanged(const QString& newName);
|
||||
};
|
||||
|
|
|
@ -192,11 +192,6 @@ Status::Status Friend::getStatus() const
|
|||
return isNegotiating ? Status::Status::Negotiating : friendStatus;
|
||||
}
|
||||
|
||||
bool Friend::useHistory() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void Friend::setExtendedMessageSupport(bool supported)
|
||||
{
|
||||
supportedExtensions[ExtensionType::messages] = supported;
|
||||
|
|
|
@ -54,7 +54,6 @@ public:
|
|||
void finishNegotiation();
|
||||
void setStatus(Status::Status s);
|
||||
Status::Status getStatus() const;
|
||||
bool useHistory() const final;
|
||||
|
||||
void setExtendedMessageSupport(bool supported);
|
||||
ExtensionSet getSupportedExtensions() const;
|
||||
|
|
|
@ -201,8 +201,3 @@ QString Group::getSelfName() const
|
|||
{
|
||||
return selfName;
|
||||
}
|
||||
|
||||
bool Group::useHistory() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -60,8 +60,6 @@ public:
|
|||
void setSelfName(const QString& name);
|
||||
QString getSelfName() const;
|
||||
|
||||
bool useHistory() const final;
|
||||
|
||||
signals:
|
||||
void titleChangedByUser(const QString& title);
|
||||
void titleChanged(const QString& author, const QString& title);
|
||||
|
|
|
@ -69,7 +69,7 @@ struct SearchPos
|
|||
|
||||
struct SearchResult
|
||||
{
|
||||
bool found{false};
|
||||
bool found;
|
||||
SearchPos pos;
|
||||
size_t start;
|
||||
size_t len;
|
||||
|
|
|
@ -1139,14 +1139,19 @@ QDateTime History::getDateWhereFindPhrase(const ToxPk& friendPk, const QDateTime
|
|||
break;
|
||||
}
|
||||
|
||||
QDateTime time = from;
|
||||
QDateTime date = from;
|
||||
|
||||
if (!time.isValid()) {
|
||||
time = QDateTime::currentDateTime();
|
||||
if (!date.isValid()) {
|
||||
date = QDateTime::currentDateTime();
|
||||
}
|
||||
|
||||
if (parameter.period == PeriodSearch::AfterDate || parameter.period == PeriodSearch::BeforeDate) {
|
||||
time = parameter.time;
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
|
||||
date = parameter.date.startOfDay();
|
||||
#else
|
||||
date = QDateTime(parameter.date);
|
||||
#endif
|
||||
}
|
||||
|
||||
QString period;
|
||||
|
@ -1156,15 +1161,15 @@ QDateTime History::getDateWhereFindPhrase(const ToxPk& friendPk, const QDateTime
|
|||
break;
|
||||
case PeriodSearch::AfterDate:
|
||||
period = QStringLiteral("AND timestamp > '%1' ORDER BY timestamp ASC LIMIT 1;")
|
||||
.arg(time.toMSecsSinceEpoch());
|
||||
.arg(date.toMSecsSinceEpoch());
|
||||
break;
|
||||
case PeriodSearch::BeforeDate:
|
||||
period = QStringLiteral("AND timestamp < '%1' ORDER BY timestamp DESC LIMIT 1;")
|
||||
.arg(time.toMSecsSinceEpoch());
|
||||
.arg(date.toMSecsSinceEpoch());
|
||||
break;
|
||||
default:
|
||||
period = QStringLiteral("AND timestamp < '%1' ORDER BY timestamp DESC LIMIT 1;")
|
||||
.arg(time.toMSecsSinceEpoch());
|
||||
.arg(date.toMSecsSinceEpoch());
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
#include "chatform.h"
|
||||
#include "src/chatlog/chatlinecontentproxy.h"
|
||||
#include "src/chatlog/chatlog.h"
|
||||
#include "src/chatlog/chatwidget.h"
|
||||
#include "src/chatlog/chatmessage.h"
|
||||
#include "src/chatlog/content/filetransferwidget.h"
|
||||
#include "src/chatlog/content/text.h"
|
||||
|
@ -633,14 +633,6 @@ void ChatForm::sendImageFromPreview()
|
|||
}
|
||||
}
|
||||
|
||||
void ChatForm::insertChatMessage(ChatMessage::Ptr msg)
|
||||
{
|
||||
GenericChatForm::insertChatMessage(msg);
|
||||
if (netcam && bodySplitter->sizes()[1] == 0) {
|
||||
netcam->setShowMessages(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatForm::onCopyStatusMessage()
|
||||
{
|
||||
// make sure to copy not truncated text directly from the friend
|
||||
|
|
|
@ -118,7 +118,6 @@ private:
|
|||
|
||||
protected:
|
||||
std::unique_ptr<NetCamView> createNetcam();
|
||||
void insertChatMessage(ChatMessage::Ptr msg) final;
|
||||
void dragEnterEvent(QDragEnterEvent* ev) final;
|
||||
void dropEvent(QDropEvent* ev) final;
|
||||
void hideEvent(QHideEvent* event) final;
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
#include "genericchatform.h"
|
||||
|
||||
#include "src/chatlog/chatlinecontentproxy.h"
|
||||
#include "src/chatlog/chatlog.h"
|
||||
#include "src/chatlog/chatwidget.h"
|
||||
#include "src/chatlog/content/filetransferwidget.h"
|
||||
#include "src/chatlog/content/timestamp.h"
|
||||
#include "src/core/core.h"
|
||||
|
@ -36,7 +36,7 @@
|
|||
#include "src/widget/contentlayout.h"
|
||||
#include "src/widget/emoticonswidget.h"
|
||||
#include "src/widget/form/chatform.h"
|
||||
#include "src/widget/gui.h"
|
||||
#include "src/widget/form/loadhistorydialog.h"
|
||||
#include "src/widget/maskablepixmapwidget.h"
|
||||
#include "src/widget/searchform.h"
|
||||
#include "src/widget/style.h"
|
||||
|
@ -44,6 +44,7 @@
|
|||
#include "src/widget/tool/flyoutoverlaywidget.h"
|
||||
#include "src/widget/translator.h"
|
||||
#include "src/widget/widget.h"
|
||||
#include "src/widget/gui.h"
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QFileDialog>
|
||||
|
@ -53,8 +54,6 @@
|
|||
#include <QStringBuilder>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#ifdef SPELL_CHECKING
|
||||
#include <KF5/SonnetUi/sonnet/spellcheckdecorator.h>
|
||||
#endif
|
||||
|
@ -133,108 +132,6 @@ QPushButton* createButton(const QString& name, T* self, Fun onClickSlot)
|
|||
return btn;
|
||||
}
|
||||
|
||||
ChatMessage::Ptr getChatMessageForIdx(ChatLogIdx idx,
|
||||
const std::map<ChatLogIdx, ChatMessage::Ptr>& messages)
|
||||
{
|
||||
auto existingMessageIt = messages.find(idx);
|
||||
|
||||
if (existingMessageIt == messages.end()) {
|
||||
return ChatMessage::Ptr();
|
||||
}
|
||||
|
||||
return existingMessageIt->second;
|
||||
}
|
||||
|
||||
bool shouldRenderDate(ChatLogIdx idxToRender, const IChatLog& chatLog)
|
||||
{
|
||||
if (idxToRender == chatLog.getFirstIdx())
|
||||
return true;
|
||||
|
||||
return chatLog.at(idxToRender - 1).getTimestamp().date()
|
||||
!= chatLog.at(idxToRender).getTimestamp().date();
|
||||
}
|
||||
|
||||
ChatMessage::Ptr dateMessageForItem(const ChatLogItem& item)
|
||||
{
|
||||
const auto& s = Settings::getInstance();
|
||||
const auto date = item.getTimestamp().date();
|
||||
auto dateText = date.toString(s.getDateFormat());
|
||||
return ChatMessage::createChatInfoMessage(dateText, ChatMessage::INFO, QDateTime());
|
||||
}
|
||||
|
||||
ChatMessage::Ptr createMessage(const QString& displayName, bool isSelf, bool colorizeNames,
|
||||
const ChatLogMessage& chatLogMessage)
|
||||
{
|
||||
auto messageType = chatLogMessage.message.isAction ? ChatMessage::MessageType::ACTION
|
||||
: ChatMessage::MessageType::NORMAL;
|
||||
|
||||
const bool bSelfMentioned =
|
||||
std::any_of(chatLogMessage.message.metadata.begin(), chatLogMessage.message.metadata.end(),
|
||||
[](const MessageMetadata& metadata) {
|
||||
return metadata.type == MessageMetadataType::selfMention;
|
||||
});
|
||||
|
||||
if (bSelfMentioned) {
|
||||
messageType = ChatMessage::MessageType::ALERT;
|
||||
}
|
||||
|
||||
const auto timestamp = chatLogMessage.message.timestamp;
|
||||
return ChatMessage::createChatMessage(displayName, chatLogMessage.message.content, messageType,
|
||||
isSelf, chatLogMessage.state, timestamp, colorizeNames);
|
||||
}
|
||||
|
||||
void renderMessageRaw(const QString& displayName, bool isSelf, bool colorizeNames,
|
||||
const ChatLogMessage& chatLogMessage, ChatMessage::Ptr& chatMessage)
|
||||
{
|
||||
|
||||
if (chatMessage) {
|
||||
if (chatLogMessage.state == MessageState::complete) {
|
||||
chatMessage->markAsDelivered(chatLogMessage.message.timestamp);
|
||||
} else if (chatLogMessage.state == MessageState::broken) {
|
||||
chatMessage->markAsBroken();
|
||||
}
|
||||
} else {
|
||||
chatMessage = createMessage(displayName, isSelf, colorizeNames, chatLogMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
ChatLogIdx firstItemAfterDate(QDate date, const IChatLog& chatLog)
|
||||
{
|
||||
auto idxs = chatLog.getDateIdxs(date, 1);
|
||||
if (idxs.size()) {
|
||||
return idxs[0].idx;
|
||||
} else {
|
||||
return chatLog.getNextIdx();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Chat message message type (info/warning) for the given system message
|
||||
* @param[in] systemMessage
|
||||
*/
|
||||
ChatMessage::SystemMessageType getChatMessageType(const SystemMessage& systemMessage)
|
||||
{
|
||||
switch (systemMessage.messageType) {
|
||||
case SystemMessageType::fileSendFailed:
|
||||
case SystemMessageType::messageSendFailed:
|
||||
case SystemMessageType::unexpectedCallEnd:
|
||||
return ChatMessage::ERROR;
|
||||
case SystemMessageType::userJoinedGroup:
|
||||
case SystemMessageType::userLeftGroup:
|
||||
case SystemMessageType::peerNameChanged:
|
||||
case SystemMessageType::peerStateChange:
|
||||
case SystemMessageType::titleChanged:
|
||||
case SystemMessageType::cleared:
|
||||
case SystemMessageType::outgoingCall:
|
||||
case SystemMessageType::incomingCall:
|
||||
case SystemMessageType::callEnd:
|
||||
return ChatMessage::INFO;
|
||||
}
|
||||
|
||||
return ChatMessage::INFO;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
GenericChatForm::GenericChatForm(const Core& _core, const Contact* contact, IChatLog& chatLog,
|
||||
|
@ -250,15 +147,14 @@ GenericChatForm::GenericChatForm(const Core& _core, const Contact* contact, ICha
|
|||
headWidget = new ChatFormHeader();
|
||||
searchForm = new SearchForm();
|
||||
dateInfo = new QLabel(this);
|
||||
chatWidget = new ChatLog(this);
|
||||
chatWidget->setBusyNotification(ChatMessage::createBusyNotification());
|
||||
chatWidget = new ChatWidget(chatLog, core, this);
|
||||
searchForm->hide();
|
||||
dateInfo->setAlignment(Qt::AlignHCenter);
|
||||
dateInfo->setVisible(false);
|
||||
|
||||
// settings
|
||||
const Settings& s = Settings::getInstance();
|
||||
connect(&s, &Settings::emojiFontPointSizeChanged, chatWidget, &ChatLog::forceRelayout);
|
||||
connect(&s, &Settings::emojiFontPointSizeChanged, chatWidget, &ChatWidget::forceRelayout);
|
||||
connect(&s, &Settings::chatMessageFontChanged, this, &GenericChatForm::onChatMessageFontChanged);
|
||||
|
||||
msgEdit = new ChatTextEdit();
|
||||
|
@ -318,12 +214,11 @@ GenericChatForm::GenericChatForm(const Core& _core, const Contact* contact, ICha
|
|||
quoteAction = menu.addAction(QIcon(), QString(), this, SLOT(quoteSelectedText()),
|
||||
QKeySequence(Qt::ALT + Qt::Key_Q));
|
||||
addAction(quoteAction);
|
||||
|
||||
menu.addSeparator();
|
||||
|
||||
goCurrentDateAction = menu.addAction(QIcon(), QString(), this, SLOT(goToCurrentDate()),
|
||||
goToCurrentDateAction = menu.addAction(QIcon(), QString(), this, SLOT(goToCurrentDate()),
|
||||
QKeySequence(Qt::CTRL + Qt::Key_G));
|
||||
addAction(goCurrentDateAction);
|
||||
addAction(goToCurrentDateAction);
|
||||
|
||||
menu.addSeparator();
|
||||
|
||||
|
@ -348,19 +243,15 @@ GenericChatForm::GenericChatForm(const Core& _core, const Contact* contact, ICha
|
|||
exportChatAction =
|
||||
menu.addAction(QIcon::fromTheme("document-save"), QString(), this, SLOT(onExportChat()));
|
||||
|
||||
connect(chatWidget, &ChatLog::customContextMenuRequested, this,
|
||||
connect(chatWidget, &ChatWidget::customContextMenuRequested, this,
|
||||
&GenericChatForm::onChatContextMenuRequested);
|
||||
connect(chatWidget, &ChatLog::firstVisibleLineChanged, this, &GenericChatForm::updateShowDateInfo);
|
||||
connect(chatWidget, &ChatLog::loadHistoryLower, this, &GenericChatForm::loadHistoryLower);
|
||||
connect(chatWidget, &ChatLog::loadHistoryUpper, this, &GenericChatForm::loadHistoryUpper);
|
||||
connect(chatWidget, &ChatWidget::firstVisibleLineChanged, this, &GenericChatForm::updateShowDateInfo);
|
||||
|
||||
connect(searchForm, &SearchForm::searchInBegin, this, &GenericChatForm::searchInBegin);
|
||||
connect(searchForm, &SearchForm::searchUp, this, &GenericChatForm::onSearchUp);
|
||||
connect(searchForm, &SearchForm::searchDown, this, &GenericChatForm::onSearchDown);
|
||||
connect(searchForm, &SearchForm::visibleChanged, this, &GenericChatForm::onSearchTriggered);
|
||||
connect(this, &GenericChatForm::messageNotFoundShow, searchForm, &SearchForm::showMessageNotFound);
|
||||
|
||||
connect(&chatLog, &IChatLog::itemUpdated, this, &GenericChatForm::renderMessage);
|
||||
connect(searchForm, &SearchForm::searchInBegin, chatWidget, &ChatWidget::startSearch);
|
||||
connect(searchForm, &SearchForm::searchUp, chatWidget, &ChatWidget::onSearchUp);
|
||||
connect(searchForm, &SearchForm::searchDown, chatWidget, &ChatWidget::onSearchDown);
|
||||
connect(searchForm, &SearchForm::visibleChanged, chatWidget, &ChatWidget::removeSearchPhrase);
|
||||
connect(chatWidget, &ChatWidget::messageNotFoundShow, searchForm, &SearchForm::showMessageNotFound);
|
||||
|
||||
connect(msgEdit, &ChatTextEdit::enterPressed, this, &GenericChatForm::onSendTriggered);
|
||||
|
||||
|
@ -379,10 +270,6 @@ GenericChatForm::GenericChatForm(const Core& _core, const Contact* contact, ICha
|
|||
// update header on name/title change
|
||||
connect(contact, &Contact::displayedNameChanged, this, &GenericChatForm::setName);
|
||||
|
||||
auto chatLogIdxRange = chatLog.getNextIdx() - chatLog.getFirstIdx();
|
||||
auto firstChatLogIdx = (chatLogIdxRange < DEF_NUM_MSG_TO_LOAD) ? chatLog.getFirstIdx() : chatLog.getNextIdx() - DEF_NUM_MSG_TO_LOAD;
|
||||
|
||||
renderMessages(firstChatLogIdx, chatLog.getNextIdx());
|
||||
}
|
||||
|
||||
GenericChatForm::~GenericChatForm()
|
||||
|
@ -391,21 +278,6 @@ GenericChatForm::~GenericChatForm()
|
|||
delete searchForm;
|
||||
}
|
||||
|
||||
void GenericChatForm::renderFile(QString displayName, ToxFile file, bool isSelf, QDateTime timestamp,
|
||||
ChatMessage::Ptr& chatMessage)
|
||||
{
|
||||
if (!chatMessage) {
|
||||
CoreFile* coreFile = core.getCoreFile();
|
||||
assert(coreFile);
|
||||
chatMessage = ChatMessage::createFileTransferMessage(displayName, *coreFile, file, isSelf, timestamp);
|
||||
} else {
|
||||
auto proxy = static_cast<ChatLineContentProxy*>(chatMessage->getContent(1));
|
||||
assert(proxy->getWidgetType() == ChatLineContentProxy::FileTransferWidgetType);
|
||||
auto ftWidget = static_cast<FileTransferWidget*>(proxy->getWidget());
|
||||
ftWidget->onFileTransferUpdate(file);
|
||||
}
|
||||
}
|
||||
|
||||
void GenericChatForm::adjustFileMenuPosition()
|
||||
{
|
||||
QPoint pos = fileButton->mapTo(bodySplitter, QPoint());
|
||||
|
@ -571,33 +443,6 @@ void GenericChatForm::onSendTriggered()
|
|||
messageDispatcher.sendMessage(isAction, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Show, is it needed to hide message author name or not
|
||||
* @param idx ChatLogIdx of the message
|
||||
* @return True if the name should be hidden, false otherwise
|
||||
*/
|
||||
bool GenericChatForm::needsToHideName(ChatLogIdx idx) const
|
||||
{
|
||||
// If the previous message is not rendered we should show the name
|
||||
// regardless of other constraints
|
||||
auto itemBefore = messages.find(idx - 1);
|
||||
if (itemBefore == messages.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& prevItem = chatLog.at(idx - 1);
|
||||
const auto& currentItem = chatLog.at(idx);
|
||||
|
||||
// Always show the * in the name field for action messages
|
||||
if (currentItem.getContentType() == ChatLogItem::ContentType::message
|
||||
&& currentItem.getContentAsMessage().message.isAction) {
|
||||
return false;
|
||||
}
|
||||
|
||||
qint64 messagesTimeDiff = prevItem.getTimestamp().secsTo(currentItem.getTimestamp());
|
||||
return currentItem.getSender() == prevItem.getSender()
|
||||
&& messagesTimeDiff < chatWidget->repNameAfter;
|
||||
}
|
||||
|
||||
void GenericChatForm::onEmoteButtonClicked()
|
||||
{
|
||||
|
@ -649,7 +494,7 @@ void GenericChatForm::onChatMessageFontChanged(const QFont& font)
|
|||
|
||||
void GenericChatForm::setColorizedNames(bool enable)
|
||||
{
|
||||
colorizeNames = enable;
|
||||
chatWidget->setColorizedNames(enable);
|
||||
}
|
||||
|
||||
void GenericChatForm::addSystemInfoMessage(const QDateTime& datetime, SystemMessageType messageType,
|
||||
|
@ -662,14 +507,6 @@ void GenericChatForm::addSystemInfoMessage(const QDateTime& datetime, SystemMess
|
|||
chatLog.addSystemMessage(systemMessage);
|
||||
}
|
||||
|
||||
void GenericChatForm::addSystemDateMessage(const QDate& date)
|
||||
{
|
||||
const Settings& s = Settings::getInstance();
|
||||
QString dateText = date.toString(s.getDateFormat());
|
||||
|
||||
insertChatMessage(ChatMessage::createChatInfoMessage(dateText, ChatMessage::INFO, QDateTime()));
|
||||
}
|
||||
|
||||
QDateTime GenericChatForm::getTime(const ChatLine::Ptr &chatLine) const
|
||||
{
|
||||
if (chatLine) {
|
||||
|
@ -685,124 +522,6 @@ QDateTime GenericChatForm::getTime(const ChatLine::Ptr &chatLine) const
|
|||
return QDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief GenericChatForm::loadHistory load history
|
||||
* @param time start date
|
||||
* @param type indicates the direction of loading history
|
||||
*/
|
||||
void GenericChatForm::loadHistory(const QDateTime &time, const LoadHistoryDialog::LoadType type)
|
||||
{
|
||||
chatWidget->clear();
|
||||
messages.clear();
|
||||
|
||||
if (type == LoadHistoryDialog::from) {
|
||||
loadHistoryFrom(time);
|
||||
auto msg = messages.cbegin()->second;
|
||||
chatWidget->setScroll(true);
|
||||
chatWidget->scrollToLine(msg);
|
||||
} else {
|
||||
loadHistoryTo(time);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief GenericChatForm::loadHistoryTo load history before to date "time" or before the first "messages" item
|
||||
* @param time start date
|
||||
*/
|
||||
void GenericChatForm::loadHistoryTo(const QDateTime &time)
|
||||
{
|
||||
chatWidget->setScroll(false);
|
||||
auto end = chatLog.getFirstIdx();
|
||||
if (time.isNull()) {
|
||||
end = messages.begin()->first;
|
||||
} else {
|
||||
end = firstItemAfterDate(time.date(), chatLog);
|
||||
}
|
||||
|
||||
auto begin = chatLog.getFirstIdx();
|
||||
if (end - begin > DEF_NUM_MSG_TO_LOAD) {
|
||||
begin = end - DEF_NUM_MSG_TO_LOAD;
|
||||
}
|
||||
|
||||
if (begin != end) {
|
||||
if (searchResult.found == true && searchResult.pos.logIdx == end) {
|
||||
renderMessages(begin, end, [this]{enableSearchText();});
|
||||
} else {
|
||||
renderMessages(begin, end);
|
||||
}
|
||||
} else {
|
||||
chatWidget->setScroll(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief GenericChatForm::loadHistoryFrom load history starting from date "time" or from the last "messages" item
|
||||
* @param time start date
|
||||
* @return true if function loaded history else false
|
||||
*/
|
||||
bool GenericChatForm::loadHistoryFrom(const QDateTime &time)
|
||||
{
|
||||
chatWidget->setScroll(false);
|
||||
auto begin = chatLog.getFirstIdx();
|
||||
if (time.isNull()) {
|
||||
begin = messages.rbegin()->first;
|
||||
} else {
|
||||
begin = firstItemAfterDate(time.date(), chatLog);
|
||||
}
|
||||
|
||||
const auto end = chatLog.getNextIdx() < begin + DEF_NUM_MSG_TO_LOAD
|
||||
? chatLog.getNextIdx()
|
||||
: begin + DEF_NUM_MSG_TO_LOAD;
|
||||
|
||||
// The chatLog.getNextIdx() is usually 1 more than the idx on last "messages" item
|
||||
// so if we have nothing to load, "add" is equal 1
|
||||
if (end - begin <= 1) {
|
||||
chatWidget->setScroll(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
renderMessages(begin, end);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GenericChatForm::removeFirstsMessages(const int num)
|
||||
{
|
||||
if (static_cast<int>(messages.size()) > num) {
|
||||
messages.erase(messages.begin(), std::next(messages.begin(), num));
|
||||
} else {
|
||||
messages.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void GenericChatForm::removeLastsMessages(const int num)
|
||||
{
|
||||
if (static_cast<int>(messages.size()) > num) {
|
||||
messages.erase(std::next(messages.end(), -num), messages.end());
|
||||
} else {
|
||||
messages.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void GenericChatForm::disableSearchText()
|
||||
{
|
||||
auto msgIt = messages.find(searchResult.pos.logIdx);
|
||||
if (msgIt != messages.end()) {
|
||||
auto text = qobject_cast<Text*>(msgIt->second->getContent(1));
|
||||
text->deselectText();
|
||||
}
|
||||
}
|
||||
|
||||
void GenericChatForm::enableSearchText()
|
||||
{
|
||||
auto msg = messages.at(searchResult.pos.logIdx);
|
||||
chatWidget->scrollToLine(msg);
|
||||
|
||||
auto text = qobject_cast<Text*>(msg->getContent(1));
|
||||
text->visibilityChanged(true);
|
||||
text->selectText(searchResult.exp, std::make_pair(searchResult.start, searchResult.len));
|
||||
}
|
||||
|
||||
void GenericChatForm::clearChatArea()
|
||||
{
|
||||
|
@ -825,8 +544,6 @@ void GenericChatForm::clearChatArea(bool confirm, bool inform)
|
|||
|
||||
if (inform)
|
||||
addSystemInfoMessage(QDateTime::currentDateTime(), SystemMessageType::cleared, {});
|
||||
|
||||
messages.clear();
|
||||
}
|
||||
|
||||
void GenericChatForm::onSelectAllClicked()
|
||||
|
@ -834,12 +551,6 @@ void GenericChatForm::onSelectAllClicked()
|
|||
chatWidget->selectAll();
|
||||
}
|
||||
|
||||
void GenericChatForm::insertChatMessage(ChatMessage::Ptr msg)
|
||||
{
|
||||
chatWidget->insertChatlineAtBottom(std::static_pointer_cast<ChatLine>(msg));
|
||||
emit messageInserted();
|
||||
}
|
||||
|
||||
void GenericChatForm::hideEvent(QHideEvent* event)
|
||||
{
|
||||
hideFileMenu();
|
||||
|
@ -940,10 +651,7 @@ void GenericChatForm::onLoadHistory()
|
|||
{
|
||||
LoadHistoryDialog dlg(&chatLog);
|
||||
if (dlg.exec()) {
|
||||
QDateTime time = dlg.getFromDate();
|
||||
auto type = dlg.getLoadType();
|
||||
|
||||
loadHistory(time, type);
|
||||
chatWidget->jumpToDate(dlg.getFromDate().date());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -978,272 +686,9 @@ void GenericChatForm::onExportChat()
|
|||
file.close();
|
||||
}
|
||||
|
||||
void GenericChatForm::onSearchTriggered()
|
||||
{
|
||||
if (searchForm->isHidden()) {
|
||||
searchResult.found = false;
|
||||
searchForm->removeSearchPhrase();
|
||||
}
|
||||
disableSearchText();
|
||||
}
|
||||
|
||||
void GenericChatForm::searchInBegin(const QString& phrase, const ParameterSearch& parameter)
|
||||
{
|
||||
if (phrase.isEmpty()) {
|
||||
disableSearchText();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (messages.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (chatLog.getNextIdx() == messages.rbegin()->first + 1) {
|
||||
disableSearchText();
|
||||
} else {
|
||||
goToCurrentDate();
|
||||
}
|
||||
|
||||
bool bForwardSearch = false;
|
||||
switch (parameter.period) {
|
||||
case PeriodSearch::WithTheFirst: {
|
||||
bForwardSearch = true;
|
||||
searchResult.pos.logIdx = chatLog.getFirstIdx();
|
||||
searchResult.pos.numMatches = 0;
|
||||
break;
|
||||
}
|
||||
case PeriodSearch::WithTheEnd:
|
||||
case PeriodSearch::None: {
|
||||
bForwardSearch = false;
|
||||
searchResult.pos.logIdx = chatLog.getNextIdx();
|
||||
searchResult.pos.numMatches = 0;
|
||||
break;
|
||||
}
|
||||
case PeriodSearch::AfterDate: {
|
||||
bForwardSearch = true;
|
||||
searchResult.pos.logIdx = firstItemAfterDate(parameter.time.date(), chatLog);
|
||||
searchResult.pos.numMatches = 0;
|
||||
break;
|
||||
}
|
||||
case PeriodSearch::BeforeDate: {
|
||||
bForwardSearch = false;
|
||||
searchResult.pos.logIdx = firstItemAfterDate(parameter.time.date(), chatLog);
|
||||
searchResult.pos.numMatches = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bForwardSearch) {
|
||||
onSearchDown(phrase, parameter);
|
||||
} else {
|
||||
onSearchUp(phrase, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
void GenericChatForm::onSearchUp(const QString& phrase, const ParameterSearch& parameter)
|
||||
{
|
||||
auto result = chatLog.searchBackward(searchResult.pos, phrase, parameter);
|
||||
handleSearchResult(result, SearchDirection::Up);
|
||||
}
|
||||
|
||||
void GenericChatForm::onSearchDown(const QString& phrase, const ParameterSearch& parameter)
|
||||
{
|
||||
auto result = chatLog.searchForward(searchResult.pos, phrase, parameter);
|
||||
handleSearchResult(result, SearchDirection::Down);
|
||||
}
|
||||
|
||||
void GenericChatForm::handleSearchResult(SearchResult result, SearchDirection direction)
|
||||
{
|
||||
if (!result.found) {
|
||||
emit messageNotFoundShow(direction);
|
||||
return;
|
||||
}
|
||||
|
||||
disableSearchText();
|
||||
|
||||
searchResult = result;
|
||||
|
||||
auto searchIdx = result.pos.logIdx;
|
||||
|
||||
auto firstRenderedIdx = messages.begin()->first;
|
||||
auto endRenderedIdx = messages.rbegin()->first;
|
||||
|
||||
if (direction == SearchDirection::Up) {
|
||||
if (searchIdx < firstRenderedIdx) {
|
||||
if (searchIdx - chatLog.getFirstIdx() > DEF_NUM_MSG_TO_LOAD / 2) {
|
||||
firstRenderedIdx = searchIdx - DEF_NUM_MSG_TO_LOAD / 2;
|
||||
} else {
|
||||
firstRenderedIdx = chatLog.getFirstIdx();
|
||||
}
|
||||
}
|
||||
|
||||
if (endRenderedIdx - firstRenderedIdx > DEF_NUM_MSG_TO_LOAD) {
|
||||
endRenderedIdx = firstRenderedIdx + DEF_NUM_MSG_TO_LOAD;
|
||||
}
|
||||
} else {
|
||||
if (searchIdx < firstRenderedIdx) {
|
||||
firstRenderedIdx = searchIdx;
|
||||
}
|
||||
|
||||
if (firstRenderedIdx == searchIdx || searchIdx > endRenderedIdx) {
|
||||
if (searchIdx + DEF_NUM_MSG_TO_LOAD > chatLog.getNextIdx()) {
|
||||
endRenderedIdx = chatLog.getNextIdx();
|
||||
} else {
|
||||
endRenderedIdx = searchIdx + DEF_NUM_MSG_TO_LOAD;
|
||||
}
|
||||
}
|
||||
|
||||
if (endRenderedIdx - firstRenderedIdx > DEF_NUM_MSG_TO_LOAD) {
|
||||
if (endRenderedIdx - chatLog.getFirstIdx() > DEF_NUM_MSG_TO_LOAD) {
|
||||
firstRenderedIdx = endRenderedIdx - DEF_NUM_MSG_TO_LOAD;
|
||||
} else {
|
||||
firstRenderedIdx = chatLog.getFirstIdx();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!messages.empty() && (firstRenderedIdx < messages.begin()->first
|
||||
|| endRenderedIdx > messages.rbegin()->first)) {
|
||||
chatWidget->clear();
|
||||
messages.clear();
|
||||
|
||||
auto mediator = endRenderedIdx;
|
||||
endRenderedIdx = firstRenderedIdx;
|
||||
firstRenderedIdx = mediator;
|
||||
}
|
||||
|
||||
renderMessages(endRenderedIdx, firstRenderedIdx, [this]{enableSearchText();});
|
||||
}
|
||||
|
||||
void GenericChatForm::renderItem(const ChatLogItem& item, bool hideName, bool colorizeNames, ChatMessage::Ptr& chatMessage)
|
||||
{
|
||||
const auto& sender = item.getSender();
|
||||
|
||||
bool isSelf = sender == core.getSelfId().getPublicKey();
|
||||
|
||||
switch (item.getContentType()) {
|
||||
case ChatLogItem::ContentType::message: {
|
||||
const auto& chatLogMessage = item.getContentAsMessage();
|
||||
|
||||
renderMessageRaw(item.getDisplayName(), isSelf, colorizeNames, chatLogMessage, chatMessage);
|
||||
|
||||
break;
|
||||
}
|
||||
case ChatLogItem::ContentType::fileTransfer: {
|
||||
const auto& file = item.getContentAsFile();
|
||||
renderFile(item.getDisplayName(), file.file, isSelf, item.getTimestamp(), chatMessage);
|
||||
break;
|
||||
}
|
||||
case ChatLogItem::ContentType::systemMessage: {
|
||||
const auto& systemMessage = item.getContentAsSystemMessage();
|
||||
|
||||
auto chatMessageType = getChatMessageType(systemMessage);
|
||||
chatMessage = ChatMessage::createChatInfoMessage(systemMessage.toString(), chatMessageType,
|
||||
QDateTime::currentDateTime());
|
||||
// Ignore caller's decision to hide the name. We show the icon in the
|
||||
// slot of the sender's name so we always want it visible
|
||||
hideName = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hideName) {
|
||||
chatMessage->hideSender();
|
||||
}
|
||||
}
|
||||
|
||||
void GenericChatForm::renderMessage(ChatLogIdx idx)
|
||||
{
|
||||
renderMessages(idx, idx + 1);
|
||||
}
|
||||
|
||||
void GenericChatForm::renderMessages(ChatLogIdx begin, ChatLogIdx end,
|
||||
std::function<void(void)> onCompletion)
|
||||
{
|
||||
QList<ChatLine::Ptr> beforeLines;
|
||||
QList<ChatLine::Ptr> afterLines;
|
||||
|
||||
for (auto i = begin; i < end; ++i) {
|
||||
auto chatMessage = getChatMessageForIdx(i, messages);
|
||||
renderItem(chatLog.at(i), needsToHideName(i), colorizeNames, chatMessage);
|
||||
|
||||
if (messages.find(i) == messages.end()) {
|
||||
QList<ChatLine::Ptr>* lines =
|
||||
(messages.empty() || i > messages.rbegin()->first) ? &afterLines : &beforeLines;
|
||||
|
||||
messages.insert({i, chatMessage});
|
||||
|
||||
if (shouldRenderDate(i, chatLog)) {
|
||||
lines->push_back(dateMessageForItem(chatLog.at(i)));
|
||||
}
|
||||
lines->push_back(chatMessage);
|
||||
}
|
||||
}
|
||||
|
||||
if (beforeLines.isEmpty() && afterLines.isEmpty()) {
|
||||
chatWidget->setScroll(true);
|
||||
}
|
||||
|
||||
chatWidget->insertChatlineAtBottom(afterLines);
|
||||
if (chatWidget->getNumRemove()) {
|
||||
removeFirstsMessages(chatWidget->getNumRemove());
|
||||
}
|
||||
|
||||
if (!beforeLines.empty()) {
|
||||
// Rendering upwards is expensive and has async behavior for chatWidget.
|
||||
// Once rendering completes we call our completion callback once and
|
||||
// then disconnect the signal
|
||||
if (onCompletion) {
|
||||
auto connection = std::make_shared<QMetaObject::Connection>();
|
||||
*connection = connect(chatWidget, &ChatLog::workerTimeoutFinished,
|
||||
[this, onCompletion, connection] {
|
||||
onCompletion();
|
||||
this->disconnect(*connection);
|
||||
});
|
||||
}
|
||||
|
||||
chatWidget->insertChatlinesOnTop(beforeLines);
|
||||
if (chatWidget->getNumRemove()) {
|
||||
removeLastsMessages(chatWidget->getNumRemove());
|
||||
}
|
||||
} else if (onCompletion) {
|
||||
onCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
void GenericChatForm::goToCurrentDate()
|
||||
{
|
||||
chatWidget->clear();
|
||||
messages.clear();
|
||||
auto end = chatLog.getNextIdx();
|
||||
auto numMessages = std::min(DEF_NUM_MSG_TO_LOAD, chatLog.getNextIdx() - chatLog.getFirstIdx());
|
||||
auto begin = end - numMessages;
|
||||
|
||||
renderMessages(begin, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief GenericChatForm::loadHistoryLower load history after scrolling chatlog before first "messages" item
|
||||
*/
|
||||
void GenericChatForm::loadHistoryLower()
|
||||
{
|
||||
loadHistoryTo(QDateTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief GenericChatForm::loadHistoryUpper load history after scrolling chatlog after last "messages" item
|
||||
*/
|
||||
void GenericChatForm::loadHistoryUpper()
|
||||
{
|
||||
if (messages.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto msg = messages.crbegin()->second;
|
||||
if (loadHistoryFrom(QDateTime())) {
|
||||
chatWidget->scrollToLine(msg);
|
||||
}
|
||||
chatWidget->jumpToIdx(chatLog.getNextIdx());
|
||||
}
|
||||
|
||||
void GenericChatForm::updateShowDateInfo(const ChatLine::Ptr& prevLine, const ChatLine::Ptr& topLine)
|
||||
|
@ -1274,7 +719,7 @@ void GenericChatForm::retranslateUi()
|
|||
quoteAction->setText(tr("Quote selected text"));
|
||||
copyLinkAction->setText(tr("Copy link address"));
|
||||
searchAction->setText(tr("Search in text"));
|
||||
goCurrentDateAction->setText(tr("Go to current date"));
|
||||
goToCurrentDateAction->setText(tr("Go to current date"));
|
||||
loadHistoryAction->setText(tr("Load chat history..."));
|
||||
exportChatAction->setText(tr("Export to file"));
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
#include "src/chatlog/chatmessage.h"
|
||||
#include "src/core/toxpk.h"
|
||||
#include "src/model/ichatlog.h"
|
||||
#include "src/widget/form/loadhistorydialog.h"
|
||||
#include "src/widget/searchtypes.h"
|
||||
|
||||
#include <QMenu>
|
||||
|
@ -35,7 +34,7 @@
|
|||
*/
|
||||
|
||||
class ChatFormHeader;
|
||||
class ChatLog;
|
||||
class ChatWidget;
|
||||
class ChatTextEdit;
|
||||
class Contact;
|
||||
class ContentLayout;
|
||||
|
@ -83,7 +82,6 @@ public:
|
|||
|
||||
signals:
|
||||
void messageInserted();
|
||||
void messageNotFoundShow(SearchDirection direction);
|
||||
|
||||
public slots:
|
||||
void focusInput();
|
||||
|
@ -109,46 +107,23 @@ protected slots:
|
|||
void onLoadHistory();
|
||||
void onExportChat();
|
||||
void searchFormShow();
|
||||
void onSearchTriggered();
|
||||
void updateShowDateInfo(const ChatLine::Ptr& prevLine, const ChatLine::Ptr& topLine);
|
||||
|
||||
void searchInBegin(const QString& phrase, const ParameterSearch& parameter);
|
||||
void onSearchUp(const QString& phrase, const ParameterSearch& parameter);
|
||||
void onSearchDown(const QString& phrase, const ParameterSearch& parameter);
|
||||
void handleSearchResult(SearchResult result, SearchDirection direction);
|
||||
void renderMessage(ChatLogIdx idx);
|
||||
void renderMessages(ChatLogIdx begin, ChatLogIdx end,
|
||||
std::function<void(void)> onCompletion = std::function<void(void)>());
|
||||
void goToCurrentDate();
|
||||
|
||||
void loadHistoryLower();
|
||||
void loadHistoryUpper();
|
||||
|
||||
private:
|
||||
void retranslateUi();
|
||||
void addSystemDateMessage(const QDate& date);
|
||||
QDateTime getTime(const ChatLine::Ptr& chatLine) const;
|
||||
void loadHistory(const QDateTime& time, const LoadHistoryDialog::LoadType type);
|
||||
void loadHistoryTo(const QDateTime& time);
|
||||
bool loadHistoryFrom(const QDateTime& time);
|
||||
void removeFirstsMessages(const int num);
|
||||
void removeLastsMessages(const int num);
|
||||
|
||||
void renderItem(const ChatLogItem &item, bool hideName, bool colorizeNames, ChatMessage::Ptr &chatMessage);
|
||||
void renderFile(QString displayName, ToxFile file, bool isSelf, QDateTime timestamp, ChatMessage::Ptr &chatMessage);
|
||||
protected:
|
||||
ChatMessage::Ptr createMessage(const ToxPk& author, const QString& message,
|
||||
const QDateTime& datetime, bool isAction, bool isSent, bool colorizeName = false);
|
||||
bool needsToHideName(ChatLogIdx idx) const;
|
||||
virtual void insertChatMessage(ChatMessage::Ptr msg);
|
||||
void adjustFileMenuPosition();
|
||||
void hideEvent(QHideEvent* event) override;
|
||||
void showEvent(QShowEvent*) override;
|
||||
bool event(QEvent*) final;
|
||||
void resizeEvent(QResizeEvent* event) final;
|
||||
bool eventFilter(QObject* object, QEvent* event) final;
|
||||
void disableSearchText();
|
||||
void enableSearchText();
|
||||
bool searchInText(const QString& phrase, const ParameterSearch& parameter, SearchDirection direction);
|
||||
std::pair<int, int> indexForSearchInLine(const QString& txt, const QString& phrase, const ParameterSearch& parameter, SearchDirection direction);
|
||||
|
||||
|
@ -162,9 +137,9 @@ protected:
|
|||
QAction* quoteAction;
|
||||
QAction* copyLinkAction;
|
||||
QAction* searchAction;
|
||||
QAction* goToCurrentDateAction;
|
||||
QAction* loadHistoryAction;
|
||||
QAction* exportChatAction;
|
||||
QAction* goCurrentDateAction;
|
||||
|
||||
QMenu menu;
|
||||
|
||||
|
@ -180,7 +155,7 @@ protected:
|
|||
|
||||
SearchForm *searchForm;
|
||||
QLabel *dateInfo;
|
||||
ChatLog* chatWidget;
|
||||
ChatWidget* chatWidget;
|
||||
ChatTextEdit* msgEdit;
|
||||
#ifdef SPELL_CHECKING
|
||||
Sonnet::SpellCheckDecorator* decorator{nullptr};
|
||||
|
@ -190,7 +165,4 @@ protected:
|
|||
|
||||
IChatLog& chatLog;
|
||||
IMessageDispatcher& messageDispatcher;
|
||||
SearchResult searchResult;
|
||||
std::map<ChatLogIdx, ChatMessage::Ptr> messages;
|
||||
bool colorizeNames = false;
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
#include "src/core/core.h"
|
||||
#include "src/core/coreav.h"
|
||||
#include "src/core/groupid.h"
|
||||
#include "src/chatlog/chatlog.h"
|
||||
#include "src/chatlog/chatwidget.h"
|
||||
#include "src/chatlog/content/text.h"
|
||||
#include "src/model/friend.h"
|
||||
#include "src/friendlist.h"
|
||||
|
|
|
@ -38,15 +38,11 @@ LoadHistoryDialog::LoadHistoryDialog(const IChatLog* chatLog, QWidget* parent)
|
|||
&LoadHistoryDialog::highlightDates);
|
||||
}
|
||||
|
||||
LoadHistoryDialog::LoadHistoryDialog(Mode mode, QWidget* parent)
|
||||
LoadHistoryDialog::LoadHistoryDialog(QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, ui(new Ui::LoadHistoryDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
if (mode == Mode::search) {
|
||||
enableSearchMode();
|
||||
}
|
||||
}
|
||||
|
||||
LoadHistoryDialog::~LoadHistoryDialog()
|
||||
|
@ -70,21 +66,14 @@ QDateTime LoadHistoryDialog::getFromDate()
|
|||
return res;
|
||||
}
|
||||
|
||||
LoadHistoryDialog::LoadType LoadHistoryDialog::getLoadType()
|
||||
void LoadHistoryDialog::setTitle(const QString& title)
|
||||
{
|
||||
if (ui->loadTypeComboBox->currentIndex() == 0) {
|
||||
return LoadType::from;
|
||||
}
|
||||
|
||||
return LoadType::to;
|
||||
setWindowTitle(title);
|
||||
}
|
||||
|
||||
void LoadHistoryDialog::enableSearchMode()
|
||||
void LoadHistoryDialog::setInfoLabel(const QString& info)
|
||||
{
|
||||
setWindowTitle(tr("Select date dialog"));
|
||||
ui->fromLabel->setText(tr("Select a date"));
|
||||
ui->loadTypeComboBox->setVisible(false);
|
||||
ui->infoLabel->setVisible(false);
|
||||
ui->fromLabel->setText(info);
|
||||
}
|
||||
|
||||
void LoadHistoryDialog::highlightDates(int year, int month)
|
||||
|
|
|
@ -33,29 +33,18 @@ class LoadHistoryDialog : public QDialog
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum LoadType {
|
||||
from,
|
||||
to
|
||||
};
|
||||
|
||||
enum Mode {
|
||||
common,
|
||||
search
|
||||
};
|
||||
|
||||
explicit LoadHistoryDialog(const IChatLog* chatLog, QWidget* parent = nullptr);
|
||||
explicit LoadHistoryDialog(Mode mode, QWidget* parent = nullptr);
|
||||
explicit LoadHistoryDialog(QWidget* parent = nullptr);
|
||||
~LoadHistoryDialog();
|
||||
|
||||
QDateTime getFromDate();
|
||||
LoadType getLoadType();
|
||||
void setTitle(const QString& title);
|
||||
void setInfoLabel(const QString& info);
|
||||
|
||||
public slots:
|
||||
void highlightDates(int year, int month);
|
||||
|
||||
private:
|
||||
void enableSearchMode();
|
||||
|
||||
Ui::LoadHistoryDialog* ui;
|
||||
const IChatLog* chatLog;
|
||||
};
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>410</width>
|
||||
<height>332</height>
|
||||
<width>347</width>
|
||||
<height>264</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -16,60 +16,22 @@
|
|||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="fromLabel">
|
||||
<property name="text">
|
||||
<string>Load history</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="loadTypeComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>from</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>to</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="infoLabel">
|
||||
<property name="text">
|
||||
<string>(about 100 messages are loaded)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>17</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="fromLabel">
|
||||
<property name="text">
|
||||
<string>Load history from:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item>
|
||||
<widget class="QCalendarWidget" name="fromDate">
|
||||
<property name="gridVisible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
|
|
|
@ -90,7 +90,7 @@ ParameterSearch SearchSettingsForm::getParameterSearch()
|
|||
break;
|
||||
}
|
||||
|
||||
ps.time = startTime;
|
||||
ps.date = startDate;
|
||||
ps.isUpdate = isUpdate;
|
||||
isUpdate = false;
|
||||
|
||||
|
@ -105,7 +105,7 @@ void SearchSettingsForm::reloadTheme()
|
|||
|
||||
void SearchSettingsForm::updateStartDateLabel()
|
||||
{
|
||||
ui->startDateLabel->setText(startTime.toString(Settings::getInstance().getDateFormat()));
|
||||
ui->startDateLabel->setText(startDate.toString(Settings::getInstance().getDateFormat()));
|
||||
}
|
||||
|
||||
void SearchSettingsForm::setUpdate(const bool isUpdate)
|
||||
|
@ -123,8 +123,8 @@ void SearchSettingsForm::onStartSearchSelected(const int index)
|
|||
ui->choiceDateButton->setProperty("state", QStringLiteral("green"));
|
||||
ui->choiceDateButton->setStyleSheet(Style::getStylesheet(QStringLiteral("chatForm/buttons.css")));
|
||||
|
||||
if (startTime.isNull()) {
|
||||
startTime = QDateTime::currentDateTime();
|
||||
if (startDate.isNull()) {
|
||||
startDate = QDate::currentDate();
|
||||
updateStartDateLabel();
|
||||
}
|
||||
|
||||
|
@ -165,9 +165,11 @@ void SearchSettingsForm::onRegularClicked(const bool checked)
|
|||
|
||||
void SearchSettingsForm::onChoiceDate()
|
||||
{
|
||||
LoadHistoryDialog dlg(LoadHistoryDialog::search);
|
||||
LoadHistoryDialog dlg;
|
||||
dlg.setTitle(tr("Select Date Dialog"));
|
||||
dlg.setInfoLabel(tr("Select a date"));
|
||||
if (dlg.exec()) {
|
||||
startTime = dlg.getFromDate();
|
||||
startDate = dlg.getFromDate().date();
|
||||
updateStartDateLabel();
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ public:
|
|||
|
||||
private:
|
||||
Ui::SearchSettingsForm *ui;
|
||||
QDateTime startTime;
|
||||
QDate startDate;
|
||||
bool isUpdate{false};
|
||||
|
||||
void updateStartDateLabel();
|
||||
|
|
|
@ -47,13 +47,13 @@ enum class SearchDirection {
|
|||
struct ParameterSearch {
|
||||
FilterSearch filter{FilterSearch::None};
|
||||
PeriodSearch period{PeriodSearch::None};
|
||||
QDateTime time;
|
||||
QDate date;
|
||||
bool isUpdate{false};
|
||||
|
||||
bool operator ==(const ParameterSearch& other) {
|
||||
return filter == other.filter &&
|
||||
period == other.period &&
|
||||
time == other.time;
|
||||
date == other.date;
|
||||
}
|
||||
|
||||
bool operator !=(const ParameterSearch& other) {
|
||||
|
|
353
test/chatlog/chatlinestorage_test.cpp
Normal file
353
test/chatlog/chatlinestorage_test.cpp
Normal file
|
@ -0,0 +1,353 @@
|
|||
/*
|
||||
Copyright © 2021 by The qTox Project Contributors
|
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||
|
||||
qTox is libre 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.
|
||||
|
||||
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "src/chatlog/chatlinestorage.h"
|
||||
#include <QTest>
|
||||
|
||||
namespace
|
||||
{
|
||||
class IdxChatLine : public ChatLine
|
||||
{
|
||||
public:
|
||||
explicit IdxChatLine(ChatLogIdx idx)
|
||||
: ChatLine()
|
||||
, idx(idx)
|
||||
{}
|
||||
|
||||
ChatLogIdx get() { return idx; }
|
||||
private:
|
||||
ChatLogIdx idx;
|
||||
|
||||
};
|
||||
|
||||
class TimestampChatLine : public ChatLine
|
||||
{
|
||||
public:
|
||||
explicit TimestampChatLine(QDateTime dateTime)
|
||||
: ChatLine()
|
||||
, timestamp(dateTime)
|
||||
{}
|
||||
|
||||
QDateTime get() { return timestamp; }
|
||||
private:
|
||||
QDateTime timestamp;
|
||||
};
|
||||
|
||||
ChatLogIdx idxFromChatLine(ChatLine::Ptr p) {
|
||||
return std::static_pointer_cast<IdxChatLine>(p)->get();
|
||||
}
|
||||
|
||||
QDateTime timestampFromChatLine(ChatLine::Ptr p) {
|
||||
return std::static_pointer_cast<TimestampChatLine>(p)->get();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
class TestChatLineStorage : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void init();
|
||||
void testChatLogIdxAccess();
|
||||
void testIndexAccess();
|
||||
void testRangeBasedIteration();
|
||||
void testAppendingItems();
|
||||
void testPrependingItems();
|
||||
void testMiddleInsertion();
|
||||
void testIndexRemoval();
|
||||
void testItRemoval();
|
||||
void testDateLineAddition();
|
||||
void testDateLineRemoval();
|
||||
void testInsertionBeforeDates();
|
||||
void testInsertionAfterDate();
|
||||
void testContainsTimestamp();
|
||||
void testContainsIdx();
|
||||
void testEndOfStorageDateRemoval();
|
||||
void testConsecutiveDateLineRemoval();
|
||||
private:
|
||||
ChatLineStorage storage;
|
||||
|
||||
static constexpr size_t initialStartIdx = 10;
|
||||
static constexpr size_t initialEndIdx = 20;
|
||||
static const QDateTime initialTimestamp;
|
||||
|
||||
};
|
||||
|
||||
constexpr size_t TestChatLineStorage::initialStartIdx;
|
||||
constexpr size_t TestChatLineStorage::initialEndIdx;
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
|
||||
const QDateTime TestChatLineStorage::initialTimestamp = QDate(2021, 01, 01).startOfDay();
|
||||
#else
|
||||
const QDateTime TestChatLineStorage::initialTimestamp(QDate(2021, 01, 01));
|
||||
#endif
|
||||
|
||||
void TestChatLineStorage::init()
|
||||
{
|
||||
storage = ChatLineStorage();
|
||||
|
||||
for (auto idx = ChatLogIdx(initialStartIdx); idx < ChatLogIdx(initialEndIdx); ++idx) {
|
||||
storage.insertChatMessage(idx, initialTimestamp, std::make_shared<IdxChatLine>(idx));
|
||||
}
|
||||
}
|
||||
|
||||
void TestChatLineStorage::testChatLogIdxAccess()
|
||||
{
|
||||
for (auto idx = ChatLogIdx(initialStartIdx); idx < ChatLogIdx(initialEndIdx); ++idx) {
|
||||
QCOMPARE(idxFromChatLine(storage[idx]).get(), idx.get());
|
||||
}
|
||||
}
|
||||
|
||||
void TestChatLineStorage::testIndexAccess()
|
||||
{
|
||||
for (size_t i = 0; i < initialEndIdx - initialStartIdx; ++i) {
|
||||
QCOMPARE(idxFromChatLine(storage[i]).get(), initialStartIdx + i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TestChatLineStorage::testRangeBasedIteration()
|
||||
{
|
||||
auto idx = ChatLogIdx(initialStartIdx);
|
||||
|
||||
for (const auto& p : storage) {
|
||||
QCOMPARE(idxFromChatLine(p).get(), idx.get());
|
||||
idx = idx + 1;
|
||||
}
|
||||
}
|
||||
|
||||
void TestChatLineStorage::testAppendingItems()
|
||||
{
|
||||
for (auto idx = ChatLogIdx(initialEndIdx); idx < ChatLogIdx(initialEndIdx + 10); ++idx) {
|
||||
storage.insertChatMessage(idx, initialTimestamp, std::make_shared<IdxChatLine>(idx));
|
||||
QCOMPARE(storage.lastIdx().get(), idx.get());
|
||||
}
|
||||
|
||||
for (auto idx = ChatLogIdx(initialEndIdx); idx < storage.lastIdx(); ++idx) {
|
||||
QCOMPARE(idxFromChatLine(storage[idx]).get(), idx.get());
|
||||
QCOMPARE(idxFromChatLine(storage[idx.get() - initialStartIdx]).get(), idx.get());
|
||||
}
|
||||
}
|
||||
|
||||
void TestChatLineStorage::testMiddleInsertion()
|
||||
{
|
||||
ChatLogIdx newEnd = ChatLogIdx(initialEndIdx + 5);
|
||||
ChatLogIdx insertIdx = ChatLogIdx(initialEndIdx + 3);
|
||||
|
||||
storage.insertChatMessage(newEnd, initialTimestamp, std::make_shared<IdxChatLine>(newEnd));
|
||||
storage.insertChatMessage(insertIdx, initialTimestamp, std::make_shared<IdxChatLine>(insertIdx));
|
||||
|
||||
QCOMPARE(idxFromChatLine(storage[insertIdx]).get(), insertIdx.get());
|
||||
QCOMPARE(idxFromChatLine(storage[initialEndIdx - initialStartIdx]).get(), insertIdx.get());
|
||||
QCOMPARE(idxFromChatLine(storage[initialEndIdx - initialStartIdx + 1]).get(), newEnd.get());
|
||||
}
|
||||
|
||||
void TestChatLineStorage::testPrependingItems()
|
||||
{
|
||||
for (auto idx = ChatLogIdx(initialStartIdx - 1); idx != ChatLogIdx(-1); idx = idx - 1) {
|
||||
storage.insertChatMessage(idx, initialTimestamp, std::make_shared<IdxChatLine>(idx));
|
||||
QCOMPARE(storage.firstIdx().get(), idx.get());
|
||||
}
|
||||
|
||||
for (auto idx = storage.firstIdx(); idx < storage.lastIdx(); ++idx) {
|
||||
QCOMPARE(idxFromChatLine(storage[idx]).get(), idx.get());
|
||||
QCOMPARE(idxFromChatLine(storage[idx.get()]).get(), idx.get());
|
||||
}
|
||||
}
|
||||
|
||||
void TestChatLineStorage::testIndexRemoval()
|
||||
{
|
||||
QCOMPARE(initialStartIdx, static_cast<size_t>(10));
|
||||
QCOMPARE(initialEndIdx, static_cast<size_t>(20));
|
||||
QCOMPARE(storage.size(), static_cast<size_t>(10));
|
||||
|
||||
storage.erase(ChatLogIdx(11));
|
||||
|
||||
QCOMPARE(storage.size(), static_cast<size_t>(9));
|
||||
|
||||
QCOMPARE(idxFromChatLine(storage[0]).get(), static_cast<size_t>(10));
|
||||
QCOMPARE(idxFromChatLine(storage[1]).get(), static_cast<size_t>(12));
|
||||
|
||||
auto idx = static_cast<size_t>(12);
|
||||
for (auto it = std::next(storage.begin()); it != storage.end(); ++it) {
|
||||
QCOMPARE(idxFromChatLine((*it)).get(), idx++);
|
||||
}
|
||||
}
|
||||
|
||||
void TestChatLineStorage::testItRemoval()
|
||||
{
|
||||
auto it = storage.begin();
|
||||
it = it + 2;
|
||||
|
||||
storage.erase(it);
|
||||
|
||||
QCOMPARE(idxFromChatLine(storage[0]).get(), initialStartIdx);
|
||||
QCOMPARE(idxFromChatLine(storage[1]).get(), initialStartIdx + 1);
|
||||
// Item should have been removed
|
||||
QCOMPARE(idxFromChatLine(storage[2]).get(), initialStartIdx + 3);
|
||||
}
|
||||
|
||||
void TestChatLineStorage::testDateLineAddition()
|
||||
{
|
||||
storage.insertDateLine(initialTimestamp, std::make_shared<TimestampChatLine>(initialTimestamp));
|
||||
auto newTimestamp = initialTimestamp.addDays(1);
|
||||
storage.insertDateLine(newTimestamp, std::make_shared<TimestampChatLine>(newTimestamp));
|
||||
|
||||
QCOMPARE(storage.size(), initialEndIdx - initialStartIdx + 2);
|
||||
QCOMPARE(timestampFromChatLine(storage[0]), initialTimestamp);
|
||||
QCOMPARE(timestampFromChatLine(storage[storage.size() - 1]), newTimestamp);
|
||||
|
||||
for (size_t i = 1; i < storage.size() - 2; ++i)
|
||||
{
|
||||
// Ensure that indexed items all stayed in the right order
|
||||
QCOMPARE(idxFromChatLine(storage[i]).get(), idxFromChatLine(storage[ChatLogIdx(initialStartIdx + i - 1)]).get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TestChatLineStorage::testDateLineRemoval()
|
||||
{
|
||||
// For the time being there is no removal requirement
|
||||
storage.insertDateLine(initialTimestamp, std::make_shared<TimestampChatLine>(initialTimestamp));
|
||||
|
||||
QVERIFY(storage.contains(initialTimestamp));
|
||||
QCOMPARE(timestampFromChatLine(storage[0]), initialTimestamp);
|
||||
|
||||
storage.erase(storage.begin());
|
||||
|
||||
QVERIFY(!storage.contains(initialTimestamp));
|
||||
QCOMPARE(idxFromChatLine(storage[0]).get(), initialStartIdx);
|
||||
}
|
||||
|
||||
void TestChatLineStorage::testInsertionBeforeDates()
|
||||
{
|
||||
storage.insertDateLine(initialTimestamp, std::make_shared<TimestampChatLine>(initialTimestamp));
|
||||
|
||||
auto yesterday = initialTimestamp.addDays(-1);
|
||||
storage.insertDateLine(yesterday, std::make_shared<TimestampChatLine>(yesterday));
|
||||
|
||||
auto firstIdx = ChatLogIdx(initialStartIdx - 2);
|
||||
storage.insertChatMessage(firstIdx, initialTimestamp.addDays(-2), std::make_shared<IdxChatLine>(firstIdx));
|
||||
|
||||
QCOMPARE(idxFromChatLine(storage[0]).get(), firstIdx.get());
|
||||
QCOMPARE(timestampFromChatLine(storage[1]), yesterday);
|
||||
QCOMPARE(timestampFromChatLine(storage[2]), initialTimestamp);
|
||||
QCOMPARE(idxFromChatLine(storage[3]).get(), initialStartIdx);
|
||||
|
||||
auto secondIdx = ChatLogIdx(initialStartIdx - 1);
|
||||
storage.insertChatMessage(secondIdx, initialTimestamp.addDays(-1), std::make_shared<IdxChatLine>(secondIdx));
|
||||
|
||||
QCOMPARE(idxFromChatLine(storage[0]).get(), firstIdx.get());
|
||||
QCOMPARE(timestampFromChatLine(storage[1]), yesterday);
|
||||
QCOMPARE(idxFromChatLine(storage[2]).get(), secondIdx.get());
|
||||
QCOMPARE(timestampFromChatLine(storage[3]), initialTimestamp);
|
||||
QCOMPARE(idxFromChatLine(storage[4]).get(), initialStartIdx);
|
||||
}
|
||||
|
||||
void TestChatLineStorage::testInsertionAfterDate()
|
||||
{
|
||||
auto newTimestamp = initialTimestamp.addDays(1);
|
||||
storage.insertDateLine(newTimestamp, std::make_shared<TimestampChatLine>(newTimestamp));
|
||||
|
||||
QCOMPARE(storage.size(), initialEndIdx - initialStartIdx + 1);
|
||||
QCOMPARE(timestampFromChatLine(storage[initialEndIdx - initialStartIdx]), newTimestamp);
|
||||
|
||||
storage.insertChatMessage(ChatLogIdx(initialEndIdx), newTimestamp, std::make_shared<IdxChatLine>(ChatLogIdx(initialEndIdx)));
|
||||
QCOMPARE(idxFromChatLine(storage[initialEndIdx - initialStartIdx + 1]).get(), initialEndIdx);
|
||||
QCOMPARE(idxFromChatLine(storage[ChatLogIdx(initialEndIdx)]).get(), initialEndIdx);
|
||||
}
|
||||
|
||||
void TestChatLineStorage::testContainsTimestamp()
|
||||
{
|
||||
QCOMPARE(storage.contains(initialTimestamp), false);
|
||||
storage.insertDateLine(initialTimestamp, std::make_shared<TimestampChatLine>(initialTimestamp));
|
||||
QCOMPARE(storage.contains(initialTimestamp), true);
|
||||
}
|
||||
|
||||
void TestChatLineStorage::testContainsIdx()
|
||||
{
|
||||
QCOMPARE(storage.contains(ChatLogIdx(initialEndIdx)), false);
|
||||
QCOMPARE(storage.contains(ChatLogIdx(initialStartIdx)), true);
|
||||
}
|
||||
|
||||
void TestChatLineStorage::testEndOfStorageDateRemoval()
|
||||
{
|
||||
auto tomorrow = initialTimestamp.addDays(1);
|
||||
storage.insertDateLine(tomorrow, std::make_shared<TimestampChatLine>(tomorrow));
|
||||
storage.insertChatMessage(ChatLogIdx(initialEndIdx), tomorrow, std::make_shared<IdxChatLine>(ChatLogIdx(initialEndIdx)));
|
||||
|
||||
QCOMPARE(storage.size(), initialEndIdx - initialStartIdx + 2);
|
||||
|
||||
auto it = storage.begin() + storage.size() - 2;
|
||||
QCOMPARE(timestampFromChatLine(*it++), tomorrow);
|
||||
QCOMPARE(idxFromChatLine(*it).get(), initialEndIdx);
|
||||
|
||||
storage.erase(it);
|
||||
|
||||
QCOMPARE(storage.size(), initialEndIdx - initialStartIdx);
|
||||
|
||||
it = storage.begin() + storage.size() - 1;
|
||||
QCOMPARE(idxFromChatLine(*it++).get(), initialEndIdx - 1);
|
||||
}
|
||||
|
||||
void TestChatLineStorage::testConsecutiveDateLineRemoval()
|
||||
{
|
||||
auto todayPlus1 = initialTimestamp.addDays(1);
|
||||
auto todayPlus2 = initialTimestamp.addDays(2);
|
||||
|
||||
auto todayPlus1Idx = ChatLogIdx(initialEndIdx);
|
||||
auto todayPlus1Idx2 = ChatLogIdx(initialEndIdx + 1);
|
||||
auto todayPlus2Idx = ChatLogIdx(initialEndIdx + 2);
|
||||
|
||||
|
||||
storage.insertDateLine(todayPlus1, std::make_shared<TimestampChatLine>(todayPlus1));
|
||||
storage.insertChatMessage(todayPlus1Idx, todayPlus1, std::make_shared<IdxChatLine>(todayPlus1Idx));
|
||||
storage.insertChatMessage(todayPlus1Idx2, todayPlus1, std::make_shared<IdxChatLine>(todayPlus1Idx2));
|
||||
|
||||
storage.insertDateLine(todayPlus2, std::make_shared<TimestampChatLine>(todayPlus2));
|
||||
storage.insertChatMessage(todayPlus2Idx, todayPlus2, std::make_shared<IdxChatLine>(todayPlus2Idx));
|
||||
|
||||
// 2 date lines and 3 messages were inserted for a total of 5 new lines
|
||||
QCOMPARE(storage.size(), initialEndIdx - initialStartIdx + 5);
|
||||
|
||||
storage.erase(storage.find(todayPlus1Idx2));
|
||||
|
||||
auto newItemIdxStart = initialEndIdx - initialStartIdx;
|
||||
|
||||
// Only the chat message should have been removed
|
||||
QCOMPARE(storage.size(), initialEndIdx - initialStartIdx + 4);
|
||||
|
||||
QCOMPARE(timestampFromChatLine(storage[newItemIdxStart]), todayPlus1);
|
||||
QCOMPARE(idxFromChatLine(storage[newItemIdxStart + 1]).get(), todayPlus1Idx.get());
|
||||
QCOMPARE(timestampFromChatLine(storage[newItemIdxStart + 2]), todayPlus2);
|
||||
QCOMPARE(idxFromChatLine(storage[newItemIdxStart + 3]).get(), todayPlus2Idx.get());
|
||||
|
||||
storage.erase(storage.find(todayPlus1Idx));
|
||||
|
||||
// The chat message + the dateline for it should have been removed as there
|
||||
// were 2 adjacent datelines caused by the removal
|
||||
QCOMPARE(storage.size(), initialEndIdx - initialStartIdx + 2);
|
||||
QCOMPARE(timestampFromChatLine(storage[newItemIdxStart]), todayPlus2);
|
||||
QCOMPARE(idxFromChatLine(storage[newItemIdxStart + 1]).get(), todayPlus2Idx.get());
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(TestChatLineStorage);
|
||||
#include "chatlinestorage_test.moc"
|
Loading…
Reference in New Issue
Block a user