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