created base for all the list based settings

This commit is contained in:
fourtf 2018-05-06 00:32:45 +02:00
parent 4c3f0921e2
commit ba4173822e
21 changed files with 646 additions and 317 deletions

View file

@ -188,7 +188,10 @@ SOURCES += \
src/util/signalvectormodel.cpp \ src/util/signalvectormodel.cpp \
src/controllers/commands/command.cpp \ src/controllers/commands/command.cpp \
src/controllers/commands/commandmodel.cpp \ src/controllers/commands/commandmodel.cpp \
src/controllers/commands/commandcontroller.cpp src/controllers/commands/commandcontroller.cpp \
src/controllers/highlights/highlightcontroller.cpp \
src/controllers/highlights/highlightmodel.cpp \
src/widgets/helper/editablemodelview.cpp
HEADERS += \ HEADERS += \
src/precompiled_header.hpp \ src/precompiled_header.hpp \
@ -196,7 +199,6 @@ HEADERS += \
src/const.hpp \ src/const.hpp \
src/debug/log.hpp \ src/debug/log.hpp \
src/emojis.hpp \ src/emojis.hpp \
src/messages/highlightphrase.hpp \
src/messages/image.hpp \ src/messages/image.hpp \
src/messages/layouts/messagelayout.hpp \ src/messages/layouts/messagelayout.hpp \
src/messages/layouts/messagelayoutcontainer.hpp \ src/messages/layouts/messagelayoutcontainer.hpp \
@ -324,7 +326,11 @@ HEADERS += \
src/util/signalvectormodel.hpp \ src/util/signalvectormodel.hpp \
src/controllers/commands/command.hpp \ src/controllers/commands/command.hpp \
src/controllers/commands/commandmodel.hpp \ src/controllers/commands/commandmodel.hpp \
src/controllers/commands/commandcontroller.hpp src/controllers/commands/commandcontroller.hpp \
src/controllers/highlights/highlightcontroller.hpp \
src/controllers/highlights/highlightphrase.hpp \
src/controllers/highlights/highlightmodel.hpp \
src/widgets/helper/editablemodelview.hpp
RESOURCES += \ RESOURCES += \
resources/resources.qrc resources/resources.qrc

View file

@ -1,6 +1,7 @@
#include "application.hpp" #include "application.hpp"
#include "controllers/commands/commandcontroller.hpp" #include "controllers/commands/commandcontroller.hpp"
#include "controllers/highlights/highlightcontroller.hpp"
#include "providers/twitch/pubsub.hpp" #include "providers/twitch/pubsub.hpp"
#include "providers/twitch/twitchserver.hpp" #include "providers/twitch/twitchserver.hpp"
#include "singletons/accountmanager.hpp" #include "singletons/accountmanager.hpp"
@ -64,6 +65,7 @@ void Application::construct()
this->windows = new singletons::WindowManager; this->windows = new singletons::WindowManager;
this->logging = new singletons::LoggingManager; this->logging = new singletons::LoggingManager;
this->commands = new controllers::commands::CommandController; this->commands = new controllers::commands::CommandController;
this->highlights = new controllers::highlights::HighlightController;
this->accounts = new singletons::AccountManager; this->accounts = new singletons::AccountManager;
this->emotes = new singletons::EmoteManager; this->emotes = new singletons::EmoteManager;
this->settings = new singletons::SettingManager; this->settings = new singletons::SettingManager;
@ -95,6 +97,8 @@ void Application::initialize()
this->settings->load(); this->settings->load();
this->commands->load(); this->commands->load();
this->highlights->initialize();
this->emotes->loadGlobalEmotes(); this->emotes->loadGlobalEmotes();
this->accounts->load(); this->accounts->load();

View file

@ -20,6 +20,9 @@ namespace controllers {
namespace commands { namespace commands {
class CommandController; class CommandController;
} }
namespace highlights {
class HighlightController;
}
} }
namespace singletons { namespace singletons {
@ -59,6 +62,7 @@ public:
singletons::WindowManager *windows = nullptr; singletons::WindowManager *windows = nullptr;
singletons::LoggingManager *logging = nullptr; singletons::LoggingManager *logging = nullptr;
controllers::commands::CommandController *commands = nullptr; controllers::commands::CommandController *commands = nullptr;
controllers::highlights::HighlightController *highlights = nullptr;
singletons::AccountManager *accounts = nullptr; singletons::AccountManager *accounts = nullptr;
singletons::EmoteManager *emotes = nullptr; singletons::EmoteManager *emotes = nullptr;
singletons::NativeMessagingManager *nativeMessaging = nullptr; singletons::NativeMessagingManager *nativeMessaging = nullptr;

View file

@ -14,13 +14,13 @@ Command::Command(const QString &_text)
return; return;
} }
this->name = _text.mid(0, index); this->name = _text.mid(0, index).trimmed();
this->func = _text.mid(index + 1); this->func = _text.mid(index + 1).trimmed();
} }
Command::Command(const QString &_name, const QString &_func) Command::Command(const QString &_name, const QString &_func)
: name(_name) : name(_name.trimmed())
, func(_func) , func(_func.trimmed())
{ {
} }

View file

@ -25,18 +25,6 @@ void CommandModel::getRowFromItem(const Command &item, std::vector<QStandardItem
row[1]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); row[1]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
} }
// returns the related index of the SignalVector
int CommandModel::getVectorIndexFromModelIndex(int index)
{
return index;
}
// returns the related index of the model
int CommandModel::getModelIndexFromVectorIndex(int index)
{
return index;
}
} // namespace commands } // namespace commands
} // namespace controllers } // namespace controllers
} // namespace chatterino } // namespace chatterino

View file

