1 #include "ChannelFilterEditorDialog.hpp"
2 
3 #include "controllers/filters/parser/FilterParser.hpp"
4 
5 #include <QLabel>
6 #include <QPushButton>
7 
8 namespace chatterino {
9 
10 namespace {
11     const QStringList friendlyBinaryOps = {
12         "and", "or",       "+",           "-",         "*",        "/",
13         "%",   "equals",   "not equals",  "<",         ">",        "<=",
14         ">=",  "contains", "starts with", "ends with", "(nothing)"};
15     const QStringList realBinaryOps = {
16         "&&", "||",       "+",          "-",        "*", "/",
17         "%",  "==",       "!=",         "<",        ">", "<=",
18         ">=", "contains", "startswith", "endswith", ""};
19 }  // namespace
20 
ChannelFilterEditorDialog(QWidget * parent)21 ChannelFilterEditorDialog::ChannelFilterEditorDialog(QWidget *parent)
22     : QDialog(parent)
23 {
24     auto vbox = new QVBoxLayout(this);
25     auto filterVbox = new QVBoxLayout;
26     auto buttonBox = new QHBoxLayout;
27     auto okButton = new QPushButton("Ok");
28     auto cancelButton = new QPushButton("Cancel");
29 
30     okButton->setDefault(true);
31     cancelButton->setDefault(false);
32 
33     auto helpLabel =
34         new QLabel(QString("<a href='%1'><span "
35                            "style='color:#99f'>variable help</span></a>")
36                        .arg("https://wiki.chatterino.com/Filters/#variables"));
37     helpLabel->setOpenExternalLinks(true);
38 
39     buttonBox->addWidget(helpLabel);
40     buttonBox->addStretch(1);
41     buttonBox->addWidget(okButton);
42     buttonBox->addWidget(cancelButton);
43 
44     QObject::connect(okButton, &QAbstractButton::clicked, [this] {
45         this->accept();
46         this->close();
47     });
48     QObject::connect(cancelButton, &QAbstractButton::clicked, [this] {
49         this->reject();
50         this->close();
51     });
52 
53     this->setWindowFlags(
54         (this->windowFlags() & ~(Qt::WindowContextHelpButtonHint)) |
55         Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint);
56     this->setWindowTitle("Channel Filter Creator");
57 
58     auto titleInput = new QLineEdit;
59     titleInput->setPlaceholderText("Filter name");
60     titleInput->setText("My filter");
61 
62     this->titleInput_ = titleInput;
63     filterVbox->addWidget(titleInput);
64 
65     auto left = new ChannelFilterEditorDialog::ValueSpecifier;
66     auto right = new ChannelFilterEditorDialog::ValueSpecifier;
67     auto exp =
68         new ChannelFilterEditorDialog::BinaryOperationSpecifier(left, right);
69 
70     this->expressionSpecifier_ = exp;
71     filterVbox->addLayout(exp->layout());
72     vbox->addLayout(filterVbox);
73     vbox->addLayout(buttonBox);
74 
75     // setup default values
76     left->setType("Variable");
77     left->setValue("message.content");
78     exp->setOperation("contains");
79     right->setType("Text");
80     right->setValue("hello");
81 }
82 
getFilter() const83 const QString ChannelFilterEditorDialog::getFilter() const
84 {
85     return this->expressionSpecifier_->expressionText();
86 }
87 
getTitle() const88 const QString ChannelFilterEditorDialog::getTitle() const
89 {
90     return this->titleInput_->text();
91 }
92 
ValueSpecifier()93 ChannelFilterEditorDialog::ValueSpecifier::ValueSpecifier()
94 {
95     this->typeCombo_ = new QComboBox;
96     this->varCombo_ = new QComboBox;
97     this->valueInput_ = new QLineEdit;
98     this->layout_ = new QHBoxLayout;
99 
100     this->typeCombo_->insertItems(
101         0, {"Constant Text", "Constant Number", "Variable"});
102     this->varCombo_->insertItems(0, filterparser::validIdentifiersMap.values());
103 
104     this->layout_->addWidget(this->typeCombo_);
105     this->layout_->addWidget(this->varCombo_, 1);
106     this->layout_->addWidget(this->valueInput_, 1);
107     this->layout_->setContentsMargins(5, 5, 5, 5);
108 
109     QObject::connect(
110         this->typeCombo_, QOverload<int>::of(&QComboBox::currentIndexChanged),
111         [this](int index) {
112             const auto isNumber = (index == 1);
113             const auto isVariable = (index == 2);
114 
115             this->valueInput_->setVisible(!isVariable);
116             this->varCombo_->setVisible(isVariable);
117             this->valueInput_->setValidator(
118                 isNumber ? (new QIntValidator(0, INT_MAX)) : nullptr);
119 
120             this->valueInput_->clear();
121         });
122 
123     this->varCombo_->hide();
124     this->typeCombo_->setCurrentIndex(0);
125 }
126 
setEnabled(bool enabled)127 void ChannelFilterEditorDialog::ValueSpecifier::setEnabled(bool enabled)
128 {
129     this->typeCombo_->setEnabled(enabled);
130     this->varCombo_->setEnabled(enabled);
131     this->valueInput_->setEnabled(enabled);
132 }
133 
setType(const QString & type)134 void ChannelFilterEditorDialog::ValueSpecifier::setType(const QString &type)
135 {
136     this->typeCombo_->setCurrentText(type);
137 }
138 
setValue(const QString & value)139 void ChannelFilterEditorDialog::ValueSpecifier::setValue(const QString &value)
140 {
141     if (this->typeCombo_->currentIndex() == 2)
142     {
143         this->varCombo_->setCurrentText(
144             filterparser::validIdentifiersMap.value(value));
145     }
146     else
147     {
148         this->valueInput_->setText(value);
149     }
150 }
151 
layout() const152 QLayout *ChannelFilterEditorDialog::ValueSpecifier::layout() const
153 {
154     return this->layout_;
155 }
156 
expressionText()157 QString ChannelFilterEditorDialog::ValueSpecifier::expressionText()
158 {
159     switch (this->typeCombo_->currentIndex())
160     {
161         case 0:  // text
162             return QString("\"%1\"").arg(
163                 this->valueInput_->text().replace("\"", "\\\""));
164         case 1:  // number
165             return this->valueInput_->text();
166         case 2:  // variable
167             return filterparser::validIdentifiersMap.key(
168                 this->varCombo_->currentText());
169         default:
170             return "";
171     }
172 }
173 
BinaryOperationSpecifier(ExpressionSpecifier * left,ExpressionSpecifier * right)174 ChannelFilterEditorDialog::BinaryOperationSpecifier::BinaryOperationSpecifier(
175     ExpressionSpecifier *left, ExpressionSpecifier *right)
176     : left_(left)
177     , right_(right)
178 {
179     this->opCombo_ = new QComboBox;
180     this->layout_ = new QVBoxLayout;
181 
182     this->opCombo_->insertItems(0, friendlyBinaryOps);
183 
184     this->layout_->addLayout(this->left_->layout());
185     this->layout_->addWidget(this->opCombo_);
186     this->layout_->addLayout(this->right_->layout());
187     this->layout_->setContentsMargins(5, 5, 5, 5);
188 
189     QObject::connect(
190         this->opCombo_, QOverload<int>::of(&QComboBox::currentIndexChanged),
191         [this](int index) {
192             // disable if set to "(nothing)"
193             this->right_->setEnabled(!realBinaryOps.at(index).isEmpty());
194         });
195 }
196 
setEnabled(bool enabled)197 void ChannelFilterEditorDialog::BinaryOperationSpecifier::setEnabled(
198     bool enabled)
199 {
200     this->opCombo_->setEnabled(enabled);
201     this->left_->setEnabled(enabled);
202     this->right_->setEnabled(enabled);
203 }
204 
setOperation(const QString & op)205 void ChannelFilterEditorDialog::BinaryOperationSpecifier::setOperation(
206     const QString &op)
207 {
208     this->opCombo_->setCurrentText(op);
209 }
210 
layout() const211 QLayout *ChannelFilterEditorDialog::BinaryOperationSpecifier::layout() const
212 {
213     return this->layout_;
214 }
215 
expressionText()216 QString ChannelFilterEditorDialog::BinaryOperationSpecifier::expressionText()
217 {
218     QString opText = realBinaryOps.at(this->opCombo_->currentIndex());
219     if (opText.isEmpty())
220     {
221         return this->left_->expressionText();
222     }
223 
224     return QString("(%1) %2 (%3)")
225         .arg(this->left_->expressionText())
226         .arg(opText)
227         .arg(this->right_->expressionText());
228 }
229 
230 }  // namespace chatterino
231