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