1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

Merge pull request #5672

TriKriSta (10):
      feat: load messages from the database before date
      feat: load messages from the database after date
      feat: edit function "Load chat history"
      feat: edit load history in search
      feat: add action "Go to current date"
      feat: edit position chat after load history
      feat: remove part messages from chat
      refactor: simple edit code
      refactor: edit load history when scrolling
      feat: prohibition to remove messages in group chat
This commit is contained in:
sudden6 2019-07-22 22:49:15 +02:00
commit be55759555
No known key found for this signature in database
GPG Key ID: 279509B499E032B9
23 changed files with 436 additions and 76 deletions

View File

@ -49,8 +49,8 @@ T clamp(T x, T min, T max)
return x; return x;
} }
ChatLog::ChatLog(QWidget* parent) ChatLog::ChatLog(const bool canRemove, QWidget* parent)
: QGraphicsView(parent) : QGraphicsView(parent), canRemove(canRemove)
{ {
// Create the scene // Create the scene
busyScene = new QGraphicsScene(this); busyScene = new QGraphicsScene(this);
@ -168,8 +168,9 @@ void ChatLog::updateSceneRect()
void ChatLog::layout(int start, int end, qreal width) void ChatLog::layout(int start, int end, qreal width)
{ {
if (lines.empty()) if (lines.empty()) {
return; return;
}
qreal h = 0.0; qreal h = 0.0;
@ -312,8 +313,9 @@ void ChatLog::mouseMoveEvent(QMouseEvent* ev)
// Much faster than QGraphicsScene::itemAt()! // Much faster than QGraphicsScene::itemAt()!
ChatLineContent* ChatLog::getContentFromPos(QPointF scenePos) const ChatLineContent* ChatLog::getContentFromPos(QPointF scenePos) const
{ {
if (lines.empty()) if (lines.empty()) {
return nullptr; return nullptr;
}
auto itr = auto itr =
std::lower_bound(lines.cbegin(), lines.cend(), scenePos.y(), ChatLine::lessThanBSRectBottom); std::lower_bound(lines.cbegin(), lines.cend(), scenePos.y(), ChatLine::lessThanBSRectBottom);
@ -361,6 +363,7 @@ void ChatLog::reposition(int start, int end, qreal deltaY)
void ChatLog::insertChatlineAtBottom(ChatLine::Ptr l) void ChatLog::insertChatlineAtBottom(ChatLine::Ptr l)
{ {
numRemove = 0;
if (!l.get()) if (!l.get())
return; return;
@ -382,8 +385,35 @@ void ChatLog::insertChatlineAtBottom(ChatLine::Ptr l)
updateTypingNotification(); updateTypingNotification();
} }
void ChatLog::insertChatlineAtBottom(const QList<ChatLine::Ptr>& newLines)
{
numRemove = 0;
if (newLines.isEmpty())
return;
if (canRemove && lines.size() + DEF_NUM_MSG_TO_LOAD >= maxMessages) {
removeFirsts(DEF_NUM_MSG_TO_LOAD);
}
for (ChatLine::Ptr l : newLines) {
l->setRow(lines.size());
l->addToScene(scene);
l->visibilityChanged(false);
lines.append(l);
}
layout(lines.last()->getRow(), lines.size(), useableWidth());
if (visibleLines.size() > 1) {
startResizeWorker(visibleLines[1]);
} else {
startResizeWorker();
}
}
void ChatLog::insertChatlineOnTop(ChatLine::Ptr l) void ChatLog::insertChatlineOnTop(ChatLine::Ptr l)
{ {
numRemove = 0;
if (!l.get()) if (!l.get())
return; return;
@ -392,6 +422,7 @@ void ChatLog::insertChatlineOnTop(ChatLine::Ptr l)
void ChatLog::insertChatlinesOnTop(const QList<ChatLine::Ptr>& newLines) void ChatLog::insertChatlinesOnTop(const QList<ChatLine::Ptr>& newLines)
{ {
numRemove = 0;
if (newLines.isEmpty()) if (newLines.isEmpty())
return; return;
@ -411,6 +442,10 @@ void ChatLog::insertChatlinesOnTop(const QList<ChatLine::Ptr>& newLines)
combLines.push_back(l); combLines.push_back(l);
} }
if (canRemove && lines.size() + DEF_NUM_MSG_TO_LOAD >= maxMessages) {
removeLasts(DEF_NUM_MSG_TO_LOAD);
}
// add the old lines // add the old lines
for (ChatLine::Ptr l : lines) { for (ChatLine::Ptr l : lines) {
l->setRow(i++); l->setRow(i++);
@ -422,7 +457,12 @@ void ChatLog::insertChatlinesOnTop(const QList<ChatLine::Ptr>& newLines)
scene->setItemIndexMethod(oldIndexMeth); scene->setItemIndexMethod(oldIndexMeth);
// redo layout // redo layout
if (visibleLines.size() > 1) {
startResizeWorker(visibleLines[1]);
} else {
startResizeWorker(); startResizeWorker();
}
} }
bool ChatLog::stickToBottom() const bool ChatLog::stickToBottom() const
@ -436,18 +476,22 @@ void ChatLog::scrollToBottom()
verticalScrollBar()->setValue(verticalScrollBar()->maximum()); verticalScrollBar()->setValue(verticalScrollBar()->maximum());
} }
void ChatLog::startResizeWorker() void ChatLog::startResizeWorker(ChatLine::Ptr anchorLine)
{ {
if (lines.empty()) if (lines.empty()) {
isScroll = true;
return; return;
}
// (re)start the worker // (re)start the worker
if (!workerTimer->isActive()) { if (!workerTimer->isActive()) {
// these values must not be reevaluated while the worker is running // these values must not be reevaluated while the worker is running
if (anchorLine) {
workerAnchorLine = anchorLine;
workerStb = false;
} else {
workerStb = stickToBottom(); workerStb = stickToBottom();
}
if (!visibleLines.empty())
workerAnchorLine = visibleLines.first();
} }
// switch to busy scene displaying the busy notification if there is a lot // switch to busy scene displaying the busy notification if there is a lot
@ -634,14 +678,20 @@ void ChatLog::scrollToLine(ChatLine::Ptr line)
if (!line.get()) if (!line.get())
return; return;
if (workerTimer->isActive()) {
workerAnchorLine = line;
workerStb = false;
} else {
updateSceneRect(); updateSceneRect();
verticalScrollBar()->setValue(line->sceneBoundingRect().top()); verticalScrollBar()->setValue(line->sceneBoundingRect().top()); // NOTE: start here
}
} }
void ChatLog::selectAll() void ChatLog::selectAll()
{ {
if (lines.empty()) if (lines.empty()) {
return; return;
}
clearSelection(); clearSelection();
@ -672,15 +722,50 @@ void ChatLog::reloadTheme()
} }
} }
void ChatLog::removeFirsts(const int num)
{
if (lines.size() > num) {
lines.erase(lines.begin(), lines.begin()+num);
numRemove = num;
} else {
lines.clear();
}
for (int i = 0; i < lines.size(); ++i) {
lines[i]->setRow(i);
}
}
void ChatLog::removeLasts(const int num)
{
if (lines.size() > num) {
lines.erase(lines.end()-num, lines.end());
numRemove = num;
} else {
lines.clear();
}
}
void ChatLog::setScroll(const bool scroll)
{
isScroll = scroll;
}
int ChatLog::getNumRemove() const
{
return numRemove;
}
void ChatLog::forceRelayout() void ChatLog::forceRelayout()
{ {
startResizeWorker(); startResizeWorker();
} }
void ChatLog::checkVisibility() void ChatLog::checkVisibility(bool causedWheelEvent)
{ {
if (lines.empty()) if (lines.empty()) {
return; return;
}
// find first visible line // find first visible line
auto lowerBound = std::lower_bound(lines.cbegin(), lines.cend(), getVisibleRect().top(), auto lowerBound = std::lower_bound(lines.cbegin(), lines.cend(), getVisibleRect().top(),
@ -717,6 +802,14 @@ void ChatLog::checkVisibility()
if (!visibleLines.isEmpty()) { if (!visibleLines.isEmpty()) {
emit firstVisibleLineChanged(visibleLines.at(0)); emit firstVisibleLineChanged(visibleLines.at(0));
} }
if (causedWheelEvent) {
if (lowerBound != lines.cend() && lowerBound->get()->row == 0) {
emit loadHistoryLower();
} else if (upperBound == lines.cend()) {
emit loadHistoryUpper();
}
}
} }
void ChatLog::scrollContentsBy(int dx, int dy) void ChatLog::scrollContentsBy(int dx, int dy)
@ -767,8 +860,9 @@ void ChatLog::updateTypingNotification()
qreal posY = 0.0; qreal posY = 0.0;
if (!lines.empty()) if (!lines.empty()) {
posY = lines.last()->sceneBoundingRect().bottom() + lineSpacing; posY = lines.last()->sceneBoundingRect().bottom() + lineSpacing;
}
notification->layout(useableWidth(), QPointF(0.0, posY)); notification->layout(useableWidth(), QPointF(0.0, posY));
} }
@ -853,6 +947,7 @@ void ChatLog::onWorkerTimeout()
// hidden during busy screen // hidden during busy screen
verticalScrollBar()->show(); verticalScrollBar()->show();
isScroll = true;
emit workerTimeoutFinished(); emit workerTimeoutFinished();
} }
} }
@ -918,6 +1013,16 @@ void ChatLog::focusOutEvent(QFocusEvent* ev)
} }
} }
void ChatLog::wheelEvent(QWheelEvent *event)
{
if (!isScroll) {
return;
}
QGraphicsView::wheelEvent(event);
checkVisibility(true);
}
void ChatLog::retranslateUi() void ChatLog::retranslateUi()
{ {
copyAction->setText(tr("Copy")); copyAction->setText(tr("Copy"));

View File

@ -35,14 +35,17 @@ class QTimer;
class ChatLineContent; class ChatLineContent;
struct ToxFile; struct ToxFile;
static const auto DEF_NUM_MSG_TO_LOAD = 100;
class ChatLog : public QGraphicsView class ChatLog : public QGraphicsView
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit ChatLog(QWidget* parent = nullptr); explicit ChatLog(const bool canRemove, QWidget* parent = nullptr);
virtual ~ChatLog(); virtual ~ChatLog();
void insertChatlineAtBottom(ChatLine::Ptr l); void insertChatlineAtBottom(ChatLine::Ptr l);
void insertChatlineAtBottom(const QList<ChatLine::Ptr>& newLines);
void insertChatlineOnTop(ChatLine::Ptr l); void insertChatlineOnTop(ChatLine::Ptr l);
void insertChatlinesOnTop(const QList<ChatLine::Ptr>& newLines); void insertChatlinesOnTop(const QList<ChatLine::Ptr>& newLines);
void clearSelection(); void clearSelection();
@ -55,6 +58,10 @@ public:
void selectAll(); void selectAll();
void fontChanged(const QFont& font); void fontChanged(const QFont& font);
void reloadTheme(); void reloadTheme();
void removeFirsts(const int num);
void removeLasts(const int num);
void setScroll(const bool scroll);
int getNumRemove() const;
QString getSelectedText() const; QString getSelectedText() const;
@ -72,6 +79,8 @@ signals:
void selectionChanged(); void selectionChanged();
void workerTimeoutFinished(); void workerTimeoutFinished();
void firstVisibleLineChanged(const ChatLine::Ptr&); void firstVisibleLineChanged(const ChatLine::Ptr&);
void loadHistoryLower();
void loadHistoryUpper();
public slots: public slots:
void forceRelayout(); void forceRelayout();
@ -94,9 +103,9 @@ protected:
void reposition(int start, int end, qreal deltaY); void reposition(int start, int end, qreal deltaY);
void updateSceneRect(); void updateSceneRect();
void checkVisibility(); void checkVisibility(bool causedWheelEvent = false);
void scrollToBottom(); void scrollToBottom();
void startResizeWorker(); void startResizeWorker(ChatLine::Ptr anchorLine = nullptr);
virtual void mouseDoubleClickEvent(QMouseEvent* ev) final override; virtual void mouseDoubleClickEvent(QMouseEvent* ev) final override;
virtual void mousePressEvent(QMouseEvent* ev) final override; virtual void mousePressEvent(QMouseEvent* ev) final override;
@ -107,6 +116,7 @@ protected:
virtual void showEvent(QShowEvent*) final override; virtual void showEvent(QShowEvent*) final override;
virtual void focusInEvent(QFocusEvent* ev) final override; virtual void focusInEvent(QFocusEvent* ev) final override;
virtual void focusOutEvent(QFocusEvent* ev) final override; virtual void focusOutEvent(QFocusEvent* ev) final override;
virtual void wheelEvent(QWheelEvent *event) final override;
void updateMultiSelectionRect(); void updateMultiSelectionRect();
void updateTypingNotification(); void updateTypingNotification();
@ -159,6 +169,7 @@ private:
int clickCount = 0; int clickCount = 0;
QPoint lastClickPos; QPoint lastClickPos;
Qt::MouseButton lastClickButton; Qt::MouseButton lastClickButton;
bool isScroll{true};
// worker vars // worker vars
int workerLastIndex = 0; int workerLastIndex = 0;
@ -168,6 +179,10 @@ private:
// layout // layout
QMargins margins = QMargins(10, 10, 10, 10); QMargins margins = QMargins(10, 10, 10, 10);
qreal lineSpacing = 5.0f; qreal lineSpacing = 5.0f;
int numRemove{0};
const int maxMessages{300};
bool canRemove;
}; };
#endif // CHATLOG_H #endif // CHATLOG_H

