From 859f4aefcbd8076a9ca4f170fc5a39d77d81188a Mon Sep 17 00:00:00 2001 From: fourtf Date: Wed, 25 Apr 2018 14:49:30 +0200 Subject: [PATCH] added new TupleTableModel for settingsdialog --- browser_ext/background.js | 60 +++-- chatterino.pro | 12 +- src/messages/highlightphrase.hpp | 17 +- src/messages/layouts/messagelayout.cpp | 2 +- .../layouts/messagelayoutcontainer.cpp | 12 +- src/singletons/nativemessagingmanager.cpp | 40 +++- src/util/tupletablemodel.cpp | 7 + src/util/tupletablemodel.hpp | 211 ++++++++++++++++++ src/widgets/attachedwindow.cpp | 112 ++++++++++ src/widgets/attachedwindow.hpp | 48 ++++ src/widgets/basewidget.hpp | 2 +- src/widgets/helper/channelview.cpp | 10 +- .../settingspages/highlightingpage.cpp | 195 ++++------------ .../settingspages/highlightingpage.hpp | 8 +- src/widgets/split.cpp | 53 +++-- src/widgets/split.hpp | 15 +- src/widgets/window.cpp | 4 + src/widgets/window.hpp | 2 +- 18 files changed, 574 insertions(+), 236 deletions(-) create mode 100644 src/util/tupletablemodel.cpp create mode 100644 src/util/tupletablemodel.hpp create mode 100644 src/widgets/attachedwindow.cpp create mode 100644 src/widgets/attachedwindow.hpp diff --git a/browser_ext/background.js b/browser_ext/background.js index 85846c7fe..5e34c3a4d 100644 --- a/browser_ext/background.js +++ b/browser_ext/background.js @@ -9,19 +9,18 @@ const ignoredPages = { }; const appName = "com.chatterino.chatterino"; - -/// Connect to port - let port = null; + +/// Connect to port function connectPort() { port = chrome.runtime.connectNative("com.chatterino.chatterino"); console.log("port connected"); - port.onMessage.addListener(function(msg) { + port.onMessage.addListener(function (msg) { console.log(msg); }); - port.onDisconnect.addListener(function() { + port.onDisconnect.addListener(function () { console.log("port disconnected"); port = null; @@ -39,8 +38,8 @@ function getPort() { } } -/// Tab listeners +/// Tab listeners chrome.tabs.onActivated.addListener((activeInfo) => { chrome.tabs.get(activeInfo.tabId, (tab) => { if (!tab) @@ -49,7 +48,7 @@ chrome.tabs.onActivated.addListener((activeInfo) => { if (!tab.url) return; - matchUrl(tab.url); + matchUrl(tab.url, tab); }); }); @@ -57,32 +56,47 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { if (!tab.highlighted) return; - matchUrl(changeInfo.url); + matchUrl(changeInfo.url, tab); }); /// Misc - -function matchUrl(url) { +function matchUrl(url, tab) { if (!url) return; const match = url.match(/^https?:\/\/(www\.)?twitch.tv\/([a-zA-Z0-9]+)\/?$/); - if (match) { - const channelName = match[2]; + let channelName; - if (!ignoredPages[channelName]) { - selectChannel(channelName); + console.log(tab); + + if (match && (channelName = match[2], !ignoredPages[channelName])) { + console.log("channelName " + channelName); + console.log("winId " + tab.windowId); + + chrome.windows.get(tab.windowId, {}, (window) => { + let yOffset = window.height - tab.height; + + let port = getPort(); + if (port) { + port.postMessage({ + action: "select", + attach: true, + type: "twitch", + name: channelName, + winId: "" + tab.windowId, + yOffset: yOffset + }); + } + }); + } else { + let port = getPort(); + if (port) { + port.postMessage({ + action: "detach", + winId: "" + tab.windowId + }) } } } - -function selectChannel(channelName) { - console.log("select" + channelName); - - let port = getPort(); - if (port) { - port.postMessage({action: "select", type: "twitch", name: channelName}); - } -} diff --git a/chatterino.pro b/chatterino.pro index 679c6d80f..49afc8881 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -181,7 +181,9 @@ SOURCES += \ src/singletons/helper/pubsubactions.cpp \ src/widgets/selectchanneldialog.cpp \ src/singletons/updatemanager.cpp \ - src/widgets/lastruncrashdialog.cpp + src/widgets/lastruncrashdialog.cpp \ + src/widgets/attachedwindow.cpp \ + src/util/tupletablemodel.cpp HEADERS += \ src/precompiled_header.hpp \ @@ -305,7 +307,9 @@ HEADERS += \ src/singletons/helper/pubsubactions.hpp \ src/widgets/selectchanneldialog.hpp \ src/singletons/updatemanager.hpp \ - src/widgets/lastruncrashdialog.hpp + src/widgets/lastruncrashdialog.hpp \ + src/widgets/attachedwindow.hpp \ + src/util/tupletablemodel.hpp RESOURCES += \ resources/resources.qrc @@ -359,10 +363,6 @@ win32-msvc* { #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 -win32::exists(C:\fourtf) { - DEFINES += "OHHEYITSFOURTF" -} - linux { QMAKE_LFLAGS += -lrt } diff --git a/src/messages/highlightphrase.hpp b/src/messages/highlightphrase.hpp index f5ea0f29b..93721b1c8 100644 --- a/src/messages/highlightphrase.hpp +++ b/src/messages/highlightphrase.hpp @@ -10,13 +10,14 @@ namespace messages { struct HighlightPhrase { QString key; - bool sound; bool alert; + bool sound; + bool regex; - bool operator==(const HighlightPhrase &rhs) const + bool operator==(const HighlightPhrase &other) const { - return std::tie(this->key, this->sound, this->alert) == - std::tie(rhs.key, rhs.sound, rhs.alert); + return std::tie(this->key, this->sound, this->alert, this->regex) == + std::tie(other.key, other.sound, other.alert, other.regex); } }; } // namespace messages @@ -35,6 +36,7 @@ struct Serialize { AddMember(ret, "key", value.key, a); AddMember(ret, "alert", value.alert, a); AddMember(ret, "sound", value.sound, a); + AddMember(ret, "regex", value.regex, a); return ret; } @@ -70,6 +72,13 @@ struct Deserialize { } } + if (value.HasMember("regex")) { + const rapidjson::Value ®ex = value["regex"]; + if (regex.IsBool()) { + ret.regex = regex.GetBool(); + } + } + return ret; } }; diff --git a/src/messages/layouts/messagelayout.cpp b/src/messages/layouts/messagelayout.cpp index db9efa6a4..025ffaea6 100644 --- a/src/messages/layouts/messagelayout.cpp +++ b/src/messages/layouts/messagelayout.cpp @@ -198,7 +198,7 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int messageIndex, Selection &s // draw message this->container.paintElements(painter); -#ifdef OHHEYITSFOURTF +#ifdef FOURTF // debug painter.setPen(QColor(255, 0, 0)); painter.drawRect(buffer->rect().x(), buffer->rect().y(), buffer->rect().width() - 1, diff --git a/src/messages/layouts/messagelayoutcontainer.cpp b/src/messages/layouts/messagelayoutcontainer.cpp index c6e6f9db4..52fd29323 100644 --- a/src/messages/layouts/messagelayoutcontainer.cpp +++ b/src/messages/layouts/messagelayoutcontainer.cpp @@ -191,7 +191,7 @@ MessageLayoutElement *MessageLayoutContainer::getElementAt(QPoint point) void MessageLayoutContainer::paintElements(QPainter &painter) { for (const std::unique_ptr &element : this->elements) { -#ifdef OHHEYITSFOURTF +#ifdef FOURTF painter.setPen(QColor(0, 255, 0)); painter.drawRect(element->getRect()); #endif @@ -214,12 +214,14 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex, QColor selectionColor = themeManager.messages.selection; // don't draw anything - if (selection.selectionMin.messageIndex > messageIndex || selection.selectionMax.messageIndex < messageIndex) { + if (selection.selectionMin.messageIndex > messageIndex || + selection.selectionMax.messageIndex < messageIndex) { return; } // fully selected - if (selection.selectionMin.messageIndex < messageIndex && selection.selectionMax.messageIndex > messageIndex) { + if (selection.selectionMin.messageIndex < messageIndex && + selection.selectionMax.messageIndex > messageIndex) { for (Line &line : this->lines) { QRect rect = line.rect; @@ -267,8 +269,8 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex, int c = this->elements[i]->getSelectionIndexCount(); if (index + c > selection.selectionMax.charIndex) { - r = this->elements[i]->getXFromIndex(selection.selectionMax.charIndex - - index); + r = this->elements[i]->getXFromIndex( + selection.selectionMax.charIndex - index); break; } index += c; diff --git a/src/singletons/nativemessagingmanager.cpp b/src/singletons/nativemessagingmanager.cpp index 369d41ec8..9c3bb907d 100644 --- a/src/singletons/nativemessagingmanager.cpp +++ b/src/singletons/nativemessagingmanager.cpp @@ -16,6 +16,10 @@ namespace ipc = boost::interprocess; #ifdef Q_OS_WIN #include + +#include +#include "singletons/windowmanager.hpp" +#include "widgets/attachedwindow.hpp" #endif #include @@ -56,9 +60,14 @@ void NativeMessagingManager::registerHost() root_obj.insert("path", QCoreApplication::applicationFilePath()); root_obj.insert("type", "stdio"); + // chrome QJsonArray allowed_origins_arr = {"chrome-extension://aeicjepmjkgmbeohnchmpfjbpchogmjn/"}; root_obj.insert("allowed_origins", allowed_origins_arr); + // firefox + QJsonArray allowed_extensions = {"585a153c7e1ac5463478f25f8f12220e9097e716@temporary-addon"}; + root_obj.insert("allowed_extensions", allowed_extensions); + // save the manifest QString manifestPath = PathManager::getInstance().settingsFolderPath + "/native-messaging-manifest.json"; @@ -70,13 +79,11 @@ void NativeMessagingManager::registerHost() file.write(document.toJson()); file.flush(); -#ifdef XD #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 on #endif -#endif } void NativeMessagingManager::openGuiMessageQueue() @@ -135,22 +142,45 @@ void NativeMessagingManager::ReceiverThread::handleMessage(const QJsonObject &ro if (action == "select") { QString _type = root.value("type").toString(); + bool attach = root.value("attach").toBool(); QString name = root.value("name").toString(); + QString winId = root.value("winId").toString(); + int yOffset = root.value("yOffset").toInt(-1); - if (_type.isNull() || name.isNull()) { - qDebug() << "NM type or name missing"; + if (_type.isNull() || name.isNull() || winId.isNull()) { + qDebug() << "NM type, name or winId missing"; + attach = false; return; } if (_type == "twitch") { - util::postToThread([name] { + util::postToThread([name, attach, winId, yOffset] { auto &ts = providers::twitch::TwitchServer::getInstance(); ts.watchingChannel.update(ts.getOrAddChannel(name)); + + if (attach) { + auto *window = + widgets::AttachedWindow::get(::GetForegroundWindow(), winId, yOffset); + window->setChannel(ts.getOrAddChannel(name)); + window->show(); + } }); + } else { qDebug() << "NM unknown channel type"; } + } else if (action == "detach") { + QString winId = root.value("winId").toString(); + + if (winId.isNull()) { + qDebug() << "NM winId missing"; + return; + } + + util::postToThread([winId] { widgets::AttachedWindow::detach(winId); }); + } else { + qDebug() << "NM unknown action " + action; } } diff --git a/src/util/tupletablemodel.cpp b/src/util/tupletablemodel.cpp new file mode 100644 index 000000000..ee804e5c1 --- /dev/null +++ b/src/util/tupletablemodel.cpp @@ -0,0 +1,7 @@ +#include "tupletablemodel.hpp" + +namespace chatterino { +namespace util { + +} // namespace util +} // namespace chatterino diff --git a/src/util/tupletablemodel.hpp b/src/util/tupletablemodel.hpp new file mode 100644 index 000000000..5b7dd2906 --- /dev/null +++ b/src/util/tupletablemodel.hpp @@ -0,0 +1,211 @@ +#pragma once + +#include +#include + +#include + +#include + +namespace chatterino { +namespace util { + +namespace { + +template +struct TupleConverter { + template + static void tupleToVariants(const std::tuple &t, std::vector &row) + { + row[I - 1] = QVariant(std::get(t)); + TupleConverter::tupleToVariants(t, row); + } + + template + static void variantsToTuple(std::vector &row, std::tuple &t) + { + std::get(t) = (decltype(std::get(t))) row[I - 1]; + TupleConverter::variantsToTuple(row, t); + } +}; + +template <> +struct TupleConverter<0> { + template + static void tupleToVariants(const std::tuple &t, std::vector &row) + { + } + + template + static void variantsToTuple(std::vector &row, std::tuple &t) + { + } +}; + +} // namespace + +template +class TupleTableModel : public QAbstractTableModel +{ + std::vector> rows; + std::vector> titleData; + +public: + pajlada::Signals::NoArgSignal itemsChanged; + + TupleTableModel() + { + titleData.resize(sizeof...(Args)); + } + + void addRow(const std::tuple &row) + { + this->beginInsertRows(QModelIndex(), this->rows.size(), this->rows.size()); + std::vector variants; + variants.resize(sizeof...(Args)); + TupleConverter::tupleToVariants(row, variants); + this->rows.push_back(variants); + this->endInsertRows(); + this->itemsChanged.invoke(); + } + + void addRow(Args... args) + { + this->beginInsertRows(QModelIndex(), this->rows.size(), this->rows.size()); + std::vector variants; + variants.resize(sizeof...(Args)); + TupleConverter::tupleToVariants(std::tuple(args...), + variants); + this->rows.push_back(variants); + this->endInsertRows(); + this->itemsChanged.invoke(); + } + + std::tuple getRow(int index) + { + std::tuple row; + TupleConverter::variantsToTuple(this->rows[index], row); + return row; + } + + void removeRow(int index) + { + this->beginRemoveRows(QModelIndex(), index, index); + this->rows.erase(this->rows.begin() + index); + this->endRemoveRows(); + this->itemsChanged.invoke(); + } + + void setTitles(std::initializer_list titles) + { + int i = 0; + + for (const QString &title : titles) { + this->setHeaderData(i++, Qt::Horizontal, title, Qt::DisplayRole); + + if (i >= sizeof...(Args)) + break; + } + } + + int getRowCount() const + { + return this->rows.size(); + } + +protected: + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + return this->rows.size(); + } + + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override + { + return sizeof...(Args); + } + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override + { + QVariant data = this->rows[index.row()][index.column()]; + + switch (role) { + case Qt::DisplayRole: { + if (data.type() == QVariant::Bool) + return QVariant(); + else + return data; + } break; + case Qt::EditRole: { + return data; + } break; + case Qt::CheckStateRole: { + if (data.type() == QVariant::Bool) + return data; + else + return QVariant(); + } break; + } + return QVariant(); + } + + virtual bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole) override + { + QVariant data = this->rows[index.row()][index.column()]; + + switch (role) { + case (Qt::EditRole): { + this->rows[index.row()][index.column()] = value; + this->itemsChanged.invoke(); + return true; + } break; + case (Qt::CheckStateRole): { + if (data.type() == QVariant::Bool) { + this->rows[index.row()][index.column()] = !data.toBool(); + this->itemsChanged.invoke(); + return true; + } + } break; + } + + return false; + } + + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const + { + if (orientation != Qt::Horizontal) + return QVariant(); + if (section < 0 || section >= sizeof...(Args)) + return QVariant(); + + auto it = this->titleData[section].find(role); + return it == this->titleData[section].end() ? QVariant() : it.value(); + } + + virtual bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, + int role) + { + if (orientation != Qt::Horizontal) + return false; + if (section < 0 || section >= sizeof...(Args)) + return false; + + this->titleData[section][role] = value; + return true; + } + + virtual Qt::ItemFlags flags(const QModelIndex &index) const + { + QVariant data = this->rows[index.row()][index.column()]; + + if (data.type() == QVariant::Bool) { + return Qt::ItemIsUserCheckable | Qt::ItemIsEditable | Qt::ItemIsEnabled | + Qt::ItemIsSelectable; + } + + return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; + } +}; + +} // namespace util +} // namespace chatterino diff --git a/src/widgets/attachedwindow.cpp b/src/widgets/attachedwindow.cpp new file mode 100644 index 000000000..d6c0110fb --- /dev/null +++ b/src/widgets/attachedwindow.cpp @@ -0,0 +1,112 @@ +#include "attachedwindow.hpp" + +#include +#include + +#include "widgets/split.hpp" + +#include "Windows.h" +#pragma comment(lib, "Dwmapi.lib") + +namespace chatterino { +namespace widgets { + +AttachedWindow::AttachedWindow(void *_target, int _yOffset) + : target(_target) + , yOffset(_yOffset) + , QWidget(nullptr, Qt::FramelessWindowHint | Qt::Window) +{ + QLayout *layout = new QVBoxLayout(this); + layout->setMargin(0); + this->setLayout(layout); + + auto *split = new Split(singletons::ThemeManager::getInstance(), this); + this->ui.split = split; + split->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::MinimumExpanding); + layout->addWidget(split); +} + +AttachedWindow::~AttachedWindow() +{ + for (auto it = items.begin(); it != items.end(); it++) { + if (it->window == this) { + items.erase(it); + break; + } + } +} + +AttachedWindow *AttachedWindow::get(void *target, const QString &winId, int yOffset) +{ + for (Item &item : items) { + if (item.hwnd == target) { + return item.window; + } + } + + auto *window = new AttachedWindow(target, yOffset); + items.push_back(Item{target, window, winId}); + return window; +} + +void AttachedWindow::detach(const QString &winId) +{ + for (Item &item : items) { + if (item.winId == winId) { + item.window->deleteLater(); + } + } +} + +void AttachedWindow::setChannel(ChannelPtr channel) +{ + this->ui.split->setChannel(channel); +} + +void AttachedWindow::showEvent(QShowEvent *) +{ + attachToHwnd(this->target); +} + +void AttachedWindow::attachToHwnd(void *_hwnd) +{ + QTimer *timer = new QTimer(this); + timer->setInterval(1); + + HWND hwnd = (HWND)this->winId(); + HWND attached = (HWND)_hwnd; + QObject::connect(timer, &QTimer::timeout, [this, hwnd, attached, timer] { + ::SetLastError(0); + RECT xD; + ::GetWindowRect(attached, &xD); + + if (::GetLastError() != 0) { + timer->stop(); + timer->deleteLater(); + this->deleteLater(); + } + + HWND next = ::GetNextWindow(attached, GW_HWNDPREV); + + ::SetWindowPos(hwnd, next ? next : HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + ::MoveWindow(hwnd, xD.right - 360, xD.top + this->yOffset - 8, 360 - 8, + xD.bottom - xD.top - this->yOffset, false); + // ::MoveWindow(hwnd, xD.right - 360, xD.top + 82, 360 - 8, xD.bottom - xD.top - 82 - + // 8, + // false); + }); + timer->start(); +} + +// void AttachedWindow::nativeEvent(const QByteArray &eventType, void *message, long *result) +//{ +// MSG *msg = reinterpret_cast + +// case WM_NCCALCSIZE: { +// } +//} + +std::vector AttachedWindow::items; + +} // namespace widgets +} // namespace chatterino diff --git a/src/widgets/attachedwindow.hpp b/src/widgets/attachedwindow.hpp new file mode 100644 index 000000000..a161f4164 --- /dev/null +++ b/src/widgets/attachedwindow.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include "channel.hpp" +#include "widgets/split.hpp" + +namespace chatterino { +namespace widgets { + +class AttachedWindow : public QWidget +{ + AttachedWindow(void *target, int asdf); + +public: + ~AttachedWindow(); + + static AttachedWindow *get(void *target, const QString &winId, int yOffset); + static void detach(const QString &winId); + + void setChannel(ChannelPtr channel); + +protected: + virtual void showEvent(QShowEvent *) override; + // virtual void nativeEvent(const QByteArray &eventType, void *message, long *result) + // override; + +private: + void *target; + int yOffset; + + struct { + Split *split; + } ui; + + void attachToHwnd(void *hwnd); + + struct Item { + void *hwnd; + AttachedWindow *window; + QString winId; + }; + + static std::vector items; +}; + +} // namespace widgets +} // namespace chatterino diff --git a/src/widgets/basewidget.hpp b/src/widgets/basewidget.hpp index 4b1e3f5fb..068297e7c 100644 --- a/src/widgets/basewidget.hpp +++ b/src/widgets/basewidget.hpp @@ -30,7 +30,7 @@ public: QSize getScaleIndependantSize() const; int getScaleIndependantWidth() const; int getScaleIndependantHeight() const; - void setScaleIndependantSize(int width, int height); + void setScaleIndependantSize(int width, int yOffset); void setScaleIndependantSize(QSize); void setScaleIndependantWidth(int value); void setScaleIndependantHeight(int value); diff --git a/src/widgets/helper/channelview.cpp b/src/widgets/helper/channelview.cpp index 5e89545bd..628a2c0f2 100644 --- a/src/widgets/helper/channelview.cpp +++ b/src/widgets/helper/channelview.cpp @@ -98,7 +98,7 @@ ChannelView::ChannelView(BaseWidget *parent) // this->resizeEvent(e); // delete e; - this->scrollBar.resize(this->scrollBar.width(), height() + 1); + this->scrollBar.resize(this->scrollBar.width(), this->height() + 1); singletons::SettingManager::getInstance().showLastMessageIndicator.connect( [this](auto, auto) { this->update(); }, this->managedConnections); @@ -198,14 +198,14 @@ void ChannelView::actuallyLayoutMessages() y += message->getHeight(); - if (y >= height()) { + if (y >= this->height()) { break; } } } // layout the messages at the bottom to determine the scrollbar thumb size - int h = height() - 8; + int h = this->height() - 8; for (int i = (int)messagesSnapshot.getLength() - 1; i >= 0; i--) { auto *message = messagesSnapshot[i].get(); @@ -472,7 +472,7 @@ void ChannelView::updateLastReadMessage() void ChannelView::resizeEvent(QResizeEvent *) { - this->scrollBar.resize(this->scrollBar.width(), height()); + this->scrollBar.resize(this->scrollBar.width(), this->height()); this->scrollBar.move(this->width() - this->scrollBar.width(), 0); this->goToBottom->setGeometry(0, this->height() - 32, this->width(), 32); @@ -559,7 +559,7 @@ void ChannelView::drawMessages(QPainter &painter) y += layout->getHeight(); end = layout; - if (y > height()) { + if (y > this->height()) { break; } } diff --git a/src/widgets/settingspages/highlightingpage.cpp b/src/widgets/settingspages/highlightingpage.cpp index bbcd1d349..40842ba11 100644 --- a/src/widgets/settingspages/highlightingpage.cpp +++ b/src/widgets/settingspages/highlightingpage.cpp @@ -4,11 +4,13 @@ #include #include #include +#include #include #include "debug/log.hpp" #include "singletons/settingsmanager.hpp" #include "util/layoutcreator.hpp" +#include "util/tupletablemodel.hpp" #define ENABLE_HIGHLIGHTS "Enable Highlighting" #define HIGHLIGHT_MSG "Highlight messages containing your name" @@ -54,15 +56,47 @@ HighlightingPage::HighlightingPage() // HIGHLIGHTS auto highlights = tabs.appendTab(new QVBoxLayout, "Highlights"); { - highlights.emplace().assign(&this->highlightList); + QTableView *view = *highlights.emplace(); + auto *model = new util::TupleTableModel; + model->setTitles({"Pattern", "Flash taskbar", "Play sound", "Regex"}); + + // fourtf: could crash + for (const messages::HighlightPhrase &phrase : + settings.highlightProperties.getValue()) { + model->addRow(phrase.key, phrase.alert, phrase.sound, phrase.regex); + } + + view->setModel(model); + view->setSelectionMode(QAbstractItemView::SingleSelection); + view->setSelectionBehavior(QAbstractItemView::SelectRows); + view->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed); + view->resizeColumnsToContents(); + view->setColumnWidth(0, 250); auto buttons = highlights.emplace(); - buttons.emplace("Add").assign(&this->highlightAdd); - buttons.emplace("Edit").assign(&this->highlightEdit); - buttons.emplace("Remove").assign(&this->highlightRemove); + model->itemsChanged.connect([model] { + std::vector phrases; + for (int i = 0; i < model->getRowCount(); i++) { + auto t = model->getRow(i); + phrases.push_back(messages::HighlightPhrase{ + std::get<0>(t), std::get<1>(t), std::get<2>(t), std::get<3>(t), + }); + } + singletons::SettingManager::getInstance().highlightProperties.setValue(phrases); + }); - this->addHighlightTabSignals(); + auto add = buttons.emplace("Add"); + QObject::connect(*add, &QPushButton::clicked, + [model] { model->addRow("", true, false, false); }); + auto remove = buttons.emplace("Remove"); + QObject::connect(*remove, &QPushButton::clicked, [view, model] { + if (view->selectionModel()->hasSelection()) { + model->removeRow(view->selectionModel()->selectedRows()[0].row()); + } + }); + + view->hideColumn(3); } // DISABLED USERS auto disabledUsers = tabs.appendTab(new QVBoxLayout, "Disabled Users"); @@ -90,157 +124,6 @@ HighlightingPage::HighlightingPage() this->disabledUsersChangedTimer.setSingleShot(true); } -// -// DISCLAIMER: -// -// If you are trying to learn from reading the chatterino code please ignore this segment. -// -void HighlightingPage::addHighlightTabSignals() -{ - singletons::SettingManager &settings = singletons::SettingManager::getInstance(); - - auto addBtn = this->highlightAdd; - auto editBtn = this->highlightEdit; - auto delBtn = this->highlightRemove; - auto highlights = this->highlightList; - - // Open "Add new highlight" dialog - QObject::connect(addBtn, &QPushButton::clicked, this, [highlights, this, &settings] { - auto show = new QWidget(); - auto box = new QBoxLayout(QBoxLayout::TopToBottom); - - auto edit = new QLineEdit(); - auto add = new QPushButton("Add"); - - auto sound = new QCheckBox("Play sound"); - auto task = new QCheckBox("Flash taskbar"); - - // Save highlight - QObject::connect(add, &QPushButton::clicked, this, [=, &settings] { - if (edit->text().length()) { - QString highlightKey = edit->text(); - highlights->addItem(highlightKey); - - auto properties = settings.highlightProperties.getValue(); - - messages::HighlightPhrase newHighlightProperty; - newHighlightProperty.key = highlightKey; - newHighlightProperty.sound = sound->isChecked(); - newHighlightProperty.alert = task->isChecked(); - - properties.push_back(newHighlightProperty); - - settings.highlightProperties = properties; - - show->close(); - } - }); - box->addWidget(edit); - box->addWidget(add); - box->addWidget(sound); - box->addWidget(task); - show->setLayout(box); - show->show(); - }); - - // Open "Edit selected highlight" dialog - QObject::connect(editBtn, &QPushButton::clicked, this, [highlights, this, &settings] { - if (highlights->selectedItems().isEmpty()) { - // No item selected - return; - } - - QListWidgetItem *selectedHighlight = highlights->selectedItems().first(); - QString highlightKey = selectedHighlight->text(); - auto properties = settings.highlightProperties.getValue(); - auto highlightIt = std::find_if(properties.begin(), properties.end(), - [highlightKey](const auto &highlight) { - return highlight.key == highlightKey; // - }); - - if (highlightIt == properties.end()) { - debug::Log("Unable to find highlight key {} in highlight properties. " - "This is weird", - highlightKey); - return; - } - - messages::HighlightPhrase &selectedSetting = *highlightIt; - auto show = new QWidget(); - auto box = new QBoxLayout(QBoxLayout::TopToBottom); - - auto edit = new QLineEdit(highlightKey); - auto apply = new QPushButton("Apply"); - - auto sound = new QCheckBox("Play sound"); - sound->setChecked(selectedSetting.sound); - auto task = new QCheckBox("Flash taskbar"); - task->setChecked(selectedSetting.alert); - - // Apply edited changes - QObject::connect(apply, &QPushButton::clicked, this, [=, &settings] { - QString newHighlightKey = edit->text(); - - if (newHighlightKey.length() == 0) { - return; - } - - auto properties = settings.highlightProperties.getValue(); - auto highlightIt = - std::find_if(properties.begin(), properties.end(), [=](const auto &highlight) { - return highlight.key == highlightKey; // - }); - - if (highlightIt == properties.end()) { - debug::Log("Unable to find highlight key {} in highlight properties. " - "This is weird", - highlightKey); - return; - } - auto &highlightProperty = *highlightIt; - highlightProperty.key = newHighlightKey; - highlightProperty.sound = sound->isCheckable(); - highlightProperty.alert = task->isCheckable(); - - settings.highlightProperties = properties; - - selectedHighlight->setText(newHighlightKey); - selectedHighlight->setText(newHighlightKey); - - show->close(); - }); - - box->addWidget(edit); - box->addWidget(apply); - box->addWidget(sound); - box->addWidget(task); - show->setLayout(box); - show->show(); - }); - - // Delete selected highlight - QObject::connect(delBtn, &QPushButton::clicked, this, [highlights, &settings] { - if (highlights->selectedItems().isEmpty()) { - // No highlight selected - return; - } - - QListWidgetItem *selectedHighlight = highlights->selectedItems().first(); - QString highlightKey = selectedHighlight->text(); - - auto properties = settings.highlightProperties.getValue(); - properties.erase(std::remove_if(properties.begin(), properties.end(), - [highlightKey](const auto &highlight) { - return highlight.key == highlightKey; // - }), - properties.end()); - - settings.highlightProperties = properties; - - delete selectedHighlight; - }); -} - } // namespace settingspages } // namespace widgets } // namespace chatterino diff --git a/src/widgets/settingspages/highlightingpage.hpp b/src/widgets/settingspages/highlightingpage.hpp index 6bf447ece..5a1c3c756 100644 --- a/src/widgets/settingspages/highlightingpage.hpp +++ b/src/widgets/settingspages/highlightingpage.hpp @@ -2,6 +2,7 @@ #include "widgets/settingspages/settingspage.hpp" +#include #include class QPushButton; @@ -17,14 +18,7 @@ public: HighlightingPage(); private: - QListWidget *highlightList; - QPushButton *highlightAdd; - QPushButton *highlightEdit; - QPushButton *highlightRemove; - QTimer disabledUsersChangedTimer; - - void addHighlightTabSignals(); }; } // namespace settingspages diff --git a/src/widgets/split.cpp b/src/widgets/split.cpp index 9135a926c..ea20069e0 100644 --- a/src/widgets/split.cpp +++ b/src/widgets/split.cpp @@ -37,16 +37,24 @@ namespace chatterino { namespace widgets { Split::Split(SplitContainer *parent) - : BaseWidget(parent) - , parentPage(*parent) + : Split((BaseWidget *)parent) +{ + this->container = parent; +} + +Split::Split(BaseWidget *widget) + : Split(widget->themeManager, widget) +{ +} + +Split::Split(singletons::ThemeManager &manager, QWidget *parent) + : BaseWidget(manager, parent) + , container(nullptr) , channel(Channel::getEmpty()) , vbox(this) , header(this) , view(this) , input(this) - , flexSizeX(1) - , flexSizeY(1) - , moderationMode(false) { this->setMouseTracking(true); @@ -122,6 +130,11 @@ Split::~Split() this->indirectChannelChangedConnection.disconnect(); } +bool Split::isInContainer() const +{ + return this->container != nullptr; +} + IndirectChannel Split::getIndirectChannel() { return this->channel; @@ -160,24 +173,26 @@ void Split::setChannel(IndirectChannel newChannel) void Split::setFlexSizeX(double x) { - this->flexSizeX = x; - this->parentPage.updateFlexValues(); + // this->flexSizeX = x; + // this->parentPage->updateFlexValues(); } double Split::getFlexSizeX() { - return this->flexSizeX; + // return this->flexSizeX; + return 1; } void Split::setFlexSizeY(double y) { - this->flexSizeY = y; - this->parentPage.updateFlexValues(); + // this->flexSizeY = y; + // this->parentPage.updateFlexValues(); } double Split::getFlexSizeY() { - return this->flexSizeY; + // return this->flexSizeY; + return 1; } void Split::setModerationMode(bool value) @@ -206,7 +221,9 @@ void Split::showChangeChannelPopup(const char *dialogTitle, bool empty, dialog->closed.connect([=] { if (dialog->hasSeletedChannel()) { this->setChannel(dialog->getSelectedChannel()); - this->parentPage.refreshTitle(); + if (this->isInContainer()) { + this->container->refreshTitle(); + } } callback(dialog->hasSeletedChannel()); @@ -286,15 +303,17 @@ void Split::handleModifiers(QEvent *event, Qt::KeyboardModifiers modifiers) /// Slots void Split::doAddSplit() { - SplitContainer *page = static_cast(this->parentWidget()); - page->addChat(true); + if (this->container) { + this->container->addChat(true); + } } void Split::doCloseSplit() { - SplitContainer *page = static_cast(this->parentWidget()); - page->removeFromLayout(this); - deleteLater(); + if (this->container) { + this->container->removeFromLayout(this); + deleteLater(); + } } void Split::doChangeChannel() diff --git a/src/widgets/split.hpp b/src/widgets/split.hpp index 871b7169a..ae64db3a2 100644 --- a/src/widgets/split.hpp +++ b/src/widgets/split.hpp @@ -43,7 +43,9 @@ class Split : public BaseWidget Q_OBJECT public: - Split(SplitContainer *parent); + explicit Split(SplitContainer *parent); + explicit Split(BaseWidget *widget); + explicit Split(singletons::ThemeManager &manager, QWidget *parent); ~Split() override; pajlada::Signals::NoArgSignal channelChanged; @@ -75,6 +77,8 @@ public: void drag(); + bool isInContainer() const; + protected: void paintEvent(QPaintEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; @@ -83,21 +87,22 @@ protected: void keyReleaseEvent(QKeyEvent *event) override; private: - SplitContainer &parentPage; + SplitContainer *container; IndirectChannel channel; QVBoxLayout vbox; SplitHeader header; ChannelView view; SplitInput input; - double flexSizeX; - double flexSizeY; + double flexSizeX = 1; + double flexSizeY = 1; - bool moderationMode; + bool moderationMode = false; pajlada::Signals::Connection channelIDChangedConnection; pajlada::Signals::Connection usermodeChangedConnection; pajlada::Signals::Connection indirectChannelChangedConnection; + void doOpenAccountPopupWidget(AccountPopupWidget *widget, QString user); void channelNameUpdated(const QString &newChannelName); void handleModifiers(QEvent *event, Qt::KeyboardModifiers modifiers); diff --git a/src/widgets/window.cpp b/src/widgets/window.cpp index 94971f4ce..7c7504c29 100644 --- a/src/widgets/window.cpp +++ b/src/widgets/window.cpp @@ -11,10 +11,14 @@ #include "widgets/split.hpp" #include +#include #include #include #include +#include +#include "util/tupletablemodel.hpp" + namespace chatterino { namespace widgets { diff --git a/src/widgets/window.hpp b/src/widgets/window.hpp index b3b7a098f..850fb06b3 100644 --- a/src/widgets/window.hpp +++ b/src/widgets/window.hpp @@ -23,7 +23,7 @@ class Window : public BaseWindow Q_OBJECT public: - enum WindowType { Main, Popup }; + enum WindowType { Main, Popup, Attached }; explicit Window(singletons::ThemeManager &_themeManager, WindowType type);