qTox  Version: nightly | Commit: bc751c8e1cac455f9690654fcfe0f560d2d7dfdd
chatwidget.cpp
Go to the documentation of this file.
1 /*
2  Copyright © 2014-2019 by The qTox Project Contributors
3 
4  This file is part of qTox, a Qt-based graphical interface for Tox.
5 
6  qTox is libre software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  qTox is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with qTox. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "chatwidget.h"
21 #include "chatlinecontent.h"
22 #include "chatlinecontentproxy.h"
23 #include "chatmessage.h"
25 #include "content/text.h"
26 #include "src/widget/gui.h"
27 #include "src/widget/translator.h"
28 #include "src/widget/style.h"
31 #include <iostream>
32 
33 #include <QAction>
34 #include <QApplication>
35 #include <QClipboard>
36 #include <QDebug>
37 #include <QMouseEvent>
38 #include <QScrollBar>
39 #include <QShortcut>
40 #include <QTimer>
41 
42 #include <algorithm>
43 #include <cassert>
44 #include <set>
45 
46 
47 namespace
48 {
49 
50 // Maximum number of rendered messages at any given time
51 static int constexpr maxWindowSize = 300;
52 // Amount of messages to purge when removing messages
53 static int constexpr windowChunkSize = 100;
54 
55 template <class T>
56 T clamp(T x, T min, T max)
57 {
58  if (x > max)
59  return max;
60  if (x < min)
61  return min;
62  return x;
63 }
64 
65 ChatMessage::Ptr createDateMessage(QDateTime timestamp)
66 {
67  const auto& s = Settings::getInstance();
68  const auto date = timestamp.date();
69  auto dateText = date.toString(s.getDateFormat());
70  return ChatMessage::createChatInfoMessage(dateText, ChatMessage::INFO, QDateTime());
71 }
72 
73 ChatMessage::Ptr createMessage(const QString& displayName, bool isSelf, bool colorizeNames,
74  const ChatLogMessage& chatLogMessage)
75 {
76  auto messageType = chatLogMessage.message.isAction ? ChatMessage::MessageType::ACTION
77  : ChatMessage::MessageType::NORMAL;
78 
79  const bool bSelfMentioned =
80  std::any_of(chatLogMessage.message.metadata.begin(), chatLogMessage.message.metadata.end(),
81  [](const MessageMetadata& metadata) {
82  return metadata.type == MessageMetadataType::selfMention;
83  });
84 
85  if (bSelfMentioned) {
86  messageType = ChatMessage::MessageType::ALERT;
87  }
88 
89  const auto timestamp = chatLogMessage.message.timestamp;
90  return ChatMessage::createChatMessage(displayName, chatLogMessage.message.content, messageType,
91  isSelf, chatLogMessage.state, timestamp, colorizeNames);
92 }
93 
94 void renderMessageRaw(const QString& displayName, bool isSelf, bool colorizeNames,
95  const ChatLogMessage& chatLogMessage, ChatLine::Ptr& chatLine)
96 {
97  // HACK: This is kind of gross, but there's not an easy way to fit this into
98  // the existing architecture. This shouldn't ever fail since we should only
99  // correlate ChatMessages created here, however a logic bug could turn into
100  // a crash due to this dangerous cast. The alternative would be to make
101  // ChatLine a QObject which I didn't think was worth it.
102  auto chatMessage = static_cast<ChatMessage*>(chatLine.get());
103 
104  if (chatMessage) {
105  if (chatLogMessage.state == MessageState::complete) {
106  chatMessage->markAsDelivered(chatLogMessage.message.timestamp);
107  } else if (chatLogMessage.state == MessageState::broken) {
108  chatMessage->markAsBroken();
109  }
110  } else {
111  chatLine = createMessage(displayName, isSelf, colorizeNames, chatLogMessage);
112  }
113 }
114 
119 ChatMessage::SystemMessageType getChatMessageType(const SystemMessage& systemMessage)
120 {
121  switch (systemMessage.messageType)
122  {
126  return ChatMessage::ERROR;
136  return ChatMessage::INFO;
137  }
138 
139  return ChatMessage::INFO;
140 }
141 
142 ChatLogIdx firstItemAfterDate(QDate date, const IChatLog& chatLog)
143 {
144  auto idxs = chatLog.getDateIdxs(date, 1);
145  if (idxs.size()) {
146  return idxs[0].idx;
147  } else {
148  return chatLog.getNextIdx();
149  }
150 }
151 
160 template <typename Fn>
161 void forEachLineIn(ChatLine::Ptr first, ChatLine::Ptr last, ChatLineStorage& storage, Fn f)
162 {
163  auto startIt = storage.find(first);
164 
165  if (startIt == storage.end()) {
166  startIt = storage.begin();
167  }
168 
169  auto endIt = storage.find(last);
170  if (endIt != storage.end()) {
171  endIt++;
172  }
173 
174  for (auto it = startIt; it != endIt; ++it) {
175  f(*it);
176  }
177 }
178 
183 ChatLogIdx clampedAdd(ChatLogIdx idx, int val, IChatLog& chatLog)
184 {
185  if (val < 0) {
186  auto distToEnd = idx - chatLog.getFirstIdx();
187  if (static_cast<size_t>(std::abs(val)) > distToEnd) {
188  return chatLog.getFirstIdx();
189  }
190 
191  return idx - std::abs(val);
192  } else {
193  auto distToEnd = chatLog.getNextIdx() - idx;
194  if (static_cast<size_t>(val) > distToEnd) {
195  return chatLog.getNextIdx();
196  }
197 
198  return idx + val;
199  }
200 }
201 
202 } // namespace
203 
204 
205 ChatWidget::ChatWidget(IChatLog& chatLog, const Core& core, QWidget* parent)
206  : QGraphicsView(parent)
207  , chatLog(chatLog)
208  , core(core)
209  , chatLineStorage(new ChatLineStorage())
210 {
211  // Create the scene
212  busyScene = new QGraphicsScene(this);
213  scene = new QGraphicsScene(this);
214  scene->setItemIndexMethod(QGraphicsScene::BspTreeIndex);
215  setScene(scene);
216 
218  busyNotification->addToScene(busyScene);
219  busyNotification->visibilityChanged(true);
220 
221  // Cfg.
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);
229  setBackgroundBrush(QBrush(Style::getColor(Style::GroundBase), Qt::SolidPattern));
230 
231  // The selection rect for multi-line selection
232  selGraphItem = scene->addRect(0, 0, 0, 0, selectionRectColor.darker(120), selectionRectColor);
233  selGraphItem->setZValue(-1.0); // behind all other items
234 
235  // copy action (ie. Ctrl+C)
236  copyAction = new QAction(this);
237  copyAction->setIcon(QIcon::fromTheme("edit-copy"));
238  copyAction->setShortcut(QKeySequence::Copy);
239  copyAction->setEnabled(false);
240  connect(copyAction, &QAction::triggered, this, [this]() { copySelectedText(); });
241  addAction(copyAction);
242 
243  // Ctrl+Insert shortcut
244  QShortcut* copyCtrlInsShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Insert), this);
245  connect(copyCtrlInsShortcut, &QShortcut::activated, this, [this]() { copySelectedText(); });
246 
247  // select all action (ie. Ctrl+A)
248  selectAllAction = new QAction(this);
249  selectAllAction->setIcon(QIcon::fromTheme("edit-select-all"));
250  selectAllAction->setShortcut(QKeySequence::SelectAll);
251  connect(selectAllAction, &QAction::triggered, this, [this]() { selectAll(); });
252  addAction(selectAllAction);
253 
254  // This timer is used to scroll the view while the user is
255  // moving the mouse past the top/bottom edge of the widget while selecting.
256  selectionTimer = new QTimer(this);
257  selectionTimer->setInterval(1000 / 30);
258  selectionTimer->setSingleShot(false);
259  selectionTimer->start();
260  connect(selectionTimer, &QTimer::timeout, this, &ChatWidget::onSelectionTimerTimeout);
261 
262  // Background worker
263  // Updates the layout of all chat-lines after a resize
264  workerTimer = new QTimer(this);
265  workerTimer->setSingleShot(false);
266  workerTimer->setInterval(5);
267  connect(workerTimer, &QTimer::timeout, this, &ChatWidget::onWorkerTimeout);
268 
269  // This timer is used to detect multiple clicks
270  multiClickTimer = new QTimer(this);
271  multiClickTimer->setSingleShot(true);
272  multiClickTimer->setInterval(QApplication::doubleClickInterval());
273  connect(multiClickTimer, &QTimer::timeout, this, &ChatWidget::onMultiClickTimeout);
274 
275  // selection
276  connect(this, &ChatWidget::selectionChanged, this, [this]() {
277  copyAction->setEnabled(hasTextToBeCopied());
278  copySelectedText(true);
279  });
280 
282 
283  reloadTheme();
284  retranslateUi();
286 
289  connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &ChatWidget::onScrollValueChanged);
290 
291  auto firstChatLogIdx = clampedAdd(chatLog.getNextIdx(), -100, chatLog);
292  renderMessages(firstChatLogIdx, chatLog.getNextIdx());
293 }
294 
296 {
298 
299  // Remove chatlines from scene
300  for (ChatLine::Ptr l : *chatLineStorage)
301  l->removeFromScene();
302 
303  if (busyNotification)
304  busyNotification->removeFromScene();
305 
306  if (typingNotification)
307  typingNotification->removeFromScene();
308 }
309 
311 {
313  return;
314 
315  forEachLineIn(selFirstRow, selLastRow, *chatLineStorage, [&] (ChatLine::Ptr& line) {
316  line->selectionCleared();
317  });
318 
319  selFirstRow.reset();
320  selLastRow.reset();
321  selClickedCol = -1;
322  selClickedRow.reset();
323 
325  emit selectionChanged();
326 
328 }
329 
331 {
332  return mapToScene(viewport()->rect()).boundingRect().toRect();
333 }
334 
336 {
337  setSceneRect(calculateSceneRect());
338 }
339 
340 void ChatWidget::layout(int start, int end, qreal width)
341 {
342  if (chatLineStorage->empty())
343  return;
344 
345  qreal h = 0.0;
346 
347  // Line at start-1 is considered to have the correct position. All following lines are
348  // positioned in respect to this line.
349  if (start - 1 >= 0)
350  h = (*chatLineStorage)[start - 1]->sceneBoundingRect().bottom() + lineSpacing;
351 
352  start = clamp<int>(start, 0, chatLineStorage->size());
353  end = clamp<int>(end + 1, 0, chatLineStorage->size());
354 
355  for (int i = start; i < end; ++i) {
356  ChatLine* l = (*chatLineStorage)[i].get();
357 
358  l->layout(width, QPointF(0.0, h));
359  h += l->sceneBoundingRect().height() + lineSpacing;
360  }
361 }
362 
363 void ChatWidget::mousePressEvent(QMouseEvent* ev)
364 {
365  QGraphicsView::mousePressEvent(ev);
366 
367  if (ev->button() == Qt::LeftButton) {
368  clickPos = ev->pos();
369  clearSelection();
370  }
371 
372  if (lastClickButton == ev->button()) {
373  // Counts only single clicks and first click of doule click
374  clickCount++;
375  }
376  else {
377  clickCount = 1; // restarting counter
378  lastClickButton = ev->button();
379  }
380  lastClickPos = ev->pos();
381 
382  // Triggers on odd click counts
384 }
385 
386 void ChatWidget::mouseReleaseEvent(QMouseEvent* ev)
387 {
388  QGraphicsView::mouseReleaseEvent(ev);
389 
391 
392  multiClickTimer->start();
393 }
394 
395 void ChatWidget::mouseMoveEvent(QMouseEvent* ev)
396 {
397  QGraphicsView::mouseMoveEvent(ev);
398 
399  QPointF scenePos = mapToScene(ev->pos());
400 
401  if (ev->buttons() & Qt::LeftButton) {
402  // autoscroll
403  if (ev->pos().y() < 0)
405  else if (ev->pos().y() > height())
407  else
409 
410  // select
412  && (clickPos - ev->pos()).manhattanLength() > QApplication::startDragDistance()) {
413  QPointF sceneClickPos = mapToScene(clickPos.toPoint());
414  ChatLine::Ptr line = findLineByPosY(scenePos.y());
415 
416  ChatLineContent* content = getContentFromPos(sceneClickPos);
417  if (content) {
418  selClickedRow = line;
419  selClickedCol = content->getColumn();
420  selFirstRow = line;
421  selLastRow = line;
422 
423  content->selectionStarted(sceneClickPos);
424 
426 
427  // ungrab mouse grabber
428  if (scene->mouseGrabberItem())
429  scene->mouseGrabberItem()->ungrabMouse();
430  } else if (line.get()) {
431  selClickedRow = line;
434 
436  }
437  }
438 
440  ChatLineContent* content = getContentFromPos(scenePos);
441  ChatLine::Ptr line = findLineByPosY(scenePos.y());
442 
443  if (content) {
444  int col = content->getColumn();
445 
446  if (line == selClickedRow && col == selClickedCol) {
448 
449  content->selectionMouseMove(scenePos);
450  selGraphItem->hide();
451  } else if (col != selClickedCol) {
453 
454  line->selectionCleared();
455  }
456  } else if (line.get()) {
457  if (line != selClickedRow) {
459  line->selectionCleared();
460  }
461  } else {
462  return;
463  }
464 
465  auto selClickedIt = chatLineStorage->find(selClickedRow);
466  auto lineIt = chatLineStorage->find(line);
467  if (lineIt > selClickedIt)
468  selLastRow = line;
469 
470  if (lineIt <= selClickedIt)
471  selFirstRow = line;
472 
474  }
475 
476  emit selectionChanged();
477  }
478 }
479 
480 // Much faster than QGraphicsScene::itemAt()!
482 {
483  if (chatLineStorage->empty())
484  return nullptr;
485 
486  auto itr =
487  std::lower_bound(chatLineStorage->begin(), chatLineStorage->end(), scenePos.y(), ChatLine::lessThanBSRectBottom);
488 
489  // find content
490  if (itr != chatLineStorage->end() && (*itr)->sceneBoundingRect().contains(scenePos))
491  return (*itr)->getContent(scenePos);
492 
493  return nullptr;
494 }
495 
496 bool ChatWidget::isOverSelection(QPointF scenePos) const
497 {
499  ChatLineContent* content = getContentFromPos(scenePos);
500 
501  if (content)
502  return content->isOverSelection(scenePos);
503  } else if (selectionMode == SelectionMode::Multi) {
504  if (selGraphItem->rect().contains(scenePos))
505  return true;
506  }
507 
508  return false;
509 }
510 
512 {
513  return width() - verticalScrollBar()->sizeHint().width() - margins.right() - margins.left();
514 }
515 
516 void ChatWidget::insertChatlines(std::map<ChatLogIdx, ChatLine::Ptr> chatLines)
517 {
518  if (chatLines.empty())
519  return;
520 
521  bool allLinesAtEnd = !chatLineStorage->hasIndexedMessage() || chatLines.begin()->first > chatLineStorage->lastIdx();
522  auto startLineSize = chatLineStorage->size();
523 
524  QGraphicsScene::ItemIndexMethod oldIndexMeth = scene->itemIndexMethod();
525  scene->setItemIndexMethod(QGraphicsScene::NoIndex);
526 
527  for (auto const& chatLine : chatLines) {
528  auto idx = chatLine.first;
529  auto const& l = chatLine.second;
530 
531  auto insertedMessageIt = chatLineStorage->insertChatMessage(idx, chatLog.at(idx).getTimestamp(), l);
532 
533 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
534  auto date = chatLog.at(idx).getTimestamp().date().startOfDay();
535 #else
536  auto date = QDateTime(chatLog.at(idx).getTimestamp().date());
537 #endif
538 
539  if (!chatLineStorage->contains(date)) {
540  // If there is no dateline for the given date we need to insert it
541  // above the line we'd like to insert.
542  auto dateLine = createDateMessage(date);
543  chatLineStorage->insertDateLine(date, dateLine);
544  dateLine->addToScene(scene);
545  dateLine->visibilityChanged(false);
546  }
547 
548  l->addToScene(scene);
549 
550  // Optimization copied from previous implementation of upwards
551  // rendering. This will be changed when we call updateVisibility
552  // later
553  l->visibilityChanged(false);
554  }
555 
556  scene->setItemIndexMethod(oldIndexMeth);
557 
558  // If all insertions are at the bottom we can get away with only rendering
559  // the updated lines, otherwise we need to go through the resize workflow to
560  // re-layout everything asynchronously.
561  //
562  // NOTE: This can make flow from the callers a little frustrating as you
563  // have to rely on the onRenderFinished callback to continue doing any work,
564  // even if all rendering is done synchronously
565  if (allLinesAtEnd) {
566  bool stickToBtm = stickToBottom();
567 
568  // partial refresh
569  layout(startLineSize, chatLineStorage->size(), useableWidth());
570  updateSceneRect();
571 
572  if (stickToBtm)
573  scrollToBottom();
574 
575  checkVisibility();
578 
579  emit renderFinished();
580  } else {
582  }
583 }
584 
586 {
587  return verticalScrollBar()->value() == verticalScrollBar()->maximum();
588 }
589 
591 {
592  updateSceneRect();
593  verticalScrollBar()->setValue(verticalScrollBar()->maximum());
594 }
595 
597 {
598  if (chatLineStorage->empty())
599  return;
600 
601  // (re)start the worker
602  if (!workerTimer->isActive()) {
603  // these values must not be reevaluated while the worker is running
605 
606  if (!visibleLines.empty())
607  workerAnchorLine = visibleLines.first();
608  }
609 
610  // switch to busy scene displaying the busy notification if there is a lot
611  // of text to be resized
612  int txt = 0;
613  for (ChatLine::Ptr line : *chatLineStorage) {
614  if (txt > 500000)
615  break;
616  for (ChatLineContent* content : line->content)
617  txt += content->getText().size();
618  }
619  if (txt > 500000)
620  setScene(busyScene);
621 
622  workerLastIndex = 0;
623  workerTimer->start();
624 
625  verticalScrollBar()->hide();
626 }
627 
629 {
630  QPointF scenePos = mapToScene(ev->pos());
631  ChatLineContent* content = getContentFromPos(scenePos);
632  ChatLine::Ptr line = findLineByPosY(scenePos.y());
633 
634  if (content) {
635  content->selectionDoubleClick(scenePos);
636  selClickedCol = content->getColumn();
637  selClickedRow = line;
638  selFirstRow = line;
639  selLastRow = line;
641 
642  emit selectionChanged();
643  }
644 
645  if (lastClickButton == ev->button()) {
646  // Counts the second click of double click
647  clickCount++;
648  }
649  else {
650  clickCount = 1; // restarting counter
651  lastClickButton = ev->button();
652  }
653  lastClickPos = ev->pos();
654 
655  // Triggers on even click counts
657 }
658 
660 {
662  return selClickedRow->content[selClickedCol]->getSelectedText();
663  } else if (selectionMode == SelectionMode::Multi) {
664  // build a nicely formatted message
665  QString out;
666 
667  forEachLineIn(selFirstRow, selLastRow, *chatLineStorage, [&] (ChatLine::Ptr& line) {
668  if (line->content[1]->getText().isEmpty())
669  return;
670 
671  QString timestamp = line->content[2]->getText().isEmpty()
672  ? tr("pending")
673  : line->content[2]->getText();
674  QString author = line->content[0]->getText();
675  QString msg = line->content[1]->getText();
676 
677  out +=
678  QString(out.isEmpty() ? "[%2] %1: %3" : "\n[%2] %1: %3").arg(author, timestamp, msg);
679  });
680 
681  return out;
682  }
683 
684  return QString();
685 }
686 
688 {
689  return chatLineStorage->empty();
690 }
691 
693 {
695 }
696 
703 {
704  return getContentFromPos(mapToScene(mapFromGlobal(pos)));
705 }
706 
708 {
709  clearSelection();
710 
711  QVector<ChatLine::Ptr> savedLines;
712 
713  for (auto it = chatLineStorage->begin(); it != chatLineStorage->end();) {
714  if (!isActiveFileTransfer(*it)) {
715  (*it)->removeFromScene();
716  it = chatLineStorage->erase(it);
717  } else {
718  it++;
719  }
720  }
721 
722  visibleLines.clear();
723 
724  checkVisibility();
725  updateSceneRect();
726 }
727 
728 void ChatWidget::copySelectedText(bool toSelectionBuffer) const
729 {
730  QString text = getSelectedText();
731  QClipboard* clipboard = QApplication::clipboard();
732 
733  if (clipboard && !text.isNull())
734  clipboard->setText(text, toSelectionBuffer ? QClipboard::Selection : QClipboard::Clipboard);
735 }
736 
738 {
739  if (typingNotification.get()) {
740  typingNotification->setVisible(visible);
742  }
743 }
744 
745 void ChatWidget::setTypingNotificationName(const QString& displayName)
746 {
747  if (!typingNotification.get()) {
749  }
750 
751  Text* text = static_cast<Text*>(typingNotification->getContent(1));
752  QString typingDiv = "<div class=typing>%1</div>";
753  text->setText(typingDiv.arg(tr("%1 is typing").arg(displayName)));
754 
756 }
757 
759 {
760  if (!line.get())
761  return;
762 
763  updateSceneRect();
764  verticalScrollBar()->setValue(line->sceneBoundingRect().top());
765 }
766 
768 {
769  if (chatLineStorage->empty())
770  return;
771 
772  clearSelection();
773 
775  selFirstRow = chatLineStorage->front();;
776  selLastRow = chatLineStorage->back();
777 
778  emit selectionChanged();
780 }
781 
782 void ChatWidget::fontChanged(const QFont& font)
783 {
784  for (ChatLine::Ptr l : *chatLineStorage) {
785  l->fontChanged(font);
786  }
787 }
788 
790 {
791  setStyleSheet(Style::getStylesheet("chatArea/chatArea.css"));
792  setBackgroundBrush(QBrush(Style::getColor(Style::GroundBase), Qt::SolidPattern));
794  selGraphItem->setBrush(QBrush(selectionRectColor));
795  selGraphItem->setPen(QPen(selectionRectColor.darker(120)));
797 
798  for (ChatLine::Ptr l : *chatLineStorage) {
799  l->reloadTheme();
800  }
801 }
802 
803 void ChatWidget::startSearch(const QString& phrase, const ParameterSearch& parameter)
804 {
806 
807  bool bForwardSearch = false;
808  switch (parameter.period) {
810  bForwardSearch = true;
812  searchPos.numMatches = 0;
813  break;
814  }
816  case PeriodSearch::None: {
817  bForwardSearch = false;
819  searchPos.numMatches = 0;
820  break;
821  }
823  bForwardSearch = true;
824  searchPos.logIdx = firstItemAfterDate(parameter.date, chatLog);
825  searchPos.numMatches = 0;
826  break;
827  }
829  bForwardSearch = false;
830  searchPos.logIdx = firstItemAfterDate(parameter.date, chatLog);
831  searchPos.numMatches = 0;
832  break;
833  }
834  }
835 
836  if (bForwardSearch) {
837  onSearchDown(phrase, parameter);
838  } else {
839  onSearchUp(phrase, parameter);
840  }
841 }
842 
843 void ChatWidget::onSearchUp(const QString& phrase, const ParameterSearch& parameter)
844 {
845  auto result = chatLog.searchBackward(searchPos, phrase, parameter);
847 }
848 
849 void ChatWidget::onSearchDown(const QString& phrase, const ParameterSearch& parameter)
850 {
851  auto result = chatLog.searchForward(searchPos, phrase, parameter);
853 }
854 
856 {
857  if (!result.found) {
858  emit messageNotFoundShow(direction);
859  return;
860  }
861 
863 
864  searchPos = result.pos;
865 
866  auto const firstRenderedIdx = (chatLineStorage->hasIndexedMessage()) ? chatLineStorage->firstIdx() : chatLog.getNextIdx();
867 
868  auto selectText = [this, result] {
869  // With fast changes our callback could become invalid, ensure that the
870  // index we want to view is still actually visible
871  if (!chatLineStorage->contains(searchPos.logIdx))
872  return;
873 
874  auto msg = (*chatLineStorage)[searchPos.logIdx];
875  scrollToLine(msg);
876 
877  auto text = qobject_cast<Text*>(msg->getContent(1));
878  text->selectText(result.exp, std::make_pair(result.start, result.len));
879  };
880 
881  // If the requested element is visible the render completion callback will
882  // not be called, we need to figure out which path we're going to take
883  // before we take it.
884  if (chatLineStorage->contains(searchPos.logIdx)) {
886  selectText();
887  } else {
888  renderCompletionFns.push_back(selectText);
890  }
891 
892 }
893 
895 {
897 }
898 
900 {
901  if (chatLineStorage->empty())
902  return;
903 
904  // find first visible line
905  auto lowerBound = std::lower_bound(chatLineStorage->begin(), chatLineStorage->end(), getVisibleRect().top(),
907 
908  // find last visible line
909  auto upperBound = std::lower_bound(lowerBound, chatLineStorage->end(), getVisibleRect().bottom(),
911 
912  const ChatLine::Ptr lastLineBeforeVisible = lowerBound == chatLineStorage->begin()
913  ? ChatLine::Ptr()
914  : *std::prev(lowerBound);
915 
916  // set visibilty
917  QList<ChatLine::Ptr> newVisibleLines;
918  for (auto itr = lowerBound; itr != upperBound; ++itr) {
919  newVisibleLines.append(*itr);
920 
921  if (!visibleLines.contains(*itr))
922  (*itr)->visibilityChanged(true);
923 
924  visibleLines.removeOne(*itr);
925  }
926 
927  // these lines are no longer visible
928  for (ChatLine::Ptr line : visibleLines)
929  line->visibilityChanged(false);
930 
931  visibleLines = newVisibleLines;
932 
933  if (!visibleLines.isEmpty()) {
934  emit firstVisibleLineChanged(lastLineBeforeVisible, visibleLines.at(0));
935  }
936 }
937 
938 void ChatWidget::scrollContentsBy(int dx, int dy)
939 {
940  QGraphicsView::scrollContentsBy(dx, dy);
941  checkVisibility();
942 }
943 
944 void ChatWidget::resizeEvent(QResizeEvent* ev)
945 {
946  bool stb = stickToBottom();
947 
948  if (ev->size().width() != ev->oldSize().width()) {
950  stb = false; // let the resize worker handle it
951  }
952 
953  QGraphicsView::resizeEvent(ev);
954 
955  if (stb)
956  scrollToBottom();
957 
959 }
960 
962 {
964  QRectF selBBox;
965  selBBox = selBBox.united(selFirstRow->sceneBoundingRect());
966  selBBox = selBBox.united(selLastRow->sceneBoundingRect());
967 
968  if (selGraphItem->rect() != selBBox)
969  scene->invalidate(selGraphItem->rect());
970 
971  selGraphItem->setRect(selBBox);
972  selGraphItem->show();
973  } else {
974  selGraphItem->hide();
975  }
976 }
977 
979 {
980  ChatLine* notification = typingNotification.get();
981  if (!notification)
982  return;
983 
984  qreal posY = 0.0;
985 
986  if (!chatLineStorage->empty())
987  posY = chatLineStorage->back()->sceneBoundingRect().bottom() + lineSpacing;
988 
989  notification->layout(useableWidth(), QPointF(0.0, posY));
990 }
991 
993 {
994  // repoisition the busy notification (centered)
995  busyNotification->layout(useableWidth(), getVisibleRect().topLeft()
996  + QPointF(0, getVisibleRect().height() / 2.0));
997 }
998 
1000 {
1001  auto itr = std::lower_bound(chatLineStorage->begin(), chatLineStorage->end(), yPos, ChatLine::lessThanBSRectBottom);
1002 
1003  if (itr != chatLineStorage->end())
1004  return *itr;
1005 
1006  return ChatLine::Ptr();
1007 }
1008 
1010 {
1011  if (!chatLineStorage->hasIndexedMessage()) {
1012  // No indexed lines to remove
1013  return;
1014  }
1015 
1016  begin = clamp<ChatLogIdx>(begin, chatLineStorage->firstIdx(), chatLineStorage->lastIdx());
1017  end = clamp<ChatLogIdx>(end, chatLineStorage->firstIdx(), chatLineStorage->lastIdx()) + 1;
1018 
1019  // NOTE: Optimization potential if this find proves to be too expensive.
1020  // Batching all our erases into one call would be more efficient
1021  for (auto it = chatLineStorage->find(begin); it != chatLineStorage->find(end);) {
1022  (*it)->removeFromScene();
1023  it = chatLineStorage->erase(it);
1024  }
1025 
1026  // We need to re-layout anything that is after any line we removed. We could
1027  // probably be smarter and try to only re-render anything under what we
1028  // removed, but with the sliding window there doesn't seem to be much need
1029  if (chatLineStorage->hasIndexedMessage() && begin <= chatLineStorage->lastIdx()) {
1030  layout(0, chatLineStorage->size(), useableWidth());
1031  }
1032 }
1033 
1035 {
1036  qreal bottom = (chatLineStorage->empty() ? 0.0 : chatLineStorage->back()->sceneBoundingRect().bottom());
1037 
1038  if (typingNotification.get() != nullptr)
1039  bottom += typingNotification->sceneBoundingRect().height() + lineSpacing;
1040 
1041  return QRectF(-margins.left(), -margins.top(), useableWidth(),
1042  bottom + margins.bottom() + margins.top());
1043 }
1044 
1046 {
1047  const int scrollSpeed = 10;
1048 
1049  switch (selectionScrollDir) {
1051  verticalScrollBar()->setValue(verticalScrollBar()->value() - scrollSpeed);
1052  break;
1054  verticalScrollBar()->setValue(verticalScrollBar()->value() + scrollSpeed);
1055  break;
1056  default:
1057  break;
1058  }
1059 }
1060 
1062 {
1063  // Fairly arbitrary but
1064  // large values will make the UI unresponsive
1065  const int stepSize = 50;
1066 
1068  workerLastIndex += stepSize;
1069 
1070  // done?
1071  if (workerLastIndex >= chatLineStorage->size()) {
1072  workerTimer->stop();
1073 
1074  // switch back to the scene containing the chat messages
1075  setScene(scene);
1076 
1077  // make sure everything gets updated
1078  updateSceneRect();
1079  checkVisibility();
1082 
1083  // scroll
1084  if (workerStb)
1085  scrollToBottom();
1086  else
1088 
1089  // don't keep a Ptr to the anchor line
1091 
1092  // hidden during busy screen
1093  verticalScrollBar()->show();
1094 
1095  emit renderFinished();
1096  }
1097 }
1098 
1100 {
1101  clickCount = 0;
1102 }
1103 
1105 {
1106  if (shouldRenderMessage(idx)) {
1107  renderMessage(idx);
1108  }
1109 }
1110 
1112 {
1113  renderMessages(idx, idx + 1);
1114 }
1115 
1117 {
1118  auto linesToRender = std::map<ChatLogIdx, ChatLine::Ptr>();
1119 
1120  for (auto i = begin; i < end; ++i) {
1121  bool alreadyRendered = chatLineStorage->contains(i);
1122  bool prevIdxRendered = i != begin || chatLineStorage->contains(i - 1);
1123 
1124  auto chatMessage = alreadyRendered ? (*chatLineStorage)[i] : ChatLine::Ptr();
1125  renderItem(chatLog.at(i), needsToHideName(i, prevIdxRendered), colorizeNames, chatMessage);
1126 
1127  if (!alreadyRendered) {
1128  linesToRender.insert({i, chatMessage});
1129  }
1130  }
1131 
1132  insertChatlines(linesToRender);
1133 }
1134 
1136 {
1137  // End of the window is pre-determined as a hardcoded window size relative
1138  // to the start
1139  auto end = clampedAdd(begin, maxWindowSize, chatLog);
1140 
1141  // Use invalid + equal ChatLogIdx to force a full re-render if we do not
1142  // have an indexed message to compare to
1143  ChatLogIdx currentStart = ChatLogIdx(-1);
1144  ChatLogIdx currentEnd = ChatLogIdx(-1);
1145 
1146  if (chatLineStorage->hasIndexedMessage()) {
1147  currentStart = chatLineStorage->firstIdx();
1148  currentEnd = chatLineStorage->lastIdx() + 1;
1149  }
1150 
1151  // If the window is already where we have no work to do
1152  if (currentStart == begin) {
1153  emit renderFinished();
1154  return;
1155  }
1156 
1157  // NOTE: This is more than an optimization, this is important for
1158  // selection consistency. If we re-create lines that are already rendered
1159  // the selXXXRow members will now be pointing to the wrong ChatLine::Ptr!
1160  // Please be sure to test selection logic when scrolling around loading
1161  // boundaries if changing this logic.
1162  if (begin < currentEnd && begin > currentStart) {
1163  // Remove leading lines
1164  removeLines(currentStart, begin);
1165  renderMessages(currentEnd, end);
1166  }
1167  else if (end <= currentEnd && end > currentStart) {
1168  // Remove trailing lines
1169  removeLines(end, currentEnd);
1170  renderMessages(begin, currentStart);
1171  }
1172  else {
1173  removeLines(currentStart, currentEnd);
1174  renderMessages(begin, end);
1175  }
1176 }
1177 
1179 {
1180  // Off by 1 since the maxWindowSize is not inclusive
1181  auto start = clampedAdd(end, -maxWindowSize + 1, chatLog);
1182 
1183  setRenderedWindowStart(start);
1184 }
1185 
1187 {
1188  // We have to back these up before we run them, because people might queue
1189  // on _more_ items on top of the ones we want. If they do this while we're
1190  // iterating we can hit some memory corruption issues
1191  auto renderCompletionFnsLocal = renderCompletionFns;
1192  renderCompletionFns.clear();
1193 
1194  while (renderCompletionFnsLocal.size()) {
1195  renderCompletionFnsLocal.back()();
1196  renderCompletionFnsLocal.pop_back();
1197  }
1198 
1199  // NOTE: this is a regression from previous behavior. We used to be able to
1200  // load an infinite amount of chat and copy paste it out. Now we limit the
1201  // user to 300 elements and any time the elements change our selection gets
1202  // invalidated. This could be improved in the future but for now I do not
1203  // believe this is a serious usage impediment. Chats can be exported if a
1204  // user really needs more than 300 messages to be copied
1205  if (chatLineStorage->find(selFirstRow) == chatLineStorage->end() ||
1206  chatLineStorage->find(selLastRow) == chatLineStorage->end() ||
1208  {
1209  // FIXME: Segfault when selecting while scrolling down
1210  clearSelection();
1211  }
1212 }
1213 
1215 {
1216  if (!chatLineStorage->hasIndexedMessage()) {
1217  // This could be a little better. On a cleared screen we should probably
1218  // be able to scroll, but this makes the rest of this function easier
1219  return;
1220  }
1221 
1222  // When we hit the end of our scroll bar we change the content that's in the
1223  // viewport. In this process our scroll value may end up changing again! We
1224  // avoid this by changing our scroll position to match where it was before
1225  // started our viewport change, but we need to filter out any intermediate
1226  // scroll events triggered before we get to that point
1227  if (!scrollMonitoringEnabled) {
1228  return;
1229  }
1230 
1231  if (value == verticalScrollBar()->minimum())
1232  {
1233  auto idx = clampedAdd(chatLineStorage->firstIdx(), -static_cast<int>(windowChunkSize), chatLog);
1234 
1235  if (idx != chatLineStorage->firstIdx()) {
1236  auto currentTop = (*chatLineStorage)[chatLineStorage->firstIdx()];
1237 
1238  renderCompletionFns.push_back([this, currentTop] {
1239  scrollToLine(currentTop);
1240  scrollMonitoringEnabled = true;
1241  });
1242 
1243  scrollMonitoringEnabled = false;
1245  }
1246 
1247  }
1248  else if (value == verticalScrollBar()->maximum())
1249  {
1250  auto idx = clampedAdd(chatLineStorage->lastIdx(), static_cast<int>(windowChunkSize), chatLog);
1251 
1252  if (idx != chatLineStorage->lastIdx() + 1) {
1253  // FIXME: This should be the top line
1254  auto currentBottomIdx = chatLineStorage->lastIdx();
1255  auto currentTopPx = mapToScene(0, 0).y();
1256  auto currentBottomPx = (*chatLineStorage)[currentBottomIdx]->sceneBoundingRect().bottom();
1257  auto bottomOffset = currentBottomPx - currentTopPx;
1258 
1259  renderCompletionFns.push_back([this, currentBottomIdx, bottomOffset] {
1260  auto it = chatLineStorage->find(currentBottomIdx);
1261  if (it != chatLineStorage->end()) {
1262  updateSceneRect();
1263  verticalScrollBar()->setValue((*it)->sceneBoundingRect().bottom() - bottomOffset);
1264  scrollMonitoringEnabled = true;
1265  }
1266  });
1267 
1268  scrollMonitoringEnabled = false;
1269  setRenderedWindowEnd(idx);
1270  }
1271  }
1272 }
1273 
1275 {
1276  // Ignore single or double clicks
1277  if (clickCount < 2)
1278  return;
1279 
1280  switch (clickCount) {
1281  case 3:
1282  QPointF scenePos = mapToScene(lastClickPos);
1283  ChatLineContent* content = getContentFromPos(scenePos);
1284  ChatLine::Ptr line = findLineByPosY(scenePos.y());
1285 
1286  if (content) {
1287  content->selectionTripleClick(scenePos);
1288  selClickedCol = content->getColumn();
1289  selClickedRow = line;
1290  selFirstRow = line;
1291  selLastRow = line;
1293 
1294  emit selectionChanged();
1295  }
1296  break;
1297  }
1298 }
1299 
1300 void ChatWidget::showEvent(QShowEvent*)
1301 {
1302  // Empty.
1303  // The default implementation calls centerOn - for some reason - causing
1304  // the scrollbar to move.
1305 }
1306 
1307 void ChatWidget::hideEvent(QHideEvent* event)
1308 {
1309  // Purge accumulated lines from the chatlog. We do not purge messages while
1310  // the chatlog is open because it causes flickers. When a user leaves the
1311  // chat we take the opportunity to remove old messages. If a user only has
1312  // one friend this could end up accumulating chat logs until they restart
1313  // qTox, but that isn't a regression from previously released behavior.
1314 
1315  auto numLinesToRemove = chatLineStorage->size() > maxWindowSize
1316  ? chatLineStorage->size() - maxWindowSize
1317  : 0;
1318 
1319  if (numLinesToRemove > 0) {
1320  removeLines(chatLineStorage->firstIdx(), chatLineStorage->firstIdx() + numLinesToRemove);
1322  }
1323 }
1324 
1325 void ChatWidget::focusInEvent(QFocusEvent* ev)
1326 {
1327  QGraphicsView::focusInEvent(ev);
1328 
1330  selGraphItem->setBrush(QBrush(selectionRectColor));
1331 
1332  auto endIt = chatLineStorage->find(selLastRow);
1333  // Increase by one since this selLastRow is inclusive, not exclusive
1334  // like our loop expects
1335  if (endIt != chatLineStorage->end()) {
1336  endIt++;
1337  }
1338 
1339  for (auto it = chatLineStorage->begin(); it != chatLineStorage->end() && it != endIt; ++it)
1340  (*it)->selectionFocusChanged(true);
1341  }
1342 }
1343 
1344 void ChatWidget::focusOutEvent(QFocusEvent* ev)
1345 {
1346  QGraphicsView::focusOutEvent(ev);
1347 
1349  selGraphItem->setBrush(QBrush(selectionRectColor.lighter(120)));
1350 
1351  auto endIt = chatLineStorage->find(selLastRow);
1352  // Increase by one since this selLastRow is inclusive, not exclusive
1353  // like our loop expects
1354  if (endIt != chatLineStorage->end()) {
1355  endIt++;
1356  }
1357 
1358  for (auto it = chatLineStorage->begin(); it != chatLineStorage->end() && it != endIt; ++it)
1359  (*it)->selectionFocusChanged(false);
1360  }
1361 }
1362 
1363 void ChatWidget::wheelEvent(QWheelEvent *event)
1364 {
1365  QGraphicsView::wheelEvent(event);
1366  checkVisibility();
1367 }
1368 
1370 {
1371  copyAction->setText(tr("Copy"));
1372  selectAllAction->setText(tr("Select all"));
1373 }
1374 
1376 {
1377  int count = l->getColumnCount();
1378  for (int i = 0; i < count; ++i) {
1379  ChatLineContent* content = l->getContent(i);
1380  ChatLineContentProxy* proxy = qobject_cast<ChatLineContentProxy*>(content);
1381  if (!proxy)
1382  continue;
1383 
1384  QWidget* widget = proxy->getWidget();
1385  FileTransferWidget* transferWidget = qobject_cast<FileTransferWidget*>(widget);
1386  if (transferWidget && transferWidget->isActive())
1387  return true;
1388  }
1389 
1390  return false;
1391 }
1392 
1394 {
1396  typingNotification->visibilityChanged(true);
1397  typingNotification->setVisible(false);
1398  typingNotification->addToScene(scene);
1400 }
1401 
1402 
1403 void ChatWidget::renderItem(const ChatLogItem& item, bool hideName, bool colorizeNames, ChatLine::Ptr& chatMessage)
1404 {
1405  const auto& sender = item.getSender();
1406 
1407  bool isSelf = sender == core.getSelfPublicKey();
1408 
1409  switch (item.getContentType()) {
1411  const auto& chatLogMessage = item.getContentAsMessage();
1412 
1413  renderMessageRaw(item.getDisplayName(), isSelf, colorizeNames, chatLogMessage, chatMessage);
1414 
1415  break;
1416  }
1418  const auto& file = item.getContentAsFile();
1419  renderFile(item.getDisplayName(), file.file, isSelf, item.getTimestamp(), chatMessage);
1420  break;
1421  }
1423  const auto& systemMessage = item.getContentAsSystemMessage();
1424 
1425  auto chatMessageType = getChatMessageType(systemMessage);
1426  chatMessage = ChatMessage::createChatInfoMessage(systemMessage.toString(), chatMessageType, QDateTime::currentDateTime());
1427  // Ignore caller's decision to hide the name. We show the icon in the
1428  // slot of the sender's name so we always want it visible
1429  hideName = false;
1430  break;
1431  }
1432  }
1433 
1434  if (hideName) {
1435  chatMessage->getContent(0)->hide();
1436  }
1437 }
1438 
1439 void ChatWidget::renderFile(QString displayName, ToxFile file, bool isSelf, QDateTime timestamp,
1440  ChatLine::Ptr& chatMessage)
1441 {
1442  if (!chatMessage) {
1443  CoreFile* coreFile = core.getCoreFile();
1444  assert(coreFile);
1445  chatMessage = ChatMessage::createFileTransferMessage(displayName, *coreFile, file, isSelf, timestamp);
1446  } else {
1447  auto proxy = static_cast<ChatLineContentProxy*>(chatMessage->getContent(1));
1448  assert(proxy->getWidgetType() == ChatLineContentProxy::FileTransferWidgetType);
1449  auto ftWidget = static_cast<FileTransferWidget*>(proxy->getWidget());
1450  ftWidget->onFileTransferUpdate(file);
1451  }
1452 }
1453 
1461 bool ChatWidget::needsToHideName(ChatLogIdx idx, bool prevIdxRendered) const
1462 {
1463  // If the previous message is not rendered we should show the name
1464  // regardless of other constraints
1465 
1466  if (!prevIdxRendered) {
1467  return false;
1468  }
1469 
1470  const auto& prevItem = chatLog.at(idx - 1);
1471  const auto& currentItem = chatLog.at(idx);
1472 
1473  // Always show the * in the name field for action messages
1474  if (currentItem.getContentType() == ChatLogItem::ContentType::message
1475  && currentItem.getContentAsMessage().message.isAction) {
1476  return false;
1477  }
1478 
1479  qint64 messagesTimeDiff = prevItem.getTimestamp().secsTo(currentItem.getTimestamp());
1480  return currentItem.getSender() == prevItem.getSender()
1481  && messagesTimeDiff < repNameAfter;
1482 
1483  return false;
1484 }
1485 
1487 {
1488  return chatLineStorage->contains(idx) ||
1489  (
1490  chatLineStorage->contains(idx - 1) && idx + 1 == chatLog.getNextIdx()
1491  ) || chatLineStorage->empty();
1492 }
1493 
1495 {
1496  if (!chatLineStorage->contains(searchPos.logIdx)) {
1497  return;
1498  }
1499 
1500  auto line = (*chatLineStorage)[searchPos.logIdx];
1501  auto text = qobject_cast<Text*>(line->getContent(1));
1502  text->deselectText();
1503 }
1504 
1506 {
1508 }
1509 
1510 void ChatWidget::jumpToDate(QDate date) {
1511  auto idx = firstItemAfterDate(date, chatLog);
1512  jumpToIdx(idx);
1513 }
1514 
1516 {
1517  if (idx == chatLog.getNextIdx()) {
1518  idx = chatLog.getNextIdx() - 1;
1519  }
1520 
1521  if (chatLineStorage->contains(idx)) {
1522  scrollToLine((*chatLineStorage)[idx]);
1523  return;
1524  }
1525 
1526  // If the requested idx is not currently rendered we need to request a
1527  // render and jump to the requested line after the render completes
1528  renderCompletionFns.push_back([this, idx] {
1529  if (chatLineStorage->contains(idx)) {
1530  scrollToLine((*chatLineStorage)[idx]);
1531  }
1532  });
1533 
1534  // If the chatlog is empty it's likely the user has just cleared. In this
1535  // case it makes more sense to present the jump as if we're coming from the
1536  // bottom
1537  if (chatLineStorage->hasIndexedMessage() && idx > chatLineStorage->lastIdx()) {
1538  setRenderedWindowEnd(clampedAdd(idx, windowChunkSize, chatLog));
1539  }
1540  else {
1541  setRenderedWindowStart(clampedAdd(idx, -windowChunkSize, chatLog));
1542  }
1543 }
ChatLogItem::getContentAsFile
ChatLogFile & getContentAsFile()
Definition: chatlogitem.cpp:75
ChatLineStorage
Definition: chatlinestorage.h:53
SearchResult::pos
SearchPos pos
Definition: ichatlog.h:73
SearchResult::len
size_t len
Definition: ichatlog.h:75
SearchDirection::Down
@ Down
SystemMessageType::cleared
@ cleared
ChatWidget::selectAllAction
QAction * selectAllAction
Definition: chatwidget.h:168
style.h
ChatWidget::workerTimer
QTimer * workerTimer
Definition: chatwidget.h:192
FileTransferWidget
Definition: filetransferwidget.h:37
ChatLogItem::getTimestamp
QDateTime getTimestamp() const
Definition: chatlogitem.cpp:112
ChatWidget::clickCount
int clickCount
Definition: chatwidget.h:195
ChatLogItem
Definition: chatlogitem.h:42
ParameterSearch::date
QDate date
Definition: searchtypes.h:50
ChatWidget::clearSelection
void clearSelection()
Definition: chatwidget.cpp:310
ChatWidget::onMultiClickTimeout
void onMultiClickTimeout()
Definition: chatwidget.cpp:1099
ChatWidget::setTypingNotificationName
void setTypingNotificationName(const QString &displayName)
Definition: chatwidget.cpp:745
SearchDirection
SearchDirection
Definition: searchtypes.h:42
ChatWidget::getContentFromPos
ChatLineContent * getContentFromPos(QPointF scenePos) const
Definition: chatwidget.cpp:481
ChatWidget::startResizeWorker
void startResizeWorker()
Definition: chatwidget.cpp:596
ChatWidget::multiClickTimer
QTimer * multiClickTimer
Definition: chatwidget.h:193
ChatWidget::resizeEvent
void resizeEvent(QResizeEvent *ev) final
Definition: chatwidget.cpp:944
ChatWidget::selectionTimer
QTimer * selectionTimer
Definition: chatwidget.h:191
ChatWidget::getContentFromGlobalPos
ChatLineContent * getContentFromGlobalPos(QPoint pos) const
Finds the chat line object at a position on screen.
Definition: chatwidget.cpp:702
ChatWidget::startSearch
void startSearch(const QString &phrase, const ParameterSearch &parameter)
Definition: chatwidget.cpp:803
settings.h
chatlinestorage.h
ChatWidget::typingNotification
ChatLine::Ptr typingNotification
Definition: chatwidget.h:172
ChatWidget::layout
void layout(int start, int end, qreal width)
Definition: chatwidget.cpp:340
chatwidget.h
ChatWidget::retranslateUi
void retranslateUi()
Definition: chatwidget.cpp:1369
ChatWidget::chatLog
IChatLog & chatLog
Definition: chatwidget.h:208
Core::getSelfPublicKey
ToxPk getSelfPublicKey() const override
Gets self public key.
Definition: core.cpp:1271
ChatWidget::selLastRow
ChatLine::Ptr selLastRow
Definition: chatwidget.h:186
PeriodSearch::AfterDate
@ AfterDate
ChatLogItem::getSender
const ToxPk & getSender() const
Definition: chatlogitem.cpp:65
ChatWidget::shouldRenderMessage
bool shouldRenderMessage(ChatLogIdx idx) const
Definition: chatwidget.cpp:1486
ChatWidget::renderMessage
void renderMessage(ChatLogIdx idx)
Definition: chatwidget.cpp:1111
ChatWidget::repNameAfter
const uint repNameAfter
Definition: chatwidget.h:63
PeriodSearch::BeforeDate
@ BeforeDate
ChatWidget::setRenderedWindowEnd
void setRenderedWindowEnd(ChatLogIdx end)
Definition: chatwidget.cpp:1178
ChatWidget::renderItem
void renderItem(const ChatLogItem &item, bool hideName, bool colorizeNames, ChatLine::Ptr &chatMessage)
Definition: chatwidget.cpp:1403
SearchDirection::Up
@ Up
ChatLine::lessThanBSRectBottom
static bool lessThanBSRectBottom(const ChatLine::Ptr &lhs, const qreal &rhs)
Definition: chatline.cpp:249
ChatMessage::ERROR
@ ERROR
Definition: chatmessage.h:39
ParameterSearch
Definition: searchtypes.h:47
ChatWidget::focusOutEvent
void focusOutEvent(QFocusEvent *ev) final
Definition: chatwidget.cpp:1344
ChatWidget::core
const Core & core
Definition: chatwidget.h:211
ChatWidget::margins
QMargins margins
Definition: chatwidget.h:205
ChatWidget::scrollMonitoringEnabled
bool scrollMonitoringEnabled
Definition: chatwidget.h:212
SystemMessageType::unexpectedCallEnd
@ unexpectedCallEnd
ChatWidget::selFirstRow
ChatLine::Ptr selFirstRow
Definition: chatwidget.h:185
ChatWidget::disableSearchText
void disableSearchText()
Definition: chatwidget.cpp:1494
ChatLogItem::ContentType::message
@ message
HistMessageContentType::file
@ file
ChatWidget::selectionScrollDir
AutoScrollDirection selectionScrollDir
Definition: chatwidget.h:194
Message::isAction
bool isAction
Definition: message.h:54
ChatWidget::colorizeNames
bool colorizeNames
Definition: chatwidget.h:209
MessageMetadata
Definition: message.h:43
ChatWidget::~ChatWidget
virtual ~ChatWidget()
Definition: chatwidget.cpp:295
ChatMessage::Ptr
std::shared_ptr< ChatMessage > Ptr
Definition: chatmessage.h:34
Translator::unregister
static void unregister(void *owner)
Unregisters all handlers of an owner.
Definition: translator.cpp:103
ChatWidget::updateTypingNotification
void updateTypingNotification()
Definition: chatwidget.cpp:978
ChatWidget::scrollToBottom
void scrollToBottom()
Definition: chatwidget.cpp:590
IChatLog::searchBackward
virtual SearchResult searchBackward(SearchPos startIdx, const QString &phrase, const ParameterSearch &parameter) const =0
searches backwards through the chat log until phrase is found according to parameter
ChatWidget::removeSearchPhrase
void removeSearchPhrase()
Definition: chatwidget.cpp:1505
SystemMessageType::messageSendFailed
@ messageSendFailed
ChatWidget::ChatWidget
ChatWidget(IChatLog &chatLog, const Core &core, QWidget *parent=nullptr)
Definition: chatwidget.cpp:205
SystemMessage
Definition: systemmessage.h:47
ChatLineContent::getColumn
int getColumn() const
Definition: chatlinecontent.cpp:28
QList< ChatLine::Ptr >
SystemMessage::toString
QString toString() const
Definition: systemmessage.h:54
ChatWidget::needsToHideName
bool needsToHideName(ChatLogIdx idx, bool prevIdxRendered) const
Determine if the name at the given idx needs to be hidden.
Definition: chatwidget.cpp:1461
SystemMessageType::callEnd
@ callEnd
ChatWidget::updateSceneRect
void updateSceneRect()
Definition: chatwidget.cpp:335
ChatLine
Definition: chatline.h:65
SearchResult::start
size_t start
Definition: ichatlog.h:74
Core::getCoreFile
CoreFile * getCoreFile() const
Definition: core.cpp:718
ChatWidget::lastClickButton
Qt::MouseButton lastClickButton
Definition: chatwidget.h:197
IChatLog::at
virtual const ChatLogItem & at(ChatLogIdx idx) const =0
Returns reference to item at idx.
ChatWidget::focusInEvent
void focusInEvent(QFocusEvent *ev) final
Definition: chatwidget.cpp:1325
ChatLogItem::getContentType
ContentType getContentType() const
Definition: chatlogitem.cpp:70
ChatLineContentProxy::FileTransferWidgetType
@ FileTransferWidgetType
Definition: chatlinecontentproxy.h:35
IChatLog::getFirstIdx
virtual ChatLogIdx getFirstIdx() const =0
The underlying chat log instance may not want to start at 0.
IChatLog
Definition: ichatlog.h:83
Message::metadata
std::vector< MessageMetadata > metadata
Definition: message.h:58
ChatWidget::messageNotFoundShow
void messageNotFoundShow(SearchDirection direction)
ChatLineContent::isOverSelection
virtual bool isOverSelection(QPointF scenePos) const
Definition: chatlinecontent.cpp:62
ChatWidget::lineSpacing
qreal lineSpacing
Definition: chatwidget.h:206
ChatWidget::scrollContentsBy
void scrollContentsBy(int dx, int dy) final
Definition: chatwidget.cpp:938
ChatWidget::removeLines
void removeLines(ChatLogIdx being, ChatLogIdx end)
Definition: chatwidget.cpp:1009
ChatWidget::clickPos
QPointF clickPos
Definition: chatwidget.h:189
ChatWidget::onScrollValueChanged
void onScrollValueChanged(int value)
Definition: chatwidget.cpp:1214
GUI::getInstance
static GUI & getInstance()
Returns the singleton instance.
Definition: gui.cpp:56
ChatWidget::workerLastIndex
size_t workerLastIndex
Definition: chatwidget.h:200
SystemMessageType::fileSendFailed
@ fileSendFailed
ChatWidget::insertChatlines
void insertChatlines(std::map< ChatLogIdx, ChatLine::Ptr > chatLines)
Definition: chatwidget.cpp:516
ChatWidget::workerStb
bool workerStb
Definition: chatwidget.h:201
PeriodSearch::WithTheEnd
@ WithTheEnd
ChatWidget::hideEvent
void hideEvent(QHideEvent *event) final
Definition: chatwidget.cpp:1307
chatlinecontent.h
ChatWidget::isOverSelection
bool isOverSelection(QPointF scenePos) const
Definition: chatwidget.cpp:496
ChatWidget::jumpToIdx
void jumpToIdx(ChatLogIdx idx)
Definition: chatwidget.cpp:1515
ChatWidget::showEvent
void showEvent(QShowEvent *) final
Definition: chatwidget.cpp:1300
IChatLog::searchForward
virtual SearchResult searchForward(SearchPos startIdx, const QString &phrase, const ParameterSearch &parameter) const =0
searches forwards through the chat log until phrase is found according to parameter
ChatLineStorage::find
iterator find(ChatLogIdx idx)
Definition: chatlinestorage.cpp:88
ChatWidget::SelectionMode::Multi
@ Multi
ChatWidget::fontChanged
void fontChanged(const QFont &font)
Definition: chatwidget.cpp:782
ChatLineStorage::begin
iterator begin()
Definition: chatlinestorage.h:95
PeriodSearch::None
@ None
ChatWidget::renderMessages
void renderMessages(ChatLogIdx begin, ChatLogIdx end)
Definition: chatwidget.cpp:1116
ChatWidget::selectionRectColor
QColor selectionRectColor
Definition: chatwidget.h:187
ChatWidget::lastClickPos
QPoint lastClickPos
Definition: chatwidget.h:196
SystemMessageType::titleChanged
@ titleChanged
ChatWidget::scene
QGraphicsScene * scene
Definition: chatwidget.h:169
ChatWidget::scrollToLine
void scrollToLine(ChatLine::Ptr line)
Definition: chatwidget.cpp:758
ChatWidget::stickToBottom
bool stickToBottom() const
Definition: chatwidget.cpp:585
filetransferwidget.h
ParameterSearch::period
PeriodSearch period
Definition: searchtypes.h:49
PeriodSearch::WithTheFirst
@ WithTheFirst
ChatWidget::firstVisibleLineChanged
void firstVisibleLineChanged(const ChatLine::Ptr &prevLine, const ChatLine::Ptr &firstLine)
IChatLog::getDateIdxs
virtual std::vector< DateChatLogIdxPair > getDateIdxs(const QDate &startDate, size_t maxDates) const =0
Gets indexes for each new date starting at startDate.
ChatWidget::workerAnchorLine
ChatLine::Ptr workerAnchorLine
Definition: chatwidget.h:202
ChatMessage::createFileTransferMessage
static ChatMessage::Ptr createFileTransferMessage(const QString &sender, CoreFile &coreFile, ToxFile file, bool isMe, const QDateTime &date)
Definition: chatmessage.cpp:166
ChatWidget::hasTextToBeCopied
bool hasTextToBeCopied() const
Definition: chatwidget.cpp:692
ChatWidget::useableWidth
qreal useableWidth() const
Definition: chatwidget.cpp:511
ChatLogMessage
Definition: chatlogitem.h:30
ChatLineContent::selectionTripleClick
virtual void selectionTripleClick(QPointF scenePos)
Definition: chatlinecontent.cpp:54
ChatWidget::chatLineStorage
std::unique_ptr< ChatLineStorage > chatLineStorage
Definition: chatwidget.h:214
text.h
SystemMessageType::peerNameChanged
@ peerNameChanged
Text
Definition: text.h:29
ChatWidget::renderFinished
void renderFinished()
ChatWidget::visibleLines
QList< ChatLine::Ptr > visibleLines
Definition: chatwidget.h:171
ChatLine::sceneBoundingRect
QRectF sceneBoundingRect() const
Definition: chatline.cpp:131
ChatLineContentProxy::getWidget
QWidget * getWidget() const
Definition: chatlinecontentproxy.cpp:74
SystemMessageType::outgoingCall
@ outgoingCall
ChatLineContent::selectionMouseMove
virtual void selectionMouseMove(QPointF scenePos)
Definition: chatlinecontent.cpp:38
ChatLineContent::selectionStarted
virtual void selectionStarted(QPointF scenePos)
Definition: chatlinecontent.cpp:42
GUI::themeReload
void themeReload()
ChatLineStorage::end
iterator end()
Definition: chatlinestorage.h:96
IChatLog::itemUpdated
void itemUpdated(ChatLogIdx idx)
SystemMessageType::userJoinedGroup
@ userJoinedGroup
ChatWidget::reloadTheme
void reloadTheme()
Definition: chatwidget.cpp:789
ChatLineContent
Definition: chatlinecontent.h:26
chatlinecontentproxy.h
Style::SelectText
@ SelectText
Definition: style.h:51
SystemMessage::messageType
SystemMessageType messageType
Definition: systemmessage.h:50
ChatWidget::selectAll
void selectAll()
Definition: chatwidget.cpp:767
ChatWidget::setTypingNotification
void setTypingNotification()
Definition: chatwidget.cpp:1393
SystemMessageType::peerStateChange
@ peerStateChange
ChatLogItem::getDisplayName
const QString & getDisplayName() const
Definition: chatlogitem.cpp:138
Style::getColor
static QColor getColor(ColorPalette entry)
Definition: style.cpp:209
Translator::registerHandler
static void registerHandler(const std::function< void()> &, void *owner)
Register a function to be called when the UI needs to be retranslated.
Definition: translator.cpp:93
ChatWidget::getSelectedText
QString getSelectedText() const
Definition: chatwidget.cpp:659
Settings::getInstance
static Settings & getInstance()
Returns the singleton instance.
Definition: settings.cpp:88
Style::GroundBase
@ GroundBase
Definition: style.h:41
ChatWidget::findLineByPosY
ChatLine::Ptr findLineByPosY(qreal yPos) const
Definition: chatwidget.cpp:999
ChatWidget::getVisibleRect
QRect getVisibleRect() const
Definition: chatwidget.cpp:330
ChatWidget::SelectionMode::Precise
@ Precise
ChatLineContentProxy
Definition: chatlinecontentproxy.h:27
SearchPos::numMatches
size_t numMatches
Definition: ichatlog.h:47
ChatWidget::onSearchDown
void onSearchDown(const QString &phrase, const ParameterSearch &parameter)
Definition: chatwidget.cpp:849
ChatWidget::renderFile
void renderFile(QString displayName, ToxFile file, bool isSelf, QDateTime timestamp, ChatLine::Ptr &chatMessage)
Definition: chatwidget.cpp:1439
ChatWidget::searchPos
SearchPos searchPos
Definition: chatwidget.h:210
ChatWidget::mousePressEvent
void mousePressEvent(QMouseEvent *ev) final
Definition: chatwidget.cpp:363
ChatLogIdx
NamedType< size_t, struct ChatLogIdxTag, Orderable, UnderlyingAddable, UnitlessDifferencable, Incrementable > ChatLogIdx
Definition: ichatlog.h:38
ChatLine::lessThanBSRectTop
static bool lessThanBSRectTop(const ChatLine::Ptr &lhs, const qreal &rhs)
Definition: chatline.cpp:244
ChatLogItem::getContentAsMessage
ChatLogMessage & getContentAsMessage()
Definition: chatlogitem.cpp:87
ChatWidget::mouseDoubleClickEvent
void mouseDoubleClickEvent(QMouseEvent *ev) final
Definition: chatwidget.cpp:628
ChatMessage::markAsDelivered
void markAsDelivered(const QDateTime &time)
Definition: chatmessage.cpp:229
ChatLine::layout
void layout(qreal width, QPointF scenePos)
Definition: chatline.cpp:164
ChatWidget::onWorkerTimeout
void onWorkerTimeout()
Definition: chatwidget.cpp:1061
ChatLineContent::getText
virtual QString getText() const
Definition: chatlinecontent.cpp:90
Style::getStylesheet
static const QString getStylesheet(const QString &filename, const QFont &baseFont=QFont())
Definition: style.cpp:165
MessageState::complete
@ complete
ChatWidget::checkVisibility
void checkVisibility()
Definition: chatwidget.cpp:899
ChatLineContent::selectionDoubleClick
virtual void selectionDoubleClick(QPointF scenePos)
Definition: chatlinecontent.cpp:50
ChatMessage::INFO
@ INFO
Definition: chatmessage.h:38
IChatLog::getNextIdx
virtual ChatLogIdx getNextIdx() const =0
ChatWidget::AutoScrollDirection::NoDirection
@ NoDirection
ChatWidget::selectionMode
SelectionMode selectionMode
Definition: chatwidget.h:188
FileTransferWidget::onFileTransferUpdate
void onFileTransferUpdate(ToxFile file)
Definition: filetransferwidget.cpp:122
Message::timestamp
QDateTime timestamp
Definition: message.h:56
ChatWidget::onSearchUp
void onSearchUp(const QString &phrase, const ParameterSearch &parameter)
Definition: chatwidget.cpp:843
ChatWidget::handleSearchResult
void handleSearchResult(SearchResult result, SearchDirection direction)
Definition: chatwidget.cpp:855
ChatMessage::createBusyNotification
static ChatMessage::Ptr createBusyNotification()
Create message placeholder while chatform restructures text.
Definition: chatmessage.cpp:216
ChatMessage::SystemMessageType
SystemMessageType
Definition: chatmessage.h:36
FileTransferWidget::isActive
bool isActive() const
Definition: filetransferwidget.cpp:127
chatmessage.h
ChatWidget::setRenderedWindowStart
void setRenderedWindowStart(ChatLogIdx start)
Definition: chatwidget.cpp:1135
ChatMessage::createTypingNotification
static ChatMessage::Ptr createTypingNotification()
Definition: chatmessage.cpp:187
ChatLogMessage::message
Message message
Definition: chatlogitem.h:33
ChatMessage
Definition: chatmessage.h:31
ChatLogItem::ContentType::systemMessage
@ systemMessage
ChatLogItem::getContentAsSystemMessage
SystemMessage & getContentAsSystemMessage()
Definition: chatlogitem.cpp:99
gui.h
Message::content
QString content
Definition: message.h:55
ChatWidget::copyAction
QAction * copyAction
Definition: chatwidget.h:167
ChatWidget::updateBusyNotification
void updateBusyNotification()
Definition: chatwidget.cpp:992
ChatWidget::forceRelayout
void forceRelayout()
Definition: chatwidget.cpp:894
ChatWidget::calculateSceneRect
QRectF calculateSceneRect() const
Definition: chatwidget.cpp:1034
ChatWidget::onRenderFinished
void onRenderFinished()
Definition: chatwidget.cpp:1186
ChatWidget::selClickedRow
ChatLine::Ptr selClickedRow
Definition: chatwidget.h:183
ChatWidget::isActiveFileTransfer
bool isActiveFileTransfer(ChatLine::Ptr l)
Definition: chatwidget.cpp:1375
ChatWidget::selClickedCol
int selClickedCol
Definition: chatwidget.h:184
ChatWidget::selectionChanged
void selectionChanged()
translator.h
ChatWidget::AutoScrollDirection::Up
@ Up
SystemMessageType::userLeftGroup
@ userLeftGroup
ToxFile
Definition: toxfile.h:32
ChatWidget::copySelectedText
void copySelectedText(bool toSelectionBuffer=false) const
Definition: chatwidget.cpp:728
ChatLogMessage::state
MessageState state
Definition: chatlogitem.h:32
MessageState::broken
@ broken
ChatWidget::updateMultiSelectionRect
void updateMultiSelectionRect()
Definition: chatwidget.cpp:961
ChatWidget::jumpToDate
void jumpToDate(QDate date)
Definition: chatwidget.cpp:1510
SearchResult::exp
QRegularExpression exp
Definition: ichatlog.h:80
ChatWidget::busyNotification
ChatLine::Ptr busyNotification
Definition: chatwidget.h:173
ChatWidget::mouseReleaseEvent
void mouseReleaseEvent(QMouseEvent *ev) final
Definition: chatwidget.cpp:386
CoreFile
Manages the file transfer service of toxcore.
Definition: corefile.h:46
ChatWidget::selGraphItem
QGraphicsRectItem * selGraphItem
Definition: chatwidget.h:190
Text::setText
void setText(const QString &txt)
Definition: text.cpp:55
ChatWidget::onMessageUpdated
void onMessageUpdated(ChatLogIdx idx)
Definition: chatwidget.cpp:1104
ChatWidget::renderCompletionFns
std::vector< std::function< void(void)> > renderCompletionFns
Definition: chatwidget.h:216
SearchPos::logIdx
ChatLogIdx logIdx
Definition: ichatlog.h:44
ChatLine::Ptr
std::shared_ptr< ChatLine > Ptr
Definition: chatline.h:68
ChatWidget::handleMultiClickEvent
void handleMultiClickEvent()
Definition: chatwidget.cpp:1274
ChatWidget::setTypingNotificationVisible
void setTypingNotificationVisible(bool visible)
Definition: chatwidget.cpp:737
ChatLogItem::ContentType::fileTransfer
@ fileTransfer
ChatWidget::mouseMoveEvent
void mouseMoveEvent(QMouseEvent *ev) final
Definition: chatwidget.cpp:395
ChatWidget::clear
void clear()
Definition: chatwidget.cpp:707
ChatMessage::createChatMessage
static ChatMessage::Ptr createChatMessage(const QString &sender, const QString &rawMessage, MessageType type, bool isMe, MessageState state, const QDateTime &date, bool colorizeName=false)
Definition: chatmessage.cpp:50
ChatWidget::SelectionMode::None
@ None
ChatWidget::onSelectionTimerTimeout
void onSelectionTimerTimeout()
Definition: chatwidget.cpp:1045
SearchResult
Definition: ichatlog.h:70
SearchResult::found
bool found
Definition: ichatlog.h:72
Core
Definition: core.h:59
ChatWidget::wheelEvent
void wheelEvent(QWheelEvent *event) final
Definition: chatwidget.cpp:1363
ChatWidget::AutoScrollDirection::Down
@ Down
ChatWidget::isEmpty
bool isEmpty() const
Definition: chatwidget.cpp:687
ChatWidget::busyScene
QGraphicsScene * busyScene
Definition: chatwidget.h:170
ChatMessage::createChatInfoMessage
static ChatMessage::Ptr createChatInfoMessage(const QString &rawMessage, SystemMessageType type, const QDateTime &date)
Definition: chatmessage.cpp:135
SystemMessageType::incomingCall
@ incomingCall