View File

@ -66,9 +66,11 @@ void Text::selectText(const QString& txt, const std::pair<int, int>& point)
return; return;
} }
auto cursor = doc->find(txt, point.first); selectCursor = doc->find(txt, point.first);
selectPoint = point;
selectText(cursor, point); regenerate();
update();
} }
void Text::selectText(const QRegularExpression &exp, const std::pair<int, int>& point) void Text::selectText(const QRegularExpression &exp, const std::pair<int, int>& point)
@ -79,14 +81,20 @@ void Text::selectText(const QRegularExpression &exp, const std::pair<int, int>&
return; return;
} }
auto cursor = doc->find(exp, point.first); selectCursor = doc->find(exp, point.first);
selectPoint = point;
selectText(cursor, point); regenerate();
update();
} }
void Text::deselectText() void Text::deselectText()
{ {
dirty = true; dirty = true;
selectCursor = QTextCursor();
selectPoint = {0, 0};
regenerate(); regenerate();
update(); update();
} }
@ -355,6 +363,10 @@ void Text::regenerate()
dirty = false; dirty = false;
} }
if (!selectCursor.isNull()) {
selectText(selectCursor, selectPoint);
}
// if we are not visible -> free mem // if we are not visible -> free mem
if (!keepInMemory) if (!keepInMemory)
freeResources(); freeResources();
@ -462,9 +474,6 @@ void Text::selectText(QTextCursor& cursor, const std::pair<int, int>& point)
QTextCharFormat format; QTextCharFormat format;
format.setBackground(QBrush(Style::getColor(Style::SearchHighlighted))); format.setBackground(QBrush(Style::getColor(Style::SearchHighlighted)));
cursor.mergeCharFormat(format); cursor.mergeCharFormat(format);
regenerate();
update();
} }
} }