@ -22,12 +22,6 @@ protected:
// turns a row in the model into a vector item // turns a row in the model into a vector item
virtual void getRowFromItem(const Command &item, std::vector<QStandardItem *> &row) override; virtual void getRowFromItem(const Command &item, std::vector<QStandardItem *> &row) override;
// returns the related index of the SignalVector
virtual int getVectorIndexFromModelIndex(int index) override;
// returns the related index of the model
virtual int getModelIndexFromVectorIndex(int index) override;
friend class CommandController; friend class CommandController;
}; };

View file

@ -0,0 +1,39 @@
#include "highlightcontroller.hpp"
#include "application.hpp"
#include "controllers/highlights/highlightmodel.hpp"
namespace chatterino {
namespace controllers {
namespace highlights {
HighlightController::HighlightController()
{
}
void HighlightController::initialize()
{
assert(!this->initialized);
this->initialized = true;
for (const HighlightPhrase &phrase : this->highlightsSetting.getValue()) {
this->phrases.appendItem(phrase);
}
this->phrases.delayedItemsChanged.connect([this] { //
int xd = this->phrases.getVector().size();
this->highlightsSetting.setValue(this->phrases.getVector());
});
}
HighlightModel *HighlightController::createModel(QObject *parent)
{
HighlightModel *model = new HighlightModel(parent);
model->init(&this->phrases);
return model;
}
} // namespace highlights
} // namespace controllers
} // namespace chatterino

View file

@ -0,0 +1,33 @@
#pragma once
#include "controllers/highlights/highlightphrase.hpp"
#include "singletons/settingsmanager.hpp"
#include "util/signalvector2.hpp"
namespace chatterino {
namespace controllers {
namespace highlights {
class HighlightModel;
class HighlightController
{
public:
HighlightController();
void initialize();
util::UnsortedSignalVector<HighlightPhrase> phrases;
HighlightModel *createModel(QObject *parent);
private:
bool initialized = false;
singletons::ChatterinoSetting<std::vector<highlights::HighlightPhrase>> highlightsSetting = {
"/highlighting/highlights"};
};
} // namespace highlights
} // namespace controllers
} // namespace chatterino

View file

@ -0,0 +1,91 @@
#include "highlightmodel.hpp"
#include "application.hpp"
#include "singletons/settingsmanager.hpp"
#include "util/standarditemhelper.hpp"
namespace chatterino {
namespace controllers {
namespace highlights {
// commandmodel
HighlightModel::HighlightModel(QObject *parent)
: util::SignalVectorModel<HighlightPhrase>(4, parent)
{
// auto app = getApp();
// std::vector<QStandardItem *> row = this->createRow();
// util::setBoolItem(row[0], app->settings->enableHighlightsSelf.getValue(), true, false);
// util::setBoolItem(row[1], app->settings->enableHighlightsSelf.getValue(), true, false);
// util::setBoolItem(row[2], app->settings->enableHighlightsSelf.getValue(), true, false);
// row[0]->setData("Your name (automatic)", Qt::DisplayRole);
// this->insertCustomRow(row, 0);
}
// app->settings->highlightProperties.setValue(phrases);
// app->settings->enableHighlightsSelf.setValue(
// model->item(0, 0)->data(Qt::CheckStateRole).toBool());
// app->settings->enableHighlightTaskbar.setValue(
// model->item(0, 1)->data(Qt::CheckStateRole).toBool());
// app->settings->enableHighlightSound.setValue(
// model->item(0, 2)->data(Qt::CheckStateRole).toBool());
// turn a vector item into a model row
HighlightPhrase HighlightModel::getItemFromRow(std::vector<QStandardItem *> &row)
{
// key, alert, sound, regex
return HighlightPhrase{
row[0]->data(Qt::DisplayRole).toString(), row[1]->data(Qt::CheckStateRole).toBool(),
row[2]->data(Qt::CheckStateRole).toBool(), row[3]->data(Qt::CheckStateRole).toBool()};
}
// turns a row in the model into a vector item
void HighlightModel::getRowFromItem(const HighlightPhrase &item, std::vector<QStandardItem *> &row)
{
util::setStringItem(row[0], item.key);
util::setBoolItem(row[1], item.alert);
util::setBoolItem(row[2], item.sound);
util::setBoolItem(row[3], item.regex);
}
void HighlightModel::afterInit()
{
std::vector<QStandardItem *> row = this->createRow();
util::setBoolItem(row[0], getApp()->settings->enableHighlightsSelf.getValue(), true, false);
row[0]->setData("Your username (automatic)", Qt::DisplayRole);
util::setBoolItem(row[1], getApp()->settings->enableHighlightTaskbar.getValue(), true, false);
util::setBoolItem(row[2], getApp()->settings->enableHighlightSound.getValue(), true, false);
this->insertCustomRow(row, 0);
}
void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row, int column,
const QVariant &value, int role)
{
switch (column) {
case 0: {
if (role == Qt::CheckStateRole) {
getApp()->settings->enableHighlightsSelf.setValue(value.toBool());
}
} break;
case 1: {
if (role == Qt::CheckStateRole) {
getApp()->settings->enableHighlightTaskbar.setValue(value.toBool());
}
} break;
case 2: {
if (role == Qt::CheckStateRole) {
getApp()->settings->enableHighlightSound.setValue(value.toBool());
}
} break;
case 3: {
// empty element
} break;
}
}
} // namespace highlights
} // namespace controllers
} // namespace chatterino

View file

