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