diff --git a/src/chatlog/chatlog.cpp b/src/chatlog/chatlog.cpp index dbc7b6fbc..5dd113365 100644 --- a/src/chatlog/chatlog.cpp +++ b/src/chatlog/chatlog.cpp @@ -49,8 +49,8 @@ T clamp(T x, T min, T max) return x; } -ChatLog::ChatLog(QWidget* parent) - : QGraphicsView(parent) +ChatLog::ChatLog(const bool canRemove, QWidget* parent) + : QGraphicsView(parent), canRemove(canRemove) { // Create the scene busyScene = new QGraphicsScene(this); @@ -168,8 +168,9 @@ void ChatLog::updateSceneRect() void ChatLog::layout(int start, int end, qreal width) { - if (lines.empty()) + if (lines.empty()) { return; + } qreal h = 0.0; @@ -312,8 +313,9 @@ void ChatLog::mouseMoveEvent(QMouseEvent* ev) // Much faster than QGraphicsScene::itemAt()! ChatLineContent* ChatLog::getContentFromPos(QPointF scenePos) const { - if (lines.empty()) + if (lines.empty()) { return nullptr; + } auto itr = 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) { + numRemove = 0; if (!l.get()) return; @@ -382,8 +385,35 @@ void ChatLog::insertChatlineAtBottom(ChatLine::Ptr l) updateTypingNotification(); } +void ChatLog::insertChatlineAtBottom(const QList& 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) { + numRemove = 0; if (!l.get()) return; @@ -392,6 +422,7 @@ void ChatLog::insertChatlineOnTop(ChatLine::Ptr l) void ChatLog::insertChatlinesOnTop(const QList& newLines) { + numRemove = 0; if (newLines.isEmpty()) return; @@ -411,6 +442,10 @@ void ChatLog::insertChatlinesOnTop(const QList& newLines) combLines.push_back(l); } + if (canRemove && lines.size() + DEF_NUM_MSG_TO_LOAD >= maxMessages) { + removeLasts(DEF_NUM_MSG_TO_LOAD); + } + // add the old lines for (ChatLine::Ptr l : lines) { l->setRow(i++); @@ -422,7 +457,12 @@ void ChatLog::insertChatlinesOnTop(const QList& newLines) scene->setItemIndexMethod(oldIndexMeth); // redo layout - startResizeWorker(); + if (visibleLines.size() > 1) { + startResizeWorker(visibleLines[1]); + } else { + startResizeWorker(); + } + } bool ChatLog::stickToBottom() const @@ -436,18 +476,22 @@ void ChatLog::scrollToBottom() verticalScrollBar()->setValue(verticalScrollBar()->maximum()); } -void ChatLog::startResizeWorker() +void ChatLog::startResizeWorker(ChatLine::Ptr anchorLine) { - if (lines.empty()) + if (lines.empty()) { + isScroll = true; return; + } // (re)start the worker if (!workerTimer->isActive()) { // these values must not be reevaluated while the worker is running - workerStb = stickToBottom(); - - if (!visibleLines.empty()) - workerAnchorLine = visibleLines.first(); + if (anchorLine) { + workerAnchorLine = anchorLine; + workerStb = false; + } else { + workerStb = stickToBottom(); + } } // 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()) return; - updateSceneRect(); - verticalScrollBar()->setValue(line->sceneBoundingRect().top()); + if (workerTimer->isActive()) { + workerAnchorLine = line; + workerStb = false; + } else { + updateSceneRect(); + verticalScrollBar()->setValue(line->sceneBoundingRect().top()); // NOTE: start here + } } void ChatLog::selectAll() { - if (lines.empty()) + if (lines.empty()) { return; + } 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() { startResizeWorker(); } -void ChatLog::checkVisibility() +void ChatLog::checkVisibility(bool causedWheelEvent) { - if (lines.empty()) + if (lines.empty()) { return; + } // find first visible line auto lowerBound = std::lower_bound(lines.cbegin(), lines.cend(), getVisibleRect().top(), @@ -717,6 +802,14 @@ void ChatLog::checkVisibility() if (!visibleLines.isEmpty()) { 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) @@ -767,8 +860,9 @@ void ChatLog::updateTypingNotification() qreal posY = 0.0; - if (!lines.empty()) + if (!lines.empty()) { posY = lines.last()->sceneBoundingRect().bottom() + lineSpacing; + } notification->layout(useableWidth(), QPointF(0.0, posY)); } @@ -853,7 +947,8 @@ void ChatLog::onWorkerTimeout() // hidden during busy screen verticalScrollBar()->show(); - emit workerTimeoutFinished(); + isScroll = true; + 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() { copyAction->setText(tr("Copy")); diff --git a/src/chatlog/chatlog.h b/src/chatlog/chatlog.h index 6975ceb9a..81e8c1d43 100644 --- a/src/chatlog/chatlog.h +++ b/src/chatlog/chatlog.h @@ -35,14 +35,17 @@ class QTimer; class ChatLineContent; struct ToxFile; +static const auto DEF_NUM_MSG_TO_LOAD = 100; + class ChatLog : public QGraphicsView { Q_OBJECT public: - explicit ChatLog(QWidget* parent = nullptr); + explicit ChatLog(const bool canRemove, QWidget* parent = nullptr); virtual ~ChatLog(); void insertChatlineAtBottom(ChatLine::Ptr l); + void insertChatlineAtBottom(const QList& newLines); void insertChatlineOnTop(ChatLine::Ptr l); void insertChatlinesOnTop(const QList& newLines); void clearSelection(); @@ -55,6 +58,10 @@ public: void selectAll(); void fontChanged(const QFont& font); void reloadTheme(); + void removeFirsts(const int num); + void removeLasts(const int num); + void setScroll(const bool scroll); + int getNumRemove() const; QString getSelectedText() const; @@ -72,6 +79,8 @@ signals: void selectionChanged(); void workerTimeoutFinished(); void firstVisibleLineChanged(const ChatLine::Ptr&); + void loadHistoryLower(); + void loadHistoryUpper(); public slots: void forceRelayout(); @@ -94,9 +103,9 @@ protected: void reposition(int start, int end, qreal deltaY); void updateSceneRect(); - void checkVisibility(); + void checkVisibility(bool causedWheelEvent = false); void scrollToBottom(); - void startResizeWorker(); + void startResizeWorker(ChatLine::Ptr anchorLine = nullptr); virtual void mouseDoubleClickEvent(QMouseEvent* ev) final override; virtual void mousePressEvent(QMouseEvent* ev) final override; @@ -107,6 +116,7 @@ protected: virtual void showEvent(QShowEvent*) final override; virtual void focusInEvent(QFocusEvent* ev) final override; virtual void focusOutEvent(QFocusEvent* ev) final override; + virtual void wheelEvent(QWheelEvent *event) final override; void updateMultiSelectionRect(); void updateTypingNotification(); @@ -159,6 +169,7 @@ private: int clickCount = 0; QPoint lastClickPos; Qt::MouseButton lastClickButton; + bool isScroll{true}; // worker vars int workerLastIndex = 0; @@ -168,6 +179,10 @@ private: // layout QMargins margins = QMargins(10, 10, 10, 10); qreal lineSpacing = 5.0f; + + int numRemove{0}; + const int maxMessages{300}; + bool canRemove; }; #endif // CHATLOG_H diff --git a/src/chatlog/content/text.cpp b/src/chatlog/content/text.cpp index d32adc4e8..e1877ad23 100644 --- a/src/chatlog/content/text.cpp +++ b/src/chatlog/content/text.cpp @@ -66,9 +66,11 @@ void Text::selectText(const QString& txt, const std::pair& point) 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& point) @@ -79,14 +81,20 @@ void Text::selectText(const QRegularExpression &exp, const std::pair& return; } - auto cursor = doc->find(exp, point.first); + selectCursor = doc->find(exp, point.first); + selectPoint = point; - selectText(cursor, point); + regenerate(); + update(); } void Text::deselectText() { dirty = true; + + selectCursor = QTextCursor(); + selectPoint = {0, 0}; + regenerate(); update(); } @@ -355,6 +363,10 @@ void Text::regenerate() dirty = false; } + if (!selectCursor.isNull()) { + selectText(selectCursor, selectPoint); + } + // if we are not visible -> free mem if (!keepInMemory) freeResources(); @@ -462,9 +474,6 @@ void Text::selectText(QTextCursor& cursor, const std::pair& point) QTextCharFormat format; format.setBackground(QBrush(Style::getColor(Style::SearchHighlighted))); cursor.mergeCharFormat(format); - - regenerate(); - update(); } } diff --git a/src/chatlog/content/text.h b/src/chatlog/content/text.h index e1f69112b..0f58bdb4a 100644 --- a/src/chatlog/content/text.h +++ b/src/chatlog/content/text.h @@ -24,6 +24,7 @@ #include "src/widget/style.h" #include +#include class QTextDocument; @@ -110,6 +111,9 @@ private: TextType textType; QColor color; QColor customColor; + + QTextCursor selectCursor; + std::pair selectPoint{0, 0}; }; #endif // TEXT_H diff --git a/src/model/chathistory.cpp b/src/model/chathistory.cpp index 3151a5d1f..24096f710 100644 --- a/src/model/chathistory.cpp +++ b/src/model/chathistory.cpp @@ -220,6 +220,15 @@ std::vector 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) { if (canUseHistory()) { diff --git a/src/model/chathistory.h b/src/model/chathistory.h index 15433baf6..8260acff6 100644 --- a/src/model/chathistory.h +++ b/src/model/chathistory.h @@ -42,6 +42,7 @@ public: ChatLogIdx getFirstIdx() const override; ChatLogIdx getNextIdx() const override; std::vector getDateIdxs(const QDate& startDate, size_t maxDates) const override; + std::size_t size() const override; public slots: void onFileUpdated(const ToxPk& sender, const ToxFile& file); diff --git a/src/model/contact.h b/src/model/contact.h index e2faa5799..182d1ce90 100644 --- a/src/model/contact.h +++ b/src/model/contact.h @@ -37,6 +37,8 @@ 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); }; diff --git a/src/model/friend.cpp b/src/model/friend.cpp index abb2bd6bb..08f80dadf 100644 --- a/src/model/friend.cpp +++ b/src/model/friend.cpp @@ -166,3 +166,8 @@ bool Friend::isOnline() const { return friendStatus != Status::Status::Offline && friendStatus != Status::Status::Blocked; } + +bool Friend::useHistory() const +{ + return true; +} diff --git a/src/model/friend.h b/src/model/friend.h index b62fceb1f..a3eaee790 100644 --- a/src/model/friend.h +++ b/src/model/friend.h @@ -55,6 +55,8 @@ public: Status::Status getStatus() const; bool isOnline() const; + bool useHistory() const override final; + signals: void nameChanged(const ToxPk& friendId, const QString& name); void aliasChanged(const ToxPk& friendId, QString alias); diff --git a/src/model/group.cpp b/src/model/group.cpp index 39f7e58ab..f08d4397d 100644 --- a/src/model/group.cpp +++ b/src/model/group.cpp @@ -205,6 +205,11 @@ QString Group::getSelfName() const return selfName; } +bool Group::useHistory() const +{ + return false; +} + void Group::stopAudioOfDepartedPeers(const ToxPk& peerPk) { if (avGroupchat) { diff --git a/src/model/group.h b/src/model/group.h index 4e70a407e..fd19461ca 100644 --- a/src/model/group.h +++ b/src/model/group.h @@ -61,6 +61,8 @@ public: void setSelfName(const QString& name); QString getSelfName() const; + bool useHistory() const override final; + signals: void titleChangedByUser(const QString& title); void titleChanged(const QString& author, const QString& title); diff --git a/src/model/ichatlog.h b/src/model/ichatlog.h index bc3b4ad72..2144c2d0d 100644 --- a/src/model/ichatlog.h +++ b/src/model/ichatlog.h @@ -138,6 +138,8 @@ public: virtual std::vector getDateIdxs(const QDate& startDate, size_t maxDates) const = 0; + virtual std::size_t size() const = 0; + signals: void itemUpdated(ChatLogIdx idx); }; diff --git a/src/model/sessionchatlog.cpp b/src/model/sessionchatlog.cpp index 023747288..dcc095997 100644 --- a/src/model/sessionchatlog.cpp +++ b/src/model/sessionchatlog.cpp @@ -289,6 +289,11 @@ std::vector SessionChatLog::getDateIdxs(const QDat return ret; } +std::size_t SessionChatLog::size() const +{ + return items.size(); +} + void SessionChatLog::insertMessageAtIdx(ChatLogIdx idx, ToxPk sender, QString senderName, ChatLogMessage message) { diff --git a/src/model/sessionchatlog.h b/src/model/sessionchatlog.h index 87bd96def..1c8317f79 100644 --- a/src/model/sessionchatlog.h +++ b/src/model/sessionchatlog.h @@ -45,6 +45,7 @@ public: ChatLogIdx getFirstIdx() const override; ChatLogIdx getNextIdx() const override; std::vector 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 insertFileAtIdx(ChatLogIdx idx, ToxPk sender, QString senderName, ChatLogFile file); diff --git a/src/persistence/history.cpp b/src/persistence/history.cpp index a309eb10c..41c9323d3 100644 --- a/src/persistence/history.cpp +++ b/src/persistence/history.cpp @@ -681,14 +681,14 @@ QDateTime History::getDateWhereFindPhrase(const QString& friendPk, const QDateTi break; } - QDateTime date = from; + QDateTime time = from; - if (!date.isValid()) { - date = QDateTime::currentDateTime(); + if (!time.isValid()) { + time = QDateTime::currentDateTime(); } if (parameter.period == PeriodSearch::AfterDate || parameter.period == PeriodSearch::BeforeDate) { - date = QDateTime(parameter.date); + time = parameter.time; } QString period; @@ -698,15 +698,15 @@ QDateTime History::getDateWhereFindPhrase(const QString& friendPk, const QDateTi break; case PeriodSearch::AfterDate: period = QStringLiteral("AND timestamp > '%1' ORDER BY timestamp ASC LIMIT 1;") - .arg(date.toMSecsSinceEpoch()); + .arg(time.toMSecsSinceEpoch()); break; case PeriodSearch::BeforeDate: period = QStringLiteral("AND timestamp < '%1' ORDER BY timestamp DESC LIMIT 1;") - .arg(date.toMSecsSinceEpoch()); + .arg(time.toMSecsSinceEpoch()); break; default: period = QStringLiteral("AND timestamp < '%1' ORDER BY timestamp DESC LIMIT 1;") - .arg(date.toMSecsSinceEpoch()); + .arg(time.toMSecsSinceEpoch()); break; } diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp index ffb4a4b7a..ebde18293 100644 --- a/src/widget/form/genericchatform.cpp +++ b/src/widget/form/genericchatform.cpp @@ -37,7 +37,6 @@ #include "src/widget/contentlayout.h" #include "src/widget/emoticonswidget.h" #include "src/widget/form/chatform.h" -#include "src/widget/form/loadhistorydialog.h" #include "src/widget/maskablepixmapwidget.h" #include "src/widget/searchform.h" #include "src/widget/style.h" @@ -54,6 +53,8 @@ #include #include +#include + #ifdef SPELL_CHECKING #include #endif @@ -260,7 +261,7 @@ GenericChatForm::GenericChatForm(const Contact* contact, IChatLog& chatLog, headWidget = new ChatFormHeader(); searchForm = new SearchForm(); dateInfo = new QLabel(this); - chatWidget = new ChatLog(this); + chatWidget = new ChatLog(contact->useHistory(), this); chatWidget->setBusyNotification(ChatMessage::createBusyNotification()); searchForm->hide(); dateInfo->setAlignment(Qt::AlignHCenter); @@ -329,6 +330,13 @@ GenericChatForm::GenericChatForm(const Contact* contact, IChatLog& chatLog, 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()), + QKeySequence(Qt::CTRL + Qt::Key_G)); + addAction(goCurrentDateAction); + menu.addSeparator(); searchAction = menu.addAction(QIcon(), QString(), this, SLOT(searchFormShow()), @@ -355,6 +363,8 @@ GenericChatForm::GenericChatForm(const Contact* contact, IChatLog& chatLog, connect(chatWidget, &ChatLog::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(searchForm, &SearchForm::searchInBegin, this, &GenericChatForm::searchInBegin); 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); 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()); @@ -644,6 +654,79 @@ QDateTime GenericChatForm::getTime(const ChatLine::Ptr &chatLine) const 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() { @@ -810,8 +893,9 @@ void GenericChatForm::onLoadHistory() LoadHistoryDialog dlg(&chatLog); if (dlg.exec()) { QDateTime time = dlg.getFromDate(); - auto idx = firstItemAfterDate(dlg.getFromDate().date(), chatLog); - renderMessages(idx, chatLog.getNextIdx()); + auto type = dlg.getLoadType(); + + loadHistory(time, type); } } @@ -858,6 +942,12 @@ void GenericChatForm::searchInBegin(const QString& phrase, const ParameterSearch { disableSearchText(); + if (!parameter.time.isNull()) { + LoadHistoryDialog::LoadType type = (parameter.period == PeriodSearch::BeforeDate) + ? LoadHistoryDialog::to : LoadHistoryDialog::from; + loadHistory(parameter.time, type); + } + bool bForwardSearch = false; switch (parameter.period) { case PeriodSearch::WithTheFirst: { @@ -875,13 +965,13 @@ void GenericChatForm::searchInBegin(const QString& phrase, const ParameterSearch } case PeriodSearch::AfterDate: { bForwardSearch = true; - searchPos.logIdx = firstItemAfterDate(parameter.date, chatLog); + searchPos.logIdx = firstItemAfterDate(parameter.time.date(), chatLog); searchPos.numMatches = 0; break; } case PeriodSearch::BeforeDate: { bForwardSearch = false; - searchPos.logIdx = firstItemAfterDate(parameter.date, chatLog); + searchPos.logIdx = firstItemAfterDate(parameter.time.date(), chatLog); searchPos.numMatches = 0; break; } @@ -939,6 +1029,11 @@ void GenericChatForm::renderMessages(ChatLogIdx begin, ChatLogIdx end, QList beforeLines; QList 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) { auto chatMessage = getChatMessageForIdx(i, messages); renderItem(chatLog.at(i), needsToHideName(i), colorizeNames, chatMessage); @@ -956,8 +1051,13 @@ void GenericChatForm::renderMessages(ChatLogIdx begin, ChatLogIdx end, } } - for (auto const& line : afterLines) { - chatWidget->insertChatlineAtBottom(line); + if (beforeLines.isEmpty() && afterLines.isEmpty()) { + chatWidget->setScroll(true); + } + + chatWidget->insertChatlineAtBottom(afterLines); + if (chatWidget->getNumRemove()) { + removeFirstsMessages(chatWidget->getNumRemove()); } if (!beforeLines.empty()) { @@ -974,11 +1074,36 @@ void GenericChatForm::renderMessages(ChatLogIdx begin, ChatLogIdx end, } chatWidget->insertChatlinesOnTop(beforeLines); + if (chatWidget->getNumRemove()) { + removeLastsMessages(chatWidget->getNumRemove()); + } } else if (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) { const auto date = getTime(line); @@ -1002,6 +1127,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")); loadHistoryAction->setText(tr("Load chat history...")); exportChatAction->setText(tr("Export to file")); } diff --git a/src/widget/form/genericchatform.h b/src/widget/form/genericchatform.h index af640e4dd..e4cb253ba 100644 --- a/src/widget/form/genericchatform.h +++ b/src/widget/form/genericchatform.h @@ -23,6 +23,7 @@ #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 @@ -125,11 +126,20 @@ protected slots: void renderMessage(ChatLogIdx idx); void renderMessages(ChatLogIdx begin, ChatLogIdx end, std::function onCompletion = std::function()); + 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); + void loadHistoryFrom(const QDateTime& time); + void removeFirstsMessages(const int num); + void removeLastsMessages(const int num); protected: ChatMessage::Ptr createMessage(const ToxPk& author, const QString& message, @@ -160,11 +170,10 @@ protected: QAction* searchAction; QAction* loadHistoryAction; QAction* exportChatAction; + QAction* goCurrentDateAction; ToxPk previousId; - QDateTime earliestMessage; - QMenu menu; QPushButton* emoteButton; diff --git a/src/widget/form/loadhistorydialog.cpp b/src/widget/form/loadhistorydialog.cpp index 0e1925245..c3246bb8f 100644 --- a/src/widget/form/loadhistorydialog.cpp +++ b/src/widget/form/loadhistorydialog.cpp @@ -38,11 +38,15 @@ LoadHistoryDialog::LoadHistoryDialog(const IChatLog* chatLog, QWidget* parent) &LoadHistoryDialog::highlightDates); } -LoadHistoryDialog::LoadHistoryDialog(QWidget* parent) +LoadHistoryDialog::LoadHistoryDialog(Mode mode, QWidget* parent) : QDialog(parent) , ui(new Ui::LoadHistoryDialog) { ui->setupUi(this); + + if (mode == Mode::search) { + enableSearchMode(); + } } LoadHistoryDialog::~LoadHistoryDialog() @@ -62,14 +66,21 @@ QDateTime LoadHistoryDialog::getFromDate() 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) diff --git a/src/widget/form/loadhistorydialog.h b/src/widget/form/loadhistorydialog.h index 092bd8b15..7fdd178b6 100644 --- a/src/widget/form/loadhistorydialog.h +++ b/src/widget/form/loadhistorydialog.h @@ -34,18 +34,29 @@ 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(QWidget* parent = nullptr); + explicit LoadHistoryDialog(Mode mode, QWidget* parent = nullptr); ~LoadHistoryDialog(); QDateTime getFromDate(); - void setTitle(const QString& title); - void setInfoLabel(const QString& info); + LoadType getLoadType(); public slots: void highlightDates(int year, int month); private: + void enableSearchMode(); + Ui::LoadHistoryDialog* ui; const IChatLog* chatLog; }; diff --git a/src/widget/form/loadhistorydialog.ui b/src/widget/form/loadhistorydialog.ui index fc4ab5029..b382c706d 100644 --- a/src/widget/form/loadhistorydialog.ui +++ b/src/widget/form/loadhistorydialog.ui @@ -6,8 +6,8 @@ 0 0 - 347 - 264 + 410 + 332 @@ -16,22 +16,60 @@ true - - - - - Load history from: - - + + + + + + + Load history + + + + + + + + from + + + + + to + + + + + + + + (about 100 messages are loaded) + + + + + + + Qt::Horizontal + + + + 17 + 20 + + + + + - + false - + Qt::Horizontal diff --git a/src/widget/form/searchsettingsform.cpp b/src/widget/form/searchsettingsform.cpp index abdf6b40c..c55735099 100644 --- a/src/widget/form/searchsettingsform.cpp +++ b/src/widget/form/searchsettingsform.cpp @@ -86,7 +86,7 @@ ParameterSearch SearchSettingsForm::getParameterSearch() break; } - ps.date = startDate; + ps.time = startTime; ps.isUpdate = isUpdate; isUpdate = false; @@ -101,7 +101,7 @@ void SearchSettingsForm::reloadTheme() 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) @@ -119,8 +119,8 @@ void SearchSettingsForm::onStartSearchSelected(const int index) ui->choiceDateButton->setProperty("state", QStringLiteral("green")); ui->choiceDateButton->setStyleSheet(Style::getStylesheet(QStringLiteral("chatForm/buttons.css"))); - if (startDate.isNull()) { - startDate = QDate::currentDate(); + if (startTime.isNull()) { + startTime = QDateTime::currentDateTime(); updateStartDateLabel(); } @@ -161,11 +161,9 @@ void SearchSettingsForm::onRegularClicked(const bool checked) void SearchSettingsForm::onChoiceDate() { - LoadHistoryDialog dlg; - dlg.setTitle(tr("Select Date Dialog")); - dlg.setInfoLabel(tr("Select a date")); + LoadHistoryDialog dlg(LoadHistoryDialog::search); if (dlg.exec()) { - startDate = dlg.getFromDate().date(); + startTime = dlg.getFromDate(); updateStartDateLabel(); } diff --git a/src/widget/form/searchsettingsform.h b/src/widget/form/searchsettingsform.h index 91d665be5..1814c4e75 100644 --- a/src/widget/form/searchsettingsform.h +++ b/src/widget/form/searchsettingsform.h @@ -40,7 +40,7 @@ public: private: Ui::SearchSettingsForm *ui; - QDate startDate; + QDateTime startTime; bool isUpdate{false}; void updateStartDateLabel(); diff --git a/src/widget/searchtypes.h b/src/widget/searchtypes.h index cbfce5738..7130887b6 100644 --- a/src/widget/searchtypes.h +++ b/src/widget/searchtypes.h @@ -48,13 +48,13 @@ enum class SearchDirection { struct ParameterSearch { FilterSearch filter{FilterSearch::None}; PeriodSearch period{PeriodSearch::None}; - QDate date; + QDateTime time; bool isUpdate{false}; bool operator ==(const ParameterSearch& other) { return filter == other.filter && period == other.period && - date == other.date; + time == other.time; } bool operator !=(const ParameterSearch& other) {