diff --git a/lib/appbase/common/ChatterinoSetting.hpp b/lib/appbase/common/ChatterinoSetting.hpp index cebcb33f2..d77648414 100644 --- a/lib/appbase/common/ChatterinoSetting.hpp +++ b/lib/appbase/common/ChatterinoSetting.hpp @@ -51,4 +51,38 @@ using IntSetting = ChatterinoSetting; using StringSetting = ChatterinoSetting; using QStringSetting = ChatterinoSetting; +template +class EnumSetting + : public ChatterinoSetting::type> +{ + using Underlying = typename std::underlying_type::type; + +public: + using ChatterinoSetting::ChatterinoSetting; + + EnumSetting(const std::string &path, const Enum &defaultValue) + : ChatterinoSetting(path, Underlying(defaultValue)) + { + _registerSetting(this->getData()); + } + + template + EnumSetting &operator=(Enum newValue) + { + this->setValue(Underlying(newValue)); + + return *this; + } + + operator Enum() + { + return Enum(this->getValue()); + } + + Enum getEnum() + { + return Enum(this->getValue()); + } +}; + } // namespace AB_NAMESPACE diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index 83ec61117..a516cdd2a 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -102,7 +102,9 @@ public: BoolSetting prefixOnlyEmoteCompletion = { "/behaviour/autocompletion/prefixOnlyCompletion", true}; - BoolSetting pauseChatOnHover = {"/behaviour/pauseChatHover", false}; + FloatSetting pauseOnHoverDuration = {"/behaviour/pauseOnHoverDuration", 0}; + EnumSetting pauseChatModifier = { + "/behaviour/pauseChatModifier", Qt::KeyboardModifier::NoModifier}; BoolSetting autorun = {"/behaviour/autorun", false}; BoolSetting mentionUsersWithComma = {"/behaviour/mentionUsersWithComma", true}; diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 7668a1983..d9dc36a64 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -241,6 +241,14 @@ void ChannelView::unpause(PauseReason reason) this->pauses_.erase(reason); this->updatePauseTimer(); + + /// Move selection + this->selection_.selectionMin.messageIndex -= this->pauseSelectionOffset_; + this->selection_.selectionMax.messageIndex -= this->pauseSelectionOffset_; + this->selection_.start.messageIndex -= this->pauseSelectionOffset_; + this->selection_.end.messageIndex -= this->pauseSelectionOffset_; + + this->pauseSelectionOffset_ = 0; } void ChannelView::updatePauseTimer() @@ -527,64 +535,64 @@ ChannelPtr ChannelView::channel() return this->channel_; } -void ChannelView::setChannel(ChannelPtr newChannel) +void ChannelView::setChannel(ChannelPtr channel) { /// Clear connections from the last channel this->channelConnections_.clear(); this->clearMessages(); + this->scrollBar_->clearHighlights(); // on new message - this->channelConnections_.push_back(newChannel->messageAppended.connect( + this->channelConnections_.push_back(channel->messageAppended.connect( [this](MessagePtr &message, boost::optional overridingFlags) { this->messageAppended(message, overridingFlags); })); - this->channelConnections_.push_back( - newChannel->messagesAddedAtStart.connect( - [this](std::vector &messages) { - this->messageAddedAtStart(messages); - })); + this->channelConnections_.push_back(channel->messagesAddedAtStart.connect( + [this](std::vector &messages) { + this->messageAddedAtStart(messages); + })); // on message removed this->channelConnections_.push_back( - newChannel->messageRemovedFromStart.connect( - [this](MessagePtr &message) { - this->messageRemoveFromStart(message); - })); + channel->messageRemovedFromStart.connect([this](MessagePtr &message) { + this->messageRemoveFromStart(message); + })); // on message replaced - this->channelConnections_.push_back(newChannel->messageReplaced.connect( + this->channelConnections_.push_back(channel->messageReplaced.connect( [this](size_t index, MessagePtr replacement) { this->messageReplaced(index, replacement); })); - auto snapshot = newChannel->getMessageSnapshot(); + auto snapshot = channel->getMessageSnapshot(); for (size_t i = 0; i < snapshot.size(); i++) { MessageLayoutPtr deleted; - auto messageRef = new MessageLayout(snapshot[i]); + auto messageLayout = new MessageLayout(snapshot[i]); if (this->lastMessageHasAlternateBackground_) { - messageRef->flags.set(MessageLayoutFlag::AlternateBackground); + messageLayout->flags.set(MessageLayoutFlag::AlternateBackground); } this->lastMessageHasAlternateBackground_ = !this->lastMessageHasAlternateBackground_; - this->messages_.pushBack(MessageLayoutPtr(messageRef), deleted); + this->messages_.pushBack(MessageLayoutPtr(messageLayout), deleted); + this->scrollBar_->addHighlight(snapshot[i]->getScrollBarHighlight()); } - this->channel_ = newChannel; + this->channel_ = channel; this->queueLayout(); this->queueUpdate(); // Notifications - if (auto tc = dynamic_cast(newChannel.get())) + if (auto tc = dynamic_cast(channel.get())) { this->connections_.push_back(tc->liveStatusChanged.connect([this]() { this->liveStatusChanged.invoke(); // @@ -697,10 +705,17 @@ void ChannelView::messageAddedAtStart(std::vector &messages) void ChannelView::messageRemoveFromStart(MessagePtr &message) { - this->selection_.selectionMin.messageIndex--; - this->selection_.selectionMax.messageIndex--; - this->selection_.start.messageIndex--; - this->selection_.end.messageIndex--; + if (this->paused()) + { + this->pauseSelectionOffset_ += 1; + } + else + { + this->selection_.selectionMin.messageIndex--; + this->selection_.selectionMax.messageIndex--; + this->selection_.start.messageIndex--; + this->selection_.end.messageIndex--; + } this->queueLayout(); } @@ -1023,9 +1038,14 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) } /// Pause on hover - if (getSettings()->pauseChatOnHover.getValue()) + if (float pauseTime = getSettings()->pauseOnHoverDuration; + pauseTime > 0.001f) { - this->pause(PauseReason::Mouse, 500); + this->pause(PauseReason::Mouse, uint(pauseTime * 1000.f)); + } + else if (pauseTime < -0.5f) + { + this->pause(PauseReason::Mouse); } auto tooltipWidget = TooltipWidget::getInstance(); diff --git a/src/widgets/helper/ChannelView.hpp b/src/widgets/helper/ChannelView.hpp index 0ea042e13..0aed55a5c 100644 --- a/src/widgets/helper/ChannelView.hpp +++ b/src/widgets/helper/ChannelView.hpp @@ -44,6 +44,7 @@ enum class PauseReason { Mouse, Selection, DoubleClick, + KeyboardModifier, }; using SteadyClock = std::chrono::steady_clock; @@ -162,6 +163,7 @@ private: pauses_; boost::optional pauseEnd_; int pauseScrollOffset_ = 0; + int pauseSelectionOffset_ = 0; boost::optional overrideFlags_; MessageLayoutPtr lastReadMessage_; diff --git a/src/widgets/settingspages/GeneralPage.cpp b/src/widgets/settingspages/GeneralPage.cpp index ad20aa5a5..d49ac2da0 100644 --- a/src/widgets/settingspages/GeneralPage.cpp +++ b/src/widgets/settingspages/GeneralPage.cpp @@ -21,9 +21,56 @@ #define FIREFOX_EXTENSION_LINK \ "https://addons.mozilla.org/en-US/firefox/addon/chatterino-native-host/" +// define to highlight sections in editor #define addTitle addTitle +#ifdef Q_OS_WIN +# define META_KEY "Windows" +#else +# define META_KEY "Meta" +#endif + namespace chatterino { +namespace { + void addKeyboardModifierSetting(SettingsLayout &layout, + const QString &title, + EnumSetting &setting) + { + layout.addDropdown::type>( + title, {"None", "Shift", "Control", "Alt", META_KEY}, setting, + [](int index) { + switch (index) + { + case Qt::ShiftModifier: + return 1; + case Qt::ControlModifier: + return 2; + case Qt::AltModifier: + return 3; + case Qt::MetaModifier: + return 4; + default: + return 0; + } + }, + [](DropdownArgs args) { + switch (args.index) + { + case 1: + return Qt::ShiftModifier; + case 2: + return Qt::ControlModifier; + case 3: + return Qt::AltModifier; + case 4: + return Qt::MetaModifier; + default: + return Qt::NoModifier; + } + }, + false); + } +} // namespace TitleLabel *SettingsLayout::addTitle(const QString &title) { @@ -272,7 +319,28 @@ void GeneralPage::initLayout(SettingsLayout &layout) layout.addCheckbox("Smooth scrolling", s.enableSmoothScrolling); layout.addCheckbox("Smooth scrolling on new messages", s.enableSmoothScrollingNewMessages); - layout.addCheckbox("Pause on hover", s.pauseChatOnHover); + layout.addDropdown( + "Pause on hover", {"Disabled", "0.5s", "1s", "2s", "5s", "Indefinite"}, + s.pauseOnHoverDuration, + [](auto val) { + if (val < -0.5f) + return QString("Indefinite"); + else if (val < 0.001f) + return QString("Disabled"); + else + return QString::number(val) + "s"; + }, + [](auto args) { + if (args.index == 0) + return 0.0f; + else if (args.value == "Indefinite") + return -1.0f; + else + return fuzzyToFloat(args.value, + std::numeric_limits::infinity()); + }); + addKeyboardModifierSetting(layout, "Pause while holding a key", + s.pauseChatModifier); layout.addCheckbox("Show input when it's empty", s.showEmptyInput); layout.addCheckbox("Show message length while typing", s.showMessageLength); if (!BaseWindow::supportsCustomWindowFrame()) @@ -401,8 +469,7 @@ void GeneralPage::initLayout(SettingsLayout &layout) s.mentionUsersWithComma); layout.addCheckbox("Show joined users (< 1000 chatters)", s.showJoins); layout.addCheckbox("Show parted users (< 1000 chatters)", s.showParts); - layout.addCheckbox("Lowercase domains (anti-phisching)", - s.lowercaseDomains); + layout.addCheckbox("Lowercase domains (anti-phishing)", s.lowercaseDomains); layout.addCheckbox("Bold @usernames", s.boldUsernames); layout.addDropdown( "Username font weight", {"50", "Default", "75", "100"}, s.boldScale, diff --git a/src/widgets/splits/Split.cpp b/src/widgets/splits/Split.cpp index 97b78431f..209baa028 100644 --- a/src/widgets/splits/Split.cpp +++ b/src/widgets/splits/Split.cpp @@ -14,6 +14,7 @@ #include "util/Shortcut.hpp" #include "util/StreamLink.hpp" #include "widgets/Notebook.hpp" +#include "widgets/TooltipWidget.hpp" #include "widgets/Window.hpp" #include "widgets/dialogs/QualityPopup.hpp" #include "widgets/dialogs/SelectChannelDialog.hpp" @@ -188,6 +189,16 @@ Split::Split(QWidget *parent) { this->overlay_->hide(); } + + if (getSettings()->pauseChatModifier.getEnum() != Qt::NoModifier && + status == getSettings()->pauseChatModifier.getEnum()) + { + this->view_->pause(PauseReason::KeyboardModifier); + } + else + { + this->view_->unpause(PauseReason::KeyboardModifier); + } }); this->input_->ui_.textEdit->focused.connect( @@ -401,6 +412,8 @@ void Split::leaveEvent(QEvent *event) this->overlay_->hide(); + TooltipWidget::getInstance()->hide(); + this->handleModifiers(QGuiApplication::queryKeyboardModifiers()); } diff --git a/src/widgets/splits/SplitHeader.cpp b/src/widgets/splits/SplitHeader.cpp index d50fe0716..5a7d018b6 100644 --- a/src/widgets/splits/SplitHeader.cpp +++ b/src/widgets/splits/SplitHeader.cpp @@ -301,6 +301,11 @@ std::unique_ptr SplitHeader::createMainMenu() // sub menu auto moreMenu = new QMenu("More", this); + + moreMenu->addAction("Toggle moderation mode", this->split_, [this]() { + this->split_->setModerationMode(!this->split_->getModerationMode()); + }); + if (dynamic_cast(this->split_->getChannel().get())) { moreMenu->addAction("Show viewer list", this->split_,