View File

@ -24,6 +24,7 @@
#include "src/widget/style.h" #include "src/widget/style.h"
#include <QFont> #include <QFont>
#include <QTextCursor>
class QTextDocument; class QTextDocument;
@ -110,6 +111,9 @@ private:
TextType textType; TextType textType;
QColor color; QColor color;
QColor customColor; QColor customColor;
QTextCursor selectCursor;
std::pair<int, int> selectPoint{0, 0};
}; };
#endif // TEXT_H #endif // TEXT_H

View File

@ -220,6 +220,15 @@ std::vector<IChatLog::DateChatLogIdxPair> ChatHistory::getDateIdxs(const QDate&
} }
} }
std::size_t ChatHistory::size() const
{
if (canUseHistory()) {
return history->getNumMessagesForFriend(f.getPublicKey());
}
return sessionChatLog.size();
}
void ChatHistory::onFileUpdated(const ToxPk& sender, const ToxFile& file) void ChatHistory::onFileUpdated(const ToxPk& sender, const ToxFile& file)
{ {
if (canUseHistory()) { if (canUseHistory()) {

View File

@ -42,6 +42,7 @@ public:
ChatLogIdx getFirstIdx() const override; ChatLogIdx getFirstIdx() const override;
ChatLogIdx getNextIdx() const override; ChatLogIdx getNextIdx() const override;
std::vector<DateChatLogIdxPair> getDateIdxs(const QDate& startDate, size_t maxDates) const override; std::vector<DateChatLogIdxPair> getDateIdxs(const QDate& startDate, size_t maxDates) const override;
std::size_t size() const override;
public slots: public slots:
void onFileUpdated(const ToxPk& sender, const ToxFile& file); void onFileUpdated(const ToxPk& sender, const ToxFile& file);

View File

@ -37,6 +37,8 @@ public:
virtual void setEventFlag(bool flag) = 0; virtual void setEventFlag(bool flag) = 0;
virtual bool getEventFlag() const = 0; virtual bool getEventFlag() const = 0;
virtual bool useHistory() const = 0; // TODO: remove after added history in group chat
signals: signals:
void displayedNameChanged(const QString& newName); void displayedNameChanged(const QString& newName);
}; };

View File

@ -166,3 +166,8 @@ bool Friend::isOnline() const
{ {
return friendStatus != Status::Status::Offline && friendStatus != Status::Status::Blocked; return friendStatus != Status::Status::Offline && friendStatus != Status::Status::Blocked;
} }
bool Friend::useHistory() const
{
return true;
}

View File

@ -55,6 +55,8 @@ public:
Status::Status getStatus() const; Status::Status getStatus() const;
bool isOnline() const; bool isOnline() const;
bool useHistory() const override final;
signals: signals:
void nameChanged(const ToxPk& friendId, const QString& name); void nameChanged(const ToxPk& friendId, const QString& name);
void aliasChanged(const ToxPk& friendId, QString alias); void aliasChanged(const ToxPk& friendId, QString alias);

View File

@ -205,6 +205,11 @@ QString Group::getSelfName() const
return selfName; return selfName;
} }
bool Group::useHistory() const
{
return false;
}
void Group::stopAudioOfDepartedPeers(const ToxPk& peerPk) void Group::stopAudioOfDepartedPeers(const ToxPk& peerPk)
{ {
if (avGroupchat) { if (avGroupchat) {

View File

@ -61,6 +61,8 @@ public:
void setSelfName(const QString& name); void setSelfName(const QString& name);
QString getSelfName() const; QString getSelfName() const;
bool useHistory() const override final;
signals: signals:
void titleChangedByUser(const QString& title); void titleChangedByUser(const QString& title);
void titleChanged(const QString& author, const QString& title); void titleChanged(const QString& author, const QString& title);

View File

@ -138,6 +138,8 @@ public:
virtual std::vector<DateChatLogIdxPair> getDateIdxs(const QDate& startDate, virtual std::vector<DateChatLogIdxPair> getDateIdxs(const QDate& startDate,
size_t maxDates) const = 0; size_t maxDates) const = 0;
virtual std::size_t size() const = 0;
signals: signals:
void itemUpdated(ChatLogIdx idx); void itemUpdated(ChatLogIdx idx);
}; };

View File

@ -289,6 +289,11 @@ std::vector<IChatLog::DateChatLogIdxPair> SessionChatLog::getDateIdxs(const QDat
return ret; return ret;
} }
std::size_t SessionChatLog::size() const
{
return items.size();
}
void SessionChatLog::insertMessageAtIdx(ChatLogIdx idx, ToxPk sender, QString senderName, void SessionChatLog::insertMessageAtIdx(ChatLogIdx idx, ToxPk sender, QString senderName,
ChatLogMessage message) ChatLogMessage message)
{ {

View File

@ -45,6 +45,7 @@ public:
ChatLogIdx getFirstIdx() const override; ChatLogIdx getFirstIdx() const override;
ChatLogIdx getNextIdx() const override; ChatLogIdx getNextIdx() const override;
std::vector<DateChatLogIdxPair> getDateIdxs(const QDate& startDate, size_t maxDates) const override; std::vector<DateChatLogIdxPair> getDateIdxs(const QDate& startDate, size_t maxDates) const override;
std::size_t size() const override;
void insertMessageAtIdx(ChatLogIdx idx, ToxPk sender, QString senderName, ChatLogMessage message); void insertMessageAtIdx(ChatLogIdx idx, ToxPk sender, QString senderName, ChatLogMessage message);
void insertFileAtIdx(ChatLogIdx idx, ToxPk sender, QString senderName, ChatLogFile file); void insertFileAtIdx(ChatLogIdx idx, ToxPk sender, QString senderName, ChatLogFile file);

View File

@ -681,14 +681,14 @@ QDateTime History::getDateWhereFindPhrase(const QString& friendPk, const QDateTi
break; break;
} }
QDateTime date = from; QDateTime time = from;
if (!date.isValid()) { if (!time.isValid()) {
date = QDateTime::currentDateTime(); time = QDateTime::currentDateTime();
} }
if (parameter.period == PeriodSearch::AfterDate || parameter.period == PeriodSearch::BeforeDate) { if (parameter.period == PeriodSearch::AfterDate || parameter.period == PeriodSearch::BeforeDate) {
date = QDateTime(parameter.date); time = parameter.time;
} }
QString period; QString period;
@ -698,15 +698,15 @@ QDateTime History::getDateWhereFindPhrase(const QString& friendPk, const QDateTi
break; break;
case PeriodSearch::AfterDate: case PeriodSearch::AfterDate:
period = QStringLiteral("AND timestamp > '%1' ORDER BY timestamp ASC LIMIT 1;") period = QStringLiteral("AND timestamp > '%1' ORDER BY timestamp ASC LIMIT 1;")
.arg(date.toMSecsSinceEpoch()); .arg(time.toMSecsSinceEpoch());
break; break;
case PeriodSearch::BeforeDate: case PeriodSearch::BeforeDate:
period = QStringLiteral("AND timestamp < '%1' ORDER BY timestamp DESC LIMIT 1;") period = QStringLiteral("AND timestamp < '%1' ORDER BY timestamp DESC LIMIT 1;")
.arg(date.toMSecsSinceEpoch()); .arg(time.toMSecsSinceEpoch());
break; break;
default: default:
period = QStringLiteral("AND timestamp < '%1' ORDER BY timestamp DESC LIMIT 1;") period = QStringLiteral("AND timestamp < '%1' ORDER BY timestamp DESC LIMIT 1;")
.arg(date.toMSecsSinceEpoch()); .arg(time.toMSecsSinceEpoch());
break; break;
} }

View File

@ -37,7 +37,6 @@
#include "src/widget/contentlayout.h" #include "src/widget/contentlayout.h"
#include "src/widget/emoticonswidget.h" #include "src/widget/emoticonswidget.h"
#include "src/widget/form/chatform.h" #include "src/widget/form/chatform.h"
#include "src/widget/form/loadhistorydialog.h"
#include "src/widget/maskablepixmapwidget.h" #include "src/widget/maskablepixmapwidget.h"
#include "src/widget/searchform.h" #include "src/widget/searchform.h"
#include "src/widget/style.h" #include "src/widget/style.h"
@ -54,6 +53,8 @@
#include <QStringBuilder> #include <QStringBuilder>
#include <QtGlobal> #include <QtGlobal>
#include <QDebug>
#ifdef SPELL_CHECKING #ifdef SPELL_CHECKING
#include <KF5/SonnetUi/sonnet/spellcheckdecorator.h> #include <KF5/SonnetUi/sonnet/spellcheckdecorator.h>
#endif #endif
@ -260,7 +261,7 @@ GenericChatForm::GenericChatForm(const Contact* contact, IChatLog& chatLog,
headWidget = new ChatFormHeader(); headWidget = new ChatFormHeader();
searchForm = new SearchForm(); searchForm = new SearchForm();
dateInfo = new QLabel(this); dateInfo = new QLabel(this);
chatWidget = new ChatLog(this); chatWidget = new ChatLog(contact->useHistory(), this);
chatWidget->setBusyNotification(ChatMessage::createBusyNotification()); chatWidget->setBusyNotification(ChatMessage::createBusyNotification());
searchForm->hide(); searchForm->hide();
dateInfo->setAlignment(Qt::AlignHCenter); dateInfo->setAlignment(Qt::AlignHCenter);
@ -329,6 +330,13 @@ GenericChatForm::GenericChatForm(const Contact* contact, IChatLog& chatLog,
quoteAction = menu.addAction(QIcon(), QString(), this, SLOT(quoteSelectedText()), quoteAction = menu.addAction(QIcon(), QString(), this, SLOT(quoteSelectedText()),
QKeySequence(Qt::ALT + Qt::Key_Q)); QKeySequence(Qt::ALT + Qt::Key_Q));
addAction(quoteAction); addAction(quoteAction);
menu.addSeparator();
goCurrentDateAction = menu.addAction(QIcon(), QString(), this, SLOT(goToCurrentDate()),
QKeySequence(Qt::CTRL + Qt::Key_G));
addAction(goCurrentDateAction);
menu.addSeparator(); menu.addSeparator();
searchAction = menu.addAction(QIcon(), QString(), this, SLOT(searchFormShow()), searchAction = menu.addAction(QIcon(), QString(), this, SLOT(searchFormShow()),
@ -355,6 +363,8 @@ GenericChatForm::GenericChatForm(const Contact* contact, IChatLog& chatLog,
connect(chatWidget, &ChatLog::customContextMenuRequested, this, connect(chatWidget, &ChatLog::customContextMenuRequested, this,
&GenericChatForm::onChatContextMenuRequested); &GenericChatForm::onChatContextMenuRequested);
connect(chatWidget, &ChatLog::firstVisibleLineChanged, this, &GenericChatForm::updateShowDateInfo); connect(chatWidget, &ChatLog::firstVisibleLineChanged, this, &GenericChatForm::updateShowDateInfo);
connect(chatWidget, &ChatLog::loadHistoryLower, this, &GenericChatForm::loadHistoryLower);
connect(chatWidget, &ChatLog::loadHistoryUpper, this, &GenericChatForm::loadHistoryUpper);
connect(searchForm, &SearchForm::searchInBegin, this, &GenericChatForm::searchInBegin); connect(searchForm, &SearchForm::searchInBegin, this, &GenericChatForm::searchInBegin);
connect(searchForm, &SearchForm::searchUp, this, &GenericChatForm::onSearchUp); connect(searchForm, &SearchForm::searchUp, this, &GenericChatForm::onSearchUp);
@ -380,7 +390,7 @@ GenericChatForm::GenericChatForm(const Contact* contact, IChatLog& chatLog,
connect(contact, &Contact::displayedNameChanged, this, &GenericChatForm::setName); connect(contact, &Contact::displayedNameChanged, this, &GenericChatForm::setName);
auto chatLogIdxRange = chatLog.getNextIdx() - chatLog.getFirstIdx(); auto chatLogIdxRange = chatLog.getNextIdx() - chatLog.getFirstIdx();
auto firstChatLogIdx = (chatLogIdxRange < 100) ? chatLog.getFirstIdx() : chatLog.getNextIdx() - 100; auto firstChatLogIdx = (chatLogIdxRange < DEF_NUM_MSG_TO_LOAD) ? chatLog.getFirstIdx() : chatLog.getNextIdx() - DEF_NUM_MSG_TO_LOAD;
renderMessages(firstChatLogIdx, chatLog.getNextIdx()); renderMessages(firstChatLogIdx, chatLog.getNextIdx());
@ -644,6 +654,79 @@ QDateTime GenericChatForm::getTime(const ChatLine::Ptr &chatLine) const
return QDateTime(); return QDateTime();
} }
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);
}
}
void GenericChatForm::loadHistoryTo(const QDateTime &time)
{
chatWidget->setScroll(false);
auto end = ChatLogIdx(0);
if (time.isNull()) {
end = messages.begin()->first;
} else {
end = firstItemAfterDate(time.date(), chatLog);
}
auto begin = ChatLogIdx(0);
if (end.get() > DEF_NUM_MSG_TO_LOAD) {
begin = ChatLogIdx(end.get() - DEF_NUM_MSG_TO_LOAD);
}
if (begin != end) {
renderMessages(begin, end);
} else {
chatWidget->setScroll(true);
}
}
void GenericChatForm::loadHistoryFrom(const QDateTime &time)
{
chatWidget->setScroll(false);
auto begin = ChatLogIdx(0);
if (time.isNull()) {
begin = messages.rbegin()->first;
} else {
begin = firstItemAfterDate(time.date(), chatLog);
}
int add = DEF_NUM_MSG_TO_LOAD;
if (begin.get() + DEF_NUM_MSG_TO_LOAD > chatLog.getNextIdx().get()) {
add = chatLog.getNextIdx().get() - begin.get();
}
auto end = ChatLogIdx(begin.get() + add);
renderMessages(begin, end);
}
void GenericChatForm::removeFirstsMessages(const int num)
{
if (messages.size() > num) {
messages.erase(messages.begin(), std::next(messages.begin(), num));
} else {
messages.clear();
}
}
void GenericChatForm::removeLastsMessages(const int num)
{
if (messages.size() > num) {
messages.erase(std::next(messages.end(), -num), messages.end());
} else {
messages.clear();
}
}
void GenericChatForm::disableSearchText() void GenericChatForm::disableSearchText()
{ {
@ -810,8 +893,9 @@ void GenericChatForm::onLoadHistory()
LoadHistoryDialog dlg(&chatLog); LoadHistoryDialog dlg(&chatLog);
if (dlg.exec()) { if (dlg.exec()) {
QDateTime time = dlg.getFromDate(); QDateTime time = dlg.getFromDate();
auto idx = firstItemAfterDate(dlg.getFromDate().date(), chatLog); auto type = dlg.getLoadType();
renderMessages(idx, chatLog.getNextIdx());
loadHistory(time, type);
} }
} }
@ -858,6 +942,12 @@ void GenericChatForm::searchInBegin(const QString& phrase, const ParameterSearch
{ {
disableSearchText(); disableSearchText();
if (!parameter.time.isNull()) {
LoadHistoryDialog::LoadType type = (parameter.period == PeriodSearch::BeforeDate)
? LoadHistoryDialog::to : LoadHistoryDialog::from;
loadHistory(parameter.time, type);
}
bool bForwardSearch = false; bool bForwardSearch = false;
switch (parameter.period) { switch (parameter.period) {
case PeriodSearch::WithTheFirst: { case PeriodSearch::WithTheFirst: {
@ -875,13 +965,13 @@ void GenericChatForm::searchInBegin(const QString& phrase, const ParameterSearch
} }
case PeriodSearch::AfterDate: { case PeriodSearch::AfterDate: {
bForwardSearch = true; bForwardSearch = true;
searchPos.logIdx = firstItemAfterDate(parameter.date, chatLog); searchPos.logIdx = firstItemAfterDate(parameter.time.date(), chatLog);
searchPos.numMatches = 0; searchPos.numMatches = 0;
break; break;
} }
case PeriodSearch::BeforeDate: { case PeriodSearch::BeforeDate: {
bForwardSearch = false; bForwardSearch = false;
searchPos.logIdx = firstItemAfterDate(parameter.date, chatLog); searchPos.logIdx = firstItemAfterDate(parameter.time.date(), chatLog);
searchPos.numMatches = 0; searchPos.numMatches = 0;
break; break;
} }
@ -939,6 +1029,11 @@ void GenericChatForm::renderMessages(ChatLogIdx begin, ChatLogIdx end,
QList<ChatLine::Ptr> beforeLines; QList<ChatLine::Ptr> beforeLines;
QList<ChatLine::Ptr> afterLines; QList<ChatLine::Ptr> afterLines;
if (!messages.empty() && !(messages.rbegin()->first == begin || messages.begin()->first == end
|| messages.rbegin()->first.get() + 1 == begin.get())) {
return;
}
for (auto i = begin; i < end; ++i) { for (auto i = begin; i < end; ++i) {
auto chatMessage = getChatMessageForIdx(i, messages); auto chatMessage = getChatMessageForIdx(i, messages);
renderItem(chatLog.at(i), needsToHideName(i), colorizeNames, chatMessage); renderItem(chatLog.at(i), needsToHideName(i), colorizeNames, chatMessage);
@ -956,8 +1051,13 @@ void GenericChatForm::renderMessages(ChatLogIdx begin, ChatLogIdx end,
} }
} }
for (auto const& line : afterLines) { if (beforeLines.isEmpty() && afterLines.isEmpty()) {
chatWidget->insertChatlineAtBottom(line); chatWidget->setScroll(true);
}
chatWidget->insertChatlineAtBottom(afterLines);
if (chatWidget->getNumRemove()) {
removeFirstsMessages(chatWidget->getNumRemove());
} }
if (!beforeLines.empty()) { if (!beforeLines.empty()) {
@ -974,11 +1074,36 @@ void GenericChatForm::renderMessages(ChatLogIdx begin, ChatLogIdx end,
} }
chatWidget->insertChatlinesOnTop(beforeLines); chatWidget->insertChatlinesOnTop(beforeLines);
if (chatWidget->getNumRemove()) {
removeLastsMessages(chatWidget->getNumRemove());
}
} else if (onCompletion) { } else if (onCompletion) {
onCompletion(); onCompletion();
} }
} }
void GenericChatForm::goToCurrentDate()
{
chatWidget->clear();
messages.clear();
auto end = ChatLogIdx(chatLog.size());
auto begin = end.get() > DEF_NUM_MSG_TO_LOAD ? ChatLogIdx(end.get() - DEF_NUM_MSG_TO_LOAD) : ChatLogIdx(0);
renderMessages(begin, end);
}
void GenericChatForm::loadHistoryLower()
{
loadHistoryTo(QDateTime());
}
void GenericChatForm::loadHistoryUpper()
{
auto msg = messages.crbegin()->second;
loadHistoryFrom(QDateTime());
chatWidget->scrollToLine(msg);
}
void GenericChatForm::updateShowDateInfo(const ChatLine::Ptr& line) void GenericChatForm::updateShowDateInfo(const ChatLine::Ptr& line)
{ {
const auto date = getTime(line); const auto date = getTime(line);
@ -1002,6 +1127,7 @@ void GenericChatForm::retranslateUi()
quoteAction->setText(tr("Quote selected text")); quoteAction->setText(tr("Quote selected text"));
copyLinkAction->setText(tr("Copy link address")); copyLinkAction->setText(tr("Copy link address"));
searchAction->setText(tr("Search in text")); searchAction->setText(tr("Search in text"));
goCurrentDateAction->setText(tr("Go to current date"));
loadHistoryAction->setText(tr("Load chat history...")); loadHistoryAction->setText(tr("Load chat history..."));
exportChatAction->setText(tr("Export to file")); exportChatAction->setText(tr("Export to file"));
} }

View File

@ -23,6 +23,7 @@
#include "src/chatlog/chatmessage.h" #include "src/chatlog/chatmessage.h"
#include "src/core/toxpk.h" #include "src/core/toxpk.h"
#include "src/model/ichatlog.h" #include "src/model/ichatlog.h"
#include "src/widget/form/loadhistorydialog.h"
#include "src/widget/searchtypes.h" #include "src/widget/searchtypes.h"
#include <QMenu> #include <QMenu>
@ -125,11 +126,20 @@ protected slots:
void renderMessage(ChatLogIdx idx); void renderMessage(ChatLogIdx idx);
void renderMessages(ChatLogIdx begin, ChatLogIdx end, void renderMessages(ChatLogIdx begin, ChatLogIdx end,
std::function<void(void)> onCompletion = std::function<void(void)>()); std::function<void(void)> onCompletion = std::function<void(void)>());
void goToCurrentDate();
void loadHistoryLower();
void loadHistoryUpper();
private: private:
void retranslateUi(); void retranslateUi();
void addSystemDateMessage(const QDate& date); void addSystemDateMessage(const QDate& date);
QDateTime getTime(const ChatLine::Ptr& chatLine) const; QDateTime getTime(const ChatLine::Ptr& chatLine) const;
void loadHistory(const QDateTime& time, const LoadHistoryDialog::LoadType type);
void loadHistoryTo(const QDateTime& time);
void loadHistoryFrom(const QDateTime& time);
void removeFirstsMessages(const int num);
void removeLastsMessages(const int num);
protected: protected:
ChatMessage::Ptr createMessage(const ToxPk& author, const QString& message, ChatMessage::Ptr createMessage(const ToxPk& author, const QString& message,
@ -160,11 +170,10 @@ protected:
QAction* searchAction; QAction* searchAction;
QAction* loadHistoryAction; QAction* loadHistoryAction;
QAction* exportChatAction; QAction* exportChatAction;
QAction* goCurrentDateAction;
ToxPk previousId; ToxPk previousId;
QDateTime earliestMessage;
QMenu menu; QMenu menu;
QPushButton* emoteButton; QPushButton* emoteButton;

View File

@ -38,11 +38,15 @@ LoadHistoryDialog::LoadHistoryDialog(const IChatLog* chatLog, QWidget* parent)
&LoadHistoryDialog::highlightDates); &LoadHistoryDialog::highlightDates);
} }
LoadHistoryDialog::LoadHistoryDialog(QWidget* parent) LoadHistoryDialog::LoadHistoryDialog(Mode mode, QWidget* parent)
: QDialog(parent) : QDialog(parent)
, ui(new Ui::LoadHistoryDialog) , ui(new Ui::LoadHistoryDialog)
{ {
ui->setupUi(this); ui->setupUi(this);
if (mode == Mode::search) {
enableSearchMode();
}
} }
LoadHistoryDialog::~LoadHistoryDialog() LoadHistoryDialog::~LoadHistoryDialog()
@ -62,14 +66,21 @@ QDateTime LoadHistoryDialog::getFromDate()
return res; return res;
} }
void LoadHistoryDialog::setTitle(const QString& title) LoadHistoryDialog::LoadType LoadHistoryDialog::getLoadType()
{ {
setWindowTitle(title); if (ui->loadTypeComboBox->currentIndex() == 0) {
return LoadType::from;
}
return LoadType::to;
} }
void LoadHistoryDialog::setInfoLabel(const QString& info) void LoadHistoryDialog::enableSearchMode()
{ {
ui->fromLabel->setText(info); setWindowTitle(tr("Select Date Dialog"));
ui->fromLabel->setText(tr("Select a date"));
ui->loadTypeComboBox->setVisible(false);
ui->infoLabel->setVisible(false);
} }
void LoadHistoryDialog::highlightDates(int year, int month) void LoadHistoryDialog::highlightDates(int year, int month)

View File

@ -34,18 +34,29 @@ class LoadHistoryDialog : public QDialog
Q_OBJECT Q_OBJECT
public: public:
enum LoadType {
from,
to
};
enum Mode {
common,
search
};
explicit LoadHistoryDialog(const IChatLog* chatLog, QWidget* parent = nullptr); explicit LoadHistoryDialog(const IChatLog* chatLog, QWidget* parent = nullptr);
explicit LoadHistoryDialog(QWidget* parent = nullptr); explicit LoadHistoryDialog(Mode mode, QWidget* parent = nullptr);
~LoadHistoryDialog(); ~LoadHistoryDialog();
QDateTime getFromDate(); QDateTime getFromDate();
void setTitle(const QString& title); LoadType getLoadType();
void setInfoLabel(const QString& info);
public slots: public slots:
void highlightDates(int year, int month); void highlightDates(int year, int month);
private: private:
void enableSearchMode();
Ui::LoadHistoryDialog* ui; Ui::LoadHistoryDialog* ui;
const IChatLog* chatLog; const IChatLog* chatLog;
}; };

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>347</width> <width>410</width>
<height>264</height> <height>332</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -16,22 +16,60 @@
<property name="modal"> <property name="modal">
<bool>true</bool> <bool>true</bool>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QLabel" name="fromLabel"> <widget class="QLabel" name="fromLabel">
<property name="text"> <property name="text">
<string>Load history from:</string> <string>Load history</string>
</property> </property>
</widget> </widget>
</item> </item>
<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"> <widget class="QCalendarWidget" name="fromDate">
<property name="gridVisible"> <property name="gridVisible">
<bool>false</bool> <bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item row="2" column="0">
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>

