1
0
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:
Anthony Bilinski 2021-11-21 17:34:37 -08:00
commit 6a10abf1b3
No known key found for this signature in database
GPG Key ID: 2AA8E0DA1B31FB3C
33 changed files with 2394 additions and 1970 deletions

View File

@ -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

View File

@ -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 "")

View File

@ -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();
}

View File

@ -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:

View File

@ -30,11 +30,6 @@ int ChatLineContent::getColumn() const
return col;
}
int ChatLineContent::getRow() const
{
return row;
}
int ChatLineContent::type() const
{
return GraphicsItemType::ChatLineContentType;

View File

@ -35,7 +35,6 @@ public:
};
int getColumn() const;
int getRow() const;
virtual void setWidth(qreal width) = 0;
int type() const final;

View 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
);
}

View 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

File diff suppressed because it is too large Load Diff

View File

@ -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;
};

View File

@ -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();
}
}

View File

@ -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};
};

View File

@ -160,7 +160,6 @@ 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));
@ -168,11 +167,6 @@ SearchResult ChatHistory::searchBackward(SearchPos startIdx, const QString& phra
startIdx.logIdx = ChatLogIdx(loadIdx);
startIdx.numMatches = 0;
return sessionChatLog.searchBackward(startIdx, phrase, parameter);
}
SearchResult ret;
ret.found = false;
return ret;
}
ChatLogIdx ChatHistory::getFirstIdx() const

View File

@ -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);
};

View File

@ -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;

View File

@ -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;

View File

@ -201,8 +201,3 @@ QString Group::getSelfName() const
{
return selfName;
}
bool Group::useHistory() const
{
return false;
}

View File

@ -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);

View File

@ -69,7 +69,7 @@ struct SearchPos
struct SearchResult
{
bool found{false};
bool found;
SearchPos pos;
size_t start;
size_t len;

View File

@ -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;
}

View File

@ -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

View File

@ -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;

View File

@ -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"));
}

View 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;
};

View File

@ -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"

View File

@ -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)

View File

@ -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;
};

View File

@ -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">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="fromLabel">
<property name="text">
<string>Load history</string>
<string>Load history from:</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>
</item>
<item row="1" column="0">
<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>

View File

@ -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();
}

View File

@ -39,7 +39,7 @@ public:
private:
Ui::SearchSettingsForm *ui;
QDateTime startTime;
QDate startDate;
bool isUpdate{false};
void updateStartDateLabel();

View File

@ -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) {

View 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"