diff --git a/chatterino.pro b/chatterino.pro index 3b704d192..068fdf9e1 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -170,7 +170,8 @@ HEADERS += \ src/widgets/splitcontainer.hpp \ src/widgets/helper/droppreview.hpp \ src/widgets/helper/splitcolumn.hpp \ - src/util/irchelpers.hpp + src/util/irchelpers.hpp \ + src/util/helpers.hpp PRECOMPILED_HEADER = diff --git a/src/channel.cpp b/src/channel.cpp index 51b599b44..da7643d62 100644 --- a/src/channel.cpp +++ b/src/channel.cpp @@ -38,6 +38,13 @@ void Channel::addMessage(std::shared_ptr message) { std::shared_ptr deleted; + const QString &username = message->username; + + if (!username.isEmpty()) { + // TODO: Add recent chatters display name. This should maybe be a setting + this->addRecentChatter(username); + } + // if (_loggingChannel.get() != nullptr) { // _loggingChannel->append(message); // } @@ -49,6 +56,24 @@ void Channel::addMessage(std::shared_ptr message) this->messageAppended(message); } +void Channel::addRecentChatter(const QString &username) +{ + std::lock_guard lock(this->recentChattersMutex); + + this->recentChatters.insert(username); +} + +std::set Channel::getUsernamesForCompletions() +{ + std::set usernames; + + this->recentChattersMutex.lock(); + usernames.insert(this->recentChatters.begin(), this->recentChatters.end()); + this->recentChattersMutex.unlock(); + + return usernames; +} + bool Channel::canSendMessage() const { return false; diff --git a/src/channel.hpp b/src/channel.hpp index ceca68f28..685503dbf 100644 --- a/src/channel.hpp +++ b/src/channel.hpp @@ -13,6 +13,7 @@ #include #include +#include namespace chatterino { namespace messages { @@ -30,16 +31,21 @@ public: virtual bool isEmpty() const; messages::LimitedQueueSnapshot getMessageSnapshot(); - // methods void addMessage(messages::SharedMessage message); + void addRecentChatter(const QString &username); + + std::set getUsernamesForCompletions(); + QString name; QStringList modList; + std::mutex recentChattersMutex; + std::set recentChatters; + virtual bool canSendMessage() const; virtual void sendMessage(const QString &message); private: - // variables messages::LimitedQueue messages; // std::shared_ptr loggingChannel; diff --git a/src/channelmanager.cpp b/src/channelmanager.cpp index 6371de46e..3df5e97e4 100644 --- a/src/channelmanager.cpp +++ b/src/channelmanager.cpp @@ -5,6 +5,8 @@ using namespace chatterino::twitch; namespace chatterino { +ChannelManager *ChannelManager::instance = nullptr; + ChannelManager::ChannelManager(WindowManager &_windowManager, IrcManager &_ircManager) : windowManager(_windowManager) , ircManager(_ircManager) @@ -12,6 +14,7 @@ ChannelManager::ChannelManager(WindowManager &_windowManager, IrcManager &_ircMa , mentionsChannel(new TwitchChannel(_ircManager, "/mentions", true)) , emptyChannel(new TwitchChannel(_ircManager, "", true)) { + ChannelManager::instance = this; } const std::vector> ChannelManager::getItems() diff --git a/src/channelmanager.hpp b/src/channelmanager.hpp index 0484360c4..02200e646 100644 --- a/src/channelmanager.hpp +++ b/src/channelmanager.hpp @@ -16,6 +16,8 @@ class ChannelManager public: explicit ChannelManager(WindowManager &_windowManager, IrcManager &_ircManager); + static ChannelManager *instance; + WindowManager &windowManager; IrcManager &ircManager; diff --git a/src/completionmanager.cpp b/src/completionmanager.cpp index 51dfa41a3..520cf1d6e 100644 --- a/src/completionmanager.cpp +++ b/src/completionmanager.cpp @@ -1,11 +1,12 @@ #include "completionmanager.hpp" +#include "channelmanager.hpp" #include "common.hpp" #include "debug/log.hpp" #include "emotemanager.hpp" namespace chatterino { -CompletionModel::CompletionModel(const std::string &_channelName) +CompletionModel::CompletionModel(const QString &_channelName) : channelName(_channelName) { } @@ -39,14 +40,14 @@ void CompletionModel::refresh() // Channel-specific: BTTV Channel Emotes std::vector &bttvChannelEmoteCodes = - emoteManager.bttvChannelEmoteCodes[this->channelName]; + emoteManager.bttvChannelEmoteCodes[this->channelName.toStdString()]; for (const auto &m : bttvChannelEmoteCodes) { this->addString(m); } // Channel-specific: FFZ Channel Emotes std::vector &ffzChannelEmoteCodes = - emoteManager.ffzChannelEmoteCodes[this->channelName]; + emoteManager.ffzChannelEmoteCodes[this->channelName.toStdString()]; for (const auto &m : ffzChannelEmoteCodes) { this->addString(m); } @@ -57,7 +58,17 @@ void CompletionModel::refresh() this->addString(":" + m + ":"); } - // TODO: Add Channel-specific: Usernames + // Channel-specific: Usernames + auto *channelManager = ChannelManager::instance; + auto c = channelManager->getTwitchChannel(this->channelName); + if (!c) { + return; + } + auto usernames = c->getUsernamesForCompletions(); + for (const auto &username : usernames) { + this->addString(username); + this->addString('@' + username); + } } void CompletionModel::addString(const std::string &str) @@ -66,6 +77,12 @@ void CompletionModel::addString(const std::string &str) this->emotes.push_back(qS(str) + " "); } +void CompletionModel::addString(const QString &str) +{ + // Always add a space at the end of completions + this->emotes.push_back(str + " "); +} + CompletionModel *CompletionManager::createModel(const std::string &channelName) { auto it = this->models.find(channelName); @@ -73,7 +90,7 @@ CompletionModel *CompletionManager::createModel(const std::string &channelName) return it->second; } - CompletionModel *ret = new CompletionModel(channelName); + CompletionModel *ret = new CompletionModel(qS(channelName)); this->models[channelName] = ret; return ret; diff --git a/src/completionmanager.hpp b/src/completionmanager.hpp index 778b80706..e35c027be 100644 --- a/src/completionmanager.hpp +++ b/src/completionmanager.hpp @@ -11,7 +11,7 @@ namespace chatterino { class CompletionModel : public QAbstractListModel { public: - CompletionModel(const std::string &_channelName); + CompletionModel(const QString &_channelName); virtual int columnCount(const QModelIndex & /*parent*/) const override { @@ -33,10 +33,11 @@ public: private: void addString(const std::string &str); + void addString(const QString &str); QVector emotes; - std::string channelName; + QString channelName; }; class CompletionManager diff --git a/src/logging/loggingchannel.cpp b/src/logging/loggingchannel.cpp index b38deb6c3..d3fe1dd17 100644 --- a/src/logging/loggingchannel.cpp +++ b/src/logging/loggingchannel.cpp @@ -37,7 +37,7 @@ void Channel::append(std::shared_ptr message) str.append('['); str.append(now.toString("HH:mm:ss")); str.append("] "); - str.append(message->getUserName()); + str.append(message->username); str.append(": "); str.append(message->getContent()); str.append('\n'); diff --git a/src/messages/message.cpp b/src/messages/message.cpp index 0e675b1b4..d0b84f174 100644 --- a/src/messages/message.cpp +++ b/src/messages/message.cpp @@ -36,11 +36,6 @@ int Message::getTimeoutCount() const return this->timeoutCount; } -const QString &Message::getUserName() const -{ - return this->userName; -} - const QString &Message::getDisplayName() const { return this->displayName; diff --git a/src/messages/message.hpp b/src/messages/message.hpp index cb49366f3..670ea744a 100644 --- a/src/messages/message.hpp +++ b/src/messages/message.hpp @@ -22,7 +22,6 @@ public: void setHighlight(bool value); const QString &getTimeoutUser() const; int getTimeoutCount() const; - const QString &getUserName() const; const QString &getDisplayName() const; const QString &getContent() const; const std::chrono::time_point &getParseTime() const; @@ -30,6 +29,8 @@ public: bool isDisabled() const; const QString &getId() const; + QString username; + const QString text; bool centered = false; @@ -57,7 +58,6 @@ private: bool disabled = false; std::chrono::time_point parseTime; - QString userName = ""; QString displayName = ""; QString content; QString id = ""; diff --git a/src/messages/messagebuilder.hpp b/src/messages/messagebuilder.hpp index adda6e0f5..ec61994f2 100644 --- a/src/messages/messagebuilder.hpp +++ b/src/messages/messagebuilder.hpp @@ -26,8 +26,10 @@ public: QString originalMessage; -private: +protected: std::shared_ptr message; + +private: std::vector _words; bool highlight = false; std::chrono::time_point _parseTime; diff --git a/src/twitch/twitchmessagebuilder.cpp b/src/twitch/twitchmessagebuilder.cpp index a6e3b70d9..e028a8713 100644 --- a/src/twitch/twitchmessagebuilder.cpp +++ b/src/twitch/twitchmessagebuilder.cpp @@ -287,6 +287,8 @@ void TwitchMessageBuilder::parseUsername() if (this->userName.isEmpty()) { this->userName = this->tags.value(QLatin1String("login")).toString(); } + + this->message->username = this->userName; } void TwitchMessageBuilder::appendUsername() diff --git a/src/util/helpers.hpp b/src/util/helpers.hpp new file mode 100644 index 000000000..8435a3494 --- /dev/null +++ b/src/util/helpers.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace chatterino { + +template +auto fS(Args &&... args) -> decltype(fmt::format(std::forward(args)...)) +{ + return fmt::format(std::forward(args)...); +} + +} // namespace chatterino diff --git a/src/widgets/window.cpp b/src/widgets/window.cpp index bbd805496..28859f3d3 100644 --- a/src/widgets/window.cpp +++ b/src/widgets/window.cpp @@ -6,23 +6,22 @@ #include "widgets/settingsdialog.hpp" #include "widgets/split.hpp" -#include -#include #include #include #include -#include namespace chatterino { namespace widgets { -Window::Window(ChannelManager &_channelManager, ColorScheme &_colorScheme, bool _isMainWindow) +Window::Window(const QString &_windowName, ChannelManager &_channelManager, + ColorScheme &_colorScheme, bool _isMainWindow) : BaseWidget(_colorScheme, nullptr) + , windowName(_windowName) + , windowGeometry(this->windowName.toStdString()) , channelManager(_channelManager) , colorScheme(_colorScheme) , notebook(this->channelManager, this, _isMainWindow) , dpi(this->getDpiMultiplier()) -// , windowGeometry("/windows/0/geometry") { this->initAsWindow(); @@ -45,14 +44,7 @@ Window::Window(ChannelManager &_channelManager, ColorScheme &_colorScheme, bool this->refreshTheme(); - if (/*this->windowGeometry->isFilled()*/ false) { - // Load geometry from settings file - // this->setGeometry(this->windowGeometry.getValueRef()); - } else { - // Set default geometry - // Default position is in the middle of the current monitor or the primary monitor - this->resize(1280, 800); - } + this->loadGeometry(); // Initialize program-wide hotkeys { @@ -120,8 +112,12 @@ Notebook &Window::getNotebook() void Window::closeEvent(QCloseEvent *) { - // Save closing window position - // this->windowGeometry = this->geometry(); + const QRect &geom = this->geometry(); + + this->windowGeometry.x = geom.x(); + this->windowGeometry.y = geom.y(); + this->windowGeometry.width = geom.width(); + this->windowGeometry.height = geom.height(); this->closed(); } @@ -133,5 +129,19 @@ void Window::refreshTheme() this->setPalette(palette); } +void Window::loadGeometry() +{ + if (!this->windowGeometry.x.isDefaultValue() && !this->windowGeometry.y.isDefaultValue()) { + this->move(this->windowGeometry.x, this->windowGeometry.y); + } + + if (!this->windowGeometry.width.isDefaultValue() && + !this->windowGeometry.height.isDefaultValue()) { + this->resize(this->windowGeometry.width, this->windowGeometry.height); + } else { + this->resize(1280, 800); + } +} + } // namespace widgets } // namespace chatterino diff --git a/src/widgets/window.hpp b/src/widgets/window.hpp index a9c747e3e..4d5c7a76a 100644 --- a/src/widgets/window.hpp +++ b/src/widgets/window.hpp @@ -1,5 +1,6 @@ #pragma once +#include "util/helpers.hpp" #include "widgets/basewidget.hpp" #include "widgets/notebook.hpp" #include "widgets/titlebar.hpp" @@ -10,8 +11,7 @@ #include #include -#include -#include +#include namespace chatterino { @@ -21,12 +21,32 @@ class CompletionManager; namespace widgets { +struct WindowGeometry { + WindowGeometry(const std::string &key) + : x(fS("/windows/{}/geometry/x", key)) + , y(fS("/windows/{}/geometry/y", key)) + , width(fS("/windows/{}/geometry/width", key)) + , height(fS("/windows/{}/geometry/height", key)) + { + } + + pajlada::Settings::Setting x; + pajlada::Settings::Setting y; + pajlada::Settings::Setting width; + pajlada::Settings::Setting height; +}; + class Window : public BaseWidget { Q_OBJECT + QString windowName; + + WindowGeometry windowGeometry; + public: - explicit Window(ChannelManager &_channelManager, ColorScheme &_colorScheme, bool isMainWindow); + explicit Window(const QString &_windowName, ChannelManager &_channelManager, + ColorScheme &_colorScheme, bool isMainWindow); void repaintVisibleChatWidgets(Channel *channel = nullptr); @@ -48,6 +68,8 @@ private: virtual void refreshTheme() override; + void loadGeometry(); + ChannelManager &channelManager; ColorScheme &colorScheme; @@ -55,92 +77,6 @@ private: bool loaded = false; TitleBar titleBar; - /* - class QRectWrapper : public pajlada::Settings::ISettingData, public QRect - { - public: - QRectWrapper() - : QRect(-1, -1, -1, -1) - { - } - - pajlada::Signals::Signal valueChanged; - - const QRectWrapper &getValueRef() const - { - return *this; - } - - virtual rapidjson::Value marshalInto(rapidjson::Document &d) override - { - using namespace pajlada::Settings; - - rapidjson::Value obj(rapidjson::kObjectType); - - auto _x = serializeToJSON::serialize(this->x(), d.GetAllocator()); - auto _y = serializeToJSON::serialize(this->y(), d.GetAllocator()); - auto _width = serializeToJSON::serialize(this->width(), d.GetAllocator()); - auto _height = serializeToJSON::serialize(this->height(), d.GetAllocator()); - - obj.AddMember("x", _x, d.GetAllocator()); - obj.AddMember("y", _y, d.GetAllocator()); - obj.AddMember("width", _width, d.GetAllocator()); - obj.AddMember("height", _height, d.GetAllocator()); - - return obj; - } - - virtual bool unmarshalFrom(rapidjson::Document &document) override - { - using namespace pajlada::Settings; - - auto vXp = this->getValueWithSuffix("/x", document); - auto vYp = this->getValueWithSuffix("/y", document); - auto vWidthp = this->getValueWithSuffix("/width", document); - auto vHeightp = this->getValueWithSuffix("/height", document); - if (vXp != nullptr) { - this->setX(deserializeJSON::deserialize(*vXp)); - this->filled = true; - } - if (vYp != nullptr) { - this->setY(deserializeJSON::deserialize(*vYp)); - this->filled = true; - } - if (vWidthp != nullptr) { - this->setWidth(deserializeJSON::deserialize(*vWidthp)); - this->filled = true; - } - if (vHeightp != nullptr) { - this->setHeight(deserializeJSON::deserialize(*vHeightp)); - this->filled = true; - } - - return true; - } - - virtual void registerDocument(rapidjson::Document &d) override - { - this->valueChanged.connect([this, &d](const auto &) { - this->marshalInto(d); // - }); - } - - QRectWrapper &operator=(const QRect &rhs) - { - static_cast(*this) = rhs; - - return *this; - } - - void setValue(const QRect &rhs) - { - static_cast(*this) = rhs; - } - }; - */ - - // pajlada::Settings::Setting windowGeometry; - friend class Notebook; }; diff --git a/src/windowmanager.cpp b/src/windowmanager.cpp index 80cd9b4dc..bb3719d1e 100644 --- a/src/windowmanager.cpp +++ b/src/windowmanager.cpp @@ -22,7 +22,7 @@ WindowManager::WindowManager(ChannelManager &_channelManager, ColorScheme &_colo void WindowManager::initMainWindow() { this->selectedWindow = this->mainWindow = - new widgets::Window(this->channelManager, this->colorScheme, true); + new widgets::Window("main", this->channelManager, this->colorScheme, true); } static const std::string &getSettingsPath() @@ -68,7 +68,7 @@ widgets::Window &WindowManager::getSelectedWindow() widgets::Window &WindowManager::createWindow() { - auto *window = new widgets::Window(this->channelManager, this->colorScheme, false); + auto *window = new widgets::Window("external", this->channelManager, this->colorScheme, false); window->loadDefaults();