@ -0,0 +1,36 @@
#pragma once
#include <QObject>
#include "controllers/highlights/highlightphrase.hpp"
#include "util/signalvectormodel.hpp"
namespace chatterino {
namespace controllers {
namespace highlights {
class HighlightController;
class HighlightModel : public util::SignalVectorModel<HighlightPhrase>
{
explicit HighlightModel(QObject *parent);
protected:
// turn a vector item into a model row
virtual HighlightPhrase getItemFromRow(std::vector<QStandardItem *> &row) override;
// turns a row in the model into a vector item
virtual void getRowFromItem(const HighlightPhrase &item,
std::vector<QStandardItem *> &row) override;
virtual void afterInit() override;
virtual void customRowSetData(const std::vector<QStandardItem *> &row, int column,
const QVariant &value, int role) override;
friend class HighlightController;
};
} // namespace highlights
} // namespace controllers
} // namespace chatterino

View file

@ -0,0 +1,89 @@
#pragma once
#include "util/serialize-custom.hpp"
#include <QString>
#include <pajlada/settings/serialize.hpp>
namespace chatterino {
namespace controllers {
namespace highlights {
struct HighlightPhrase {
QString key;
bool alert;
bool sound;
bool regex;
bool operator==(const HighlightPhrase &other) const
{
return std::tie(this->key, this->sound, this->alert, this->regex) ==
std::tie(other.key, other.sound, other.alert, other.regex);
}
};
} // namespace highlights
} // namespace controllers
} // namespace chatterino
namespace pajlada {
namespace Settings {
template <>
struct Serialize<chatterino::controllers::highlights::HighlightPhrase> {
static rapidjson::Value get(const chatterino::controllers::highlights::HighlightPhrase &value,
rapidjson::Document::AllocatorType &a)
{
rapidjson::Value ret(rapidjson::kObjectType);
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;
}
};
template <>
struct Deserialize<chatterino::controllers::highlights::HighlightPhrase> {
static chatterino::controllers::highlights::HighlightPhrase get(const rapidjson::Value &value)
{
chatterino::controllers::highlights::HighlightPhrase ret;
if (!value.IsObject()) {
return ret;
}
if (value.HasMember("key")) {
const rapidjson::Value &key = value["key"];
if (key.IsString()) {
ret.key = key.GetString();
}
}
if (value.HasMember("alert")) {
const rapidjson::Value &alert = value["alert"];
if (alert.IsBool()) {
ret.alert = alert.GetBool();
}
}
if (value.HasMember("sound")) {
const rapidjson::Value &sound = value["sound"];
if (sound.IsBool()) {
ret.sound = sound.GetBool();
}
}
if (value.HasMember("regex")) {
const rapidjson::Value &regex = value["regex"];
if (regex.IsBool()) {
ret.regex = regex.GetBool();
}
}
return ret;
}
};
} // namespace Settings
} // namespace pajlada

View file

@ -1,87 +1,87 @@
#pragma once //#pragma once
#include "util/serialize-custom.hpp" //#include "util/serialize-custom.hpp"
#include <QString> //#include <QString>
#include <pajlada/settings/serialize.hpp> //#include <pajlada/settings/serialize.hpp>
namespace chatterino { //namespace chatterino {
namespace messages { //namespace messages {
struct HighlightPhrase { //struct HighlightPhrase {
QString key; // QString key;
bool alert; // bool alert;
bool sound; // bool sound;
bool regex; // bool regex;
bool operator==(const HighlightPhrase &other) const // bool operator==(const HighlightPhrase &other) const
{ // {
return std::tie(this->key, this->sound, this->alert, this->regex) == // return std::tie(this->key, this->sound, this->alert, this->regex) ==
std::tie(other.key, other.sound, other.alert, other.regex); // std::tie(other.key, other.sound, other.alert, other.regex);
} // }
}; //};
} // namespace messages //} // namespace messages
} // namespace chatterino //} // namespace chatterino
namespace pajlada { //namespace pajlada {
namespace Settings { //namespace Settings {
template <> //template <>
struct Serialize<chatterino::messages::HighlightPhrase> { //struct Serialize<chatterino::messages::HighlightPhrase> {
static rapidjson::Value get(const chatterino::messages::HighlightPhrase &value, // static rapidjson::Value get(const chatterino::messages::HighlightPhrase &value,
rapidjson::Document::AllocatorType &a) // rapidjson::Document::AllocatorType &a)
{ // {
rapidjson::Value ret(rapidjson::kObjectType); // rapidjson::Value ret(rapidjson::kObjectType);
AddMember(ret, "key", value.key, a); // AddMember(ret, "key", value.key, a);
AddMember(ret, "alert", value.alert, a); // AddMember(ret, "alert", value.alert, a);
AddMember(ret, "sound", value.sound, a); // AddMember(ret, "sound", value.sound, a);
AddMember(ret, "regex", value.regex, a); // AddMember(ret, "regex", value.regex, a);
return ret; // return ret;
} // }
}; //};
template <> //template <>
struct Deserialize<chatterino::messages::HighlightPhrase> { //struct Deserialize<chatterino::messages::HighlightPhrase> {
static chatterino::messages::HighlightPhrase get(const rapidjson::Value &value) // static chatterino::messages::HighlightPhrase get(const rapidjson::Value &value)
{ // {
chatterino::messages::HighlightPhrase ret; // chatterino::messages::HighlightPhrase ret;
if (!value.IsObject()) { // if (!value.IsObject()) {
return ret; // return ret;
} // }
if (value.HasMember("key")) { // if (value.HasMember("key")) {
const rapidjson::Value &key = value["key"]; // const rapidjson::Value &key = value["key"];
if (key.IsString()) { // if (key.IsString()) {
ret.key = key.GetString(); // ret.key = key.GetString();
} // }
} // }
if (value.HasMember("alert")) { // if (value.HasMember("alert")) {
const rapidjson::Value &alert = value["alert"]; // const rapidjson::Value &alert = value["alert"];
if (alert.IsBool()) { // if (alert.IsBool()) {
ret.alert = alert.GetBool(); // ret.alert = alert.GetBool();
} // }
} // }
if (value.HasMember("sound")) { // if (value.HasMember("sound")) {
const rapidjson::Value &sound = value["sound"]; // const rapidjson::Value &sound = value["sound"];
if (sound.IsBool()) { // if (sound.IsBool()) {
ret.sound = sound.GetBool(); // ret.sound = sound.GetBool();
} // }
} // }
if (value.HasMember("regex")) { // if (value.HasMember("regex")) {
const rapidjson::Value &regex = value["regex"]; // const rapidjson::Value &regex = value["regex"];
if (regex.IsBool()) { // if (regex.IsBool()) {
ret.regex = regex.GetBool(); // ret.regex = regex.GetBool();
} // }
} // }
return ret; // return ret;
} // }
}; //};
} // namespace Settings //} // namespace Settings
} // namespace pajlada //} // namespace pajlada

