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