Chatterino
ChannelView.hpp
Go to the documentation of this file.
1 #pragma once
2 
3 #include <QPaintEvent>
4 #include <QScroller>
5 #include <QTimer>
6 #include <QVariantAnimation>
7 #include <QWheelEvent>
8 #include <QWidget>
9 #include <pajlada/signals/signal.hpp>
10 #include <unordered_map>
11 #include <unordered_set>
12 
13 #include "common/FlagsEnum.hpp"
15 #include "messages/Image.hpp"
18 #include "messages/Selection.hpp"
19 #include "widgets/BaseWidget.hpp"
20 
21 namespace chatterino {
22 enum class HighlightState;
23 
24 class Channel;
25 using ChannelPtr = std::shared_ptr<Channel>;
26 
27 struct Message;
28 using MessagePtr = std::shared_ptr<const Message>;
29 
30 enum class MessageFlag : int64_t;
31 using MessageFlags = FlagsEnum<MessageFlag>;
32 
33 class MessageLayout;
34 using MessageLayoutPtr = std::shared_ptr<MessageLayout>;
35 
36 enum class MessageElementFlag : int64_t;
37 using MessageElementFlags = FlagsEnum<MessageElementFlag>;
38 
39 class Scrollbar;
40 class EffectLabel;
41 struct Link;
42 class MessageLayoutElement;
43 class Split;
44 
45 enum class PauseReason {
46  Mouse,
47  Selection,
50 };
51 
53  Split,
54  Tab,
56  Streamlink,
57 };
58 
59 using SteadyClock = std::chrono::steady_clock;
60 
61 class ChannelView final : public BaseWidget
62 {
63  Q_OBJECT
64 
65 public:
66  enum class Context {
67  None,
68  UserCard,
69  ReplyThread,
70  Search,
71  };
72 
73  explicit ChannelView(BaseWidget *parent = nullptr, Split *split = nullptr,
74  Context context = Context::None,
75  size_t messagesLimit = 1000);
76 
77  void queueUpdate();
78  Scrollbar &getScrollBar();
79  QString getSelectedText();
80  bool hasSelection();
81  void clearSelection();
82  void setEnableScrollingToBottom(bool);
83  bool getEnableScrollingToBottom() const;
84  void setOverrideFlags(boost::optional<MessageElementFlags> value);
85  const boost::optional<MessageElementFlags> &getOverrideFlags() const;
86  void updateLastReadMessage();
87 
92  bool scrollToMessage(const MessagePtr &message);
97  bool scrollToMessageId(const QString &id);
98 
100  bool pausable() const;
101  void setPausable(bool value);
102  bool paused() const;
103  void pause(PauseReason reason, boost::optional<uint> msecs = boost::none);
104  void unpause(PauseReason reason);
105 
106  MessageElementFlags getFlags() const;
107 
108  ChannelPtr channel();
109  void setChannel(ChannelPtr channel_);
110 
111  void setFilters(const QList<QUuid> &ids);
112  const QList<QUuid> getFilterIds() const;
113  FilterSetPtr getFilterSet() const;
114 
115  ChannelPtr sourceChannel() const;
116  void setSourceChannel(ChannelPtr sourceChannel);
117  bool hasSourceChannel() const;
118 
119  LimitedQueueSnapshot<MessageLayoutPtr> &getMessagesSnapshot();
120  void queueLayout();
121 
122  void clearMessages();
123 
124  Context getContext() const;
125 
132  void showUserInfoPopup(const QString &userName,
133  QString alternativePopoutChannel = QString());
134 
140  bool mayContainMessage(const MessagePtr &message);
141 
142  pajlada::Signals::Signal<QMouseEvent *> mouseDown;
143  pajlada::Signals::NoArgSignal selectionChanged;
144  pajlada::Signals::Signal<HighlightState> tabHighlightRequested;
145  pajlada::Signals::NoArgSignal liveStatusChanged;
146  pajlada::Signals::Signal<const Link &> linkClicked;
147  pajlada::Signals::Signal<QString, FromTwitchLinkOpenChannelIn>
149 
150 protected:
151  void themeChangedEvent() override;
152  void scaleChangedEvent(float scale) override;
153 
154  void resizeEvent(QResizeEvent *) override;
155 
156  void paintEvent(QPaintEvent *) override;
157  void wheelEvent(QWheelEvent *event) override;
158 
159  void enterEvent(QEvent *) override;
160  void leaveEvent(QEvent *) override;
161 
162  void mouseMoveEvent(QMouseEvent *event) override;
163  void mousePressEvent(QMouseEvent *event) override;
164  void mouseReleaseEvent(QMouseEvent *event) override;
165  void mouseDoubleClickEvent(QMouseEvent *event) override;
166 
167  void hideEvent(QHideEvent *) override;
168 
169  void handleLinkClick(QMouseEvent *event, const Link &link,
170  MessageLayout *layout);
171 
172  bool tryGetMessageAt(QPoint p, std::shared_ptr<MessageLayout> &message,
173  QPoint &relativePos, int &index);
174 
175 private:
176  void initializeLayout();
177  void initializeScrollbar();
178  void initializeSignals();
179 
180  void messageAppended(MessagePtr &message,
181  boost::optional<MessageFlags> overridingFlags);
182  void messageAddedAtStart(std::vector<MessagePtr> &messages);
183  void messageRemoveFromStart(MessagePtr &message);
184  void messageReplaced(size_t index, MessagePtr &replacement);
185  void messagesUpdated();
186 
187  void performLayout(bool causedByScrollbar = false);
188  void layoutVisibleMessages(
190  void updateScrollbar(LimitedQueueSnapshot<MessageLayoutPtr> &messages,
191  bool causedByScrollbar);
192 
193  void drawMessages(QPainter &painter);
194  void setSelection(const SelectionItem &start, const SelectionItem &end);
195  void selectWholeMessage(MessageLayout *layout, int &messageIndex);
196  void getWordBounds(MessageLayout *layout,
197  const MessageLayoutElement *element,
198  const QPoint &relativePos, int &wordStart, int &wordEnd);
199 
200  void handleMouseClick(QMouseEvent *event,
201  const MessageLayoutElement *hoveredElement,
202  MessageLayoutPtr layout);
203  void addContextMenuItems(const MessageLayoutElement *hoveredElement,
204  MessageLayoutPtr layout, QMouseEvent *event);
205  void addImageContextMenuItems(const MessageLayoutElement *hoveredElement,
206  MessageLayoutPtr layout, QMouseEvent *event,
207  QMenu &menu);
208  void addLinkContextMenuItems(const MessageLayoutElement *hoveredElement,
209  MessageLayoutPtr layout, QMouseEvent *event,
210  QMenu &menu);
211  void addMessageContextMenuItems(const MessageLayoutElement *hoveredElement,
212  MessageLayoutPtr layout, QMouseEvent *event,
213  QMenu &menu);
214  void addTwitchLinkContextMenuItems(
215  const MessageLayoutElement *hoveredElement, MessageLayoutPtr layout,
216  QMouseEvent *event, QMenu &menu);
217  void addHiddenContextMenuItems(const MessageLayoutElement *hoveredElement,
218  MessageLayoutPtr layout, QMouseEvent *event,
219  QMenu &menu);
220  void addCommandExecutionContextMenuItems(
221  const MessageLayoutElement *hoveredElement, MessageLayoutPtr layout,
222  QMouseEvent *event, QMenu &menu);
223 
224  int getLayoutWidth() const;
225  void updatePauses();
226  void unpaused();
227 
228  void enableScrolling(const QPointF &scrollStart);
229  void disableScrolling();
230 
237  void scrollToMessageLayout(MessageLayout *layout, size_t messageIdx);
238 
239  void setInputReply(const MessagePtr &message);
240  void showReplyThreadPopup(const MessagePtr &message);
241  bool canReplyToMessages() const;
242 
243  QTimer *layoutCooldown_;
244  bool layoutQueued_;
245 
246  QTimer updateTimer_;
247  bool updateQueued_ = false;
248  bool messageWasAdded_ = false;
249  bool lastMessageHasAlternateBackground_ = false;
250  bool lastMessageHasAlternateBackgroundReverse_ = true;
251 
252  bool pausable_ = false;
253  QTimer pauseTimer_;
254  std::unordered_map<PauseReason, boost::optional<SteadyClock::time_point>>
255  pauses_;
256  boost::optional<SteadyClock::time_point> pauseEnd_;
257  int pauseScrollOffset_ = 0;
258  // Keeps track how many message indices we need to offset the selection when we resume scrolling
259  uint32_t pauseSelectionOffset_ = 0;
260 
261  boost::optional<MessageElementFlags> overrideFlags_;
262  MessageLayoutPtr lastReadMessage_;
263 
265 
266  ChannelPtr channel_ = nullptr;
267  ChannelPtr underlyingChannel_ = nullptr;
268  ChannelPtr sourceChannel_ = nullptr;
269  Split *split_ = nullptr;
270 
271  Scrollbar *scrollBar_;
272  EffectLabel *goToBottom_;
273  bool showScrollBar_ = false;
274 
275  FilterSetPtr channelFilters_;
276 
277  // Returns true if message should be included
278  bool shouldIncludeMessage(const MessagePtr &m) const;
279 
280  // Returns whether the scrollbar should have highlights
281  bool showScrollbarHighlights() const;
282 
283  // This variable can be used to decide whether or not we should render the
284  // "Show latest messages" button
285  bool showingLatestMessages_ = true;
286  bool enableScrollingToBottom_ = true;
287 
288  bool onlyUpdateEmotes_ = false;
289 
290  // Mouse event variables
291  bool isLeftMouseDown_ = false;
292  bool isRightMouseDown_ = false;
293  bool isDoubleClick_ = false;
294  DoubleClickSelection doubleClickSelection_;
295  QPointF lastLeftPressPosition_;
296  QPointF lastRightPressPosition_;
297  QPointF lastDClickPosition_;
298  QTimer *clickTimer_;
299 
300  bool isScrolling_ = false;
301  QPointF lastMiddlePressPosition_;
302  QPointF currentMousePosition_;
303  QTimer scrollTimer_;
304 
305  // We're only interested in the pointer, not the contents
306  MessageLayout *highlightedMessage_ = nullptr;
307  QVariantAnimation highlightAnimation_;
308  void setupHighlightAnimationColors();
309 
310  struct {
311  QCursor neutral;
312  QCursor up;
313  QCursor down;
314  } cursors_;
315 
316  Selection selection_;
317  bool selecting_ = false;
318 
319  const Context context_;
320 
322 
323  pajlada::Signals::SignalHolder signalHolder_;
324 
325  // channelConnections_ will be cleared when the underlying channel of the channelview changes
326  pajlada::Signals::SignalHolder channelConnections_;
327 
328  std::unordered_set<std::shared_ptr<MessageLayout>> messagesOnScreen_;
329 
330  static constexpr int leftPadding = 8;
331  static constexpr int scrollbarPadding = 8;
332 
333 private slots:
334  void wordFlagsChanged()
335  {
336  queueLayout();
337  update();
338  }
339 
340  void scrollUpdateRequested();
341 };
342 
343 } // namespace chatterino
Definition: Selection.hpp:9
int64_t FlagsEnum< MessageElementFlag > MessageElementFlags
Definition: MessageLayout.hpp:21
Definition: MessageLayoutElement.hpp:26
pajlada::Signals::NoArgSignal liveStatusChanged
Definition: ChannelView.hpp:145
Definition: MessageLayout.hpp:33
Definition: EffectLabel.hpp:15
Definition: Selection.hpp:44
Definition: Application.cpp:48
Definition: Split.hpp:36
MessageElementFlag
Definition: MessageElement.hpp:29
HighlightState
Definition: Common.hpp:16
std::chrono::steady_clock SteadyClock
Definition: ChannelView.hpp:59
pajlada::Signals::Signal< const Link & > linkClicked
Definition: ChannelView.hpp:146
Definition: Selection.hpp:116
MessageFlag
Definition: Message.hpp:18
pajlada::Signals::NoArgSignal selectionChanged
Definition: ChannelView.hpp:143
int64_t FlagsEnum< MessageFlag > MessageFlags
Definition: Channel.hpp:20
PauseReason
Definition: ChannelView.hpp:45
QCursor down
Definition: ChannelView.hpp:313
pajlada::Signals::Signal< QMouseEvent * > mouseDown
Definition: ChannelView.hpp:142
Definition: ChannelView.hpp:61
FromTwitchLinkOpenChannelIn
Definition: ChannelView.hpp:52
std::shared_ptr< const Message > MessagePtr
Definition: Channel.hpp:18
QCursor neutral
Definition: ChannelView.hpp:311
pajlada::Signals::Signal< HighlightState > tabHighlightRequested
Definition: ChannelView.hpp:144
std::shared_ptr< Channel > ChannelPtr
Definition: Channel.hpp:125
Definition: BaseWidget.hpp:14
std::shared_ptr< MessageLayout > MessageLayoutPtr
Definition: MessageLayout.hpp:94
Definition: Scrollbar.hpp:16
std::shared_ptr< FilterSet > FilterSetPtr
Definition: FilterSet.hpp:86
Context
Definition: ChannelView.hpp:66
pajlada::Signals::Signal< QString, FromTwitchLinkOpenChannelIn > openChannelIn
Definition: ChannelView.hpp:148
QCursor up
Definition: ChannelView.hpp:312