View file

@ -1,6 +1,7 @@
#include "providers/twitch/twitchmessagebuilder.hpp" #include "providers/twitch/twitchmessagebuilder.hpp"
#include "application.hpp" #include "application.hpp"
#include "controllers/highlights/highlightcontroller.hpp"
#include "debug/log.hpp" #include "debug/log.hpp"
#include "providers/twitch/twitchchannel.hpp" #include "providers/twitch/twitchchannel.hpp"
#include "singletons/accountmanager.hpp" #include "singletons/accountmanager.hpp"
@ -398,10 +399,12 @@ void TwitchMessageBuilder::parseHighlights()
app->settings->highlightUserBlacklist.getValue().split("\n", QString::SkipEmptyParts); app->settings->highlightUserBlacklist.getValue().split("\n", QString::SkipEmptyParts);
// TODO: This vector should only be rebuilt upon highlights being changed // TODO: This vector should only be rebuilt upon highlights being changed
auto activeHighlights = app->settings->highlightProperties.getValue(); // fourtf: should be implemented in the HighlightsController
std::vector<controllers::highlights::HighlightPhrase> activeHighlights =
app->highlights->phrases.getVector();
if (app->settings->enableHighlightsSelf && currentUsername.size() > 0) { if (app->settings->enableHighlightsSelf && currentUsername.size() > 0) {
messages::HighlightPhrase selfHighlight; controllers::highlights::HighlightPhrase selfHighlight;
selfHighlight.key = currentUsername; selfHighlight.key = currentUsername;
selfHighlight.sound = app->settings->enableHighlightSound; selfHighlight.sound = app->settings->enableHighlightSound;
selfHighlight.alert = app->settings->enableHighlightTaskbar; selfHighlight.alert = app->settings->enableHighlightTaskbar;
@ -415,7 +418,7 @@ void TwitchMessageBuilder::parseHighlights()
bool hasFocus = (QApplication::focusWidget() != nullptr); bool hasFocus = (QApplication::focusWidget() != nullptr);
if (!blackList.contains(this->ircMessage->nick(), Qt::CaseInsensitive)) { if (!blackList.contains(this->ircMessage->nick(), Qt::CaseInsensitive)) {
for (const messages::HighlightPhrase &highlight : activeHighlights) { for (const controllers::highlights::HighlightPhrase &highlight : activeHighlights) {
int index = -1; int index = -1;
while ((index = this->originalMessage.indexOf(highlight.key, index + 1, while ((index = this->originalMessage.indexOf(highlight.key, index + 1,

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "messages/highlightphrase.hpp" #include "controllers/highlights/highlightphrase.hpp"
#include "messages/messageelement.hpp" #include "messages/messageelement.hpp"
#include "singletons/helper/chatterinosetting.hpp" #include "singletons/helper/chatterinosetting.hpp"
#include "singletons/helper/moderationaction.hpp" #include "singletons/helper/moderationaction.hpp"
@ -104,9 +104,6 @@ public:
/// Logging /// Logging
BoolSetting enableLogging = {"/logging/enabled", false}; BoolSetting enableLogging = {"/logging/enabled", false};
ChatterinoSetting<std::vector<messages::HighlightPhrase>> highlightProperties = {
"/highlighting/highlights"};
QStringSetting pathHighlightSound = {"/highlighting/highlightSoundPath", QStringSetting pathHighlightSound = {"/highlighting/highlightSoundPath",
"qrc:/sounds/ping2.wav"}; "qrc:/sounds/ping2.wav"};
QStringSetting highlightUserBlacklist = {"/highlighting/blacklistedUsers", ""}; QStringSetting highlightUserBlacklist = {"/highlighting/blacklistedUsers", ""};

View file

@ -19,6 +19,8 @@ public:
{ {
QObject::connect(&this->itemsChangedTimer, &QTimer::timeout, QObject::connect(&this->itemsChangedTimer, &QTimer::timeout,
[this] { this->delayedItemsChanged.invoke(); }); [this] { this->delayedItemsChanged.invoke(); });
this->itemsChangedTimer.setInterval(100);
this->itemsChangedTimer.setSingleShot(true);
} }
virtual ~ReadOnlySignalVector() = default; virtual ~ReadOnlySignalVector() = default;
@ -69,6 +71,8 @@ public:
this->vector.erase(this->vector.begin() + index); this->vector.erase(this->vector.begin() + index);
typename ReadOnlySignalVector<TVectorItem>::ItemArgs args{item, index, caller}; typename ReadOnlySignalVector<TVectorItem>::ItemArgs args{item, index, caller};
this->itemRemoved.invoke(args); this->itemRemoved.invoke(args);
this->invokeDelayedItemsChanged();
} }
int appendItem(const TVectorItem &item, void *caller = 0) int appendItem(const TVectorItem &item, void *caller = 0)
@ -94,6 +98,7 @@ public:
typename ReadOnlySignalVector<TVectorItem>::ItemArgs args{item, index, caller}; typename ReadOnlySignalVector<TVectorItem>::ItemArgs args{item, index, caller};
this->itemInserted.invoke(args); this->itemInserted.invoke(args);
this->invokeDelayedItemsChanged();
return index; return index;
} }
}; };
@ -111,6 +116,7 @@ public:
this->vector.begin(); this->vector.begin();
typename ReadOnlySignalVector<TVectorItem>::ItemArgs args{item, index, caller}; typename ReadOnlySignalVector<TVectorItem>::ItemArgs args{item, index, caller};
this->itemInserted.invoke(args); this->itemInserted.invoke(args);
this->invokeDelayedItemsChanged();
return index; return index;
} }
}; };

View file

@ -32,20 +32,18 @@ public:
} }
// get row index // get row index
int row = this->getModelIndexFromVectorIndex(args.index); int index = this->getModelIndexFromVectorIndex(args.index);
assert(row >= 0 && row <= this->rows.size()); assert(index >= 0 && index <= this->rows.size());
// get row items // get row items
std::vector<QStandardItem *> items; std::vector<QStandardItem *> row = this->createRow();
for (int i = 0; i < this->_columnCount; i++) { this->getRowFromItem(args.item, row);
items.push_back(new QStandardItem());
}
this->getRowFromItem(args.item, items);
// insert row // insert row
this->beginInsertRows(QModelIndex(), row, row); index = this->beforeInsert(args.item, row, index);
this->rows.insert(this->rows.begin() + row, Row(items));
this->beginInsertRows(QModelIndex(), index, index);
this->rows.insert(this->rows.begin() + index, Row(row));
this->endInsertRows(); this->endInsertRows();
}; };
@ -74,6 +72,8 @@ public:
this->rows.erase(this->rows.begin() + row); this->rows.erase(this->rows.begin() + row);
this->endRemoveRows(); this->endRemoveRows();
}); });
this->afterInit();
} }
virtual ~SignalVectorModel() virtual ~SignalVectorModel()
@ -108,12 +108,18 @@ public:
int row = index.row(), column = index.column(); int row = index.row(), column = index.column();
assert(row >= 0 && row < this->rows.size() && column >= 0 && column < this->_columnCount); assert(row >= 0 && row < this->rows.size() && column >= 0 && column < this->_columnCount);
this->rows[row].items[column]->setData(value, role); Row &rowItem = this->rows[row];
int vecRow = this->getVectorIndexFromModelIndex(row); rowItem.items[column]->setData(value, role);
this->vector->removeItem(vecRow, this);
TVectorItem item = this->getItemFromRow(this->rows[row].items); if (rowItem.isCustomRow) {
this->vector->insertItem(item, vecRow, this); this->customRowSetData(rowItem.items, column, value, role);
} else {
int vecRow = this->getVectorIndexFromModelIndex(row);
this->vector->removeItem(vecRow, this);
TVectorItem item = this->getItemFromRow(this->rows[row].items);
this->vector->insertItem(item, vecRow, this);
}
return true; return true;
} }
@ -140,6 +146,8 @@ public:
} }
this->_headerData[section][role] = value; this->_headerData[section][role] = value;
emit this->headerDataChanged(Qt::Horizontal, section, section);
return true; return true;
} }
@ -151,51 +159,133 @@ public:
return this->rows[index.row()].items[index.column()]->flags(); return this->rows[index.row()].items[index.column()]->flags();
} }
virtual QStandardItem *getItem(int row, int column) QStandardItem *getItem(int row, int column)
{ {
assert(row >= 0 && row < this->rows.size() && column >= 0 && column < this->_columnCount); assert(row >= 0 && row < this->rows.size() && column >= 0 && column < this->_columnCount);
return rows[row].items[column]; return rows[row].items[column];
} }
void removeRow(int row) void deleteRow(int row)
{ {
assert(row >= 0 && row <= this->rows.size());
int signalVectorRow = this->getVectorIndexFromModelIndex(row); int signalVectorRow = this->getVectorIndexFromModelIndex(row);
this->vector->removeItem(signalVectorRow); this->vector->removeItem(signalVectorRow);
} }
virtual bool removeRows(int row, int count, const QModelIndex &parent) override
{
if (count != 1) {
return false;
}
assert(row >= 0 && row < this->rows.size());
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
this->vector->removeItem(signalVectorRow);
return true;
}
protected: protected:
virtual void afterInit()
{
}
// turn a vector item into a model row // turn a vector item into a model row
virtual TVectorItem getItemFromRow(std::vector<QStandardItem *> &row) = 0; virtual TVectorItem getItemFromRow(std::vector<QStandardItem *> &row) = 0;
// turns a row in the model into a vector item // turns a row in the model into a vector item
virtual void getRowFromItem(const TVectorItem &item, std::vector<QStandardItem *> &row) = 0; virtual void getRowFromItem(const TVectorItem &item, std::vector<QStandardItem *> &row) = 0;
// returns the related index of the SignalVector virtual int beforeInsert(const TVectorItem &item, std::vector<QStandardItem *> &row,
virtual int getVectorIndexFromModelIndex(int index) = 0; int proposedIndex)
{
return proposedIndex;
}
// returns the related index of the model virtual void afterRemoved(const TVectorItem &item, std::vector<QStandardItem *> &row, int index)
virtual int getModelIndexFromVectorIndex(int index) = 0; {
}
virtual void customRowSetData(const std::vector<QStandardItem *> &row, int column,
const QVariant &value, int role)
{
}
void insertCustomRow(std::vector<QStandardItem *> row, int index)
{
assert(index >= 0 && index <= this->rows.size());
this->beginInsertRows(QModelIndex(), index, index);
this->rows.insert(this->rows.begin() + index, Row(std::move(row), true));
this->endInsertRows();
}
std::vector<QStandardItem *> createRow()
{
std::vector<QStandardItem *> row;
for (int i = 0; i < this->_columnCount; i++) {
row.push_back(new QStandardItem());
}
return row;
}
private:
struct Row { struct Row {
std::vector<QStandardItem *> items; std::vector<QStandardItem *> items;
bool isCustomRow; bool isCustomRow;
Row(const std::vector<QStandardItem *> _items, bool _isCustomRow = false) Row(std::vector<QStandardItem *> _items, bool _isCustomRow = false)
: items(_items) : items(std::move(_items))
, isCustomRow(_isCustomRow) , isCustomRow(_isCustomRow)
{ {
} }
}; };
std::vector<Row> rows; std::vector<Row> rows;
private:
std::vector<QMap<int, QVariant>> _headerData; std::vector<QMap<int, QVariant>> _headerData;
BaseSignalVector<TVectorItem> *vector; BaseSignalVector<TVectorItem> *vector;
int _columnCount; int _columnCount;
// returns the related index of the SignalVector
int getVectorIndexFromModelIndex(int index)
{
int i = 0;
for (auto &row : this->rows) {
if (row.isCustomRow) {
index--;
continue;
}
if (i == index) {
return i;
}
i++;
}
return i;
}
// returns the related index of the model
int getModelIndexFromVectorIndex(int index)
{
int i = 0;
for (auto &row : this->rows) {
if (row.isCustomRow) {
index++;
}
if (i == index) {
return i;
}
i++;
}
return i;
}
}; };
} // namespace util } // namespace util

