3a6d6eb04b76dab1654e633a3e67dfbace9c733f
[quassel.git] / src / qtui / settingspages / corehighlightsettingspage.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2016 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
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 2 of the License, or     *
8  *   (at your option) version 3.                                           *
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, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include <QHeaderView>
22 #include <QTableWidget>
23
24 #include "client.h"
25 #include "corehighlightsettingspage.h"
26 #include "qtui.h"
27
28 CoreHighlightSettingsPage::CoreHighlightSettingsPage(QWidget *parent)
29     : SettingsPage(tr("Interface"), tr("Remote Highlights"), parent)
30 {
31     ui.setupUi(this);
32
33     setupRuleTable(ui.highlightTable);
34     setupRuleTable(ui.ignoredTable);
35
36     ui.highlightNicksComboBox->addItem(tr("All Nicks from Identity"), QVariant(HighlightRuleManager::AllNicks));
37     ui.highlightNicksComboBox->addItem(tr("Current Nick"), QVariant(HighlightRuleManager::CurrentNick));
38     ui.highlightNicksComboBox->addItem(tr("None"), QVariant(HighlightRuleManager::NoNick));
39
40     coreConnectionStateChanged(Client::isConnected()); // need a core connection!
41     connect(Client::instance(), SIGNAL(coreConnectionStateChanged(bool)), this, SLOT(coreConnectionStateChanged(bool)));
42
43     connect(ui.highlightAdd, SIGNAL(clicked(bool)), this, SLOT(addNewHighlightRow()));
44     connect(ui.highlightRemove, SIGNAL(clicked(bool)), this, SLOT(removeSelectedHighlightRows()));
45     connect(ui.highlightImport, SIGNAL(clicked(bool)), this, SLOT(importRules()));
46
47     connect(ui.ignoredAdd, SIGNAL(clicked(bool)), this, SLOT(addNewIgnoredRow()));
48     connect(ui.ignoredRemove, SIGNAL(clicked(bool)), this, SLOT(removeSelectedIgnoredRows()));
49
50     // TODO: search for a better signal (one that emits everytime a selection has been changed for one item)
51     connect(ui.highlightTable,
52             SIGNAL(itemClicked(QTableWidgetItem * )),
53             this,
54             SLOT(selectHighlightRow(QTableWidgetItem * )));
55     connect(ui.ignoredTable,
56             SIGNAL(itemClicked(QTableWidgetItem * )),
57             this,
58             SLOT(selectIgnoredRow(QTableWidgetItem * )));
59
60
61     connect(ui.highlightNicksComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(widgetHasChanged()));
62     connect(ui.nicksCaseSensitive, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
63
64     connect(ui.highlightAdd, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
65     connect(ui.highlightRemove, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
66
67     connect(ui.ignoredAdd, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
68     connect(ui.ignoredRemove, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
69
70     connect(ui.highlightTable,
71             SIGNAL(itemChanged(QTableWidgetItem * )),
72             this,
73             SLOT(highlightTableChanged(QTableWidgetItem * )));
74
75     connect(ui.ignoredTable,
76             SIGNAL(itemChanged(QTableWidgetItem * )),
77             this,
78             SLOT(ignoredTableChanged(QTableWidgetItem * )));
79
80     connect(Client::instance(), SIGNAL(connected()), this, SLOT(clientConnected()));
81 }
82
83 void CoreHighlightSettingsPage::coreConnectionStateChanged(bool state)
84 {
85     setEnabled(state);
86     if (state) {
87         load();
88     } else {
89         revert();
90     }
91 }
92
93 void CoreHighlightSettingsPage::setupRuleTable(QTableWidget *table) const
94 {
95     table->verticalHeader()->hide();
96     table->setShowGrid(false);
97
98     table->horizontalHeaderItem(CoreHighlightSettingsPage::RegExColumn)->setToolTip(
99         tr("<b>RegEx</b>: This option determines if the highlight rule should be interpreted as a <b>regular expression</b> or just as a keyword."));
100     table->horizontalHeaderItem(CoreHighlightSettingsPage::RegExColumn)->setWhatsThis(
101         tr("<b>RegEx</b>: This option determines if the highlight rule should be interpreted as a <b>regular expression</b> or just as a keyword."));
102
103     table->horizontalHeaderItem(CoreHighlightSettingsPage::CsColumn)->setToolTip(
104         tr("<b>CS</b>: This option determines if the highlight rule should be interpreted <b>case sensitive</b>."));
105     table->horizontalHeaderItem(CoreHighlightSettingsPage::CsColumn)->setWhatsThis(
106         tr("<b>CS</b>: This option determines if the highlight rule should be interpreted <b>case sensitive</b>."));
107
108     table->horizontalHeaderItem(CoreHighlightSettingsPage::ChanColumn)->setToolTip(
109         tr("<b>Channel</b>: This regular expression determines for which <b>channels</b> the highlight rule works. Leave blank to match any channel. Put <b>!</b> in the beginning to negate. Case insensitive."));
110     table->horizontalHeaderItem(CoreHighlightSettingsPage::ChanColumn)->setWhatsThis(
111         tr("<b>Channel</b>: This regular expression determines for which <b>channels</b> the highlight rule works. Leave blank to match any channel. Put <b>!</b> in the beginning to negate. Case insensitive."));
112
113 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
114     table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::EnableColumn, QHeaderView::ResizeToContents);
115     table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::NameColumn, QHeaderView::Stretch);
116     table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::RegExColumn, QHeaderView::ResizeToContents);
117     table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::CsColumn, QHeaderView::ResizeToContents);
118     table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::ChanColumn, QHeaderView::ResizeToContents);
119 #else
120     table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::EnableColumn, QHeaderView::ResizeToContents);
121     table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::NameColumn, QHeaderView::Stretch);
122     table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::RegExColumn, QHeaderView::ResizeToContents);
123     table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::CsColumn, QHeaderView::ResizeToContents);
124     table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::ChanColumn, QHeaderView::ResizeToContents);
125 #endif
126 }
127
128 void CoreHighlightSettingsPage::clientConnected()
129 {
130     connect(Client::highlightRuleManager(), SIGNAL(updated()), SLOT(revert()));
131 }
132
133 void CoreHighlightSettingsPage::revert()
134 {
135     if (!hasChanged())
136         return;
137
138     setChangedState(false);
139     load();
140 }
141
142 bool CoreHighlightSettingsPage::hasDefaults() const
143 {
144     return true;
145 }
146
147 void CoreHighlightSettingsPage::defaults()
148 {
149     int highlightNickType = HighlightRuleManager::HighlightNickType::CurrentNick;
150     int defaultIndex = ui.highlightNicksComboBox->findData(QVariant(highlightNickType));
151     ui.highlightNicksComboBox->setCurrentIndex(defaultIndex);
152     ui.nicksCaseSensitive->setChecked(false);
153     emptyHighlightTable();
154     emptyIgnoredTable();
155
156     widgetHasChanged();
157 }
158
159 void CoreHighlightSettingsPage::addNewHighlightRow(bool enable, const QString &name, bool regex, bool cs,
160                                                    const QString &sender, const QString &chanName, bool self)
161 {
162     ui.highlightTable->setRowCount(ui.highlightTable->rowCount() + 1);
163
164     auto *nameItem = new QTableWidgetItem(name);
165
166     auto *regexItem = new QTableWidgetItem("");
167     if (regex)
168         regexItem->setCheckState(Qt::Checked);
169     else
170         regexItem->setCheckState(Qt::Unchecked);
171     regexItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
172
173     auto *csItem = new QTableWidgetItem("");
174     if (cs)
175         csItem->setCheckState(Qt::Checked);
176     else
177         csItem->setCheckState(Qt::Unchecked);
178     csItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
179
180     auto *enableItem = new QTableWidgetItem("");
181     if (enable)
182         enableItem->setCheckState(Qt::Checked);
183     else
184         enableItem->setCheckState(Qt::Unchecked);
185     enableItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
186
187     auto *chanNameItem = new QTableWidgetItem(chanName);
188
189     auto *senderItem = new QTableWidgetItem(sender);
190
191     int lastRow = ui.highlightTable->rowCount() - 1;
192     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::NameColumn, nameItem);
193     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::RegExColumn, regexItem);
194     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::CsColumn, csItem);
195     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::EnableColumn, enableItem);
196     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::SenderColumn, senderItem);
197     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::ChanColumn, chanNameItem);
198
199     if (!self)
200         ui.highlightTable->setCurrentItem(nameItem);
201
202     highlightList << HighlightRuleManager::HighlightRule(name, regex, cs, enable, false, sender, chanName);
203 }
204
205 void CoreHighlightSettingsPage::addNewIgnoredRow(bool enable, const QString &name, bool regex, bool cs,
206                                                  const QString &sender, const QString &chanName, bool self)
207 {
208     ui.ignoredTable->setRowCount(ui.ignoredTable->rowCount() + 1);
209
210     auto *nameItem = new QTableWidgetItem(name);
211
212     auto *regexItem = new QTableWidgetItem("");
213     if (regex)
214         regexItem->setCheckState(Qt::Checked);
215     else
216         regexItem->setCheckState(Qt::Unchecked);
217     regexItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
218
219     auto *csItem = new QTableWidgetItem("");
220     if (cs)
221         csItem->setCheckState(Qt::Checked);
222     else
223         csItem->setCheckState(Qt::Unchecked);
224     csItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
225
226     auto *enableItem = new QTableWidgetItem("");
227     if (enable)
228         enableItem->setCheckState(Qt::Checked);
229     else
230         enableItem->setCheckState(Qt::Unchecked);
231     enableItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
232
233     auto *chanNameItem = new QTableWidgetItem(chanName);
234
235     auto *senderItem = new QTableWidgetItem(sender);
236
237     int lastRow = ui.ignoredTable->rowCount() - 1;
238     ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::NameColumn, nameItem);
239     ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::RegExColumn, regexItem);
240     ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::CsColumn, csItem);
241     ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::EnableColumn, enableItem);
242     ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::SenderColumn, senderItem);
243     ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::ChanColumn, chanNameItem);
244
245     if (!self)
246         ui.ignoredTable->setCurrentItem(nameItem);
247
248     ignoredList << HighlightRuleManager::HighlightRule(name, regex, cs, enable, true, sender, chanName);
249 }
250
251 void CoreHighlightSettingsPage::removeSelectedHighlightRows()
252 {
253     QList<int> selectedRows;
254     QList<QTableWidgetItem *> selectedItemList = ui.highlightTable->selectedItems();
255     for (auto selectedItem : selectedItemList) {
256         selectedRows.append(selectedItem->row());
257     }
258     qSort(selectedRows.begin(), selectedRows.end(), qGreater<int>());
259     int lastRow = -1;
260     for (auto row : selectedRows) {
261         if (row != lastRow) {
262             ui.highlightTable->removeRow(row);
263             highlightList.removeAt(row);
264         }
265         lastRow = row;
266     }
267 }
268
269 void CoreHighlightSettingsPage::removeSelectedIgnoredRows()
270 {
271     QList<int> selectedRows;
272     QList<QTableWidgetItem *> selectedItemList = ui.ignoredTable->selectedItems();
273     for (auto selectedItem : selectedItemList) {
274         selectedRows.append(selectedItem->row());
275     }
276     qSort(selectedRows.begin(), selectedRows.end(), qGreater<int>());
277     int lastRow = -1;
278     for (auto row : selectedRows) {
279         if (row != lastRow) {
280             ui.ignoredTable->removeRow(row);
281             ignoredList.removeAt(row);
282         }
283         lastRow = row;
284     }
285 }
286
287 void CoreHighlightSettingsPage::selectHighlightRow(QTableWidgetItem *item)
288 {
289     int row = item->row();
290     bool selected = item->isSelected();
291     ui.highlightTable
292         ->setRangeSelected(QTableWidgetSelectionRange(row, 0, row, CoreHighlightSettingsPage::ColumnCount - 1),
293                            selected);
294 }
295
296 void CoreHighlightSettingsPage::selectIgnoredRow(QTableWidgetItem *item)
297 {
298     int row = item->row();
299     bool selected = item->isSelected();
300     ui.ignoredTable
301         ->setRangeSelected(QTableWidgetSelectionRange(row, 0, row, CoreHighlightSettingsPage::ColumnCount - 1),
302                            selected);
303 }
304
305 void CoreHighlightSettingsPage::emptyHighlightTable()
306 {
307     // ui.highlight and highlightList should have the same size, but just to make sure.
308     if (ui.highlightTable->rowCount() != highlightList.size()) {
309         qDebug() << "something is wrong: ui.highlight and highlightList don't have the same size!";
310     }
311     ui.highlightTable->clearContents();
312     highlightList.clear();
313 }
314
315 void CoreHighlightSettingsPage::emptyIgnoredTable()
316 {
317     // ui.highlight and highlightList should have the same size, but just to make sure.
318     if (ui.ignoredTable->rowCount() != ignoredList.size()) {
319         qDebug() << "something is wrong: ui.highlight and highlightList don't have the same size!";
320     }
321     ui.ignoredTable->clearContents();
322     ignoredList.clear();
323 }
324
325 void CoreHighlightSettingsPage::highlightTableChanged(QTableWidgetItem *item)
326 {
327     if (item->row() + 1 > highlightList.size())
328         return;
329
330     auto highlightRule = highlightList.value(item->row());
331
332
333     switch (item->column()) {
334         case CoreHighlightSettingsPage::EnableColumn:
335             highlightRule.isEnabled = (item->checkState() == Qt::Checked);
336             break;
337         case CoreHighlightSettingsPage::NameColumn:
338             if (item->text() == "")
339                 item->setText(tr("this shouldn't be empty"));
340             highlightRule.name = item->text();
341             break;
342         case CoreHighlightSettingsPage::RegExColumn:
343             highlightRule.isRegEx = (item->checkState() == Qt::Checked);
344             break;
345         case CoreHighlightSettingsPage::CsColumn:
346             highlightRule.isCaseSensitive = (item->checkState() == Qt::Checked);
347             break;
348         case CoreHighlightSettingsPage::SenderColumn:
349             if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
350                 item->setText("");
351             highlightRule.sender = item->text();
352             break;
353         case CoreHighlightSettingsPage::ChanColumn:
354             if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
355                 item->setText("");
356             highlightRule.chanName = item->text();
357             break;
358     }
359     highlightList[item->row()] = highlightRule;
360     emit widgetHasChanged();
361 }
362
363 void CoreHighlightSettingsPage::ignoredTableChanged(QTableWidgetItem *item)
364 {
365     if (item->row() + 1 > ignoredList.size())
366         return;
367
368     auto ignoredRule = ignoredList.value(item->row());
369
370
371     switch (item->column()) {
372         case CoreHighlightSettingsPage::EnableColumn:
373             ignoredRule.isEnabled = (item->checkState() == Qt::Checked);
374             break;
375         case CoreHighlightSettingsPage::NameColumn:
376             if (item->text() == "")
377                 item->setText(tr("this shouldn't be empty"));
378             ignoredRule.name = item->text();
379             break;
380         case CoreHighlightSettingsPage::RegExColumn:
381             ignoredRule.isRegEx = (item->checkState() == Qt::Checked);
382             break;
383         case CoreHighlightSettingsPage::CsColumn:
384             ignoredRule.isCaseSensitive = (item->checkState() == Qt::Checked);
385             break;
386         case CoreHighlightSettingsPage::SenderColumn:
387             if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
388                 item->setText("");
389             ignoredRule.sender = item->text();
390             break;
391         case CoreHighlightSettingsPage::ChanColumn:
392             if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
393                 item->setText("");
394             ignoredRule.chanName = item->text();
395             break;
396     }
397     ignoredList[item->row()] = ignoredRule;
398     emit widgetHasChanged();
399 }
400
401 void CoreHighlightSettingsPage::load()
402 {
403     emptyHighlightTable();
404     emptyIgnoredTable();
405
406     auto ruleManager = Client::highlightRuleManager();
407     if (ruleManager) {
408         for (auto &rule : ruleManager->highlightRuleList()) {
409             if (rule.isInverse) {
410                 addNewIgnoredRow(rule.isEnabled,
411                                  rule.name,
412                                  rule.isRegEx,
413                                  rule.isCaseSensitive,
414                                  rule.sender,
415                                  rule.chanName);
416             }
417             else {
418                 addNewHighlightRow(rule.isEnabled, rule.name, rule.isRegEx, rule.isCaseSensitive, rule.sender,
419                                    rule.chanName);
420             }
421         }
422
423         int highlightNickType = ruleManager->highlightNick();
424         ui.highlightNicksComboBox->setCurrentIndex(ui.highlightNicksComboBox->findData(QVariant(highlightNickType)));
425         ui.nicksCaseSensitive->setChecked(ruleManager->nicksCaseSensitive());
426
427         setChangedState(false);
428         _initialized = true;
429     } else {
430         defaults();
431     }
432 }
433
434 void CoreHighlightSettingsPage::save()
435 {
436     if (!hasChanged())
437         return;
438
439     if (!_initialized)
440         return;
441
442     auto ruleManager = Client::highlightRuleManager();
443     if (ruleManager == nullptr)
444         return;
445
446     auto clonedManager = HighlightRuleManager();
447     clonedManager.fromVariantMap(ruleManager->toVariantMap());
448     clonedManager.clear();
449
450     for (auto &rule : highlightList) {
451         clonedManager.addHighlightRule(rule.name, rule.isRegEx, rule.isCaseSensitive, rule.isEnabled, false,
452                                        rule.sender, rule.chanName);
453     }
454
455     for (auto &rule : ignoredList) {
456         clonedManager.addHighlightRule(rule.name, rule.isRegEx, rule.isCaseSensitive, rule.isEnabled, true,
457                                        rule.sender, rule.chanName);
458     }
459
460     auto highlightNickType = ui.highlightNicksComboBox->itemData(ui.highlightNicksComboBox->currentIndex()).value<int>();
461
462     clonedManager.setHighlightNick(HighlightRuleManager::HighlightNickType(highlightNickType));
463     clonedManager.setNicksCaseSensitive(ui.nicksCaseSensitive->isChecked());
464
465     ruleManager->requestUpdate(clonedManager.toVariantMap());
466     setChangedState(false);
467     load();
468 }
469
470 void CoreHighlightSettingsPage::widgetHasChanged()
471 {
472     setChangedState(true);
473 }
474
475 void CoreHighlightSettingsPage::importRules() {
476     NotificationSettings notificationSettings;
477
478     auto clonedManager = HighlightRuleManager();
479     clonedManager.fromVariantMap(Client::highlightRuleManager()->toVariantMap());
480
481     for (const auto &variant : notificationSettings.highlightList()) {
482         auto highlightRule = variant.toMap();
483
484         clonedManager.addHighlightRule(
485                 highlightRule["Name"].toString(),
486                 highlightRule["RegEx"].toBool(),
487                 highlightRule["CS"].toBool(),
488                 highlightRule["Enable"].toBool(),
489                 false,
490                 "",
491                 highlightRule["Channel"].toString()
492         );
493     }
494
495     Client::highlightRuleManager()->requestUpdate(clonedManager.toVariantMap());
496     setChangedState(false);
497     load();
498 }
499
500 bool CoreHighlightSettingsPage::isSelectable() const {
501     return Client::isConnected() && Client::isCoreFeatureEnabled(Quassel::Feature::CoreSideHighlights);
502 }