diff --git a/src/application.cpp b/src/application.cpp index 07178d52c..dbbf78421 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -57,7 +57,7 @@ void Application::construct() // 1. Instantiate all classes this->settings = new singletons::SettingManager; - this->paths = new singletons::PathManager(this->argc, this->argv); + this->paths = singletons::PathManager::getInstance(); this->themes = new singletons::ThemeManager; this->windows = new singletons::WindowManager; this->logging = new singletons::LoggingManager; @@ -89,7 +89,9 @@ void Application::initialize() // 2. Initialize/load classes this->settings->initialize(); +#ifdef Q_OS_WIN this->nativeMessaging->registerHost(); +#endif this->settings->load(); this->commands->load(); diff --git a/src/main.cpp b/src/main.cpp index c7249734e..19e435e53 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,7 @@ #include "singletons/nativemessagingmanager.hpp" #include "singletons/pathmanager.hpp" #include "singletons/updatemanager.hpp" +#include "util/debugcount.hpp" #include "util/networkmanager.hpp" #include "widgets/lastruncrashdialog.hpp" @@ -25,30 +26,11 @@ #include #endif -int runGui(int argc, char *argv[]); +int runGui(QApplication &a, int argc, char *argv[]); void runNativeMessagingHost(); void installCustomPalette(); int main(int argc, char *argv[]) -{ - // read args - QStringList args; - - for (int i = 1; i < argc; i++) { - args << argv[i]; - } - - // TODO: can be any argument - if (args.size() > 0 && args[0].startsWith("chrome-extension://")) { - runNativeMessagingHost(); - return 0; - } - - // run gui - return runGui(argc, argv); -} - -int runGui(int argc, char *argv[]) { QApplication::setAttribute(Qt::AA_Use96Dpi, true); #ifdef Q_OS_WIN32 @@ -57,6 +39,32 @@ int runGui(int argc, char *argv[]) // QApplication::setAttribute(Qt::AA_UseSoftwareOpenGL, true); QApplication a(argc, argv); + chatterino::singletons::PathManager::initInstance(argc, argv); + + // read args + QStringList args; + + for (int i = 1; i < argc; i++) { + args << argv[i]; + } + + for (auto &arg : args) { + chatterino::util::DebugCount::increase(arg); + } + + // TODO: can be any argument + if (args.size() > 0 && + (args[0].startsWith("chrome-extension://") || args[0].endsWith(".json"))) { + runNativeMessagingHost(); + return 0; + } + + // run gui + return runGui(a, argc, argv); +} + +int runGui(QApplication &a, int argc, char *argv[]) +{ QApplication::setStyle(QStyleFactory::create("Fusion")); installCustomPalette(); diff --git a/src/singletons/nativemessagingmanager.cpp b/src/singletons/nativemessagingmanager.cpp index 64578e75f..a77c931b8 100644 --- a/src/singletons/nativemessagingmanager.cpp +++ b/src/singletons/nativemessagingmanager.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -25,7 +26,7 @@ namespace ipc = boost::interprocess; #include -#define EXTENSION_ID "aeicjepmjkgmbeohnchmpfjbpchogmjn" +#define EXTENSION_ID "lgnlbmhbnbncmiohgcbhgiaddibhinon" #define MESSAGE_SIZE 1024 namespace chatterino { @@ -55,37 +56,60 @@ void NativeMessagingManager::registerHost() return; } - // create manifest - QJsonDocument document; - QJsonObject root_obj; - root_obj.insert("name", "com.chatterino.chatterino"); - root_obj.insert("description", "Browser interaction with chatterino."); - root_obj.insert("path", QCoreApplication::applicationFilePath()); - root_obj.insert("type", "stdio"); + auto getBaseDocument = [&] { + QJsonObject obj; + obj.insert("name", "com.chatterino.chatterino"); + obj.insert("description", "Browser interaction with chatterino."); + obj.insert("path", QCoreApplication::applicationFilePath()); + obj.insert("type", "stdio"); - // chrome - QJsonArray allowed_origins_arr = {"chrome-extension://aeicjepmjkgmbeohnchmpfjbpchogmjn/"}; - root_obj.insert("allowed_origins", allowed_origins_arr); + return obj; + }; - // firefox - QJsonArray allowed_extensions = {"585a153c7e1ac5463478f25f8f12220e9097e716@temporary-addon"}; - root_obj.insert("allowed_extensions", allowed_extensions); - - // save the manifest - QString manifestPath = app->paths->settingsFolderPath + "/native-messaging-manifest.json"; - - document.setObject(root_obj); - - QFile file(manifestPath); - file.open(QIODevice::WriteOnly | QIODevice::Truncate); - file.write(document.toJson()); - file.flush(); + auto registerManifest = [&](const QString &manifestFilename, const QString ®istryKeyName, + const QJsonDocument &document) { + // save the manifest + QString manifestPath = app->paths->settingsFolderPath + manifestFilename; + QFile file(manifestPath); + file.open(QIODevice::WriteOnly | QIODevice::Truncate); + file.write(document.toJson()); + file.flush(); #ifdef Q_OS_WIN - // clang-format off - QProcess::execute("REG ADD \"HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\com.chatterino.chatterino\" /ve /t REG_SZ /d \"" + manifestPath + "\" /f"); + // clang-format off + QProcess::execute("REG ADD \"" + registryKeyName + "\" /ve /t REG_SZ /d \"" + manifestPath + "\" /f"); // clang-format on #endif + }; + + // chrome + { + QJsonDocument document; + + auto obj = getBaseDocument(); + QJsonArray allowed_origins_arr = {"chrome-extension://aeicjepmjkgmbeohnchmpfjbpchogmjn/"}; + obj.insert("allowed_origins", allowed_origins_arr); + document.setObject(obj); + + registerManifest( + "/native-messaging-manifest-chrome.json", + "HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\com.chatterino.chatterino", + document); + } + + // firefox + { + QJsonDocument document; + + auto obj = getBaseDocument(); + QJsonArray allowed_extensions = {"chatterino_native@chatterino.com"}; + obj.insert("allowed_extensions", allowed_extensions); + document.setObject(obj); + + registerManifest("/native-messaging-manifest-firefox.json", + "HKCU\\Software\\Mozilla\\NativeMessagingHosts\\com.chatterino.chatterino", + document); + } } void NativeMessagingManager::openGuiMessageQueue() @@ -150,6 +174,8 @@ void NativeMessagingManager::ReceiverThread::handleMessage(const QJsonObject &ro bool attach = root.value("attach").toBool(); QString name = root.value("name").toString(); + qDebug() << attach; + #ifdef USEWINSDK widgets::AttachedWindow::GetArgs args; args.winId = root.value("winId").toString(); @@ -173,12 +199,12 @@ void NativeMessagingManager::ReceiverThread::handleMessage(const QJsonObject &ro if (attach) { #ifdef USEWINSDK - if (args.height != -1) { - auto *window = widgets::AttachedWindow::get(::GetForegroundWindow(), args); - if (!name.isEmpty()) { - window->setChannel(app->twitch.server->getOrAddChannel(name)); - } + // if (args.height != -1) { + auto *window = widgets::AttachedWindow::get(::GetForegroundWindow(), args); + if (!name.isEmpty()) { + window->setChannel(app->twitch.server->getOrAddChannel(name)); } +// } // window->show(); #endif } @@ -204,7 +230,14 @@ void NativeMessagingManager::ReceiverThread::handleMessage(const QJsonObject &ro } else { qDebug() << "NM unknown action " + action; } -} // namespace singletons +} + +std::string &NativeMessagingManager::getGuiMessageQueueName() +{ + static std::string name = + "chatterino_gui" + singletons::PathManager::getInstance()->appPathHash.toStdString(); + return name; +} } // namespace singletons } // namespace chatterino diff --git a/src/singletons/nativemessagingmanager.hpp b/src/singletons/nativemessagingmanager.hpp index 36e8a78ec..1c590b308 100644 --- a/src/singletons/nativemessagingmanager.hpp +++ b/src/singletons/nativemessagingmanager.hpp @@ -26,6 +26,8 @@ public: void registerHost(); void openGuiMessageQueue(); void sendToGuiProcess(const QByteArray &array); + + static std::string &getGuiMessageQueueName(); }; } // namespace singletons diff --git a/src/singletons/pathmanager.cpp b/src/singletons/pathmanager.cpp index 98553f941..b830a68db 100644 --- a/src/singletons/pathmanager.cpp +++ b/src/singletons/pathmanager.cpp @@ -4,10 +4,13 @@ #include #include #include +#include namespace chatterino { namespace singletons { +PathManager *PathManager::instance = nullptr; + PathManager::PathManager(int argc, char **argv) { // hash of app path @@ -68,6 +71,20 @@ PathManager::PathManager(int argc, char **argv) } } +void PathManager::initInstance(int argc, char **argv) +{ + assert(!instance); + + instance = new PathManager(argc, argv); +} + +PathManager *PathManager::getInstance() +{ + assert(instance); + + return instance; +} + bool PathManager::createFolder(const QString &folderPath) { return QDir().mkpath(folderPath); diff --git a/src/singletons/pathmanager.hpp b/src/singletons/pathmanager.hpp index bd3e079b7..003c4e192 100644 --- a/src/singletons/pathmanager.hpp +++ b/src/singletons/pathmanager.hpp @@ -7,9 +7,12 @@ namespace singletons { class PathManager { -public: PathManager(int argc, char **argv); +public: + static void initInstance(int argc, char **argv); + static PathManager *getInstance(); + // %APPDATA%/chatterino or ExecutablePath for portable mode QString settingsFolderPath; @@ -28,6 +31,7 @@ public: bool isPortable(); private: + static PathManager *instance; bool portable; }; diff --git a/src/widgets/attachedwindow.cpp b/src/widgets/attachedwindow.cpp index 36dfca81d..12e376924 100644 --- a/src/widgets/attachedwindow.cpp +++ b/src/widgets/attachedwindow.cpp @@ -106,7 +106,7 @@ void AttachedWindow::setChannel(ChannelPtr channel) void AttachedWindow::showEvent(QShowEvent *) { - attachToHwnd_(this->target_); + this->attachToHwnd_(this->target_); } void AttachedWindow::attachToHwnd_(void *_attachedPtr) @@ -135,7 +135,7 @@ void AttachedWindow::attachToHwnd_(void *_attachedPtr) DWORD filenameLength = ::GetModuleFileNameEx(process, nullptr, filename.get(), 512); QString qfilename = QString::fromWCharArray(filename.get(), filenameLength); - if (!qfilename.endsWith("chrome.exe")) { + if (!qfilename.endsWith("chrome.exe") && !qfilename.endsWith("firefox.exe")) { qDebug() << "NM Illegal caller" << qfilename; this->timer_.stop(); this->deleteLater(); @@ -178,15 +178,15 @@ void AttachedWindow::updateWindowRect_(void *_attachedPtr) SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); if (this->height_ == -1) { - // ::MoveWindow(hwnd, rect.right - this->width_ - 8, rect.top + this->yOffset_ - 8, - // this->width_, rect.bottom - rect.top - this->yOffset_, false); + ::MoveWindow(hwnd, rect.right - this->width_ - 8, rect.top + this->yOffset_ - 8, + this->width_, rect.bottom - rect.top - this->yOffset_, false); } else { ::MoveWindow(hwnd, rect.right - this->width_ - 8, rect.bottom - this->height_ - 8, this->width_, this->height_, false); } - // ::MoveWindow(hwnd, rect.right - 360, rect.top + 82, 360 - 8, rect.bottom - - // rect.top - 82 - 8, false); +// ::MoveWindow(hwnd, rect.right - 360, rect.top + 82, 360 - 8, rect.bottom - +// rect.top - 82 - 8, false); #endif } diff --git a/src/widgets/helper/channelview.cpp b/src/widgets/helper/channelview.cpp index 7053653af..237706c6d 100644 --- a/src/widgets/helper/channelview.cpp +++ b/src/widgets/helper/channelview.cpp @@ -29,7 +29,7 @@ #define DRAW_WIDTH (this->width()) #define SELECTION_RESUME_SCROLLING_MSG_THRESHOLD 3 -#define CHAT_HOVER_PAUSE_DURATION 400 +#define CHAT_HOVER_PAUSE_DURATION 1000 using namespace chatterino::messages; using namespace chatterino::providers::twitch; @@ -39,7 +39,7 @@ namespace widgets { ChannelView::ChannelView(BaseWidget *parent) : BaseWidget(parent) - , scrollBar(this) + , scrollBar_(this) { auto app = getApp(); @@ -50,15 +50,17 @@ ChannelView::ChannelView(BaseWidget *parent) this->update(); })); - this->scrollBar.getCurrentValueChanged().connect([this] { + this->scrollBar_.getCurrentValueChanged().connect([this] { + qDebug() << "getCurrentValueChanged"; + // Whenever the scrollbar value has been changed, re-render the ChatWidgetView this->actuallyLayoutMessages(true); - if (!this->isPaused()) { - this->goToBottom->setVisible(this->enableScrollingToBottom && - this->scrollBar.isVisible() && - !this->scrollBar.isAtBottom()); - } + // if (!this->isPaused()) { + this->goToBottom_->setVisible(this->enableScrollingToBottom_ && + this->scrollBar_.isVisible() && + !this->scrollBar_.isAtBottom()); + // } this->queueUpdate(); }); @@ -73,10 +75,10 @@ ChannelView::ChannelView(BaseWidget *parent) } })); - this->goToBottom = new RippleEffectLabel(this, 0); - this->goToBottom->setStyleSheet("background-color: rgba(0,0,0,0.66); color: #FFF;"); - this->goToBottom->getLabel().setText("More messages below"); - this->goToBottom->setVisible(false); + this->goToBottom_ = new RippleEffectLabel(this, 0); + this->goToBottom_->setStyleSheet("background-color: rgba(0,0,0,0.66); color: #FFF;"); + this->goToBottom_->getLabel().setText("More messages below"); + this->goToBottom_->setVisible(false); this->connections_.emplace_back(app->fonts->fontChanged.connect([this] { this->layoutMessages(); // @@ -84,7 +86,7 @@ ChannelView::ChannelView(BaseWidget *parent) QObject::connect(goToBottom, &RippleEffectLabel::clicked, this, [=] { QTimer::singleShot(180, [=] { - this->scrollBar.scrollToBottom( + this->scrollBar_.scrollToBottom( app->settings->enableSmoothScrollingNewMessages.getValue()); }); }); @@ -99,9 +101,10 @@ ChannelView::ChannelView(BaseWidget *parent) // } // }); - this->pauseTimeout.setSingleShot(true); - QObject::connect(&this->pauseTimeout, &QTimer::timeout, [this] { - this->pausedTemporarily = false; + this->pauseTimeout_.setSingleShot(true); + QObject::connect(&this->pauseTimeout_, &QTimer::timeout, [this] { + this->pausedTemporarily_ = false; + this->updatePauseStatus(); this->layoutMessages(); }); @@ -111,22 +114,17 @@ ChannelView::ChannelView(BaseWidget *parent) }, this->connections_); - this->layoutCooldown = new QTimer(this); - this->layoutCooldown->setSingleShot(true); - this->layoutCooldown->setInterval(66); + this->layoutCooldown_ = new QTimer(this); + this->layoutCooldown_->setSingleShot(true); + this->layoutCooldown_->setInterval(66); - QObject::connect(this->layoutCooldown, &QTimer::timeout, [this] { - if (this->layoutQueued) { + QObject::connect(this->layoutCooldown_, &QTimer::timeout, [this] { + if (this->layoutQueued_) { this->layoutMessages(); - this->layoutQueued = false; + this->layoutQueued_ = false; } }); - QTimer::singleShot(1000, this, [this] { - this->scrollBar.setGeometry(this->width() - this->scrollBar.width(), 0, - this->scrollBar.width(), this->height()); - }); - QShortcut *shortcut = new QShortcut(QKeySequence("Ctrl+C"), this); QObject::connect(shortcut, &QShortcut::activated, [this] { QGuiApplication::clipboard()->setText(this->getSelectedText()); }); @@ -176,7 +174,7 @@ void ChannelView::actuallyLayoutMessages(bool causedByScrollbar) auto messagesSnapshot = this->getMessagesSnapshot(); if (messagesSnapshot.getLength() == 0) { - this->scrollBar.setVisible(false); + this->scrollBar_.setVisible(false); return; } @@ -188,19 +186,17 @@ void ChannelView::actuallyLayoutMessages(bool causedByScrollbar) // True if one of the following statements are true: // The scrollbar was not visible // The scrollbar was visible and at the bottom - this->showingLatestMessages = this->scrollBar.isAtBottom() || !this->scrollBar.isVisible(); + this->showingLatestMessages_ = this->scrollBar_.isAtBottom() || !this->scrollBar_.isVisible(); - size_t start = this->scrollBar.getCurrentValue(); - // int layoutWidth = - // (this->scrollBar.isVisible() ? width() - this->scrollBar.width() : width()) - 4; + size_t start = size_t(this->scrollBar_.getCurrentValue()); int layoutWidth = this->getLayoutWidth(); MessageElement::Flags flags = this->getFlags(); // layout the visible messages in the view if (messagesSnapshot.getLength() > start) { - int y = - -(messagesSnapshot[start]->getHeight() * (fmod(this->scrollBar.getCurrentValue(), 1))); + int y = int(-(messagesSnapshot[start]->getHeight() * + (fmod(this->scrollBar_.getCurrentValue(), 1)))); for (size_t i = start; i < messagesSnapshot.getLength(); ++i) { auto message = messagesSnapshot[i]; @@ -218,7 +214,7 @@ void ChannelView::actuallyLayoutMessages(bool causedByScrollbar) // layout the messages at the bottom to determine the scrollbar thumb size int h = this->height() - 8; - for (int i = (int)messagesSnapshot.getLength() - 1; i >= 0; i--) { + for (int i = int(messagesSnapshot.getLength()) - 1; i >= 0; i--) { auto *message = messagesSnapshot[i].get(); message->layout(layoutWidth, this->getScale(), flags); @@ -226,8 +222,8 @@ void ChannelView::actuallyLayoutMessages(bool causedByScrollbar) h -= message->getHeight(); if (h < 0) { - this->scrollBar.setLargeChange((messagesSnapshot.getLength() - i) + - (qreal)h / message->getHeight()); + this->scrollBar_.setLargeChange((messagesSnapshot.getLength() - i) + + qreal(h) / message->getHeight()); // this->scrollBar.setDesiredValue(this->scrollBar.getDesiredValue()); showScrollbar = true; @@ -235,28 +231,23 @@ void ChannelView::actuallyLayoutMessages(bool causedByScrollbar) } } - this->scrollBar.setVisible(showScrollbar); + this->scrollBar_.setVisible(showScrollbar); - if (!showScrollbar) { - if (!causedByScrollbar) { - this->scrollBar.setDesiredValue(0); - } + if (!showScrollbar && !causedByScrollbar) { + this->scrollBar_.setDesiredValue(0); } - this->scrollBar.setMaximum(messagesSnapshot.getLength()); + this->scrollBar_.setMaximum(messagesSnapshot.getLength()); // If we were showing the latest messages and the scrollbar now wants to be rendered, scroll // to bottom - // TODO: Do we want to check if the user is currently moving the scrollbar? - // Perhaps also if the user scrolled with the scrollwheel in this ChatWidget in the last 0.2 - // seconds or something - if (this->enableScrollingToBottom && this->showingLatestMessages && showScrollbar) { + if (this->enableScrollingToBottom_ && this->showingLatestMessages_ && showScrollbar) { if (!this->isPaused()) { - this->scrollBar.scrollToBottom( + this->scrollBar_.scrollToBottom( // this->messageWasAdded && app->settings->enableSmoothScrollingNewMessages.getValue()); } - this->messageWasAdded = false; + this->messageWasAdded_ = false; } if (redrawRequired) { @@ -276,7 +267,7 @@ void ChannelView::clearMessages() Scrollbar &ChannelView::getScrollBar() { - return this->scrollBar; + return this->scrollBar_; } QString ChannelView::getSelectedText() @@ -285,7 +276,7 @@ QString ChannelView::getSelectedText() messages::LimitedQueueSnapshot messagesSnapshot = this->getMessagesSnapshot(); - Selection _selection = this->selection; + Selection _selection = this->selection_; if (_selection.isEmpty()) { return result; @@ -311,48 +302,47 @@ QString ChannelView::getSelectedText() bool ChannelView::hasSelection() { - return !this->selection.isEmpty(); + return !this->selection_.isEmpty(); } void ChannelView::clearSelection() { - this->selection = Selection(); + this->selection_ = Selection(); layoutMessages(); } void ChannelView::setEnableScrollingToBottom(bool value) { - this->enableScrollingToBottom = value; + this->enableScrollingToBottom_ = value; } bool ChannelView::getEnableScrollingToBottom() const { - return this->enableScrollingToBottom; + return this->enableScrollingToBottom_; } void ChannelView::setOverrideFlags(boost::optional value) { - this->overrideFlags = value; + this->overrideFlags_ = value; } const boost::optional &ChannelView::getOverrideFlags() const { - return this->overrideFlags; + return this->overrideFlags_; } messages::LimitedQueueSnapshot ChannelView::getMessagesSnapshot() { - // if (!this->isPaused()) { - this->snapshot = this->messages.getSnapshot(); - // } + if (!this->isPaused() /*|| this->scrollBar_.isVisible()*/) { + this->snapshot_ = this->messages.getSnapshot(); + } - // return this->snapshot; - return this->snapshot; + return this->snapshot_; } void ChannelView::setChannel(ChannelPtr newChannel) { - if (this->channel) { + if (this->channel_) { this->detachChannel(); } @@ -365,21 +355,21 @@ void ChannelView::setChannel(ChannelPtr newChannel) auto messageRef = new MessageLayout(message); - if (this->lastMessageHasAlternateBackground) { + if (this->lastMessageHasAlternateBackground_) { messageRef->flags |= MessageLayout::AlternateBackground; } - this->lastMessageHasAlternateBackground = !this->lastMessageHasAlternateBackground; + this->lastMessageHasAlternateBackground_ = !this->lastMessageHasAlternateBackground_; if (this->isPaused()) { - this->messagesAddedSinceSelectionPause++; + this->messagesAddedSinceSelectionPause_++; } if (this->messages.pushBack(MessageLayoutPtr(messageRef), deleted)) { // if (!this->isPaused()) { - if (this->scrollBar.isAtBottom()) { - this->scrollBar.scrollToBottom(); + if (this->scrollBar_.isAtBottom()) { + this->scrollBar_.scrollToBottom(); } else { - this->scrollBar.offset(-1); + this->scrollBar_.offset(-1); } // } } @@ -392,9 +382,9 @@ void ChannelView::setChannel(ChannelPtr newChannel) } } - this->scrollBar.addHighlight(message->getScrollBarHighlight()); + this->scrollBar_.addHighlight(message->getScrollBarHighlight()); - this->messageWasAdded = true; + this->messageWasAdded_ = true; this->layoutMessages(); })); @@ -408,10 +398,10 @@ void ChannelView::setChannel(ChannelPtr newChannel) if (!this->isPaused()) { if (this->messages.pushFront(messageRefs).size() > 0) { - if (this->scrollBar.isAtBottom()) { - this->scrollBar.scrollToBottom(); + if (this->scrollBar_.isAtBottom()) { + this->scrollBar_.scrollToBottom(); } else { - this->scrollBar.offset((qreal)messages.size()); + this->scrollBar_.offset(qreal(messages.size())); } } } @@ -422,19 +412,19 @@ void ChannelView::setChannel(ChannelPtr newChannel) highlights.push_back(messages.at(i)->getScrollBarHighlight()); } - this->scrollBar.addHighlightsAtStart(highlights); + this->scrollBar_.addHighlightsAtStart(highlights); - this->messageWasAdded = true; + this->messageWasAdded_ = true; this->layoutMessages(); })); // on message removed this->channelConnections_.push_back( newChannel->messageRemovedFromStart.connect([this](MessagePtr &) { - this->selection.selectionMin.messageIndex--; - this->selection.selectionMax.messageIndex--; - this->selection.start.messageIndex--; - this->selection.end.messageIndex--; + this->selection_.selectionMin.messageIndex--; + this->selection_.selectionMax.messageIndex--; + this->selection_.start.messageIndex--; + this->selection_.end.messageIndex--; this->layoutMessages(); })); @@ -442,6 +432,10 @@ void ChannelView::setChannel(ChannelPtr newChannel) // on message replaced this->channelConnections_.push_back( newChannel->messageReplaced.connect([this](size_t index, MessagePtr replacement) { + if (this->messages.getSnapshot().getLength() >= index || index < 0) { + return; + } + MessageLayoutPtr newItem(new MessageLayout(replacement)); auto snapshot = this->messages.getSnapshot(); if (index >= snapshot.getLength()) { @@ -455,7 +449,7 @@ void ChannelView::setChannel(ChannelPtr newChannel) newItem->flags |= MessageLayout::AlternateBackground; } - this->scrollBar.replaceHighlight(index, replacement->getScrollBarHighlight()); + this->scrollBar_.replaceHighlight(index, replacement->getScrollBarHighlight()); this->messages.replaceItem(message, newItem); this->layoutMessages(); @@ -468,15 +462,15 @@ void ChannelView::setChannel(ChannelPtr newChannel) auto messageRef = new MessageLayout(snapshot[i]); - if (this->lastMessageHasAlternateBackground) { + if (this->lastMessageHasAlternateBackground_) { messageRef->flags |= MessageLayout::AlternateBackground; } - this->lastMessageHasAlternateBackground = !this->lastMessageHasAlternateBackground; + this->lastMessageHasAlternateBackground_ = !this->lastMessageHasAlternateBackground_; this->messages.pushBack(MessageLayoutPtr(messageRef), deleted); } - this->channel = newChannel; + this->channel_ = newChannel; this->layoutMessages(); this->queueUpdate(); @@ -489,9 +483,15 @@ void ChannelView::detachChannel() void ChannelView::pause(int msecTimeout) { - this->pausedTemporarily = true; + this->pausedTemporarily_ = true; + this->updatePauseStatus(); - this->pauseTimeout.start(msecTimeout); + if (this->pauseTimeout_.remainingTime() < msecTimeout) { + this->pauseTimeout_.stop(); + this->pauseTimeout_.start(msecTimeout); + + qDebug() << "pause" << msecTimeout; + } } void ChannelView::updateLastReadMessage() @@ -499,7 +499,7 @@ void ChannelView::updateLastReadMessage() auto _snapshot = this->getMessagesSnapshot(); if (_snapshot.getLength() > 0) { - this->lastReadMessage = _snapshot[_snapshot.getLength() - 1]; + this->lastReadMessage_ = _snapshot[_snapshot.getLength() - 1]; } this->update(); @@ -507,12 +507,12 @@ void ChannelView::updateLastReadMessage() void ChannelView::resizeEvent(QResizeEvent *) { - this->scrollBar.setGeometry(this->width() - this->scrollBar.width(), 0, this->scrollBar.width(), - this->height()); + this->scrollBar_.setGeometry(this->width() - this->scrollBar_.width(), 0, + this->scrollBar_.width(), this->height()); - this->goToBottom->setGeometry(0, this->height() - 32, this->width(), 32); + this->goToBottom_->setGeometry(0, this->height() - 32, this->width(), 32); - this->scrollBar.raise(); + this->scrollBar_.raise(); this->layoutMessages(); @@ -522,14 +522,14 @@ void ChannelView::resizeEvent(QResizeEvent *) void ChannelView::setSelection(const SelectionItem &start, const SelectionItem &end) { // selections - if (!this->selecting && start != end) { - this->messagesAddedSinceSelectionPause = 0; + if (!this->selecting_ && start != end) { + this->messagesAddedSinceSelectionPause_ = 0; - this->selecting = true; - this->pausedBySelection = true; + this->selecting_ = true; + this->pausedBySelection_ = true; } - this->selection = Selection(start, end); + this->selection_ = Selection(start, end); this->selectionChanged.invoke(); } @@ -538,8 +538,8 @@ messages::MessageElement::Flags ChannelView::getFlags() const { auto app = getApp(); - if (this->overrideFlags) { - return this->overrideFlags.get(); + if (this->overrideFlags_) { + return this->overrideFlags_.get(); } MessageElement::Flags flags = app->settings->getWordFlags(); @@ -548,10 +548,10 @@ messages::MessageElement::Flags ChannelView::getFlags() const if (split != nullptr) { if (split->getModerationMode()) { - flags = (MessageElement::Flags)(flags | MessageElement::ModeratorTools); + flags = MessageElement::Flags(flags | MessageElement::ModeratorTools); } - if (this->channel == app->twitch.server->mentionsChannel) { - flags = (MessageElement::Flags)(flags | MessageElement::ChannelName); + if (this->channel_ == app->twitch.server->mentionsChannel) { + flags = MessageElement::Flags(flags | MessageElement::ChannelName); } } @@ -560,20 +560,17 @@ messages::MessageElement::Flags ChannelView::getFlags() const bool ChannelView::isPaused() { - return this->pausedTemporarily || this->pausedBySelection; + return this->pausedTemporarily_ || this->pausedBySelection_ || this->pausedByScrollingUp_; } -// void ChannelView::beginPause() -//{ -// if (this->scrollBar.isAtBottom()) { -// this->scrollBar.setDesiredValue(this->scrollBar.getDesiredValue() - 0.001); -// this->layoutMessages(); -// } -//} - -// void ChannelView::endPause() -//{ -//} +void ChannelView::updatePauseStatus() +{ + if (this->isPaused()) { + this->scrollBar_.pauseHighlights(); + } else { + this->scrollBar_.unpauseHighlights(); + } +} void ChannelView::paintEvent(QPaintEvent * /*event*/) { @@ -595,14 +592,14 @@ void ChannelView::drawMessages(QPainter &painter) auto messagesSnapshot = this->getMessagesSnapshot(); - size_t start = size_t(this->scrollBar.getCurrentValue()); + size_t start = size_t(this->scrollBar_.getCurrentValue()); if (start >= messagesSnapshot.getLength()) { return; } int y = int(-(messagesSnapshot[start].get()->getHeight() * - (fmod(this->scrollBar.getCurrentValue(), 1)))); + (fmod(this->scrollBar_.getCurrentValue(), 1)))); messages::MessageLayout *end = nullptr; bool windowFocused = this->window() == QApplication::activeWindow(); @@ -612,10 +609,10 @@ void ChannelView::drawMessages(QPainter &painter) bool isLastMessage = false; if (app->settings->showLastMessageIndicator) { - isLastMessage = this->lastReadMessage.get() == layout; + isLastMessage = this->lastReadMessage_.get() == layout; } - layout->paint(painter, DRAW_WIDTH, y, i, this->selection, isLastMessage, windowFocused); + layout->paint(painter, DRAW_WIDTH, y, i, this->selection_, isLastMessage, windowFocused); y += layout->getHeight(); @@ -632,24 +629,24 @@ void ChannelView::drawMessages(QPainter &painter) // remove messages that are on screen // the messages that are left at the end get their buffers reset for (size_t i = start; i < messagesSnapshot.getLength(); ++i) { - auto it = this->messagesOnScreen.find(messagesSnapshot[i]); - if (it != this->messagesOnScreen.end()) { - this->messagesOnScreen.erase(it); + auto it = this->messagesOnScreen_.find(messagesSnapshot[i]); + if (it != this->messagesOnScreen_.end()) { + this->messagesOnScreen_.erase(it); } } // delete the message buffers that aren't on screen - for (const std::shared_ptr &item : this->messagesOnScreen) { + for (const std::shared_ptr &item : this->messagesOnScreen_) { item->deleteBuffer(); } - this->messagesOnScreen.clear(); + this->messagesOnScreen_.clear(); // add all messages on screen to the map for (size_t i = start; i < messagesSnapshot.getLength(); ++i) { std::shared_ptr layout = messagesSnapshot[i]; - this->messagesOnScreen.insert(layout); + this->messagesOnScreen_.insert(layout); if (layout.get() == end) { break; @@ -659,29 +656,25 @@ void ChannelView::drawMessages(QPainter &painter) void ChannelView::wheelEvent(QWheelEvent *event) { - if (event->modifiers() & Qt::ControlModifier) { - event->ignore(); - return; - } + this->pausedBySelection_ = false; + this->pausedTemporarily_ = false; + this->updatePauseStatus(); - this->pausedBySelection = false; - this->pausedTemporarily = false; - - if (this->scrollBar.isVisible()) { + if (this->scrollBar_.isVisible()) { auto app = getApp(); float mouseMultiplier = app->settings->mouseScrollMultiplier; - float desired = this->scrollBar.getDesiredValue(); - float delta = event->delta() * 1.5 * mouseMultiplier; + qreal desired = this->scrollBar_.getDesiredValue(); + qreal delta = event->delta() * qreal(1.5) * mouseMultiplier; auto snapshot = this->getMessagesSnapshot(); - int snapshotLength = (int)snapshot.getLength(); - int i = std::min((int)desired, snapshotLength); + int snapshotLength = int(snapshot.getLength()); + int i = std::min(int(desired), snapshotLength); if (delta > 0) { - float scrollFactor = fmod(desired, 1); - float currentScrollLeft = (int)(scrollFactor * snapshot[i]->getHeight()); + qreal scrollFactor = fmod(desired, 1); + qreal currentScrollLeft = int(scrollFactor * snapshot[i]->getHeight()); for (; i >= 0; i--) { if (delta < currentScrollLeft) { @@ -703,12 +696,12 @@ void ChannelView::wheelEvent(QWheelEvent *event) } } else { delta = -delta; - float scrollFactor = 1 - fmod(desired, 1); - float currentScrollLeft = (int)(scrollFactor * snapshot[i]->getHeight()); + qreal scrollFactor = 1 - fmod(desired, 1); + qreal currentScrollLeft = int(scrollFactor * snapshot[i]->getHeight()); for (; i < snapshotLength; i++) { if (delta < currentScrollLeft) { - desired += scrollFactor * ((double)delta / currentScrollLeft); + desired += scrollFactor * (qreal(delta) / currentScrollLeft); break; } else { delta -= currentScrollLeft; @@ -727,7 +720,7 @@ void ChannelView::wheelEvent(QWheelEvent *event) } } - this->scrollBar.setDesiredValue(desired, true); + this->scrollBar_.setDesiredValue(desired, true); } } @@ -738,7 +731,8 @@ void ChannelView::enterEvent(QEvent *) void ChannelView::leaveEvent(QEvent *) { - this->pausedTemporarily = false; + this->pausedTemporarily_ = false; + this->updatePauseStatus(); this->layoutMessages(); } @@ -770,11 +764,11 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) } // is selecting - if (this->isMouseDown) { + if (this->isMouseDown_) { this->pause(300); int index = layout->getSelectionIndex(relativePos); - this->setSelection(this->selection.start, SelectionItem(messageIndex, index)); + this->setSelection(this->selection_.start, SelectionItem(messageIndex, index)); this->queueUpdate(); } @@ -846,8 +840,8 @@ void ChannelView::mousePressEvent(QMouseEvent *event) // check if message is collapsed switch (event->button()) { case Qt::LeftButton: { - this->lastPressPosition = event->screenPos(); - this->isMouseDown = true; + this->lastPressPosition_ = event->screenPos(); + this->isMouseDown_ = true; if (layout->flags & MessageLayout::Collapsed) { return; @@ -864,8 +858,8 @@ void ChannelView::mousePressEvent(QMouseEvent *event) } break; case Qt::RightButton: { - this->lastRightPressPosition = event->screenPos(); - this->isRightMouseDown = true; + this->lastRightPressPosition_ = event->screenPos(); + this->isRightMouseDown_ = true; } break; default:; @@ -878,10 +872,10 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event) { // check if mouse was pressed if (event->button() == Qt::LeftButton) { - if (this->isMouseDown) { - this->isMouseDown = false; + if (this->isMouseDown_) { + this->isMouseDown_ = false; - if (fabsf(util::distanceBetweenPoints(this->lastPressPosition, event->screenPos())) > + if (fabsf(util::distanceBetweenPoints(this->lastPressPosition_, event->screenPos())) > 15.f) { return; } @@ -889,10 +883,10 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event) return; } } else if (event->button() == Qt::RightButton) { - if (this->isRightMouseDown) { - this->isRightMouseDown = false; + if (this->isRightMouseDown_) { + this->isRightMouseDown_ = false; - if (fabsf(util::distanceBetweenPoints(this->lastRightPressPosition, + if (fabsf(util::distanceBetweenPoints(this->lastRightPressPosition_, event->screenPos())) > 15.f) { return; } @@ -942,16 +936,16 @@ void ChannelView::handleMouseClick(QMouseEvent *event, { switch (event->button()) { case Qt::LeftButton: { - if (this->selecting) { - if (this->messagesAddedSinceSelectionPause > + if (this->selecting_) { + if (this->messagesAddedSinceSelectionPause_ > SELECTION_RESUME_SCROLLING_MSG_THRESHOLD) { - this->showingLatestMessages = false; + this->showingLatestMessages_ = false; } - this->pausedBySelection = false; - this->selecting = false; - this->pauseTimeout.stop(); - this->pausedTemporarily = false; + // this->pausedBySelection = false; + this->selecting_ = false; + // this->pauseTimeout.stop(); + // this->pausedTemporarily = false; this->layoutMessages(); } @@ -1067,7 +1061,7 @@ void ChannelView::addContextMenuItems(const messages::MessageLayoutElement *hove } // Copy actions - if (!this->selection.isEmpty()) { + if (!this->selection_.isEmpty()) { menu->addAction("Copy selection", [this] { QGuiApplication::clipboard()->setText(this->getSelectedText()); }); } @@ -1125,11 +1119,11 @@ void ChannelView::mouseDoubleClickEvent(QMouseEvent *event) void ChannelView::hideEvent(QHideEvent *) { - for (auto &layout : this->messagesOnScreen) { + for (auto &layout : this->messagesOnScreen_) { layout->deleteBuffer(); } - this->messagesOnScreen.clear(); + this->messagesOnScreen_.clear(); } void ChannelView::handleLinkClick(QMouseEvent *event, const messages::Link &link, @@ -1143,7 +1137,7 @@ void ChannelView::handleLinkClick(QMouseEvent *event, const messages::Link &link case messages::Link::UserInfo: { auto user = link.value; auto *userPopup = new UserInfoPopup; - userPopup->setData(user, this->channel); + userPopup->setData(user, this->channel_); userPopup->setAttribute(Qt::WA_DeleteOnClose); userPopup->move(event->globalPos()); userPopup->show(); @@ -1165,7 +1159,7 @@ void ChannelView::handleLinkClick(QMouseEvent *event, const messages::Link &link case messages::Link::UserAction: { QString value = link.value; value.replace("{user}", layout->getMessage()->loginName); - this->channel->sendMessage(value); + this->channel_->sendMessage(value); } default:; @@ -1177,13 +1171,13 @@ bool ChannelView::tryGetMessageAt(QPoint p, std::shared_ptrgetMessagesSnapshot(); - size_t start = this->scrollBar.getCurrentValue(); + size_t start = this->scrollBar_.getCurrentValue(); if (start >= messagesSnapshot.getLength()) { return false; } - int y = -(messagesSnapshot[start]->getHeight() * (fmod(this->scrollBar.getCurrentValue(), 1))); + int y = -(messagesSnapshot[start]->getHeight() * (fmod(this->scrollBar_.getCurrentValue(), 1))); for (size_t i = start; i < messagesSnapshot.getLength(); ++i) { auto message = messagesSnapshot[i]; diff --git a/src/widgets/helper/channelview.hpp b/src/widgets/helper/channelview.hpp index 0631c477e..d92499960 100644 --- a/src/widgets/helper/channelview.hpp +++ b/src/widgets/helper/channelview.hpp @@ -28,7 +28,7 @@ class ChannelView : public BaseWidget public: explicit ChannelView(BaseWidget *parent = nullptr); - virtual ~ChannelView(); + virtual ~ChannelView() override; void queueUpdate(); Scrollbar &getScrollBar(); @@ -42,7 +42,7 @@ public: void pause(int msecTimeout); void updateLastReadMessage(); - void setChannel(ChannelPtr channel); + void setChannel(ChannelPtr channel_); messages::LimitedQueueSnapshot getMessagesSnapshot(); void layoutMessages(); @@ -78,24 +78,25 @@ protected: QPoint &relativePos, int &index); private: - QTimer *layoutCooldown; - bool layoutQueued; + QTimer *layoutCooldown_; + bool layoutQueued_; - QTimer updateTimer; - bool updateQueued = false; - bool messageWasAdded = false; - bool lastMessageHasAlternateBackground = false; + QTimer updateTimer_; + bool updateQueued_ = false; + bool messageWasAdded_ = false; + bool lastMessageHasAlternateBackground_ = false; - bool pausedTemporarily = false; - bool pausedBySelection = false; - int messagesAddedSinceSelectionPause = 0; - int getLayoutWidth() const; + bool pausedTemporarily_ = false; + bool pausedBySelection_ = false; + bool pausedByScrollingUp_ = false; + void updatePauseStatus(); + int messagesAddedSinceSelectionPause_ = 0; - QTimer pauseTimeout; - boost::optional overrideFlags; - messages::MessageLayoutPtr lastReadMessage; + QTimer pauseTimeout_; + boost::optional overrideFlags_; + messages::MessageLayoutPtr lastReadMessage_; - messages::LimitedQueueSnapshot snapshot; + messages::LimitedQueueSnapshot snapshot_; void detachChannel(); void actuallyLayoutMessages(bool causedByScollbar = false); @@ -114,40 +115,40 @@ private: // void beginPause(); // void endPause(); - ChannelPtr channel; + ChannelPtr channel_; - Scrollbar scrollBar; - RippleEffectLabel *goToBottom; + Scrollbar scrollBar_; + RippleEffectLabel *goToBottom_; // 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 showingLatestMessages_ = true; + bool enableScrollingToBottom_ = true; - bool onlyUpdateEmotes = false; + bool onlyUpdateEmotes_ = false; // Mouse event variables - bool isMouseDown = false; - bool isRightMouseDown = false; - QPointF lastPressPosition; - QPointF lastRightPressPosition; + bool isMouseDown_ = false; + bool isRightMouseDown_ = false; + QPointF lastPressPosition_; + QPointF lastRightPressPosition_; - messages::Selection selection; - bool selecting = false; + messages::Selection selection_; + bool selecting_ = false; messages::LimitedQueue messages; - pajlada::Signals::Connection messageAppendedConnection; - pajlada::Signals::Connection messageAddedAtStartConnection; - pajlada::Signals::Connection messageRemovedConnection; - pajlada::Signals::Connection messageReplacedConnection; - pajlada::Signals::Connection repaintGifsConnection; - pajlada::Signals::Connection layoutConnection; + pajlada::Signals::Connection messageAppendedConnection_; + pajlada::Signals::Connection messageAddedAtStartConnection_; + pajlada::Signals::Connection messageRemovedConnection_; + pajlada::Signals::Connection messageReplacedConnection_; + pajlada::Signals::Connection repaintGifsConnection_; + pajlada::Signals::Connection layoutConnection_; std::vector connections_; std::vector channelConnections_; - std::unordered_set> messagesOnScreen; + std::unordered_set> messagesOnScreen_; private slots: void wordFlagsChanged() diff --git a/src/widgets/scrollbar.cpp b/src/widgets/scrollbar.cpp index ae8c193d1..cc5bfa675 100644 --- a/src/widgets/scrollbar.cpp +++ b/src/widgets/scrollbar.cpp @@ -18,76 +18,84 @@ namespace widgets { Scrollbar::Scrollbar(ChannelView *parent) : BaseWidget(parent) - , currentValueAnimation(this, "currentValue") + , currentValueAnimation_(this, "currentValue") { resize(int(16 * this->getScale()), 100); - this->currentValueAnimation.setDuration(150); - this->currentValueAnimation.setEasingCurve(QEasingCurve(QEasingCurve::OutCubic)); + this->currentValueAnimation_.setDuration(150); + this->currentValueAnimation_.setEasingCurve(QEasingCurve(QEasingCurve::OutCubic)); setMouseTracking(true); - - // don't do this at home kids - QTimer *timer = new QTimer(this); - timer->setSingleShot(true); - - connect(timer, &QTimer::timeout, [=]() { - resize(int(16 * this->getScale()), 100); - timer->deleteLater(); - }); - - timer->start(10); } void Scrollbar::addHighlight(ScrollbarHighlight highlight) { ScrollbarHighlight deleted; - this->highlights.pushBack(highlight, deleted); + this->highlights_.pushBack(highlight, deleted); } void Scrollbar::addHighlightsAtStart(const std::vector &_highlights) { - this->highlights.pushFront(_highlights); + this->highlights_.pushFront(_highlights); } void Scrollbar::replaceHighlight(size_t index, ScrollbarHighlight replacement) { - this->highlights.replaceItem(index, replacement); + this->highlights_.replaceItem(index, replacement); +} + +void Scrollbar::pauseHighlights() +{ + this->highlightsPaused_ = true; +} + +void Scrollbar::unpauseHighlights() +{ + this->highlightsPaused_ = false; +} + +messages::LimitedQueueSnapshot Scrollbar::getHighlightSnapshot() +{ + if (!this->highlightsPaused_) { + this->highlightSnapshot_ = this->highlights_.getSnapshot(); + } + + return this->highlightSnapshot_; } void Scrollbar::scrollToBottom(bool animate) { - this->setDesiredValue(this->maximum - this->getLargeChange(), animate); + this->setDesiredValue(this->maximum_ - this->getLargeChange(), animate); } bool Scrollbar::isAtBottom() const { - return this->atBottom; + return this->atBottom_; } void Scrollbar::setMaximum(qreal value) { - this->maximum = value; + this->maximum_ = value; updateScroll(); } void Scrollbar::setMinimum(qreal value) { - this->minimum = value; + this->minimum_ = value; updateScroll(); } void Scrollbar::setLargeChange(qreal value) { - this->largeChange = value; + this->largeChange_ = value; updateScroll(); } void Scrollbar::setSmallChange(qreal value) { - this->smallChange = value; + this->smallChange_ = value; updateScroll(); } @@ -96,70 +104,72 @@ void Scrollbar::setDesiredValue(qreal value, bool animated) { auto app = getApp(); animated &= app->settings->enableSmoothScrolling.getValue(); - value = std::max(this->minimum, std::min(this->maximum - this->largeChange, value)); + value = std::max(this->minimum_, std::min(this->maximum_ - this->largeChange_, value)); - if (std::abs(this->desiredValue + this->smoothScrollingOffset - value) > 0.0001) { + if (std::abs(this->desiredValue_ + this->smoothScrollingOffset_ - value) > 0.0001) { if (animated) { - this->currentValueAnimation.stop(); - this->currentValueAnimation.setStartValue(this->currentValue + - this->smoothScrollingOffset); + this->currentValueAnimation_.stop(); + this->currentValueAnimation_.setStartValue(this->currentValue_ + + this->smoothScrollingOffset_); // if (((this->getMaximum() - this->getLargeChange()) - value) <= 0.01) { // value += 1; // } - this->currentValueAnimation.setEndValue(value); - this->smoothScrollingOffset = 0; - this->atBottom = ((this->getMaximum() - this->getLargeChange()) - value) <= 0.0001; - this->currentValueAnimation.start(); + this->currentValueAnimation_.setEndValue(value); + this->smoothScrollingOffset_ = 0; + this->atBottom_ = ((this->getMaximum() - this->getLargeChange()) - value) <= 0.0001; + this->currentValueAnimation_.start(); } else { - if (this->currentValueAnimation.state() != QPropertyAnimation::Running) { - this->smoothScrollingOffset = 0; - this->desiredValue = value; - this->currentValueAnimation.stop(); - this->atBottom = ((this->getMaximum() - this->getLargeChange()) - value) <= 0.0001; + if (this->currentValueAnimation_.state() != QPropertyAnimation::Running) { + this->smoothScrollingOffset_ = 0; + this->desiredValue_ = value; + this->currentValueAnimation_.stop(); + this->atBottom_ = ((this->getMaximum() - this->getLargeChange()) - value) <= 0.0001; setCurrentValue(value); } } } - this->smoothScrollingOffset = 0; - this->desiredValue = value; + this->smoothScrollingOffset_ = 0; + this->desiredValue_ = value; + + this->desiredValueChanged_.invoke(); } qreal Scrollbar::getMaximum() const { - return this->maximum; + return this->maximum_; } qreal Scrollbar::getMinimum() const { - return this->minimum; + return this->minimum_; } qreal Scrollbar::getLargeChange() const { - return this->largeChange; + return this->largeChange_; } qreal Scrollbar::getSmallChange() const { - return this->smallChange; + return this->smallChange_; } qreal Scrollbar::getDesiredValue() const { - return this->desiredValue + this->smoothScrollingOffset; + return this->desiredValue_ + this->smoothScrollingOffset_; } qreal Scrollbar::getCurrentValue() const { - return this->currentValue; + return this->currentValue_; } void Scrollbar::offset(qreal value) { - if (this->currentValueAnimation.state() == QPropertyAnimation::Running) { - this->smoothScrollingOffset += value; + if (this->currentValueAnimation_.state() == QPropertyAnimation::Running) { + this->smoothScrollingOffset_ += value; } else { this->setDesiredValue(this->getDesiredValue() + value); } @@ -167,19 +177,25 @@ void Scrollbar::offset(qreal value) pajlada::Signals::NoArgSignal &Scrollbar::getCurrentValueChanged() { - return this->currentValueChanged; + return this->currentValueChanged_; +} + +pajlada::Signals::NoArgSignal &Scrollbar::getDesiredValueChanged() +{ + return this->desiredValueChanged_; } void Scrollbar::setCurrentValue(qreal value) { - value = std::max(this->minimum, std::min(this->maximum - this->largeChange, - value + this->smoothScrollingOffset)); + value = std::max(this->minimum_, std::min(this->maximum_ - this->largeChange_, + value + this->smoothScrollingOffset_)); - if (std::abs(this->currentValue - value) > 0.000001) { - this->currentValue = value; + if (std::abs(this->currentValue_ - value) > 0.000001) { + qDebug() << "setCurrentValue"; + this->currentValue_ = value; this->updateScroll(); - this->currentValueChanged.invoke(); + this->currentValueChanged_.invoke(); this->update(); } @@ -198,7 +214,7 @@ void Scrollbar::paintEvent(QPaintEvent *) { auto *app = getApp(); - bool mouseOver = this->mouseOverIndex != -1; + bool mouseOver = this->mouseOverIndex_ != -1; int xOffset = mouseOver ? 0 : width() - int(4 * this->getScale()); QPainter painter(this); @@ -210,19 +226,19 @@ void Scrollbar::paintEvent(QPaintEvent *) // this->buttonHeight), // this->themeManager->ScrollbarArrow); - this->thumbRect.setX(xOffset); + this->thumbRect_.setX(xOffset); // mouse over thumb - if (this->mouseDownIndex == 2) { - painter.fillRect(this->thumbRect, this->themeManager->scrollbars.thumbSelected); + if (this->mouseDownIndex_ == 2) { + painter.fillRect(this->thumbRect_, this->themeManager->scrollbars.thumbSelected); } // mouse not over thumb else { - painter.fillRect(this->thumbRect, this->themeManager->scrollbars.thumb); + painter.fillRect(this->thumbRect_, this->themeManager->scrollbars.thumb); } // draw highlights - auto snapshot = this->highlights.getSnapshot(); + auto snapshot = this->getHighlightSnapshot(); size_t snapshotLength = snapshot.getLength(); if (snapshotLength == 0) { @@ -272,49 +288,49 @@ void Scrollbar::resizeEvent(QResizeEvent *) void Scrollbar::mouseMoveEvent(QMouseEvent *event) { - if (this->mouseDownIndex == -1) { + if (this->mouseDownIndex_ == -1) { int y = event->pos().y(); - auto oldIndex = this->mouseOverIndex; + auto oldIndex = this->mouseOverIndex_; - if (y < this->buttonHeight) { - this->mouseOverIndex = 0; - } else if (y < this->thumbRect.y()) { - this->mouseOverIndex = 1; - } else if (this->thumbRect.contains(2, y)) { - this->mouseOverIndex = 2; - } else if (y < height() - this->buttonHeight) { - this->mouseOverIndex = 3; + if (y < this->buttonHeight_) { + this->mouseOverIndex_ = 0; + } else if (y < this->thumbRect_.y()) { + this->mouseOverIndex_ = 1; + } else if (this->thumbRect_.contains(2, y)) { + this->mouseOverIndex_ = 2; + } else if (y < height() - this->buttonHeight_) { + this->mouseOverIndex_ = 3; } else { - this->mouseOverIndex = 4; + this->mouseOverIndex_ = 4; } - if (oldIndex != this->mouseOverIndex) { + if (oldIndex != this->mouseOverIndex_) { update(); } - } else if (this->mouseDownIndex == 2) { - int delta = event->pos().y() - this->lastMousePosition.y(); + } else if (this->mouseDownIndex_ == 2) { + int delta = event->pos().y() - this->lastMousePosition_.y(); - setDesiredValue(this->desiredValue + qreal(delta) / this->trackHeight * this->maximum); + setDesiredValue(this->desiredValue_ + qreal(delta) / this->trackHeight_ * this->maximum_); } - this->lastMousePosition = event->pos(); + this->lastMousePosition_ = event->pos(); } void Scrollbar::mousePressEvent(QMouseEvent *event) { int y = event->pos().y(); - if (y < this->buttonHeight) { - this->mouseDownIndex = 0; - } else if (y < this->thumbRect.y()) { - this->mouseDownIndex = 1; - } else if (this->thumbRect.contains(2, y)) { - this->mouseDownIndex = 2; - } else if (y < height() - this->buttonHeight) { - this->mouseDownIndex = 3; + if (y < this->buttonHeight_) { + this->mouseDownIndex_ = 0; + } else if (y < this->thumbRect_.y()) { + this->mouseDownIndex_ = 1; + } else if (this->thumbRect_.contains(2, y)) { + this->mouseDownIndex_ = 2; + } else if (y < height() - this->buttonHeight_) { + this->mouseDownIndex_ = 3; } else { - this->mouseDownIndex = 4; + this->mouseDownIndex_ = 4; } } @@ -322,46 +338,48 @@ void Scrollbar::mouseReleaseEvent(QMouseEvent *event) { int y = event->pos().y(); - if (y < this->buttonHeight) { - if (this->mouseDownIndex == 0) { - setDesiredValue(this->desiredValue - this->smallChange, true); + if (y < this->buttonHeight_) { + if (this->mouseDownIndex_ == 0) { + setDesiredValue(this->desiredValue_ - this->smallChange_, true); } - } else if (y < this->thumbRect.y()) { - if (this->mouseDownIndex == 1) { - setDesiredValue(this->desiredValue - this->smallChange, true); + } else if (y < this->thumbRect_.y()) { + if (this->mouseDownIndex_ == 1) { + setDesiredValue(this->desiredValue_ - this->smallChange_, true); } - } else if (this->thumbRect.contains(2, y)) { + } else if (this->thumbRect_.contains(2, y)) { // do nothing - } else if (y < height() - this->buttonHeight) { - if (this->mouseDownIndex == 3) { - setDesiredValue(this->desiredValue + this->smallChange, true); + } else if (y < height() - this->buttonHeight_) { + if (this->mouseDownIndex_ == 3) { + setDesiredValue(this->desiredValue_ + this->smallChange_, true); } } else { - if (this->mouseDownIndex == 4) { - setDesiredValue(this->desiredValue + this->smallChange, true); + if (this->mouseDownIndex_ == 4) { + setDesiredValue(this->desiredValue_ + this->smallChange_, true); } } - this->mouseDownIndex = -1; + this->mouseDownIndex_ = -1; update(); } void Scrollbar::leaveEvent(QEvent *) { - this->mouseOverIndex = -1; + this->mouseOverIndex_ = -1; update(); } void Scrollbar::updateScroll() { - this->trackHeight = height() - this->buttonHeight - this->buttonHeight - MIN_THUMB_HEIGHT - 1; + this->trackHeight_ = + this->height() - this->buttonHeight_ - this->buttonHeight_ - MIN_THUMB_HEIGHT - 1; - this->thumbRect = QRect( - 0, int(this->currentValue / this->maximum * this->trackHeight) + 1 + this->buttonHeight, - width(), int(this->largeChange / this->maximum * this->trackHeight) + MIN_THUMB_HEIGHT); + this->thumbRect_ = QRect( + 0, int(this->currentValue_ / this->maximum_ * this->trackHeight_) + 1 + this->buttonHeight_, + this->width(), + int(this->largeChange_ / this->maximum_ * this->trackHeight_) + MIN_THUMB_HEIGHT); - update(); + this->update(); } } // namespace widgets diff --git a/src/widgets/scrollbar.hpp b/src/widgets/scrollbar.hpp index 21f7f9718..bc8e69630 100644 --- a/src/widgets/scrollbar.hpp +++ b/src/widgets/scrollbar.hpp @@ -24,9 +24,12 @@ public: Scrollbar(ChannelView *parent = nullptr); void addHighlight(ScrollbarHighlight highlight); - void addHighlightsAtStart(const std::vector &highlights); + void addHighlightsAtStart(const std::vector &highlights_); void replaceHighlight(size_t index, ScrollbarHighlight replacement); + void pauseHighlights(); + void unpauseHighlights(); + void scrollToBottom(bool animate = false); bool isAtBottom() const; @@ -41,14 +44,16 @@ public: qreal getSmallChange() const; qreal getDesiredValue() const; qreal getCurrentValue() const; + // offset the desired value without breaking smooth scolling void offset(qreal value); pajlada::Signals::NoArgSignal &getCurrentValueChanged(); + pajlada::Signals::NoArgSignal &getDesiredValueChanged(); void setCurrentValue(qreal value); void printCurrentState(const QString &prefix = QString()) const; - Q_PROPERTY(qreal desiredValue READ getDesiredValue WRITE setDesiredValue) + Q_PROPERTY(qreal desiredValue_ READ getDesiredValue WRITE setDesiredValue) protected: void paintEvent(QPaintEvent *) override; @@ -59,34 +64,38 @@ protected: void leaveEvent(QEvent *) override; private: - Q_PROPERTY(qreal currentValue READ getCurrentValue WRITE setCurrentValue) + Q_PROPERTY(qreal currentValue_ READ getCurrentValue WRITE setCurrentValue) - QMutex mutex; + QMutex mutex_; - QPropertyAnimation currentValueAnimation; + QPropertyAnimation currentValueAnimation_; - messages::LimitedQueue highlights; + messages::LimitedQueue highlights_; + bool highlightsPaused_{false}; + messages::LimitedQueueSnapshot highlightSnapshot_; + messages::LimitedQueueSnapshot getHighlightSnapshot(); - bool atBottom = false; + bool atBottom_{false}; - int mouseOverIndex = -1; - int mouseDownIndex = -1; - QPoint lastMousePosition; + int mouseOverIndex_ = -1; + int mouseDownIndex_ = -1; + QPoint lastMousePosition_; - int buttonHeight = 0; - int trackHeight = 100; + int buttonHeight_ = 0; + int trackHeight_ = 100; - QRect thumbRect; + QRect thumbRect_; - qreal maximum = 0; - qreal minimum = 0; - qreal largeChange = 0; - qreal smallChange = 5; - qreal desiredValue = 0; - qreal currentValue = 0; - qreal smoothScrollingOffset = 0; + qreal maximum_ = 0; + qreal minimum_ = 0; + qreal largeChange_ = 0; + qreal smallChange_ = 5; + qreal desiredValue_ = 0; + qreal currentValue_ = 0; + qreal smoothScrollingOffset_ = 0; - pajlada::Signals::NoArgSignal currentValueChanged; + pajlada::Signals::NoArgSignal currentValueChanged_; + pajlada::Signals::NoArgSignal desiredValueChanged_; void updateScroll(); };