common: Apply RegEx, CS column to sender, channel
[quassel.git] / src / qtui / settingspages / highlightsettingspage.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 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 "highlightsettingspage.h"
22
23 #include "client.h"
24 #include "qtui.h"
25 #include "uisettings.h"
26
27 #include <QHeaderView>
28 #include <QMessageBox>
29
30 HighlightSettingsPage::HighlightSettingsPage(QWidget *parent)
31     : SettingsPage(tr("Interface"), tr("Local Highlights"), parent)
32 {
33     ui.setupUi(this);
34     ui.highlightTable->verticalHeader()->hide();
35     ui.highlightTable->setShowGrid(false);
36
37
38     ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::EnableColumn)->setToolTip(
39                 tr("Enable/disable this rule"));
40     ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::EnableColumn)->setWhatsThis(
41                 ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::EnableColumn)->toolTip());
42
43     ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::NameColumn)->setToolTip(
44                 tr("Phrase to match"));
45     ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::NameColumn)->setWhatsThis(
46                 ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::NameColumn)->toolTip());
47
48     ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::RegExColumn)->setToolTip(
49                 tr("<b>RegEx</b>: This option determines if the highlight rule and <i>Channel</i> "
50                    "should be interpreted as <b>regular expressions</b> or just as keywords."));
51     ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::RegExColumn)->setWhatsThis(
52                 ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::RegExColumn)->toolTip());
53
54     ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::CsColumn)->setToolTip(
55                 tr("<b>CS</b>: This option determines if the highlight rule and <i>Channel</i> "
56                    "should be interpreted <b>case sensitive</b>."));
57     ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::CsColumn)->setWhatsThis(
58                 ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::CsColumn)->toolTip());
59
60     ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::ChanColumn)->setToolTip(
61                 tr("<p><b>Channel</b>: Semicolon separated list of channel names, leave blank to "
62                    "match any name.</p>"
63                    "<p><i>Example:</i><br />"
64                    "<i>#quassel*; #foobar; !#quasseldroid</i><br />"
65                    "would match on <i>#foobar</i> and any channel starting with <i>#quassel</i> "
66                    "except for <i>#quasseldroid</i><br />"
67                    "<p>If only inverted names are specified, it will match anything except for "
68                    "what's specified (implicit wildcard).</p>"
69                    "<p><i>Example:</i><br />"
70                    "<i>!#quassel*; !#foobar</i><br />"
71                    "would match anything except for <i>#foobar</i> or any channel starting with "
72                    "<i>#quassel</i></p>"));
73     ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::ChanColumn)->setWhatsThis(
74                 ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::ChanColumn)->toolTip());
75
76 #if QT_VERSION < 0x050000
77     ui.highlightTable->horizontalHeader()->setResizeMode(HighlightSettingsPage::NameColumn, QHeaderView::Stretch);
78     ui.highlightTable->horizontalHeader()->setResizeMode(HighlightSettingsPage::RegExColumn, QHeaderView::ResizeToContents);
79     ui.highlightTable->horizontalHeader()->setResizeMode(HighlightSettingsPage::CsColumn, QHeaderView::ResizeToContents);
80     ui.highlightTable->horizontalHeader()->setResizeMode(HighlightSettingsPage::EnableColumn, QHeaderView::ResizeToContents);
81     ui.highlightTable->horizontalHeader()->setResizeMode(HighlightSettingsPage::ChanColumn, QHeaderView::ResizeToContents);
82 #else
83     ui.highlightTable->horizontalHeader()->setSectionResizeMode(HighlightSettingsPage::NameColumn, QHeaderView::Stretch);
84     ui.highlightTable->horizontalHeader()->setSectionResizeMode(HighlightSettingsPage::RegExColumn, QHeaderView::ResizeToContents);
85     ui.highlightTable->horizontalHeader()->setSectionResizeMode(HighlightSettingsPage::CsColumn, QHeaderView::ResizeToContents);
86     ui.highlightTable->horizontalHeader()->setSectionResizeMode(HighlightSettingsPage::EnableColumn, QHeaderView::ResizeToContents);
87     ui.highlightTable->horizontalHeader()->setSectionResizeMode(HighlightSettingsPage::ChanColumn, QHeaderView::ResizeToContents);
88 #endif
89
90     // Information icon
91     ui.localHighlightsIcon->setPixmap(QIcon::fromTheme("dialog-information").pixmap(16));
92
93     // Set up client/monolithic local highlights information
94     if (Quassel::runMode() == Quassel::Monolithic) {
95         // We're running in Monolithic mode, core/client version in total sync.  Discourage the use
96         // of local highlights as it's identical to setting remote highlights.
97         ui.localHighlightsLabel->setText(
98                     tr("Local Highlights are replaced by Remote Highlights"));
99     } else {
100         // We're running in client/split mode, allow for splitting the details.
101         ui.localHighlightsLabel->setText(tr("Local Highlights apply to this device only"));
102     }
103
104     connect(ui.add, SIGNAL(clicked(bool)), this, SLOT(addNewRow()));
105     connect(ui.remove, SIGNAL(clicked(bool)), this, SLOT(removeSelectedRows()));
106     //TODO: search for a better signal (one that emits everytime a selection has been changed for one item)
107     connect(ui.highlightTable, SIGNAL(itemClicked(QTableWidgetItem *)), this, SLOT(selectRow(QTableWidgetItem *)));
108
109     connect(ui.highlightAllNicks, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
110     connect(ui.highlightCurrentNick, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
111     connect(ui.highlightNoNick, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
112     connect(ui.nicksCaseSensitive, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
113     connect(ui.add, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
114     connect(ui.remove, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
115     connect(ui.highlightTable, SIGNAL(itemChanged(QTableWidgetItem *)), this, SLOT(tableChanged(QTableWidgetItem *)));
116 }
117
118
119 bool HighlightSettingsPage::hasDefaults() const
120 {
121     return true;
122 }
123
124
125 void HighlightSettingsPage::defaults()
126 {
127     ui.highlightNoNick->setChecked(true);
128     ui.nicksCaseSensitive->setChecked(false);
129     emptyTable();
130
131     widgetHasChanged();
132 }
133
134
135 void HighlightSettingsPage::addNewRow(QString name, bool regex, bool cs, bool enable, QString chanName, bool self)
136 {
137     ui.highlightTable->setRowCount(ui.highlightTable->rowCount()+1);
138
139     QTableWidgetItem *enableItem = new QTableWidgetItem("");
140     if (enable)
141         enableItem->setCheckState(Qt::Checked);
142     else
143         enableItem->setCheckState(Qt::Unchecked);
144     enableItem->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled|Qt::ItemIsSelectable);
145
146     QTableWidgetItem *nameItem = new QTableWidgetItem(name);
147
148     QTableWidgetItem *regexItem = new QTableWidgetItem("");
149     if (regex)
150         regexItem->setCheckState(Qt::Checked);
151     else
152         regexItem->setCheckState(Qt::Unchecked);
153     regexItem->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled|Qt::ItemIsSelectable);
154
155     QTableWidgetItem *csItem = new QTableWidgetItem("");
156     if (cs)
157         csItem->setCheckState(Qt::Checked);
158     else
159         csItem->setCheckState(Qt::Unchecked);
160     csItem->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled|Qt::ItemIsSelectable);
161
162     QTableWidgetItem *chanNameItem = new QTableWidgetItem(chanName);
163
164     enableItem->setToolTip(tr("Enable/disable this rule"));
165     nameItem->setToolTip(tr("Phrase to match"));
166     regexItem->setToolTip(
167                 tr("<b>RegEx</b>: This option determines if the highlight rule and <i>Channel</i> "
168                    "should be interpreted as <b>regular expressions</b> or just as keywords."));
169     csItem->setToolTip(
170                 tr("<b>CS</b>: This option determines if the highlight rule and <i>Channel</i> "
171                    "should be interpreted <b>case sensitive</b>."));
172     chanNameItem->setToolTip(
173                 tr("<p><b>Channel</b>: Semicolon separated list of channel names, leave blank to "
174                    "match any name.</p>"
175                    "<p><i>Example:</i><br />"
176                    "<i>#quassel*; #foobar; !#quasseldroid</i><br />"
177                    "would match on <i>#foobar</i> and any channel starting with <i>#quassel</i> "
178                    "except for <i>#quasseldroid</i><br />"
179                    "<p>If only inverted names are specified, it will match anything except for "
180                    "what's specified (implicit wildcard).</p>"
181                    "<p><i>Example:</i><br />"
182                    "<i>!#quassel*; !#foobar</i><br />"
183                    "would match anything except for <i>#foobar</i> or any channel starting with "
184                    "<i>#quassel</i></p>"));
185
186     int lastRow = ui.highlightTable->rowCount()-1;
187     ui.highlightTable->setItem(lastRow, HighlightSettingsPage::EnableColumn, enableItem);
188     ui.highlightTable->setItem(lastRow, HighlightSettingsPage::NameColumn, nameItem);
189     ui.highlightTable->setItem(lastRow, HighlightSettingsPage::RegExColumn, regexItem);
190     ui.highlightTable->setItem(lastRow, HighlightSettingsPage::CsColumn, csItem);
191     ui.highlightTable->setItem(lastRow, HighlightSettingsPage::ChanColumn, chanNameItem);
192
193     if (!self)
194         ui.highlightTable->setCurrentItem(nameItem);
195
196     QVariantMap highlightRule;
197     highlightRule["Name"] = name;
198     highlightRule["RegEx"] = regex;
199     highlightRule["CS"] = cs;
200     highlightRule["Enable"] = enable;
201     highlightRule["Channel"] = chanName;
202
203     highlightList.append(highlightRule);
204 }
205
206
207 void HighlightSettingsPage::removeSelectedRows()
208 {
209     QList<int> selectedRows;
210     QList<QTableWidgetItem *> selectedItemList = ui.highlightTable->selectedItems();
211     foreach(QTableWidgetItem *selectedItem, selectedItemList) {
212         selectedRows.append(selectedItem->row());
213     }
214     qSort(selectedRows.begin(), selectedRows.end(), qGreater<int>());
215     int lastRow = -1;
216     foreach(int row, selectedRows) {
217         if (row != lastRow) {
218             ui.highlightTable->removeRow(row);
219             highlightList.removeAt(row);
220         }
221         lastRow = row;
222     }
223 }
224
225
226 void HighlightSettingsPage::selectRow(QTableWidgetItem *item)
227 {
228     int row = item->row();
229     bool selected = item->isSelected();
230     ui.highlightTable->setRangeSelected(QTableWidgetSelectionRange(row, 0, row, HighlightSettingsPage::ColumnCount-1), selected);
231 }
232
233
234 void HighlightSettingsPage::emptyTable()
235 {
236     // ui.highlight and highlightList should have the same size, but just to make sure.
237     if (ui.highlightTable->rowCount() != highlightList.size()) {
238         qDebug() << "something is wrong: ui.highlight and highlightList don't have the same size!";
239     }
240     while (ui.highlightTable->rowCount()) {
241         ui.highlightTable->removeRow(0);
242     }
243     while (highlightList.size()) {
244         highlightList.removeLast();
245     }
246 }
247
248
249 void HighlightSettingsPage::tableChanged(QTableWidgetItem *item)
250 {
251     if (item->row()+1 > highlightList.size())
252         return;
253
254     QVariantMap highlightRule = highlightList.value(item->row()).toMap();
255
256     switch (item->column())
257     {
258     case HighlightSettingsPage::EnableColumn:
259         highlightRule["Enable"] = (item->checkState() == Qt::Checked);
260         break;
261     case HighlightSettingsPage::NameColumn:
262         if (item->text() == "")
263             item->setText(tr("this shouldn't be empty"));
264         highlightRule["Name"] = item->text();
265         break;
266     case HighlightSettingsPage::RegExColumn:
267         highlightRule["RegEx"] = (item->checkState() == Qt::Checked);
268         break;
269     case HighlightSettingsPage::CsColumn:
270         highlightRule["CS"] = (item->checkState() == Qt::Checked);
271         break;
272     case HighlightSettingsPage::ChanColumn:
273         if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
274             item->setText("");
275         highlightRule["Channel"] = item->text();
276         break;
277     }
278     highlightList[item->row()] = highlightRule;
279     emit widgetHasChanged();
280 }
281
282
283 void HighlightSettingsPage::on_localHighlightsDetails_clicked()
284 {
285     // Re-use translations of "Remote Highlights" as this is a word-for-word reference, forcing all
286     // spaces to non-breaking
287     const QString remoteHighlightsName = tr("Remote Highlights").replace(" ", "&nbsp;");
288     QString localHighlightsMsgText;
289
290     // Set up client/monolithic local highlights information
291     if (Quassel::runMode() == Quassel::Monolithic) {
292         // We're running in Monolithic mode, core/client version in total sync.  Discourage the use
293         // of local highlights as it's identical to setting remote highlights.
294         localHighlightsMsgText =
295                 QString("<p><b>%1</b></p></br><p>%2</p></br><p>%3</p>"
296                         ).arg(tr("Local Highlights are replaced by Remote Highlights"),
297                               tr("These highlights will keep working for now, but you should move "
298                                  "to the improved highlight rules when you can."),
299                               tr("Configure the new style of highlights in "
300                                  "<i>%1</i>.").arg(remoteHighlightsName));
301     } else {
302         // We're running in client/split mode, allow for splitting the details.
303         localHighlightsMsgText =
304                 QString("<p><b>%1</b></p></br><p>%2</p></br><p>%3</p>"
305                         ).arg(tr("Local Highlights apply to this device only"),
306                               tr("Highlights configured on this page only apply to your current "
307                                  "device."),
308                               tr("Configure highlights for all of your devices in "
309                                  "<i>%1</i>.").arg(remoteHighlightsName));
310     }
311
312     QMessageBox::information(this,
313                              tr("Local Highlights vs. Remote Highlights"),
314                              localHighlightsMsgText);
315 }
316
317
318 void HighlightSettingsPage::load()
319 {
320     NotificationSettings notificationSettings;
321
322     emptyTable();
323
324     foreach(QVariant highlight, notificationSettings.highlightList()) {
325         QVariantMap highlightRule = highlight.toMap();
326         QString name = highlightRule["Name"].toString();
327         bool regex = highlightRule["RegEx"].toBool();
328         bool cs = highlightRule["CS"].toBool();
329         bool enable = highlightRule["Enable"].toBool();
330         QString chanName = highlightRule["Channel"].toString();
331
332         addNewRow(name, regex, cs, enable, chanName, true);
333     }
334
335     switch (notificationSettings.highlightNick())
336     {
337     case NotificationSettings::NoNick:
338         ui.highlightNoNick->setChecked(true);
339         break;
340     case NotificationSettings::CurrentNick:
341         ui.highlightCurrentNick->setChecked(true);
342         break;
343     case NotificationSettings::AllNicks:
344         ui.highlightAllNicks->setChecked(true);
345         break;
346     }
347     ui.nicksCaseSensitive->setChecked(notificationSettings.nicksCaseSensitive());
348
349     setChangedState(false);
350 }
351
352
353 void HighlightSettingsPage::save()
354 {
355     NotificationSettings notificationSettings;
356     notificationSettings.setHighlightList(highlightList);
357
358     NotificationSettings::HighlightNickType highlightNickType = NotificationSettings::NoNick;
359     if (ui.highlightCurrentNick->isChecked())
360         highlightNickType = NotificationSettings::CurrentNick;
361     if (ui.highlightAllNicks->isChecked())
362         highlightNickType = NotificationSettings::AllNicks;
363
364     notificationSettings.setHighlightNick(highlightNickType);
365     notificationSettings.setNicksCaseSensitive(ui.nicksCaseSensitive->isChecked());
366
367     load();
368     setChangedState(false);
369 }
370
371
372 void HighlightSettingsPage::widgetHasChanged()
373 {
374     bool changed = testHasChanged();
375     if (changed != hasChanged()) setChangedState(changed);
376 }
377
378
379 bool HighlightSettingsPage::testHasChanged()
380 {
381     NotificationSettings notificationSettings;
382
383     NotificationSettings::HighlightNickType highlightNickType = NotificationSettings::NoNick;
384     if (ui.highlightCurrentNick->isChecked())
385         highlightNickType = NotificationSettings::CurrentNick;
386     if (ui.highlightAllNicks->isChecked())
387         highlightNickType = NotificationSettings::AllNicks;
388
389     if (notificationSettings.highlightNick() != highlightNickType)
390         return true;
391     if (notificationSettings.nicksCaseSensitive() != ui.nicksCaseSensitive->isChecked())
392         return true;
393     if (notificationSettings.highlightList() != highlightList)
394         return true;
395
396     return false;
397 }