#pragma once #include #include #include #include #include #include #include #include #include #include "common/FlagsEnum.hpp" #include "controllers/filters/FilterSet.hpp" #include "messages/Image.hpp" #include "messages/LimitedQueue.hpp" #include "messages/LimitedQueueSnapshot.hpp" #include "messages/Selection.hpp" #include "widgets/BaseWidget.hpp" namespace chatterino { enum class HighlightState; class Channel; using ChannelPtr = std::shared_ptr; struct Message; using MessagePtr = std::shared_ptr; enum class MessageFlag : uint32_t; using MessageFlags = FlagsEnum; class MessageLayout; using MessageLayoutPtr = std::shared_ptr; enum class MessageElementFlag : int64_t; using MessageElementFlags = FlagsEnum; class Scrollbar; class EffectLabel; struct Link; class MessageLayoutElement; class Split; enum class PauseReason { Mouse, Selection, DoubleClick, KeyboardModifier, }; enum class FromTwitchLinkOpenChannelIn { Split, Tab, BrowserPlayer, Streamlink, }; using SteadyClock = std::chrono::steady_clock; class ChannelView final : public BaseWidget { Q_OBJECT public: enum class Context { None, UserCard, ReplyThread, Search, }; explicit ChannelView(BaseWidget *parent = nullptr, Split *split = nullptr, Context context = Context::None); void queueUpdate(); Scrollbar &getScrollBar(); QString getSelectedText(); bool hasSelection(); void clearSelection(); void setEnableScrollingToBottom(bool); bool getEnableScrollingToBottom() const; void setOverrideFlags(boost::optional value); const boost::optional &getOverrideFlags() const; void updateLastReadMessage(); /** * Attempts to scroll to a message in this channel. * @return true if the message was found and highlighted. */ bool scrollToMessage(const MessagePtr &message); /** * Attempts to scroll to a message id in this channel. * @return true if the message was found and highlighted. */ bool scrollToMessageId(const QString &id); /// Pausing bool pausable() const; void setPausable(bool value); bool paused() const; void pause(PauseReason reason, boost::optional msecs = boost::none); void unpause(PauseReason reason); MessageElementFlags getFlags() const; ChannelPtr channel(); void setChannel(ChannelPtr channel_); void setFilters(const QList &ids); const QList getFilterIds() const; FilterSetPtr getFilterSet() const; ChannelPtr sourceChannel() const; void setSourceChannel(ChannelPtr sourceChannel); bool hasSourceChannel() const; LimitedQueueSnapshot &getMessagesSnapshot(); void queueLayout(); void clearMessages(); Context getContext() const; /** * @brief Creates and shows a UserInfoPopup dialog * * @param userName The login name of the user * @param alternativePopoutChannel Optional parameter containing the channel name to use for context **/ void showUserInfoPopup(const QString &userName, QString alternativePopoutChannel = QString()); /** * @brief This method is meant to be used when filtering out channels. * It must return true if a message belongs in this channel. * It might return true if a message doesn't belong in this channel. */ bool mayContainMessage(const MessagePtr &message); pajlada::Signals::Signal mouseDown; pajlada::Signals::NoArgSignal selectionChanged; pajlada::Signals::Signal tabHighlightRequested; pajlada::Signals::NoArgSignal liveStatusChanged; pajlada::Signals::Signal linkClicked; pajlada::Signals::Signal openChannelIn; protected: void themeChangedEvent() override; void scaleChangedEvent(float scale) override; void resizeEvent(QResizeEvent *) override; void paintEvent(QPaintEvent *) override; void wheelEvent(QWheelEvent *event) override; void enterEvent(QEvent *) override; void leaveEvent(QEvent *) override; void mouseMoveEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void mouseDoubleClickEvent(QMouseEvent *event) override; void hideEvent(QHideEvent *) override; void handleLinkClick(QMouseEvent *event, const Link &link, MessageLayout *layout); bool tryGetMessageAt(QPoint p, std::shared_ptr &message, QPoint &relativePos, int &index); private: void initializeLayout(); void initializeScrollbar(); void initializeSignals(); void messageAppended(MessagePtr &message, boost::optional overridingFlags); void messageAddedAtStart(std::vector &messages); void messageRemoveFromStart(MessagePtr &message); void messageReplaced(size_t index, MessagePtr &replacement); void messagesUpdated(); void performLayout(bool causedByScollbar = false); void layoutVisibleMessages( LimitedQueueSnapshot &messages); void updateScrollbar(LimitedQueueSnapshot &messages, bool causedByScrollbar); void drawMessages(QPainter &painter); void setSelection(const SelectionItem &start, const SelectionItem &end); void selectWholeMessage(MessageLayout *layout, int &messageIndex); void getWordBounds(MessageLayout *layout, const MessageLayoutElement *element, const QPoint &relativePos, int &wordStart, int &wordEnd); void handleMouseClick(QMouseEvent *event, const MessageLayoutElement *hoveredElement, MessageLayoutPtr layout); void addContextMenuItems(const MessageLayoutElement *hoveredElement, MessageLayoutPtr layout, QMouseEvent *event); void addImageContextMenuItems(const MessageLayoutElement *hoveredElement, MessageLayoutPtr layout, QMouseEvent *event, QMenu &menu); void addLinkContextMenuItems(const MessageLayoutElement *hoveredElement, MessageLayoutPtr layout, QMouseEvent *event, QMenu &menu); void addMessageContextMenuItems(const MessageLayoutElement *hoveredElement, MessageLayoutPtr layout, QMouseEvent *event, QMenu &menu); void addTwitchLinkContextMenuItems( const MessageLayoutElement *hoveredElement, MessageLayoutPtr layout, QMouseEvent *event, QMenu &menu); void addHiddenContextMenuItems(const MessageLayoutElement *hoveredElement, MessageLayoutPtr layout, QMouseEvent *event, QMenu &menu); void addCommandExecutionContextMenuItems( const MessageLayoutElement *hoveredElement, MessageLayoutPtr layout, QMouseEvent *event, QMenu &menu); int getLayoutWidth() const; void updatePauses(); void unpaused(); void enableScrolling(const QPointF &scrollStart); void disableScrolling(); /** * Scrolls to a message layout that must be from this view. * * @param layout Must be from this channel. * @param messageIdx Must be an index into this channel. */ void scrollToMessageLayout(MessageLayout *layout, size_t messageIdx); void setInputReply(const MessagePtr &message); void showReplyThreadPopup(const MessagePtr &message); bool canReplyToMessages() const; QTimer *layoutCooldown_; bool layoutQueued_; QTimer updateTimer_; bool updateQueued_ = false; bool messageWasAdded_ = false; bool lastMessageHasAlternateBackground_ = false; bool lastMessageHasAlternateBackgroundReverse_ = true; bool pausable_ = false; QTimer pauseTimer_; std::unordered_map> pauses_; boost::optional pauseEnd_; int pauseScrollOffset_ = 0; int pauseSelectionOffset_ = 0; boost::optional overrideFlags_; MessageLayoutPtr lastReadMessage_; LimitedQueueSnapshot snapshot_; ChannelPtr channel_ = nullptr; ChannelPtr underlyingChannel_ = nullptr; ChannelPtr sourceChannel_ = nullptr; Split *split_ = nullptr; Scrollbar *scrollBar_; EffectLabel *goToBottom_; bool showScrollBar_ = false; FilterSetPtr channelFilters_; // Returns true if message should be included bool shouldIncludeMessage(const MessagePtr &m) const; // Returns whether the scrollbar should have highlights bool showScrollbarHighlights() const; // This variable can be used to decide whether or not we should render the // "Show latest messages" button bool showingLatestMessages_ = true; bool enableScrollingToBottom_ = true; bool onlyUpdateEmotes_ = false; // Mouse event variables bool isLeftMouseDown_ = false; bool isRightMouseDown_ = false; bool isDoubleClick_ = false; DoubleClickSelection doubleClickSelection_; QPointF lastLeftPressPosition_; QPointF lastRightPressPosition_; QPointF lastDClickPosition_; QTimer *clickTimer_; bool isScrolling_ = false; QPointF lastMiddlePressPosition_; QPointF currentMousePosition_; QTimer scrollTimer_; // We're only interested in the pointer, not the contents MessageLayout *highlightedMessage_; QVariantAnimation highlightAnimation_; void setupHighlightAnimationColors(); struct { QCursor neutral; QCursor up; QCursor down; } cursors_; Selection selection_; bool selecting_ = false; const Context context_; LimitedQueue messages_; pajlada::Signals::SignalHolder signalHolder_; // channelConnections_ will be cleared when the underlying channel of the channelview changes pajlada::Signals::SignalHolder channelConnections_; std::unordered_set> messagesOnScreen_; static constexpr int leftPadding = 8; static constexpr int scrollbarPadding = 8; private slots: void wordFlagsChanged() { queueLayout(); update(); } void scrollUpdateRequested(); }; } // namespace chatterino