1 /* ============================================================
2 * QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader
3 * Copyright (C) 2011-2020 QuiteRSS Team <quiterssteam@gmail.com>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 * ============================================================ */
18 #include "filterrulesdialog.h"
19
20 #include "mainapplication.h"
21 #include "settings.h"
22
FilterRulesDialog(QWidget * parent,int filterId,int feedId)23 FilterRulesDialog::FilterRulesDialog(QWidget *parent, int filterId, int feedId)
24 : Dialog(parent, Qt::WindowMinMaxButtonsHint)
25 , filterId_(filterId)
26 {
27 setWindowTitle(tr("Filter Rules"));
28 setMinimumHeight(300);
29
30 feedsTree_ = new QTreeWidget(this);
31 feedsTree_->setObjectName("feedsTreeFR");
32 feedsTree_->setColumnCount(2);
33 feedsTree_->setColumnHidden(1, true);
34 #ifdef HAVE_QT5
35 feedsTree_->header()->setSectionsMovable(false);
36 #else
37 feedsTree_->header()->setMovable(false);
38 #endif
39
40 itemNotChecked_ = false;
41
42 QStringList treeItem;
43 treeItem << tr("Feeds") << "Id";
44 feedsTree_->setHeaderLabels(treeItem);
45
46 treeItem.clear();
47 treeItem << tr("All Feeds") << "0";
48 QTreeWidgetItem *treeWidgetItem = new QTreeWidgetItem(treeItem);
49 treeWidgetItem->setCheckState(0, Qt::Unchecked);
50 feedsTree_->addTopLevelItem(treeWidgetItem);
51
52 QSqlQuery q;
53 QQueue<int> parentIds;
54 parentIds.enqueue(0);
55 while (!parentIds.empty()) {
56 int parentId = parentIds.dequeue();
57 QString qStr = QString("SELECT text, id, image, xmlUrl FROM feeds WHERE parentId='%1' ORDER BY rowToParent").
58 arg(parentId);
59 q.exec(qStr);
60 while (q.next()) {
61 QString feedText = q.value(0).toString();
62 QString feedIdT = q.value(1).toString();
63 QByteArray byteArray = q.value(2).toByteArray();
64 QString xmlUrl = q.value(3).toString();
65
66 treeItem.clear();
67 treeItem << feedText << feedIdT;
68 treeWidgetItem = new QTreeWidgetItem(treeItem);
69
70 if ((feedId == feedIdT.toInt()) || (feedId == parentId))
71 treeWidgetItem->setCheckState(0, Qt::Checked);
72 else
73 treeWidgetItem->setCheckState(0, Qt::Unchecked);
74
75 QPixmap iconItem;
76 if (xmlUrl.isEmpty()) {
77 iconItem.load(":/images/folder");
78 } else {
79 if (byteArray.isNull() || mainApp->mainWindow()->defaultIconFeeds_) {
80 iconItem.load(":/images/feed");
81 } else {
82 iconItem.loadFromData(QByteArray::fromBase64(byteArray));
83 }
84 }
85 treeWidgetItem->setIcon(0, iconItem);
86
87 QList<QTreeWidgetItem *> treeItems =
88 feedsTree_->findItems(QString::number(parentId),
89 Qt::MatchFixedString | Qt::MatchRecursive,
90 1);
91 treeItems.at(0)->addChild(treeWidgetItem);
92 if (xmlUrl.isEmpty())
93 parentIds.enqueue(feedIdT.toInt());
94 }
95 }
96 feedsTree_->expandAll();
97
98 if (feedId != -1) {
99 int rowCount = 0;
100 QTreeWidgetItem *childItem = feedsTree_->topLevelItem(0);
101 while (childItem) {
102 if (childItem->text(1).toInt() == feedId) break;
103 rowCount++;
104 childItem = feedsTree_->itemBelow(childItem);
105 }
106 feedsTree_->verticalScrollBar()->setValue(rowCount);
107 feedsTree_->topLevelItem(0)->setCheckState(0, Qt::Unchecked);
108 }
109 connect(feedsTree_, SIGNAL(itemChanged(QTreeWidgetItem*,int)),
110 this, SLOT(feedItemChanged(QTreeWidgetItem*,int)));
111
112 filterName_ = new LineEdit(this);
113 QHBoxLayout *filterNamelayout = new QHBoxLayout();
114 filterNamelayout->addWidget(new QLabel(tr("Filter name:")));
115 filterNamelayout->addWidget(filterName_);
116
117 matchComboBox_ = new QComboBox(this);
118
119 QStringList itemList;
120 itemList << tr("Match all news") << tr("Match all conditions")
121 << tr("Match any condition");
122 matchComboBox_->addItems(itemList);
123 matchComboBox_->setCurrentIndex(1);
124 connect(matchComboBox_, SIGNAL(currentIndexChanged(int)),
125 this, SLOT(selectMatch(int)));
126
127 QHBoxLayout *matchLayout = new QHBoxLayout();
128 matchLayout->addWidget(matchComboBox_);
129 matchLayout->addStretch();
130
131 conditionScrollArea_ = new QScrollArea(this);
132 conditionScrollArea_->setFocusPolicy(Qt::NoFocus);
133 conditionScrollArea_->setWidgetResizable(true);
134
135 conditionLayout_ = new QVBoxLayout();
136 conditionLayout_->setMargin(1);
137 conditionLayout_->setSpacing(1);
138 if (filterId_ == -1)
139 addCondition();
140
141 conditionWidget_ = new QWidget(this);
142 conditionWidget_->setObjectName("infoWidgetFR");
143 conditionWidget_->setLayout(conditionLayout_);
144 conditionScrollArea_->setWidget(conditionWidget_);
145
146 QVBoxLayout *splitterLayoutV1 = new QVBoxLayout();
147 splitterLayoutV1->setMargin(0);
148 splitterLayoutV1->addLayout(matchLayout);
149 splitterLayoutV1->addWidget(conditionScrollArea_, 1);
150
151 QWidget *splitterWidget1 = new QWidget(this);
152 splitterWidget1->setMinimumWidth(400);
153 splitterWidget1->setLayout(splitterLayoutV1);
154
155 actionsScrollArea_ = new QScrollArea(this);
156 actionsScrollArea_->setWidgetResizable(true);
157 actionsScrollArea_->setFocusPolicy(Qt::NoFocus);
158
159 actionsLayout_ = new QVBoxLayout();
160 actionsLayout_->setMargin(1);
161 actionsLayout_->setSpacing(1);
162 if (filterId_ == -1)
163 addAction();
164
165 actionsWidget_ = new QWidget(this);
166 actionsWidget_->setObjectName("actionsWidgetFR");
167 actionsWidget_->setLayout(actionsLayout_);
168 actionsScrollArea_->setWidget(actionsWidget_);
169
170 QVBoxLayout *splitterLayoutV2 = new QVBoxLayout();
171 splitterLayoutV2->setMargin(0);
172 splitterLayoutV2->addWidget(new QLabel(tr("Perform these actions:")));
173 splitterLayoutV2->addWidget(actionsScrollArea_, 1);
174
175 QWidget *splitterWidget2 = new QWidget(this);
176 splitterWidget2->setLayout(splitterLayoutV2);
177
178 QSplitter *spliter = new QSplitter(Qt::Vertical, this);
179 spliter->setChildrenCollapsible(false);
180 spliter->addWidget(splitterWidget1);
181 spliter->addWidget(splitterWidget2);
182
183 QVBoxLayout *rulesLayout = new QVBoxLayout();
184 rulesLayout->setMargin(0);
185 rulesLayout->addLayout(filterNamelayout);
186 rulesLayout->addWidget(spliter);
187
188 QWidget *rulesWidget = new QWidget(this);
189 rulesWidget->setLayout(rulesLayout);
190
191 QSplitter *mainSpliter = new QSplitter(this);
192 mainSpliter->setChildrenCollapsible(false);
193 mainSpliter->addWidget(rulesWidget);
194 mainSpliter->addWidget(feedsTree_);
195
196 QLabel *iconWarning = new QLabel(this);
197 iconWarning->setPixmap(QPixmap(":/images/warning"));
198 textWarning_ = new QLabel(this);
199 QFont font = textWarning_->font();
200 font.setBold(true);
201 textWarning_->setFont(font);
202
203 QHBoxLayout *warningLayout = new QHBoxLayout();
204 warningLayout->setMargin(0);
205 warningLayout->addWidget(iconWarning);
206 warningLayout->addWidget(textWarning_, 1);
207
208 warningWidget_ = new QWidget(this);
209 warningWidget_->setLayout(warningLayout);
210 warningWidget_->setVisible(false);
211
212 buttonsLayout->insertWidget(0, warningWidget_, 1);
213 buttonBox->addButton(QDialogButtonBox::Ok);
214 buttonBox->addButton(QDialogButtonBox::Cancel);
215 connect(buttonBox, SIGNAL(accepted()), this, SLOT(acceptDialog()));
216
217 pageLayout->addWidget(mainSpliter);
218
219 setData();
220
221 filterName_->setFocus();
222 filterName_->selectAll();
223
224 Settings settings;
225 restoreGeometry(settings.value("filterRulesDlg/geometry").toByteArray());
226
227 connect(filterName_, SIGNAL(textChanged(QString)),
228 this, SLOT(filterNameChanged(QString)));
229 connect(this, SIGNAL(finished(int)), this, SLOT(closeDialog()));
230 }
231
setData()232 void FilterRulesDialog::setData()
233 {
234 if (filterId_ == -1) return;
235
236 QSqlQuery q;
237 QString qStr = QString("SELECT name, type, feeds FROM filters WHERE id=='%1'").
238 arg(filterId_);
239 q.exec(qStr);
240 if (q.next()) {
241 filterName_->setText(q.value(0).toString());
242
243 matchComboBox_->setCurrentIndex(q.value(1).toInt());
244
245 itemNotChecked_ = true;
246 QStringList strIdFeeds = q.value(2).toString().split(",", QString::SkipEmptyParts);
247 foreach (QString strIdFeed, strIdFeeds) {
248 QList<QTreeWidgetItem *> treeItems =
249 feedsTree_->findItems(strIdFeed,
250 Qt::MatchFixedString | Qt::MatchRecursive,
251 1);
252 if (treeItems.count())
253 treeItems.at(0)->setCheckState(0, Qt::Checked);
254 }
255 QTreeWidgetItem *treeItem = feedsTree_->itemBelow(feedsTree_->topLevelItem(0));
256 while (treeItem) {
257 if (treeItem->checkState(0) == Qt::Unchecked) {
258 feedsTree_->topLevelItem(0)->setCheckState(0, Qt::Unchecked);
259 break;
260 }
261 treeItem = feedsTree_->itemBelow(treeItem);
262 }
263 itemNotChecked_ = false;
264
265 qStr = QString("SELECT field, condition, content "
266 "FROM filterConditions WHERE idFilter=='%1' ORDER BY content").
267 arg(filterId_);
268 q.exec(qStr);
269 while (q.next()) {
270 ItemCondition *itemCondition = addCondition();
271 itemCondition->comboBox1_->setCurrentIndex(q.value(0).toInt());
272 itemCondition->comboBox2_->setCurrentIndex(q.value(1).toInt());
273 if (q.value(0).toInt() == 4)
274 itemCondition->comboBox3_->setCurrentIndex(q.value(2).toInt());
275 else
276 itemCondition->lineEdit_->setText(q.value(2).toString());
277 }
278 if (conditionLayout_->count() == 0)
279 addCondition();
280
281 qStr = QString("SELECT action, params "
282 "FROM filterActions WHERE idFilter=='%1'").
283 arg(filterId_);
284 q.exec(qStr);
285 while (q.next()) {
286 ItemAction *itemAction = addAction();
287 int action = q.value(0).toInt();
288 itemAction->comboBox1_->setCurrentIndex(action);
289 if (action == 3) {
290 int index = itemAction->comboBox2_->findData(q.value(1).toInt());
291 itemAction->comboBox2_->setCurrentIndex(index);
292 } else if (action == 4) {
293 itemAction->soundPathEdit_->setText(q.value(1).toString());
294 } else if (action == 5) {
295 itemAction->colorButton_->setToolTip(q.value(1).toString());
296 QPixmap pixmap(14, 14);
297 pixmap.fill(QColor(q.value(1).toString()));
298 itemAction->colorButton_->setIcon(pixmap);
299 }
300 }
301 if (actionsLayout_->count() == 0)
302 addAction();
303 }
304 }
305
closeDialog()306 void FilterRulesDialog::closeDialog()
307 {
308 Settings settings;
309 settings.setValue("filterRulesDlg/geometry", saveGeometry());
310 }
311
acceptDialog()312 void FilterRulesDialog::acceptDialog()
313 {
314 if (filterName_->text().isEmpty()) {
315 filterName_->setFocus();
316 textWarning_->setText(tr("Please enter name for the filter."));
317 warningWidget_->setVisible(true);
318 return;
319 }
320
321 if (matchComboBox_->currentIndex() != 0) {
322 for (int i = 0; i < conditionLayout_->count()-2; i++) {
323 ItemCondition *itemCondition =
324 qobject_cast<ItemCondition*>(conditionLayout_->itemAt(i)->widget());
325 if ((itemCondition->comboBox1_->currentIndex() != 4) &&
326 itemCondition->lineEdit_->text().isEmpty()) {
327 itemCondition->lineEdit_->setFocus();
328 textWarning_->setText(tr("Please enter search condition for the news filter."));
329 warningWidget_->setVisible(true);
330 return;
331 }
332 }
333 }
334
335 feedsTree_->expandAll();
336 QString strIdFeeds;
337 QTreeWidgetItem *treeItem = feedsTree_->itemBelow(feedsTree_->topLevelItem(0));
338 while (treeItem) {
339 if (treeItem->checkState(0) == Qt::Checked) {
340 strIdFeeds.append(",");
341 strIdFeeds.append(treeItem->text(1));
342 }
343 treeItem = feedsTree_->itemBelow(treeItem);
344 }
345 strIdFeeds.append(",");
346
347 QSqlQuery q;
348 if (filterId_ == -1) {
349 QString qStr = QString("INSERT INTO filters (name, type, feeds) "
350 "VALUES (?, ?, ?)");
351 q.prepare(qStr);
352 q.addBindValue(filterName_->text());
353 q.addBindValue(matchComboBox_->currentIndex());
354 q.addBindValue(strIdFeeds);
355 q.exec();
356
357 filterId_ = q.lastInsertId().toInt();
358 qStr = QString("UPDATE filters SET num='%1' WHERE id=='%1'").
359 arg(filterId_);
360 q.exec(qStr);
361
362 for (int i = 0; i < conditionLayout_->count()-2; i++) {
363 ItemCondition *itemCondition =
364 qobject_cast<ItemCondition*>(conditionLayout_->itemAt(i)->widget());
365 qStr = QString("INSERT INTO filterConditions "
366 "(idFilter, field, condition, content) "
367 "VALUES (?, ?, ?, ?)");
368 q.prepare(qStr);
369 q.addBindValue(filterId_);
370 q.addBindValue(itemCondition->comboBox1_->currentIndex());
371 q.addBindValue(itemCondition->comboBox2_->currentIndex());
372 if (itemCondition->comboBox1_->currentIndex() == 4)
373 q.addBindValue(itemCondition->comboBox3_->currentIndex());
374 else
375 q.addBindValue(itemCondition->lineEdit_->text());
376 q.exec();
377 }
378
379 for (int i = 0; i < actionsLayout_->count()-2; i++) {
380 ItemAction *itemAction =
381 qobject_cast<ItemAction*>(actionsLayout_->itemAt(i)->widget());
382 qStr = QString("INSERT INTO filterActions "
383 "(idFilter, action, params) "
384 "VALUES (?, ?, ?)");
385 q.prepare(qStr);
386 q.addBindValue(filterId_);
387 q.addBindValue(itemAction->comboBox1_->currentIndex());
388 if (itemAction->comboBox1_->currentIndex() == 3)
389 q.addBindValue(itemAction->comboBox2_->itemData(itemAction->comboBox2_->currentIndex()));
390 else if (itemAction->comboBox1_->currentIndex() == 4)
391 q.addBindValue(itemAction->soundPathEdit_->text());
392 else if (itemAction->comboBox1_->currentIndex() == 5)
393 q.addBindValue(itemAction->colorButton_->toolTip());
394 else
395 q.addBindValue(0);
396 q.exec();
397 }
398 } else {
399 q.prepare("UPDATE filters SET name=?, type=?, feeds=? WHERE id=?");
400 q.addBindValue(filterName_->text());
401 q.addBindValue(matchComboBox_->currentIndex());
402 q.addBindValue(strIdFeeds);
403 q.addBindValue(filterId_);
404 q.exec();
405
406 q.exec(QString("DELETE FROM filterConditions WHERE idFilter='%1'").arg(filterId_));
407 q.exec(QString("DELETE FROM filterActions WHERE idFilter='%1'").arg(filterId_));
408
409 for (int i = 0; i < conditionLayout_->count()-2; i++) {
410 ItemCondition *itemCondition =
411 qobject_cast<ItemCondition*>(conditionLayout_->itemAt(i)->widget());
412 QString qStr = QString("INSERT INTO filterConditions "
413 "(idFilter, field, condition, content) "
414 "VALUES (?, ?, ?, ?)");
415 q.prepare(qStr);
416 q.addBindValue(filterId_);
417 q.addBindValue(itemCondition->comboBox1_->currentIndex());
418 q.addBindValue(itemCondition->comboBox2_->currentIndex());
419 if (itemCondition->comboBox1_->currentIndex() == 4)
420 q.addBindValue(itemCondition->comboBox3_->currentIndex());
421 else
422 q.addBindValue(itemCondition->lineEdit_->text());
423 q.exec();
424 }
425
426 for (int i = 0; i < actionsLayout_->count()-2; i++) {
427 ItemAction *itemAction =
428 qobject_cast<ItemAction*>(actionsLayout_->itemAt(i)->widget());
429 QString qStr = QString("INSERT INTO filterActions "
430 "(idFilter, action, params) "
431 "VALUES (?, ?, ?)");
432 q.prepare(qStr);
433 q.addBindValue(filterId_);
434 q.addBindValue(itemAction->comboBox1_->currentIndex());
435 if (itemAction->comboBox1_->currentIndex() == 3)
436 q.addBindValue(itemAction->comboBox2_->itemData(itemAction->comboBox2_->currentIndex()));
437 else if (itemAction->comboBox1_->currentIndex() == 4)
438 q.addBindValue(itemAction->soundPathEdit_->text());
439 else if (itemAction->comboBox1_->currentIndex() == 5)
440 q.addBindValue(itemAction->colorButton_->toolTip());
441 else
442 q.addBindValue(0);
443 q.exec();
444 }
445 }
446 accept();
447 }
448
filterNameChanged(const QString &)449 void FilterRulesDialog::filterNameChanged(const QString &)
450 {
451 warningWidget_->setVisible(false);
452 }
453
selectMatch(int index)454 void FilterRulesDialog::selectMatch(int index)
455 {
456 if (index == 0) {
457 conditionWidget_->setEnabled(false);
458 } else {
459 conditionWidget_->setEnabled(true);
460 }
461 }
462
feedItemChanged(QTreeWidgetItem * item,int column)463 void FilterRulesDialog::feedItemChanged(QTreeWidgetItem *item, int column)
464 {
465 if ((column != 0) || itemNotChecked_) return;
466
467 itemNotChecked_ = true;
468 if (item->checkState(0) == Qt::Unchecked) {
469 setCheckStateItem(item, Qt::Unchecked);
470
471 QTreeWidgetItem *parentItem = item->parent();
472 while (parentItem) {
473 parentItem->setCheckState(0, Qt::Unchecked);
474 parentItem = parentItem->parent();
475 }
476 } else {
477 setCheckStateItem(item, Qt::Checked);
478 }
479 itemNotChecked_ = false;
480 }
481
setCheckStateItem(QTreeWidgetItem * item,Qt::CheckState state)482 void FilterRulesDialog::setCheckStateItem(QTreeWidgetItem *item, Qt::CheckState state)
483 {
484 for (int i = 0; i < item->childCount(); ++i) {
485 QTreeWidgetItem *childItem = item->child(i);
486 childItem->setCheckState(0, state);
487 setCheckStateItem(childItem, state);
488 }
489 }
490
addCondition()491 ItemCondition *FilterRulesDialog::addCondition()
492 {
493 conditionLayout_->removeItem(conditionLayout_->itemAt(conditionLayout_->count()-1));
494 conditionLayout_->removeItem(conditionLayout_->itemAt(conditionLayout_->count()-1));
495 ItemCondition *itemCondition = new ItemCondition(this);
496 conditionLayout_->addWidget(itemCondition);
497 conditionLayout_->addStretch();
498 conditionLayout_->addSpacing(25);
499 connect(itemCondition->addButton_, SIGNAL(clicked()), this, SLOT(addCondition()));
500 connect(itemCondition, SIGNAL(signalDeleteCondition(ItemCondition*)),
501 this, SLOT(deleteCondition(ItemCondition*)));
502
503 QScrollBar *scrollBar = conditionScrollArea_->verticalScrollBar();
504 scrollBar->setValue(scrollBar->maximum() -
505 scrollBar->minimum() +
506 scrollBar->pageStep());
507 itemCondition->lineEdit_->setFocus();
508 connect(itemCondition->lineEdit_, SIGNAL(textChanged(QString)),
509 this, SLOT(filterNameChanged(QString)));
510 connect(itemCondition->comboBox1_, SIGNAL(currentIndexChanged(QString)),
511 this, SLOT(filterNameChanged(QString)));
512 return itemCondition;
513 }
514
deleteCondition(ItemCondition * item)515 void FilterRulesDialog::deleteCondition(ItemCondition *item)
516 {
517 delete item;
518 if (conditionLayout_->count() == 2) {
519 addCondition();
520 }
521 }
522
addAction()523 ItemAction *FilterRulesDialog::addAction()
524 {
525 actionsLayout_->removeItem(actionsLayout_->itemAt(actionsLayout_->count()-1));
526 actionsLayout_->removeItem(actionsLayout_->itemAt(actionsLayout_->count()-1));
527 ItemAction *itemAction = new ItemAction(this);
528
529 QSqlQuery q;
530 q.exec("SELECT id, name, image FROM labels ORDER BY num");
531 while (q.next()) {
532 int idLabel = q.value(0).toInt();
533 QString nameLabel = q.value(1).toString();
534 if ((idLabel <= 6) && (MainWindow::nameLabels().at(idLabel-1) == nameLabel)) {
535 nameLabel = MainWindow::trNameLabels().at(idLabel-1);
536 }
537 QByteArray byteArray = q.value(2).toByteArray();
538 QPixmap imageLabel;
539 if (!byteArray.isNull())
540 imageLabel.loadFromData(byteArray);
541
542 itemAction->comboBox2_->addItem(QIcon(imageLabel), nameLabel, idLabel);
543 }
544
545 actionsLayout_->addWidget(itemAction);
546 actionsLayout_->addStretch();
547 actionsLayout_->addSpacing(25);
548 connect(itemAction, SIGNAL(signalPlaySound(QString)),
549 parent(), SLOT(slotPlaySound(QString)));
550 connect(itemAction->addButton_, SIGNAL(clicked()), this, SLOT(addAction()));
551 connect(itemAction, SIGNAL(signalDeleteAction(ItemAction*)),
552 this, SLOT(deleteAction(ItemAction*)));
553 return itemAction;
554 }
555
deleteAction(ItemAction * item)556 void FilterRulesDialog::deleteAction(ItemAction *item)
557 {
558 delete item;
559 if (actionsLayout_->count() == 2) {
560 addAction();
561 }
562 }
563