1 #pragma once
2 
3 #include <QPaintEvent>
4 #include <QScroller>
5 #include <QTimer>
6 #include <QWheelEvent>
7 #include <QWidget>
8 #include <pajlada/signals/signal.hpp>
9 #include <unordered_map>
10 #include <unordered_set>
11 
12 #include "common/FlagsEnum.hpp"
13 #include "controllers/filters/FilterSet.hpp"
14 #include "messages/Image.hpp"
15 #include "messages/LimitedQueue.hpp"
16 #include "messages/LimitedQueueSnapshot.hpp"
17 #include "messages/Selection.hpp"
18 #include "widgets/BaseWidget.hpp"
19 
20 namespace chatterino {
21 enum class HighlightState;
22 
23 class Channel;
24 using ChannelPtr = std::shared_ptr<Channel>;
25 
26 struct Message;
27 using MessagePtr = std::shared_ptr<const Message>;
28 
29 enum class MessageFlag : uint32_t;
30 using MessageFlags = FlagsEnum<MessageFlag>;
31 
32 class MessageLayout;
33 using MessageLayoutPtr = std::shared_ptr<MessageLayout>;
34 
35 enum class MessageElementFlag : int64_t;
36 using MessageElementFlags = FlagsEnum<MessageElementFlag>;
37 
38 class Scrollbar;
39 class EffectLabel;
40 struct Link;
41 class MessageLayoutElement;
42 
43 enum class PauseReason {
44     Mouse,
45     Selection,
46     DoubleClick,
47     KeyboardModifier,
48 };
49 
50 enum class FromTwitchLinkOpenChannelIn {
51     Split,
52     Tab,
53     BrowserPlayer,
54     Streamlink,
55 };
56 
57 using SteadyClock = std::chrono::steady_clock;
58 
59 class ChannelView final : public BaseWidget
60 {
61     Q_OBJECT
62 
63 public:
64     explicit ChannelView(BaseWidget *parent = nullptr);
65 
66     void queueUpdate();
67     Scrollbar &getScrollBar();
68     QString getSelectedText();
69     bool hasSelection();
70     void clearSelection();
71     void setEnableScrollingToBottom(bool);
72     bool getEnableScrollingToBottom() const;
73     void setOverrideFlags(boost::optional<MessageElementFlags> value);
74     const boost::optional<MessageElementFlags> &getOverrideFlags() const;
75     void updateLastReadMessage();
76 
77     /// Pausing
78     bool pausable() const;
79     void setPausable(bool value);
80     bool paused() const;
81     void pause(PauseReason reason, boost::optional<uint> msecs = boost::none);
82     void unpause(PauseReason reason);
83 
84     ChannelPtr channel();
85     void setChannel(ChannelPtr channel_);
86 
87     void setFilters(const QList<QUuid> &ids);
88     const QList<QUuid> getFilterIds() const;
89     FilterSetPtr getFilterSet() const;
90 
91     ChannelPtr sourceChannel() const;
92     void setSourceChannel(ChannelPtr sourceChannel);
93     bool hasSourceChannel() const;
94 
95     LimitedQueueSnapshot<MessageLayoutPtr> getMessagesSnapshot();
96     void queueLayout();
97 
98     void clearMessages();
99     void showUserInfoPopup(const QString &userName);
100 
101     pajlada::Signals::Signal<QMouseEvent *> mouseDown;
102     pajlada::Signals::NoArgSignal selectionChanged;
103     pajlada::Signals::Signal<HighlightState> tabHighlightRequested;
104     pajlada::Signals::NoArgSignal liveStatusChanged;
105     pajlada::Signals::Signal<const Link &> linkClicked;
106     pajlada::Signals::Signal<QString, FromTwitchLinkOpenChannelIn>
107         openChannelIn;
108 
109 protected:
110     void themeChangedEvent() override;
111     void scaleChangedEvent(float scale) override;
112 
113     void resizeEvent(QResizeEvent *) override;
114 
115     void paintEvent(QPaintEvent *) override;
116     void wheelEvent(QWheelEvent *event) override;
117 
118     void enterEvent(QEvent *) override;
119     void leaveEvent(QEvent *) override;
120 
121     void mouseMoveEvent(QMouseEvent *event) override;
122     void mousePressEvent(QMouseEvent *event) override;
123     void mouseReleaseEvent(QMouseEvent *event) override;
124     void mouseDoubleClickEvent(QMouseEvent *event) override;
125 
126     void hideEvent(QHideEvent *) override;
127 
128     void handleLinkClick(QMouseEvent *event, const Link &link,
129                          MessageLayout *layout);
130 
131     bool tryGetMessageAt(QPoint p, std::shared_ptr<MessageLayout> &message,
132                          QPoint &relativePos, int &index);
133 
134 private:
135     void initializeLayout();
136     void initializeScrollbar();
137     void initializeSignals();
138 
139     void messageAppended(MessagePtr &message,
140                          boost::optional<MessageFlags> overridingFlags);
141     void messageAddedAtStart(std::vector<MessagePtr> &messages);
142     void messageRemoveFromStart(MessagePtr &message);
143     void messageReplaced(size_t index, MessagePtr &replacement);
144 
145     void performLayout(bool causedByScollbar = false);
146     void layoutVisibleMessages(
147         LimitedQueueSnapshot<MessageLayoutPtr> &messages);
148     void updateScrollbar(LimitedQueueSnapshot<MessageLayoutPtr> &messages,
149                          bool causedByScrollbar);
150 
151     void drawMessages(QPainter &painter);
152     void setSelection(const SelectionItem &start, const SelectionItem &end);
153     MessageElementFlags getFlags() const;
154     void selectWholeMessage(MessageLayout *layout, int &messageIndex);
155     void getWordBounds(MessageLayout *layout,
156                        const MessageLayoutElement *element,
157                        const QPoint &relativePos, int &wordStart, int &wordEnd);
158 
159     void handleMouseClick(QMouseEvent *event,
160                           const MessageLayoutElement *hoverLayoutElement,
161                           MessageLayoutPtr layout);
162     void addContextMenuItems(const MessageLayoutElement *hoveredElement,
163                              MessageLayoutPtr layout);
164     int getLayoutWidth() const;
165     void updatePauses();
166     void unpaused();
167 
168     void enableScrolling(const QPointF &scrollStart);
169     void disableScrolling();
170 
171     QTimer *layoutCooldown_;
172     bool layoutQueued_;
173 
174     QTimer updateTimer_;
175     bool updateQueued_ = false;
176     bool messageWasAdded_ = false;
177     bool lastMessageHasAlternateBackground_ = false;
178     bool lastMessageHasAlternateBackgroundReverse_ = true;
179 
180     bool pausable_ = false;
181     QTimer pauseTimer_;
182     std::unordered_map<PauseReason, boost::optional<SteadyClock::time_point>>
183         pauses_;
184     boost::optional<SteadyClock::time_point> pauseEnd_;
185     int pauseScrollOffset_ = 0;
186     int pauseSelectionOffset_ = 0;
187 
188     boost::optional<MessageElementFlags> overrideFlags_;
189     MessageLayoutPtr lastReadMessage_;
190 
191     LimitedQueueSnapshot<MessageLayoutPtr> snapshot_;
192 
193     ChannelPtr channel_ = nullptr;
194     ChannelPtr underlyingChannel_ = nullptr;
195     ChannelPtr sourceChannel_ = nullptr;
196 
197     Scrollbar *scrollBar_;
198     EffectLabel *goToBottom_;
199 
200     FilterSetPtr channelFilters_;
201 
202     // Returns true if message should be included
203     bool shouldIncludeMessage(const MessagePtr &m) const;
204 
205     // Returns whether the scrollbar should have highlights
206     bool showScrollbarHighlights() const;
207 
208     // This variable can be used to decide whether or not we should render the
209     // "Show latest messages" button
210     bool showingLatestMessages_ = true;
211     bool enableScrollingToBottom_ = true;
212 
213     bool onlyUpdateEmotes_ = false;
214 
215     // Mouse event variables
216     bool isLeftMouseDown_ = false;
217     bool isRightMouseDown_ = false;
218     bool isDoubleClick_ = false;
219     DoubleClickSelection doubleClickSelection_;
220     QPointF lastLeftPressPosition_;
221     QPointF lastRightPressPosition_;
222     QPointF lastDClickPosition_;
223     QTimer *clickTimer_;
224 
225     bool isScrolling_ = false;
226     QPointF lastMiddlePressPosition_;
227     QPointF currentMousePosition_;
228     QTimer scrollTimer_;
229 
230     struct {
231         QCursor neutral;
232         QCursor up;
233         QCursor down;
234     } cursors_;
235 
236     Selection selection_;
237     bool selecting_ = false;
238 
239     LimitedQueue<MessageLayoutPtr> messages_;
240 
241     std::vector<pajlada::Signals::ScopedConnection> connections_;
242     std::vector<pajlada::Signals::ScopedConnection> channelConnections_;
243 
244     std::unordered_set<std::shared_ptr<MessageLayout>> messagesOnScreen_;
245 
246     static constexpr int leftPadding = 8;
247     static constexpr int scrollbarPadding = 8;
248 
249 private slots:
wordFlagsChanged()250     void wordFlagsChanged()
251     {
252         queueLayout();
253         update();
254     }
255 
256     void scrollUpdateRequested();
257 };
258 
259 }  // namespace chatterino
260