View file

@ -5,21 +5,20 @@
namespace chatterino { namespace chatterino {
namespace util { namespace util {
static QStandardItem *boolItem(bool value, bool userCheckable = true, bool selectable = true) static void setBoolItem(QStandardItem *item, bool value, bool userCheckable = true,
bool selectable = true)
{ {
auto *item = new QStandardItem();
item->setFlags((Qt::ItemFlags)(Qt::ItemIsEnabled | (selectable ? Qt::ItemIsSelectable : 0) | item->setFlags((Qt::ItemFlags)(Qt::ItemIsEnabled | (selectable ? Qt::ItemIsSelectable : 0) |
(userCheckable ? Qt::ItemIsUserCheckable : 0))); (userCheckable ? Qt::ItemIsUserCheckable : 0)));
item->setCheckState(value ? Qt::Checked : Qt::Unchecked); item->setCheckState(value ? Qt::Checked : Qt::Unchecked);
return item;
} }
static QStandardItem *stringItem(const QString &value, bool editable = true, bool selectable = true) static void setStringItem(QStandardItem *item, const QString &value, bool editable = true,
bool selectable = true)
{ {
auto *item = new QStandardItem(value); item->setData(value, Qt::EditRole);
item->setFlags((Qt::ItemFlags)(Qt::ItemIsEnabled | (selectable ? Qt::ItemIsSelectable : 0) | item->setFlags((Qt::ItemFlags)(Qt::ItemIsEnabled | (selectable ? Qt::ItemIsSelectable : 0) |
(editable ? (Qt::ItemIsEditable) : 0))); (editable ? (Qt::ItemIsEditable) : 0)));
return item;
} }
static QStandardItem *emptyItem() static QStandardItem *emptyItem()

View file

@ -0,0 +1,75 @@
#include "editablemodelview.hpp"
#include <QAbstractTableModel>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QPushButton>
#include <QTableView>
#include <QVBoxLayout>
namespace chatterino {
namespace widgets {
namespace helper {
EditableModelView::EditableModelView(QAbstractTableModel *_model)
: tableView(new QTableView(this))
, model(_model)
{
this->model->setParent(this);
this->tableView->setModel(_model);
this->tableView->setSelectionMode(QAbstractItemView::ExtendedSelection);
this->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
this->tableView->verticalHeader()->hide();
// create layout
QVBoxLayout *vbox = new QVBoxLayout(this);
vbox->addWidget(this->tableView);
// create button layout
QHBoxLayout *buttons = new QHBoxLayout(this);
vbox->addLayout(buttons);
// add
QPushButton *add = new QPushButton("Add");
buttons->addWidget(add);
QObject::connect(add, &QPushButton::clicked, [this] { this->addButtonPressed.invoke(); });
// remove
QPushButton *remove = new QPushButton("Remove");
buttons->addWidget(remove);
QObject::connect(remove, &QPushButton::clicked, [this] {
QModelIndexList list;
while ((list = this->getTableView()->selectionModel()->selectedRows(0)).length() > 0) {
model->removeRow(list[0].row());
}
});
// finish button layout
buttons->addStretch(1);
}
void EditableModelView::setTitles(std::initializer_list<QString> titles)
{
int i = 0;
for (const QString &title : titles) {
if (this->model->columnCount() == i) {
break;
}
this->model->setHeaderData(i++, Qt::Horizontal, title, Qt::DisplayRole);
}
}
QTableView *EditableModelView::getTableView()
{
return this->tableView;
}
QAbstractTableModel *EditableModelView::getModel()
{
return this->model;
}
} // namespace helper
} // namespace widgets
} // namespace chatterino

View file

@ -0,0 +1,33 @@
#pragma once
#include <QWidget>
#include <pajlada/signals/signal.hpp>
class QAbstractTableModel;
class QTableView;
namespace chatterino {
namespace widgets {
namespace helper {
class EditableModelView : public QWidget
{
public:
EditableModelView(QAbstractTableModel *model);
void setTitles(std::initializer_list<QString> titles);
QTableView *getTableView();
QAbstractTableModel *getModel();
pajlada::Signals::NoArgSignal addButtonPressed;
private:
QTableView *tableView;
QAbstractTableModel *model;
};
} // namespace helper
} // namespace widgets
} // namespace chatterino

View file

@ -1,5 +1,6 @@
#include "commandpage.hpp" #include "commandpage.hpp"
#include <QHeaderView>
#include <QLabel> #include <QLabel>
#include <QPushButton> #include <QPushButton>
#include <QStandardItemModel> #include <QStandardItemModel>
@ -11,6 +12,7 @@
#include "controllers/commands/commandmodel.hpp" #include "controllers/commands/commandmodel.hpp"
#include "util/layoutcreator.hpp" #include "util/layoutcreator.hpp"
#include "util/standarditemhelper.hpp" #include "util/standarditemhelper.hpp"
#include "widgets/helper/editablemodelview.hpp"
//#include "widgets/helper/comboboxitemdelegate.hpp" //#include "widgets/helper/comboboxitemdelegate.hpp"
#include <QLabel> #include <QLabel>
@ -34,107 +36,15 @@ CommandPage::CommandPage()
util::LayoutCreator<CommandPage> layoutCreator(this); util::LayoutCreator<CommandPage> layoutCreator(this);
auto layout = layoutCreator.emplace<QVBoxLayout>().withoutMargin(); auto layout = layoutCreator.emplace<QVBoxLayout>().withoutMargin();
QTableView *view = *layout.emplace<QTableView>(); helper::EditableModelView *view =
*layout.emplace<helper::EditableModelView>(app->commands->createModel(nullptr));
auto *model = app->commands->createModel(view); view->setTitles({"Trigger", "Command"});
view->setModel(model); view->getTableView()->horizontalHeader()->setStretchLastSection(true);
model->setHeaderData(0, Qt::Horizontal, "Trigger"); view->addButtonPressed.connect([] {
model->setHeaderData(1, Qt::Horizontal, "Command"); getApp()->commands->items.appendItem(
view->setSelectionMode(QAbstractItemView::ExtendedSelection); controllers::commands::Command{"/command", "I made a new command HeyGuys"});
view->setSelectionBehavior(QAbstractItemView::SelectRows); });
view->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
view->verticalHeader()->hide();
auto buttons = layout.emplace<QHBoxLayout>().withoutMargin();
{
auto add = buttons.emplace<QPushButton>("Add");
QObject::connect(*add, &QPushButton::clicked, [model, view] {
getApp()->commands->items.appendItem(
controllers::commands::Command{"/command", "I made a new command HeyGuys"});
view->scrollToBottom();
});
auto remove = buttons.emplace<QPushButton>("Remove");
QObject::connect(*remove, &QPushButton::clicked, [view, model] {
std::vector<int> indices;
for (const QModelIndex &index : view->selectionModel()->selectedRows(0)) {
indices.push_back(index.row());
}
std::sort(indices.begin(), indices.end());
for (int i = indices.size() - 1; i >= 0; i--) {
model->removeRow(indices[i]);
}
});
buttons->addStretch(1);
}
// QTableView *view = *layout.emplace<QTableView>();
// QStandardItemModel *model = new QStandardItemModel(0, 2, view);
// view->setModel(model);
// model->setHeaderData(0, Qt::Horizontal, "Trigger");
// model->setHeaderData(1, Qt::Horizontal, "Command");
// view->setSelectionMode(QAbstractItemView::ExtendedSelection);
// view->setSelectionBehavior(QAbstractItemView::SelectRows);
// view->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
// for (const QString &string : app->commands->getCommands()) {
// int index = string.indexOf(' ');
// if (index == -1) {
// model->appendRow({util::stringItem(string), util::stringItem("")});
// } else {
// model->appendRow(
// {util::stringItem(string.mid(0, index)), util::stringItem(string.mid(index +
// 1))});
// }
// }
// QObject::connect(
// model, &QStandardItemModel::dataChanged,
// [model](const QModelIndex &topLeft, const QModelIndex &bottomRight,
// const QVector<int> &roles) {
// QStringList list;
// for (int i = 0; i < model->rowCount(); i++) {
// QString command = model->item(i, 0)->data(Qt::EditRole).toString();
// // int index = command.indexOf(' ');
// // if (index != -1) {
// // command = command.mid(index);
// // }
// list.append(command + " " + model->item(i, 1)->data(Qt::EditRole).toString());
// }
// getApp()->commands->setCommands(list);
// });
// auto buttons = layout.emplace<QHBoxLayout>().withoutMargin();
// {
// auto add = buttons.emplace<QPushButton>("Add");
// QObject::connect(*add, &QPushButton::clicked, [model, view] {
// model->appendRow({util::stringItem("/command"), util::stringItem("")});
// view->scrollToBottom();
// });
// auto remove = buttons.emplace<QPushButton>("Remove");
// QObject::connect(*remove, &QPushButton::clicked, [view, model] {
// std::vector<int> indices;
// for (const QModelIndex &index : view->selectionModel()->selectedRows(0)) {
// indices.push_back(index.row());
// }
// std::sort(indices.begin(), indices.end());
// for (int i = indices.size() - 1; i >= 0; i--) {
// model->removeRow(indices[i]);
// }
// });
// buttons->addStretch(1);
// }
layout.append(this->createCheckBox("Also match the trigger at the end of the message", layout.append(this->createCheckBox("Also match the trigger at the end of the message",
app->settings->allowCommandsAtEnd)); app->settings->allowCommandsAtEnd));

View file

@ -1,10 +1,13 @@
#include "highlightingpage.hpp" #include "highlightingpage.hpp"
#include "application.hpp" #include "application.hpp"
#include "controllers/highlights/highlightcontroller.hpp"
#include "controllers/highlights/highlightmodel.hpp"
#include "debug/log.hpp" #include "debug/log.hpp"
#include "singletons/settingsmanager.hpp" #include "singletons/settingsmanager.hpp"
#include "util/layoutcreator.hpp" #include "util/layoutcreator.hpp"
#include "util/standarditemhelper.hpp" #include "util/standarditemhelper.hpp"
#include "widgets/helper/editablemodelview.hpp"
#include <QFileDialog> #include <QFileDialog>
#include <QListWidget> #include <QListWidget>
@ -41,95 +44,24 @@ HighlightingPage::HighlightingPage()
// HIGHLIGHTS // HIGHLIGHTS
auto highlights = tabs.appendTab(new QVBoxLayout, "Highlights"); auto highlights = tabs.appendTab(new QVBoxLayout, "Highlights");
{ {
QTableView *view = *highlights.emplace<QTableView>(); helper::EditableModelView *view = *highlights.emplace<helper::EditableModelView>(
auto *model = new QStandardItemModel(0, 4, view); app->highlights->createModel(nullptr));
model->setHeaderData(0, Qt::Horizontal, "Pattern");
model->setHeaderData(1, Qt::Horizontal, "Flash taskbar");
model->setHeaderData(2, Qt::Horizontal, "Play sound");
model->setHeaderData(3, Qt::Horizontal, "Regex");
view->setModel(model);
view->setSelectionMode(QAbstractItemView::ExtendedSelection);
view->setSelectionBehavior(QAbstractItemView::SelectRows);
view->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);
// own name view->getTableView()->hideColumn(3);
auto *yourName = util::stringItem("Your name (automatic)", false, false);
yourName->setData(QBrush("#666"), Qt::ForegroundRole);
yourName->setFlags(yourName->flags() | Qt::ItemIsUserCheckable |
Qt::ItemIsUserCheckable);
yourName->setData(app->settings->enableHighlightsSelf ? 2 : 0, Qt::CheckStateRole);
model->appendRow(
{yourName,
util::boolItem(app->settings->enableHighlightTaskbar.getValue(), true, false),
util::boolItem(app->settings->enableHighlightSound.getValue(), true, false),
util::emptyItem()});
// highlight phrases view->setTitles({"Pattern", "Flash taskbar", "Play sound", "Regex"});
// fourtf: could crash view->getTableView()->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);
for (const messages::HighlightPhrase &phrase :
app->settings->highlightProperties.getValue()) {
model->appendRow({util::stringItem(phrase.key), util::boolItem(phrase.alert),
util::boolItem(phrase.sound), util::boolItem(phrase.regex)});
}
// fourtf: make class extrend BaseWidget and add this to dpiChanged // fourtf: make class extrend BaseWidget and add this to dpiChanged
QTimer::singleShot(1, [view] { QTimer::singleShot(1, [view] {
view->resizeColumnsToContents(); view->getTableView()->resizeColumnsToContents();
view->setColumnWidth(0, 250); view->getTableView()->setColumnWidth(0, 250);
}); });
auto buttons = highlights.emplace<QHBoxLayout>().withoutMargin(); view->addButtonPressed.connect([] {
getApp()->highlights->phrases.appendItem(
QObject::connect( controllers::highlights::HighlightPhrase{"my phrase", true, false, false});
model, &QStandardItemModel::dataChanged,
[model, app](const QModelIndex &topLeft, const QModelIndex &bottomRight,
const QVector<int> &roles) {
std::vector<messages::HighlightPhrase> phrases;
for (int i = 1; i < model->rowCount(); i++) {
phrases.push_back(messages::HighlightPhrase{
model->item(i, 0)->data(Qt::DisplayRole).toString(),
model->item(i, 1)->data(Qt::CheckStateRole).toBool(),
model->item(i, 2)->data(Qt::CheckStateRole).toBool(),
model->item(i, 3)->data(Qt::CheckStateRole).toBool()});
}
app->settings->highlightProperties.setValue(phrases);
app->settings->enableHighlightsSelf.setValue(
model->item(0, 0)->data(Qt::CheckStateRole).toBool());
app->settings->enableHighlightTaskbar.setValue(
model->item(0, 1)->data(Qt::CheckStateRole).toBool());
app->settings->enableHighlightSound.setValue(
model->item(0, 2)->data(Qt::CheckStateRole).toBool());
});
auto add = buttons.emplace<QPushButton>("Add");
QObject::connect(*add, &QPushButton::clicked, [model, view] {
model->appendRow({util::stringItem(""),
util::boolItem(model->item(model->rowCount() - 1, 1)
->data(Qt::CheckStateRole)
.toBool()),
util::boolItem(model->item(model->rowCount() - 1, 2)
->data(Qt::CheckStateRole)
.toBool()),
util::boolItem(false)});
view->scrollToBottom();
}); });
auto remove = buttons.emplace<QPushButton>("Remove");
QObject::connect(*remove, &QPushButton::clicked, [view, model] {
std::vector<int> indices;
for (const QModelIndex &index : view->selectionModel()->selectedRows(0)) {
indices.push_back(index.row());
}
std::sort(indices.begin(), indices.end());
for (int i = indices.size() - 1; i >= 0; i--) {
model->removeRow(indices[i]);
}
});
buttons->addStretch(1);
view->hideColumn(3);
} }
auto disabledUsers = tabs.appendTab(new QVBoxLayout, "Disabled Users"); auto disabledUsers = tabs.appendTab(new QVBoxLayout, "Disabled Users");
{ {