View File

@ -86,7 +86,7 @@ ParameterSearch SearchSettingsForm::getParameterSearch()
break; break;
} }
ps.date = startDate; ps.time = startTime;
ps.isUpdate = isUpdate; ps.isUpdate = isUpdate;
isUpdate = false; isUpdate = false;
@ -101,7 +101,7 @@ void SearchSettingsForm::reloadTheme()
void SearchSettingsForm::updateStartDateLabel() void SearchSettingsForm::updateStartDateLabel()
{ {
ui->startDateLabel->setText(startDate.toString(Settings::getInstance().getDateFormat())); ui->startDateLabel->setText(startTime.toString(Settings::getInstance().getDateFormat()));
} }
void SearchSettingsForm::setUpdate(const bool isUpdate) void SearchSettingsForm::setUpdate(const bool isUpdate)
@ -119,8 +119,8 @@ void SearchSettingsForm::onStartSearchSelected(const int index)
ui->choiceDateButton->setProperty("state", QStringLiteral("green")); ui->choiceDateButton->setProperty("state", QStringLiteral("green"));
ui->choiceDateButton->setStyleSheet(Style::getStylesheet(QStringLiteral("chatForm/buttons.css"))); ui->choiceDateButton->setStyleSheet(Style::getStylesheet(QStringLiteral("chatForm/buttons.css")));
if (startDate.isNull()) { if (startTime.isNull()) {
startDate = QDate::currentDate(); startTime = QDateTime::currentDateTime();
updateStartDateLabel(); updateStartDateLabel();
} }
@ -161,11 +161,9 @@ void SearchSettingsForm::onRegularClicked(const bool checked)
void SearchSettingsForm::onChoiceDate() void SearchSettingsForm::onChoiceDate()
{ {
LoadHistoryDialog dlg; LoadHistoryDialog dlg(LoadHistoryDialog::search);
dlg.setTitle(tr("Select Date Dialog"));
dlg.setInfoLabel(tr("Select a date"));
if (dlg.exec()) { if (dlg.exec()) {
startDate = dlg.getFromDate().date(); startTime = dlg.getFromDate();
updateStartDateLabel(); updateStartDateLabel();
} }

View File

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

View File

@ -48,13 +48,13 @@ enum class SearchDirection {
struct ParameterSearch { struct ParameterSearch {
FilterSearch filter{FilterSearch::None}; FilterSearch filter{FilterSearch::None};
PeriodSearch period{PeriodSearch::None}; PeriodSearch period{PeriodSearch::None};
QDate date; QDateTime time;
bool isUpdate{false}; bool isUpdate{false};
bool operator ==(const ParameterSearch& other) { bool operator ==(const ParameterSearch& other) {
return filter == other.filter && return filter == other.filter &&
period == other.period && period == other.period &&
date == other.date; time == other.time;
} }
bool operator !=(const ParameterSearch& other) { bool operator !=(const ParameterSearch& other) {