34 #include <QApplication>
37 #include <QMouseEvent>
51 static int constexpr maxWindowSize = 300;
53 static int constexpr windowChunkSize = 100;
56 T clamp(T x, T min, T max)
68 const auto date = timestamp.date();
69 auto dateText = date.toString(s.getDateFormat());
73 ChatMessage::Ptr createMessage(
const QString& displayName,
bool isSelf,
bool colorizeNames,
76 auto messageType = chatLogMessage.
message.
isAction ? ChatMessage::MessageType::ACTION
77 : ChatMessage::MessageType::NORMAL;
79 const bool bSelfMentioned =
82 return metadata.type == MessageMetadataType::selfMention;
86 messageType = ChatMessage::MessageType::ALERT;
91 isSelf, chatLogMessage.
state, timestamp, colorizeNames);
94 void renderMessageRaw(
const QString& displayName,
bool isSelf,
bool colorizeNames,
102 auto chatMessage =
static_cast<ChatMessage*
>(chatLine.get());
108 chatMessage->markAsBroken();
111 chatLine = createMessage(displayName, isSelf, colorizeNames, chatLogMessage);
160 template <
typename Fn>
163 auto startIt = storage.
find(first);
165 if (startIt == storage.
end()) {
166 startIt = storage.
begin();
169 auto endIt = storage.
find(last);
170 if (endIt != storage.
end()) {
174 for (
auto it = startIt; it != endIt; ++it) {
187 if (
static_cast<size_t>(std::abs(val)) > distToEnd) {
191 return idx - std::abs(val);
194 if (
static_cast<size_t>(val) > distToEnd) {
206 : QGraphicsView(parent)
213 scene =
new QGraphicsScene(
this);
214 scene->setItemIndexMethod(QGraphicsScene::BspTreeIndex);
222 setInteractive(
true);
223 setAcceptDrops(
false);
224 setAlignment(Qt::AlignTop | Qt::AlignLeft);
225 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
226 setDragMode(QGraphicsView::NoDrag);
227 setViewportUpdateMode(MinimalViewportUpdate);
228 setContextMenuPolicy(Qt::CustomContextMenu);
237 copyAction->setIcon(QIcon::fromTheme(
"edit-copy"));
244 QShortcut* copyCtrlInsShortcut =
new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Insert),
this);
245 connect(copyCtrlInsShortcut, &QShortcut::activated,
this, [
this]() {
copySelectedText(); });
301 l->removeFromScene();
316 line->selectionCleared();
332 return mapToScene(viewport()->rect()).boundingRect().toRect();
350 h = (*chatLineStorage)[start - 1]->sceneBoundingRect().bottom() +
lineSpacing;
355 for (
int i = start; i < end; ++i) {
356 ChatLine* l = (*chatLineStorage)[i].get();
358 l->
layout(width, QPointF(0.0, h));
365 QGraphicsView::mousePressEvent(ev);
367 if (ev->button() == Qt::LeftButton) {
388 QGraphicsView::mouseReleaseEvent(ev);
397 QGraphicsView::mouseMoveEvent(ev);
399 QPointF scenePos = mapToScene(ev->pos());
401 if (ev->buttons() & Qt::LeftButton) {
403 if (ev->pos().y() < 0)
405 else if (ev->pos().y() > height())
412 && (
clickPos - ev->pos()).manhattanLength() > QApplication::startDragDistance()) {
413 QPointF sceneClickPos = mapToScene(
clickPos.toPoint());
428 if (
scene->mouseGrabberItem())
429 scene->mouseGrabberItem()->ungrabMouse();
430 }
else if (line.get()) {
454 line->selectionCleared();
456 }
else if (line.get()) {
459 line->selectionCleared();
467 if (lineIt > selClickedIt)
470 if (lineIt <= selClickedIt)
490 if (itr !=
chatLineStorage->end() && (*itr)->sceneBoundingRect().contains(scenePos))
491 return (*itr)->getContent(scenePos);
513 return width() - verticalScrollBar()->sizeHint().width() -
margins.right() -
margins.left();
518 if (chatLines.empty())
524 QGraphicsScene::ItemIndexMethod oldIndexMeth =
scene->itemIndexMethod();
525 scene->setItemIndexMethod(QGraphicsScene::NoIndex);
527 for (
auto const& chatLine : chatLines) {
528 auto idx = chatLine.first;
529 auto const& l = chatLine.second;
533 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
542 auto dateLine = createDateMessage(date);
544 dateLine->addToScene(
scene);
545 dateLine->visibilityChanged(
false);
548 l->addToScene(
scene);
553 l->visibilityChanged(
false);
556 scene->setItemIndexMethod(oldIndexMeth);
587 return verticalScrollBar()->value() == verticalScrollBar()->maximum();
593 verticalScrollBar()->setValue(verticalScrollBar()->maximum());
617 txt += content->
getText().size();
625 verticalScrollBar()->hide();
630 QPointF scenePos = mapToScene(ev->pos());
668 if (line->content[1]->getText().isEmpty())
671 QString timestamp = line->content[2]->getText().isEmpty()
673 : line->content[2]->getText();
674 QString author = line->content[0]->getText();
675 QString msg = line->content[1]->getText();
678 QString(out.isEmpty() ?
"[%2] %1: %3" :
"\n[%2] %1: %3").arg(author, timestamp, msg);
711 QVector<ChatLine::Ptr> savedLines;
715 (*it)->removeFromScene();
731 QClipboard* clipboard = QApplication::clipboard();
733 if (clipboard && !text.isNull())
734 clipboard->setText(text, toSelectionBuffer ? QClipboard::Selection : QClipboard::Clipboard);
752 QString typingDiv =
"<div class=typing>%1</div>";
753 text->
setText(typingDiv.arg(tr(
"%1 is typing").arg(displayName)));
764 verticalScrollBar()->setValue(line->sceneBoundingRect().top());
785 l->fontChanged(font);
807 bool bForwardSearch =
false;
808 switch (parameter.
period) {
810 bForwardSearch =
true;
817 bForwardSearch =
false;
823 bForwardSearch =
true;
829 bForwardSearch =
false;
836 if (bForwardSearch) {
868 auto selectText = [
this, result] {
877 auto text = qobject_cast<Text*>(msg->getContent(1));
878 text->selectText(result.
exp, std::make_pair(result.
start, result.
len));
914 : *std::prev(lowerBound);
918 for (
auto itr = lowerBound; itr != upperBound; ++itr) {
919 newVisibleLines.append(*itr);
922 (*itr)->visibilityChanged(
true);
929 line->visibilityChanged(
false);
940 QGraphicsView::scrollContentsBy(dx, dy);
948 if (ev->size().width() != ev->oldSize().width()) {
953 QGraphicsView::resizeEvent(ev);
965 selBBox = selBBox.united(
selFirstRow->sceneBoundingRect());
966 selBBox = selBBox.united(
selLastRow->sceneBoundingRect());
1022 (*it)->removeFromScene();
1029 if (
chatLineStorage->hasIndexedMessage() && begin <= chatLineStorage->lastIdx()) {
1047 const int scrollSpeed = 10;
1051 verticalScrollBar()->setValue(verticalScrollBar()->value() - scrollSpeed);
1054 verticalScrollBar()->setValue(verticalScrollBar()->value() + scrollSpeed);
1065 const int stepSize = 50;
1093 verticalScrollBar()->show();
1118 auto linesToRender = std::map<ChatLogIdx, ChatLine::Ptr>();
1120 for (
auto i = begin; i < end; ++i) {
1122 bool prevIdxRendered = i != begin ||
chatLineStorage->contains(i - 1);
1124 auto chatMessage = alreadyRendered ? (*chatLineStorage)[i] :
ChatLine::Ptr();
1127 if (!alreadyRendered) {
1128 linesToRender.insert({i, chatMessage});
1139 auto end = clampedAdd(begin, maxWindowSize,
chatLog);
1152 if (currentStart == begin) {
1162 if (begin < currentEnd && begin > currentStart) {
1167 else if (end <= currentEnd && end > currentStart) {
1181 auto start = clampedAdd(end, -maxWindowSize + 1,
chatLog);
1194 while (renderCompletionFnsLocal.size()) {
1195 renderCompletionFnsLocal.back()();
1196 renderCompletionFnsLocal.pop_back();
1231 if (value == verticalScrollBar()->minimum())
1248 else if (value == verticalScrollBar()->maximum())
1255 auto currentTopPx = mapToScene(0, 0).y();
1256 auto currentBottomPx = (*chatLineStorage)[currentBottomIdx]->sceneBoundingRect().bottom();
1257 auto bottomOffset = currentBottomPx - currentTopPx;
1263 verticalScrollBar()->setValue((*it)->sceneBoundingRect().bottom() - bottomOffset);
1264 scrollMonitoringEnabled = true;
1319 if (numLinesToRemove > 0) {
1327 QGraphicsView::focusInEvent(ev);
1340 (*it)->selectionFocusChanged(
true);
1346 QGraphicsView::focusOutEvent(ev);
1359 (*it)->selectionFocusChanged(
false);
1365 QGraphicsView::wheelEvent(event);
1377 int count = l->getColumnCount();
1378 for (
int i = 0; i < count; ++i) {
1386 if (transferWidget && transferWidget->
isActive())
1425 auto chatMessageType = getChatMessageType(systemMessage);
1435 chatMessage->getContent(0)->hide();
1466 if (!prevIdxRendered) {
1470 const auto& prevItem =
chatLog.
at(idx - 1);
1471 const auto& currentItem =
chatLog.
at(idx);
1475 && currentItem.getContentAsMessage().message.isAction) {
1479 qint64 messagesTimeDiff = prevItem.
getTimestamp().secsTo(currentItem.getTimestamp());
1480 return currentItem.getSender() == prevItem.getSender()
1501 auto text = qobject_cast<Text*>(line->getContent(1));
1502 text->deselectText();
1511 auto idx = firstItemAfterDate(date,
chatLog);
1530 scrollToLine((*chatLineStorage)[idx]);