client: Add Highlight Rules tooltips, UI polish
[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
29 HighlightSettingsPage::HighlightSettingsPage(QWidget *parent)
30     : SettingsPage(tr("Interface"), tr("Local Highlights"), parent)
31 {
32     ui.setupUi(this);
33     ui.highlightTable->verticalHeader()->hide();
34     ui.highlightTable->setShowGrid(false);
35
36     ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::RegExColumn)->setToolTip(
37                 tr("<b>RegEx</b>: This option determines if the highlight rule should be "
38                    "interpreted as a <b>regular expression</b> or just as a keyword."));
39     ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::RegExColumn)->setWhatsThis(
40                 ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::RegExColumn)->toolTip());
41
42     ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::CsColumn)->setToolTip(
43                 tr("<b>CS</b>: This option determines if the highlight rule should be interpreted "
44                    "<b>case sensitive</b>."));
45     ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::CsColumn)->setWhatsThis(
46                 ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::CsColumn)->toolTip());
47
48     ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::ChanColumn)->setToolTip(
49                 tr("<p><b>Channel</b>: Semicolon separated list of channel names.</p>"
50                    "<p><i>Example:</i><br />"
51                    "<i>#quassel*; #foobar; !#quasseldroid</i><br />"
52                    "would match on #foobar and on any channel starting with <i>#quassel</i> except "
53                    "for <i>#quasseldroid</i><br />"
54                    "<p>If only inverted names are specified, it will match anything except for "
55                    "what's specified (implicit wildcard).</p>"
56                    "<p><i>Example:</i><br />"
57                    "<i>!#quassel*; !#foobar</i><br />"
58                    "would match anything except for #foobar or any channel starting with "
59                    "<i>#quassel</i></p>"));
60     ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::ChanColumn)->setWhatsThis(
61                 ui.highlightTable->horizontalHeaderItem(HighlightSettingsPage::ChanColumn)->toolTip());
62
63 #if QT_VERSION < 0x050000
64     ui.highlightTable->horizontalHeader()->setResizeMode(HighlightSettingsPage::NameColumn, QHeaderView::Stretch);
65     ui.highlightTable->horizontalHeader()->setResizeMode(HighlightSettingsPage::RegExColumn, QHeaderView::ResizeToContents);
66     ui.highlightTable->horizontalHeader()->setResizeMode(HighlightSettingsPage::CsColumn, QHeaderView::ResizeToContents);
67     ui.highlightTable->horizontalHeader()->setResizeMode(HighlightSettingsPage::EnableColumn, QHeaderView::ResizeToContents);
68     ui.highlightTable->horizontalHeader()->setResizeMode(HighlightSettingsPage::ChanColumn, QHeaderView::ResizeToContents);
69 #else
70     ui.highlightTable->horizontalHeader()->setSectionResizeMode(HighlightSettingsPage::NameColumn, QHeaderView::Stretch);
71     ui.highlightTable->horizontalHeader()->setSectionResizeMode(HighlightSettingsPage::RegExColumn, QHeaderView::ResizeToContents);
72     ui.highlightTable->horizontalHeader()->setSectionResizeMode(HighlightSettingsPage::CsColumn, QHeaderView::ResizeToContents);
73     ui.highlightTable->horizontalHeader()->setSectionResizeMode(HighlightSettingsPage::EnableColumn, QHeaderView::ResizeToContents);
74     ui.highlightTable->horizontalHeader()->setSectionResizeMode(HighlightSettingsPage::ChanColumn, QHeaderView::ResizeToContents);
75 #endif
76
77     connect(ui.add, SIGNAL(clicked(bool)), this, SLOT(addNewRow()));
78     connect(ui.remove, SIGNAL(clicked(bool)), this, SLOT(removeSelectedRows()));
79     //TODO: search for a better signal (one that emits everytime a selection has been changed for one item)
80     connect(ui.highlightTable, SIGNAL(itemClicked(QTableWidgetItem *)), this, SLOT(selectRow(QTableWidgetItem *)));
81
82     connect(ui.highlightAllNicks, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
83     connect(ui.highlightCurrentNick, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
84     connect(ui.highlightNoNick, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
85     connect(ui.nicksCaseSensitive, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
86     connect(ui.add, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
87     connect(ui.remove, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
88     connect(ui.highlightTable, SIGNAL(itemChanged(QTableWidgetItem *)), this, SLOT(tableChanged(QTableWidgetItem *)));
89 }
90
91
92 bool HighlightSettingsPage::hasDefaults() const
93 {
94     return true;
95 }
96
97
98 void HighlightSettingsPage::defaults()
99 {
100     ui.highlightNoNick->setChecked(true);
101     ui.nicksCaseSensitive->setChecked(false);
102     emptyTable();
103
104     widgetHasChanged();
105 }
106
107
108 void HighlightSettingsPage::addNewRow(QString name, bool regex, bool cs, bool enable, QString chanName, bool self)
109 {
110     ui.highlightTable->setRowCount(ui.highlightTable->rowCount()+1);
111
112     QTableWidgetItem *enableItem = new QTableWidgetItem("");
113     if (enable)
114         enableItem->setCheckState(Qt::Checked);
115     else
116         enableItem->setCheckState(Qt::Unchecked);
117     enableItem->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled|Qt::ItemIsSelectable);
118
119     QTableWidgetItem *nameItem = new QTableWidgetItem(name);
120
121     QTableWidgetItem *regexItem = new QTableWidgetItem("");
122     if (regex)
123         regexItem->setCheckState(Qt::Checked);
124     else
125         regexItem->setCheckState(Qt::Unchecked);
126     regexItem->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled|Qt::ItemIsSelectable);
127
128     QTableWidgetItem *csItem = new QTableWidgetItem("");
129     if (cs)
130         csItem->setCheckState(Qt::Checked);
131     else
132         csItem->setCheckState(Qt::Unchecked);
133     csItem->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled|Qt::ItemIsSelectable);
134
135     QTableWidgetItem *chanNameItem = new QTableWidgetItem(chanName);
136
137     enableItem->setToolTip(tr("Enable/disable this rule"));
138     nameItem->setToolTip(tr("Phrase to match"));
139     regexItem->setToolTip(
140                 tr("<b>RegEx</b>: This option determines if the highlight rule should be "
141                    "interpreted as a <b>regular expression</b> or just as a keyword."));
142     csItem->setToolTip(
143                 tr("<b>CS</b>: This option determines if the highlight rule should be interpreted "
144                    "<b>case sensitive</b>."));
145     chanNameItem->setToolTip(
146                 tr("<p><b>Channel</b>: Semicolon separated list of channel names.</p>"
147                    "<p><i>Example:</i><br />"
148                    "<i>#quassel*; #foobar; !#quasseldroid</i><br />"
149                    "would match on #foobar and on any channel starting with <i>#quassel</i> except "
150                    "for <i>#quasseldroid</i><br />"
151                    "<p>If only inverted names are specified, it will match anything except for "
152                    "what's specified (implicit wildcard).</p>"
153                    "<p><i>Example:</i><br />"
154                    "<i>!#quassel*; !#foobar</i><br />"
155                    "would match anything except for #foobar or any channel starting with "
156                    "<i>#quassel</i></p>"));
157
158     int lastRow = ui.highlightTable->rowCount()-1;
159     ui.highlightTable->setItem(lastRow, HighlightSettingsPage::EnableColumn, enableItem);
160     ui.highlightTable->setItem(lastRow, HighlightSettingsPage::NameColumn, nameItem);
161     ui.highlightTable->setItem(lastRow, HighlightSettingsPage::RegExColumn, regexItem);
162     ui.highlightTable->setItem(lastRow, HighlightSettingsPage::CsColumn, csItem);
163     ui.highlightTable->setItem(lastRow, HighlightSettingsPage::ChanColumn, chanNameItem);
164
165     if (!self)
166         ui.highlightTable->setCurrentItem(nameItem);
167
168     QVariantMap highlightRule;
169     highlightRule["Name"] = name;
170     highlightRule["RegEx"] = regex;
171     highlightRule["CS"] = cs;
172     highlightRule["Enable"] = enable;
173     highlightRule["Channel"] = chanName;
174
175     highlightList.append(highlightRule);
176 }
177
178
179 void HighlightSettingsPage::removeSelectedRows()
180 {
181     QList<int> selectedRows;
182     QList<QTableWidgetItem *> selectedItemList = ui.highlightTable->selectedItems();
183     foreach(QTableWidgetItem *selectedItem, selectedItemList) {
184         selectedRows.append(selectedItem->row());
185     }
186     qSort(selectedRows.begin(), selectedRows.end(), qGreater<int>());
187     int lastRow = -1;
188     foreach(int row, selectedRows) {
189         if (row != lastRow) {
190             ui.highlightTable->removeRow(row);
191             highlightList.removeAt(row);
192         }
193         lastRow = row;
194     }
195 }
196
197
198 void HighlightSettingsPage::selectRow(QTableWidgetItem *item)
199 {
200     int row = item->row();
201     bool selected = item->isSelected();
202     ui.highlightTable->setRangeSelected(QTableWidgetSelectionRange(row, 0, row, HighlightSettingsPage::ColumnCount-1), selected);
203 }
204
205
206 void HighlightSettingsPage::emptyTable()
207 {
208     // ui.highlight and highlightList should have the same size, but just to make sure.
209     if (ui.highlightTable->rowCount() != highlightList.size()) {
210         qDebug() << "something is wrong: ui.highlight and highlightList don't have the same size!";
211     }
212     while (ui.highlightTable->rowCount()) {
213         ui.highlightTable->removeRow(0);
214     }
215     while (highlightList.size()) {
216         highlightList.removeLast();
217     }
218 }
219
220
221 void HighlightSettingsPage::tableChanged(QTableWidgetItem *item)
222 {
223     if (item->row()+1 > highlightList.size())
224         return;
225
226     QVariantMap highlightRule = highlightList.value(item->row()).toMap();
227
228     switch (item->column())
229     {
230     case HighlightSettingsPage::EnableColumn:
231         highlightRule["Enable"] = (item->checkState() == Qt::Checked);
232         break;
233     case HighlightSettingsPage::NameColumn:
234         if (item->text() == "")
235             item->setText(tr("this shouldn't be empty"));
236         highlightRule["Name"] = item->text();
237         break;
238     case HighlightSettingsPage::RegExColumn:
239         highlightRule["RegEx"] = (item->checkState() == Qt::Checked);
240         break;
241     case HighlightSettingsPage::CsColumn:
242         highlightRule["CS"] = (item->checkState() == Qt::Checked);
243         break;
244     case HighlightSettingsPage::ChanColumn:
245         if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
246             item->setText("");
247         highlightRule["Channel"] = item->text();
248         break;
249     }
250     highlightList[item->row()] = highlightRule;
251     emit widgetHasChanged();
252 }
253
254
255 void HighlightSettingsPage::load()
256 {
257     NotificationSettings notificationSettings;
258
259     emptyTable();
260
261     foreach(QVariant highlight, notificationSettings.highlightList()) {
262         QVariantMap highlightRule = highlight.toMap();
263         QString name = highlightRule["Name"].toString();
264         bool regex = highlightRule["RegEx"].toBool();
265         bool cs = highlightRule["CS"].toBool();
266         bool enable = highlightRule["Enable"].toBool();
267         QString chanName = highlightRule["Channel"].toString();
268
269         addNewRow(name, regex, cs, enable, chanName, true);
270     }
271
272     switch (notificationSettings.highlightNick())
273     {
274     case NotificationSettings::NoNick:
275         ui.highlightNoNick->setChecked(true);
276         break;
277     case NotificationSettings::CurrentNick:
278         ui.highlightCurrentNick->setChecked(true);
279         break;
280     case NotificationSettings::AllNicks:
281         ui.highlightAllNicks->setChecked(true);
282         break;
283     }
284     ui.nicksCaseSensitive->setChecked(notificationSettings.nicksCaseSensitive());
285
286     setChangedState(false);
287 }
288
289
290 void HighlightSettingsPage::save()
291 {
292     NotificationSettings notificationSettings;
293     notificationSettings.setHighlightList(highlightList);
294
295     NotificationSettings::HighlightNickType highlightNickType = NotificationSettings::NoNick;
296     if (ui.highlightCurrentNick->isChecked())
297         highlightNickType = NotificationSettings::CurrentNick;
298     if (ui.highlightAllNicks->isChecked())
299         highlightNickType = NotificationSettings::AllNicks;
300
301     notificationSettings.setHighlightNick(highlightNickType);
302     notificationSettings.setNicksCaseSensitive(ui.nicksCaseSensitive->isChecked());
303
304     load();
305     setChangedState(false);
306 }
307
308
309 void HighlightSettingsPage::widgetHasChanged()
310 {
311     bool changed = testHasChanged();
312     if (changed != hasChanged()) setChangedState(changed);
313 }
314
315
316 bool HighlightSettingsPage::testHasChanged()
317 {
318     NotificationSettings notificationSettings;
319
320     NotificationSettings::HighlightNickType highlightNickType = NotificationSettings::NoNick;
321     if (ui.highlightCurrentNick->isChecked())
322         highlightNickType = NotificationSettings::CurrentNick;
323     if (ui.highlightAllNicks->isChecked())
324         highlightNickType = NotificationSettings::AllNicks;
325
326     if (notificationSettings.highlightNick() != highlightNickType)
327         return true;
328     if (notificationSettings.nicksCaseSensitive() != ui.nicksCaseSensitive->isChecked())
329         return true;
330     if (notificationSettings.highlightList() != highlightList)
331         return true;
332
333     return false;
334 }