1 // For license of this file, see <project-root-folder>/LICENSE.md.
2
3 #include <QDateTime>
4 #include <QJSEngine>
5 #include <QProcess>
6
7 #include "gui/dialogs/formmessagefiltersmanager.h"
8
9 #include "3rd-party/boolinq/boolinq.h"
10 #include "core/messagefilter.h"
11 #include "core/messagesforfiltersmodel.h"
12 #include "database/databasequeries.h"
13 #include "exceptions/filteringexception.h"
14 #include "gui/guiutilities.h"
15 #include "gui/messagebox.h"
16 #include "miscellaneous/application.h"
17 #include "miscellaneous/feedreader.h"
18 #include "miscellaneous/iconfactory.h"
19 #include "network-web/webfactory.h"
20 #include "services/abstract/accountcheckmodel.h"
21 #include "services/abstract/feed.h"
22 #include "services/abstract/labelsnode.h"
23
FormMessageFiltersManager(FeedReader * reader,const QList<ServiceRoot * > & accounts,QWidget * parent)24 FormMessageFiltersManager::FormMessageFiltersManager(FeedReader* reader, const QList<ServiceRoot*>& accounts, QWidget* parent)
25 : QDialog(parent), m_feedsModel(new AccountCheckSortedModel(this)), m_rootItem(new RootItem()),
26 m_accounts(accounts), m_reader(reader), m_loadingFilter(false), m_msgModel(new MessagesForFiltersModel(this)) {
27 m_ui.setupUi(this);
28
29 std::sort(m_accounts.begin(), m_accounts.end(), [](const ServiceRoot* lhs, const ServiceRoot* rhs) {
30 return lhs->title().compare(rhs->title(), Qt::CaseSensitivity::CaseInsensitive) < 0;
31 });
32
33 m_ui.m_treeExistingMessages->setModel(m_msgModel);
34
35 GuiUtilities::applyDialogProperties(*this, qApp->icons()->fromTheme(QSL("view-list-details")));
36
37 m_ui.m_treeFeeds->setIndentation(FEEDS_VIEW_INDENTATION);
38 m_ui.m_treeFeeds->setModel(m_feedsModel);
39 m_ui.m_btnCheckAll->setIcon(qApp->icons()->fromTheme(QSL("dialog-yes")));
40 m_ui.m_btnUncheckAll->setIcon(qApp->icons()->fromTheme(QSL("dialog-no")));
41 m_ui.m_btnAddNew->setIcon(qApp->icons()->fromTheme(QSL("list-add")));
42 m_ui.m_btnRemoveSelected->setIcon(qApp->icons()->fromTheme(QSL("list-remove")));
43 m_ui.m_btnBeautify->setIcon(qApp->icons()->fromTheme(QSL("format-justify-fill")));
44 m_ui.m_btnTest->setIcon(qApp->icons()->fromTheme(QSL("media-playback-start")));
45 m_ui.m_btnRunOnMessages->setIcon(qApp->icons()->fromTheme(QSL("media-playback-start")));
46 m_ui.m_btnDetailedHelp->setIcon(qApp->icons()->fromTheme(QSL("help-contents")));
47 m_ui.m_txtScript->setFont(QFontDatabase::systemFont(QFontDatabase::SystemFont::FixedFont));
48 m_ui.m_treeExistingMessages->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
49
50 m_ui.m_treeExistingMessages->header()->setSectionResizeMode(MFM_MODEL_ISREAD, QHeaderView::ResizeMode::ResizeToContents);
51 m_ui.m_treeExistingMessages->header()->setSectionResizeMode(MFM_MODEL_ISIMPORTANT, QHeaderView::ResizeMode::ResizeToContents);
52 m_ui.m_treeExistingMessages->header()->setSectionResizeMode(MFM_MODEL_ISDELETED, QHeaderView::ResizeMode::ResizeToContents);
53 m_ui.m_treeExistingMessages->header()->setSectionResizeMode(MFM_MODEL_AUTHOR, QHeaderView::ResizeMode::ResizeToContents);
54 m_ui.m_treeExistingMessages->header()->setSectionResizeMode(MFM_MODEL_CREATED, QHeaderView::ResizeMode::ResizeToContents);
55 m_ui.m_treeExistingMessages->header()->setSectionResizeMode(MFM_MODEL_SCORE, QHeaderView::ResizeMode::ResizeToContents);
56 m_ui.m_treeExistingMessages->header()->setSectionResizeMode(MFM_MODEL_TITLE, QHeaderView::ResizeMode::Interactive);
57 m_ui.m_treeExistingMessages->header()->setSectionResizeMode(MFM_MODEL_URL, QHeaderView::ResizeMode::Interactive);
58
59 connect(m_ui.m_btnDetailedHelp, &QPushButton::clicked, this, []() {
60 qApp->web()->openUrlInExternalBrowser(QSL(MSG_FILTERING_HELP));
61 });
62 connect(m_ui.m_listFilters, &QListWidget::currentRowChanged,
63 this, &FormMessageFiltersManager::loadFilter);
64 connect(m_ui.m_btnAddNew, &QPushButton::clicked, this, [this]() {
65 addNewFilter();
66 });
67 connect(m_ui.m_btnRemoveSelected, &QPushButton::clicked,
68 this, &FormMessageFiltersManager::removeSelectedFilter);
69 connect(m_ui.m_txtTitle, &QLineEdit::textChanged, this, &FormMessageFiltersManager::saveSelectedFilter);
70 connect(m_ui.m_txtScript, &QPlainTextEdit::textChanged, this, &FormMessageFiltersManager::saveSelectedFilter);
71 connect(m_ui.m_btnTest, &QPushButton::clicked, this, &FormMessageFiltersManager::testFilter);
72 connect(m_ui.m_btnBeautify, &QPushButton::clicked, this, &FormMessageFiltersManager::beautifyScript);
73 connect(m_ui.m_cmbAccounts, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
74 this,
75 &FormMessageFiltersManager::onAccountChanged);
76 connect(m_ui.m_btnCheckAll, &QPushButton::clicked, m_feedsModel->sourceModel(), &AccountCheckModel::checkAllItems);
77 connect(m_ui.m_btnUncheckAll, &QPushButton::clicked, m_feedsModel->sourceModel(), &AccountCheckModel::uncheckAllItems);
78 connect(m_feedsModel->sourceModel(), &AccountCheckModel::checkStateChanged,
79 this, &FormMessageFiltersManager::onFeedChecked);
80 connect(m_ui.m_treeFeeds->selectionModel(), &QItemSelectionModel::selectionChanged,
81 this, &FormMessageFiltersManager::displayMessagesOfFeed);
82 connect(m_ui.m_btnRunOnMessages, &QPushButton::clicked,
83 this, &FormMessageFiltersManager::processCheckedFeeds);
84 connect(m_ui.m_treeExistingMessages, &QTreeView::customContextMenuRequested,
85 this, &FormMessageFiltersManager::showMessageContextMenu);
86
87 initializeTestingMessage();
88 loadFilters();
89 loadFilter();
90 loadAccounts();
91 }
92
~FormMessageFiltersManager()93 FormMessageFiltersManager::~FormMessageFiltersManager() {
94 delete m_rootItem;
95 }
96
selectedFilter() const97 MessageFilter* FormMessageFiltersManager::selectedFilter() const {
98 if (m_ui.m_listFilters->currentItem() == nullptr) {
99 return nullptr;
100 }
101 else {
102 return m_ui.m_listFilters->currentItem()->data(Qt::ItemDataRole::UserRole).value<MessageFilter*>();
103 }
104 }
105
selectedAccount() const106 ServiceRoot* FormMessageFiltersManager::selectedAccount() const {
107 auto dat = m_ui.m_cmbAccounts->currentData(Qt::ItemDataRole::UserRole);
108
109 return dat.isNull() ? nullptr : dat.value<ServiceRoot*>();
110 }
111
filterMessagesLikeThis(const Message & msg)112 void FormMessageFiltersManager::filterMessagesLikeThis(const Message& msg) {
113 QString filter_script = QSL("function filterMessage() {\n"
114 " // Adjust the condition to suit your needs.\n"
115 " var is_message_same =\n"
116 " msg.isRead == %1 &&\n"
117 " msg.isImportant == %2 &&\n"
118 " msg.title == '%3' &&\n"
119 " msg.url == '%4';\n"
120 "\n"
121 " if (is_message_same) {\n"
122 " return MessageObject.Accept;\n"
123 " }\n"
124 " else {\n"
125 " return MessageObject.Ignore;\n"
126 " }\n"
127 "}").arg(QString::number(int(msg.m_isRead)),
128 QString::number(int(msg.m_isImportant)),
129 msg.m_title,
130 msg.m_url);
131
132 addNewFilter(filter_script);
133 }
134
showMessageContextMenu(QPoint pos)135 void FormMessageFiltersManager::showMessageContextMenu(QPoint pos) {
136 Message* msg = m_msgModel->messageForRow(m_ui.m_treeExistingMessages->indexAt(pos).row());
137
138 if (msg != nullptr) {
139 QMenu menu(tr("Context menu"), m_ui.m_treeExistingMessages);
140
141 menu.addAction(tr("Filter articles like this"), this, [=]() {
142 filterMessagesLikeThis(*msg);
143 });
144 menu.exec(m_ui.m_treeExistingMessages->mapToGlobal(pos));
145 }
146 }
147
removeSelectedFilter()148 void FormMessageFiltersManager::removeSelectedFilter() {
149 auto* fltr = selectedFilter();
150
151 if (fltr == nullptr) {
152 return;
153 }
154
155 m_reader->removeMessageFilter(fltr);
156 delete m_ui.m_listFilters->currentItem();
157 }
158
loadFilters()159 void FormMessageFiltersManager::loadFilters() {
160 auto flt = m_reader->messageFilters();
161
162 for (auto* fltr : qAsConst(flt)) {
163 auto* it = new QListWidgetItem(fltr->name(), m_ui.m_listFilters);
164
165 it->setData(Qt::ItemDataRole::UserRole, QVariant::fromValue<MessageFilter*>(fltr));
166 }
167 }
168
addNewFilter(const QString & filter_script)169 void FormMessageFiltersManager::addNewFilter(const QString& filter_script) {
170 try {
171 auto* fltr = m_reader->addMessageFilter(
172 tr("New article filter"),
173 filter_script.isEmpty()
174 ? QSL("function filterMessage() { return MessageObject.Accept; }")
175 : filter_script);
176 auto* it = new QListWidgetItem(fltr->name(), m_ui.m_listFilters);
177
178 it->setData(Qt::ItemDataRole::UserRole, QVariant::fromValue<MessageFilter*>(fltr));
179
180 m_ui.m_listFilters->setCurrentRow(m_ui.m_listFilters->count() - 1);
181 }
182 catch (const ApplicationException& ex) {
183 MessageBox::show(this, QMessageBox::Icon::Critical, tr("Error"),
184 tr("Cannot save new filter, error: '%1'.").arg(ex.message()));
185 }
186 }
187
saveSelectedFilter()188 void FormMessageFiltersManager::saveSelectedFilter() {
189 if (m_loadingFilter) {
190 return;
191 }
192
193 auto* fltr = selectedFilter();
194
195 if (fltr == nullptr || m_ui.m_txtTitle->text().isEmpty() || m_ui.m_txtScript->toPlainText().isEmpty()) {
196 return;
197 }
198
199 fltr->setName(m_ui.m_txtTitle->text());
200 fltr->setScript(m_ui.m_txtScript->toPlainText());
201 m_ui.m_listFilters->currentItem()->setText(fltr->name());
202
203 m_reader->updateMessageFilter(fltr);
204 }
205
loadFilter()206 void FormMessageFiltersManager::loadFilter() {
207 auto* filter = selectedFilter();
208 auto* acc = selectedAccount();
209
210 loadAccount(acc);
211 showFilter(filter);
212 loadFilterFeedAssignments(filter, acc);
213 }
214
testFilter()215 void FormMessageFiltersManager::testFilter() {
216 m_ui.m_txtErrors->clear();
217
218 // Perform per-message filtering.
219 auto* selected_fd_cat = selectedCategoryFeed();
220 QJSEngine filter_engine;
221 QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className());
222 MessageObject msg_obj(&database,
223 selected_fd_cat->kind() == RootItem::Kind::Feed
224 ? selected_fd_cat->customId()
225 : QString::number(NO_PARENT_CATEGORY),
226 selectedAccount() != nullptr
227 ? selectedAccount()->accountId()
228 : NO_PARENT_CATEGORY,
229 selected_fd_cat->getParentServiceRoot()->labelsNode()->labels(),
230 false);
231 auto* fltr = selectedFilter();
232
233 MessageFilter::initializeFilteringEngine(filter_engine, &msg_obj);
234
235 // Test real messages.
236 try {
237 m_msgModel->testFilter(fltr, &filter_engine, &msg_obj);
238 }
239 catch (const FilteringException& ex) {
240 m_ui.m_txtErrors->setTextColor(Qt::GlobalColor::red);
241 m_ui.m_txtErrors->insertPlainText(tr("EXISTING articles filtering error: '%1'.\n").arg(ex.message()));
242
243 // See output.
244 m_ui.m_twMessages->setCurrentIndex(2);
245 }
246
247 // Test sample message.
248 Message msg = testingMessage();
249
250 msg_obj.setMessage(&msg);
251
252 try {
253 MessageObject::FilteringAction decision = fltr->filterMessage(&filter_engine);
254
255 m_ui.m_txtErrors->setTextColor(decision == MessageObject::FilteringAction::Accept ? Qt::GlobalColor::darkGreen : Qt::GlobalColor::red);
256
257 QString answer = tr("Article will be %1.\n\n").arg(decision == MessageObject::FilteringAction::Accept
258 ? tr("ACCEPTED")
259 : tr("REJECTED"));
260
261 answer += tr("Output (modified) article is:\n"
262 " Title = '%1'\n"
263 " URL = '%2'\n"
264 " Author = '%3'\n"
265 " Is read/important = '%4/%5'\n"
266 " Created on = '%6'\n"
267 " Contents = '%7'\n"
268 " RAW contents = '%8'").arg(msg.m_title, msg.m_url, msg.m_author,
269 msg.m_isRead ? tr("yes") : tr("no"),
270 msg.m_isImportant ? tr("yes") : tr("no"),
271 QString::number(msg.m_created.toMSecsSinceEpoch()),
272 msg.m_contents,
273 msg.m_rawContents);
274
275 m_ui.m_txtErrors->insertPlainText(answer);
276 }
277 catch (const FilteringException& ex) {
278 m_ui.m_txtErrors->setTextColor(Qt::GlobalColor::red);
279 m_ui.m_txtErrors->insertPlainText(tr("SAMPLE article filtering error: '%1'.\n").arg(ex.message()));
280
281 // See output.
282 m_ui.m_twMessages->setCurrentIndex(2);
283 }
284 }
285
displayMessagesOfFeed()286 void FormMessageFiltersManager::displayMessagesOfFeed() {
287 auto* item = selectedCategoryFeed();
288
289 if (item != nullptr) {
290 m_msgModel->setMessages(item->undeletedMessages());
291 }
292 else {
293 m_msgModel->setMessages({});
294 }
295 }
296
processCheckedFeeds()297 void FormMessageFiltersManager::processCheckedFeeds() {
298 QList<RootItem*> checked = m_feedsModel->sourceModel()->checkedItems();
299 auto* fltr = selectedFilter();
300 QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className());
301
302 for (RootItem* it : checked) {
303 if (it->kind() == RootItem::Kind::Feed) {
304 QJSEngine filter_engine;
305 MessageObject msg_obj(&database,
306 it->customId(),
307 selectedAccount()->accountId(),
308 it->getParentServiceRoot()->labelsNode()->labels(),
309 false);
310
311 MessageFilter::initializeFilteringEngine(filter_engine, &msg_obj);
312
313 // We process messages of the feed.
314 QList<Message> msgs = it->undeletedMessages();
315 QList<Message> read_msgs, important_msgs;
316
317 for (int i = 0; i < msgs.size(); i++) {
318 auto labels_in_message = DatabaseQueries::getLabelsForMessage(database, msgs[i], msg_obj.availableLabels());
319
320 // Create backup of message.
321 Message* msg = &msgs[i]; msg->m_assignedLabels = labels_in_message;
322
323 msg->m_rawContents = Message::generateRawAtomContents(*msg);
324
325 Message msg_backup(*msg);
326
327 msg_obj.setMessage(msg);
328
329 bool remove_from_list = false;
330
331 try {
332 MessageObject::FilteringAction result = fltr->filterMessage(&filter_engine);
333
334 if (result == MessageObject::FilteringAction::Purge) {
335 remove_from_list = true;
336
337 // Purge the message completely and remove leftovers.
338 DatabaseQueries::purgeMessage(database, msg->m_id);
339 DatabaseQueries::purgeLeftoverLabelAssignments(database, msg->m_accountId);
340 }
341 else if (result == MessageObject::FilteringAction::Ignore) {
342 remove_from_list = true;
343 }
344 }
345 catch (const FilteringException& ex) {
346 qCriticalNN << LOGSEC_CORE
347 << "Error when running script when processing existing messages:"
348 << QUOTE_W_SPACE_DOT(ex.message());
349
350 continue;
351 }
352
353 if (!msg_backup.m_isRead && msg->m_isRead) {
354 qDebugNN << LOGSEC_FEEDDOWNLOADER << "Message with custom ID: '" << msg_backup.m_customId << "' was marked as read by message scripts.";
355
356 read_msgs << *msg;
357 }
358
359 if (!msg_backup.m_isImportant && msg->m_isImportant) {
360 qDebugNN << LOGSEC_FEEDDOWNLOADER << "Message with custom ID: '" << msg_backup.m_customId << "' was marked as important by message scripts.";
361
362 important_msgs << *msg;
363 }
364
365 // Process changed labels.
366 for (Label* lbl : qAsConst(msg_backup.m_assignedLabels)) {
367 if (!msg->m_assignedLabels.contains(lbl)) {
368 // Label is not there anymore, it was deassigned.
369 lbl->deassignFromMessage(*msg);
370
371 qDebugNN << LOGSEC_FEEDDOWNLOADER
372 << "It was detected that label" << QUOTE_W_SPACE(lbl->customId())
373 << "was DEASSIGNED from message" << QUOTE_W_SPACE(msg->m_customId)
374 << "by message filter(s).";
375 }
376 }
377
378 for (Label* lbl : qAsConst(msg->m_assignedLabels)) {
379 if (!msg_backup.m_assignedLabels.contains(lbl)) {
380 // Label is in new message, but is not in old message, it
381 // was newly assigned.
382 lbl->assignToMessage(*msg);
383
384 qDebugNN << LOGSEC_FEEDDOWNLOADER
385 << "It was detected that label" << QUOTE_W_SPACE(lbl->customId())
386 << "was ASSIGNED to message" << QUOTE_W_SPACE(msg->m_customId)
387 << "by message filter(s).";
388 }
389 }
390
391 if (remove_from_list) {
392 // Do not update message.
393 msgs.removeAt(i--);
394 }
395 }
396
397 if (!read_msgs.isEmpty()) {
398 // Now we push new read states to the service.
399 if (it->getParentServiceRoot()->onBeforeSetMessagesRead(it, read_msgs, RootItem::ReadStatus::Read)) {
400 qDebugNN << LOGSEC_FEEDDOWNLOADER
401 << "Notified services about messages marked as read by message filters.";
402 }
403 else {
404 qCriticalNN << LOGSEC_FEEDDOWNLOADER
405 << "Notification of services about messages marked as read by message filters FAILED.";
406 }
407 }
408
409 if (!important_msgs.isEmpty()) {
410 // Now we push new read states to the service.
411 auto list = boolinq::from(important_msgs).select([](const Message& msg) {
412 return ImportanceChange(msg, RootItem::Importance::Important);
413 }).toStdList();
414 QList<ImportanceChange> chngs = FROM_STD_LIST(QList<ImportanceChange>, list);
415
416 if (it->getParentServiceRoot()->onBeforeSwitchMessageImportance(it, chngs)) {
417 qDebugNN << LOGSEC_FEEDDOWNLOADER
418 << "Notified services about messages marked as important by message filters.";
419 }
420 else {
421 qCriticalNN << LOGSEC_FEEDDOWNLOADER
422 << "Notification of services about messages marked as important by message filters FAILED.";
423 }
424 }
425
426 // Update messages in DB and reload selection.
427 it->getParentServiceRoot()->updateMessages(msgs, it->toFeed(), true);
428 displayMessagesOfFeed();
429 }
430 }
431 }
432
loadAccount(ServiceRoot * account)433 void FormMessageFiltersManager::loadAccount(ServiceRoot* account) {
434 m_feedsModel->setRootItem(account, false, true);
435
436 if (account != nullptr) {
437 m_msgModel->setMessages(account->undeletedMessages());
438 }
439 else {
440 m_msgModel->setMessages({});
441 }
442 }
443
loadFilterFeedAssignments(MessageFilter * filter,ServiceRoot * account)444 void FormMessageFiltersManager::loadFilterFeedAssignments(MessageFilter* filter, ServiceRoot* account) {
445 if (account == nullptr || filter == nullptr) {
446 return;
447 }
448
449 m_loadingFilter = true;
450 auto stf = account->getSubTreeFeeds();
451
452 for (auto* feed : qAsConst(stf)) {
453 if (feed->messageFilters().contains(filter)) {
454 m_feedsModel->sourceModel()->setItemChecked(feed, Qt::CheckState::Checked);
455 }
456 }
457
458 m_loadingFilter = false;
459 }
460
onAccountChanged()461 void FormMessageFiltersManager::onAccountChanged() {
462 // Load feeds/categories of the account and check marks.
463 auto* filter = selectedFilter();
464 auto* acc = selectedAccount();
465
466 loadAccount(acc);
467 loadFilterFeedAssignments(filter, acc);
468 }
469
onFeedChecked(RootItem * item,Qt::CheckState state)470 void FormMessageFiltersManager::onFeedChecked(RootItem* item, Qt::CheckState state) {
471 if (m_loadingFilter) {
472 return;
473 }
474
475 auto* feed = qobject_cast<Feed*>(item);
476
477 if (feed == nullptr) {
478 return;
479 }
480
481 // Update feed/filter assignemnts.
482 switch (state) {
483 case Qt::CheckState::Checked:
484 m_reader->assignMessageFilterToFeed(feed, selectedFilter());
485 break;
486
487 case Qt::CheckState::Unchecked:
488 m_reader->removeMessageFilterToFeedAssignment(feed, selectedFilter());
489 break;
490
491 case Qt::CheckState::PartiallyChecked:
492 break;
493 }
494 }
495
showFilter(MessageFilter * filter)496 void FormMessageFiltersManager::showFilter(MessageFilter* filter) {
497 m_loadingFilter = true;
498
499 if (filter == nullptr) {
500 m_ui.m_txtTitle->clear();
501 m_ui.m_txtScript->clear();
502 m_ui.m_gbDetails->setEnabled(false);
503
504 m_ui.m_treeFeeds->setEnabled(false);
505 m_ui.m_btnCheckAll->setEnabled(false);
506 m_ui.m_btnUncheckAll->setEnabled(false);
507 m_ui.m_cmbAccounts->setEnabled(false);
508 }
509 else {
510 m_ui.m_txtTitle->setText(filter->name());
511 m_ui.m_txtScript->setPlainText(filter->script());
512 m_ui.m_gbDetails->setEnabled(true);
513
514 m_ui.m_treeFeeds->setEnabled(true);
515 m_ui.m_btnCheckAll->setEnabled(true);
516 m_ui.m_btnUncheckAll->setEnabled(true);
517 m_ui.m_cmbAccounts->setEnabled(true);
518 }
519
520 // See message.
521 m_ui.m_twMessages->setCurrentIndex(0);
522 m_loadingFilter = false;
523 }
524
loadAccounts()525 void FormMessageFiltersManager::loadAccounts() {
526 for (auto* acc : qAsConst(m_accounts)) {
527 m_ui.m_cmbAccounts->addItem(acc->icon(), acc->title(), QVariant::fromValue(acc));
528 }
529 }
530
beautifyScript()531 void FormMessageFiltersManager::beautifyScript() {
532 QProcess proc_clang_format(this);
533
534 proc_clang_format.setInputChannelMode(QProcess::InputChannelMode::ManagedInputChannel);
535 proc_clang_format.setArguments({ "--assume-filename=script.js", "--style=Chromium" });
536
537 #if defined(Q_OS_WIN)
538 proc_clang_format.setProgram(qApp->applicationDirPath() + QDir::separator() +
539 QSL("clang-format") + QDir::separator() +
540 QSL("clang-format.exe"));
541 #else
542 proc_clang_format.setProgram(QSL("clang-format"));
543 #endif
544
545 if (!proc_clang_format.open() || proc_clang_format.error() == QProcess::ProcessError::FailedToStart) {
546 MessageBox::show(this, QMessageBox::Icon::Critical,
547 tr("Cannot find 'clang-format'"),
548 tr("Script was not beautified, because 'clang-format' tool was not found."));
549 return;
550 }
551
552 proc_clang_format.write(m_ui.m_txtScript->toPlainText().toUtf8());
553 proc_clang_format.closeWriteChannel();
554
555 if (proc_clang_format.waitForFinished(3000)) {
556 if (proc_clang_format.exitCode() == 0) {
557 auto script = proc_clang_format.readAllStandardOutput();
558
559 m_ui.m_txtScript->setPlainText(script);
560 }
561 else {
562 auto err = proc_clang_format.readAllStandardError();
563
564 MessageBox::show(this, QMessageBox::Icon::Critical,
565 tr("Error"),
566 tr("Script was not beautified, because 'clang-format' tool thrown error."),
567 QString(),
568 err);
569 }
570 }
571 else {
572 proc_clang_format.kill();
573 MessageBox::show(this, QMessageBox::Icon::Critical,
574 tr("Beautifier was running for too long time"),
575 tr("Script was not beautified, is 'clang-format' installed?"));
576 }
577 }
578
initializeTestingMessage()579 void FormMessageFiltersManager::initializeTestingMessage() {
580 m_ui.m_cbSampleImportant->setChecked(true);
581 m_ui.m_txtSampleUrl->setText(QSL("https://mynews.com/news/5"));
582 m_ui.m_txtSampleTitle->setText(QSL("Year of Linux Desktop"));
583 m_ui.m_txtSampleAuthor->setText(QSL("Napoleon Bonaparte"));
584 m_ui.m_txtSampleContents->setPlainText(QSL("<p>Browsers usually insert quotation marks around the q element.</p>"
585 "<p>WWF's goal is to: <q>Build a future where people live in harmony "
586 "with nature.</q></p>"));
587 m_ui.m_txtSampleCreatedOn->setText(QString::number(QDateTime::currentDateTimeUtc().toMSecsSinceEpoch()));
588 }
589
selectedCategoryFeed() const590 RootItem* FormMessageFiltersManager::selectedCategoryFeed() const {
591 return m_feedsModel->sourceModel()->itemForIndex(m_feedsModel->mapToSource(m_ui.m_treeFeeds->currentIndex()));
592 }
593
testingMessage() const594 Message FormMessageFiltersManager::testingMessage() const {
595 Message msg;
596
597 msg.m_feedId = NO_PARENT_CATEGORY;
598 msg.m_url = m_ui.m_txtSampleUrl->text();
599 msg.m_customId = m_ui.m_txtSampleUrl->text();
600 msg.m_title = m_ui.m_txtSampleTitle->text();
601 msg.m_author = m_ui.m_txtSampleAuthor->text();
602 msg.m_isRead = m_ui.m_cbSampleRead->isChecked();
603 msg.m_isImportant = m_ui.m_cbSampleImportant->isChecked();
604 msg.m_created = QDateTime::fromMSecsSinceEpoch(m_ui.m_txtSampleCreatedOn->text().toLongLong());
605 msg.m_contents = m_ui.m_txtSampleContents->toPlainText();
606 msg.m_rawContents = Message::generateRawAtomContents(msg);
607
608 return msg;
609 }
610