common: Make SyncableObject non-copyable
[quassel.git] / src / qtui / settingspages / ignorelistsettingspage.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2020 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 "ignorelistsettingspage.h"
22
23 #include <QDebug>
24 #include <QEvent>
25 #include <QHeaderView>
26 #include <QItemSelectionModel>
27 #include <QMessageBox>
28 #include <QModelIndex>
29 #include <QPainter>
30 #include <QString>
31
32 #include "expressionmatch.h"
33 #include "icon.h"
34 #include "util.h"
35
36 IgnoreListSettingsPage::IgnoreListSettingsPage(QWidget* parent)
37     : SettingsPage(tr("IRC"), tr("Ignore List"), parent)
38 {
39     ui.setupUi(this);
40     _delegate = new IgnoreListDelegate(ui.ignoreListView);
41     ui.newIgnoreRuleButton->setIcon(icon::get("list-add"));
42     ui.deleteIgnoreRuleButton->setIcon(icon::get("edit-delete"));
43     ui.editIgnoreRuleButton->setIcon(icon::get("configure"));
44
45     ui.ignoreListView->setSelectionBehavior(QAbstractItemView::SelectRows);
46     ui.ignoreListView->setSelectionMode(QAbstractItemView::SingleSelection);
47     ui.ignoreListView->setAlternatingRowColors(true);
48     ui.ignoreListView->setTabKeyNavigation(false);
49     ui.ignoreListView->setModel(&_ignoreListModel);
50     // ui.ignoreListView->setEditTriggers(QAbstractItemView::NoEditTriggers);
51
52     // ui.ignoreListView->setSortingEnabled(true);
53     ui.ignoreListView->verticalHeader()->hide();
54     ui.ignoreListView->hideColumn(1);
55     ui.ignoreListView->resizeColumnToContents(0);
56     ui.ignoreListView->horizontalHeader()->setStretchLastSection(true);
57     ui.ignoreListView->setItemDelegateForColumn(0, _delegate);
58     ui.ignoreListView->viewport()->setAttribute(Qt::WA_Hover);
59     ui.ignoreListView->viewport()->setMouseTracking(true);
60
61     connect(ui.ignoreListView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &IgnoreListSettingsPage::selectionChanged);
62     connect(ui.newIgnoreRuleButton, &QAbstractButton::clicked, this, [this]() { newIgnoreRule(); });
63     connect(ui.deleteIgnoreRuleButton, &QAbstractButton::clicked, this, &IgnoreListSettingsPage::deleteSelectedIgnoreRule);
64     connect(ui.editIgnoreRuleButton, &QAbstractButton::clicked, this, &IgnoreListSettingsPage::editSelectedIgnoreRule);
65     connect(&_ignoreListModel, &IgnoreListModel::configChanged, this, &IgnoreListSettingsPage::setChangedState);
66     connect(&_ignoreListModel, &IgnoreListModel::modelReady, this, &IgnoreListSettingsPage::enableDialog);
67
68     enableDialog(_ignoreListModel.isReady());
69 }
70
71 IgnoreListSettingsPage::~IgnoreListSettingsPage()
72 {
73     delete _delegate;
74 }
75
76 void IgnoreListSettingsPage::load()
77 {
78     if (_ignoreListModel.hasConfigChanged())
79         _ignoreListModel.revert();
80     ui.ignoreListView->selectionModel()->reset();
81     ui.editIgnoreRuleButton->setEnabled(false);
82 }
83
84 void IgnoreListSettingsPage::defaults()
85 {
86     _ignoreListModel.loadDefaults();
87 }
88
89 void IgnoreListSettingsPage::save()
90 {
91     if (_ignoreListModel.hasConfigChanged()) {
92         _ignoreListModel.commit();
93     }
94     ui.ignoreListView->selectionModel()->reset();
95     ui.editIgnoreRuleButton->setEnabled(false);
96 }
97
98 void IgnoreListSettingsPage::enableDialog(bool enabled)
99 {
100     ui.newIgnoreRuleButton->setEnabled(enabled);
101     setEnabled(enabled);
102 }
103
104 void IgnoreListSettingsPage::selectionChanged(const QItemSelection& selection, const QItemSelection&)
105 {
106     bool state = !selection.isEmpty();
107     ui.deleteIgnoreRuleButton->setEnabled(state);
108     ui.editIgnoreRuleButton->setEnabled(state);
109 }
110
111 void IgnoreListSettingsPage::deleteSelectedIgnoreRule()
112 {
113     if (!ui.ignoreListView->selectionModel()->hasSelection())
114         return;
115
116     _ignoreListModel.removeIgnoreRule(ui.ignoreListView->selectionModel()->selectedIndexes()[0].row());
117 }
118
119 void IgnoreListSettingsPage::newIgnoreRule(const QString& rule)
120 {
121     IgnoreListManager::IgnoreListItem newItem = IgnoreListManager::IgnoreListItem();
122     newItem.setStrictness(IgnoreListManager::SoftStrictness);
123     newItem.setScope(IgnoreListManager::GlobalScope);
124     newItem.setIsRegEx(false);
125     newItem.setIsEnabled(true);
126
127     bool enableOkButton = false;
128     if (!rule.isEmpty()) {
129         // we're called from contextmenu
130         newItem.setContents(rule);
131         enableOkButton = true;
132     }
133
134     auto* dlg = new IgnoreListEditDlg(newItem, this, enableOkButton);
135     dlg->enableOkButton(enableOkButton);
136     while (dlg->exec() == QDialog::Accepted) {
137         if (!_ignoreListModel.newIgnoreRule(dlg->ignoreListItem())) {
138             if (QMessageBox::warning(this,
139                                      tr("Rule already exists"),
140                                      tr("There is already a rule\n\"%1\"\nPlease choose another rule.").arg(dlg->ignoreListItem().contents()),
141                                      QMessageBox::Ok | QMessageBox::Cancel,
142                                      QMessageBox::Ok)
143                 == QMessageBox::Cancel)
144                 break;
145
146             IgnoreListManager::IgnoreListItem item = dlg->ignoreListItem();
147             delete dlg;
148             dlg = new IgnoreListEditDlg(item, this);
149         }
150         else {
151             break;
152         }
153     }
154     dlg->deleteLater();
155 }
156
157 void IgnoreListSettingsPage::editSelectedIgnoreRule()
158 {
159     if (!ui.ignoreListView->selectionModel()->hasSelection())
160         return;
161     int row = ui.ignoreListView->selectionModel()->selectedIndexes()[0].row();
162     IgnoreListEditDlg dlg(_ignoreListModel.ignoreListItemAt(row), this);
163     dlg.setAttribute(Qt::WA_DeleteOnClose, false);
164     if (dlg.exec() == QDialog::Accepted) {
165         _ignoreListModel.setIgnoreListItemAt(row, dlg.ignoreListItem());
166     }
167 }
168
169 void IgnoreListSettingsPage::editIgnoreRule(const QString& ignoreRule)
170 {
171     ui.ignoreListView->selectionModel()->select(_ignoreListModel.indexOf(ignoreRule), QItemSelectionModel::Select);
172     if (ui.ignoreListView->selectionModel()->hasSelection())  // && ui.ignoreListView->selectionModel()->selectedIndexes()[0].row() != -1)
173         editSelectedIgnoreRule();
174     else
175         newIgnoreRule(ignoreRule);
176 }
177
178 /*
179   IgnoreListDelegate
180 */
181 void IgnoreListDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
182 {
183     if (index.column() == 0) {
184         QStyle* style = QApplication::style();
185         if (option.state & QStyle::State_Selected)
186             painter->fillRect(option.rect, option.palette.highlight());
187
188         QStyleOptionButton opts;
189         opts.direction = option.direction;
190         opts.rect = option.rect;
191         opts.rect.moveLeft(option.rect.center().rx() - 10);
192         opts.state = option.state;
193         opts.state |= index.data().toBool() ? QStyle::State_On : QStyle::State_Off;
194         style->drawControl(QStyle::CE_CheckBox, &opts, painter);
195     }
196     else
197         QStyledItemDelegate::paint(painter, option, index);
198 }
199
200 // provide interactivity for the checkboxes
201 bool IgnoreListDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index)
202 {
203     Q_UNUSED(option)
204     switch (event->type()) {
205     case QEvent::MouseButtonRelease:
206         model->setData(index, !index.data().toBool());
207         return true;
208     // don't show the default editor for the column
209     case QEvent::MouseButtonDblClick:
210         return true;
211     default:
212         return false;
213     }
214 }
215
216 /*
217   IgnoreListEditDlg
218 */
219 IgnoreListEditDlg::IgnoreListEditDlg(const IgnoreListManager::IgnoreListItem& item, QWidget* parent, bool enabled)
220     : QDialog(parent)
221     , _ignoreListItem(item)
222     , _hasChanged(enabled)
223 {
224     ui.setupUi(this);
225     setAttribute(Qt::WA_DeleteOnClose, false);
226     setModal(true);
227     // FIXME patch out the bugger completely if it's good without it
228     ui.isActiveCheckBox->hide();
229
230     // setup buttongroups
231     // this could be moved to .ui file with qt4.5
232     _typeButtonGroup.addButton(ui.senderTypeButton, 0);
233     _typeButtonGroup.addButton(ui.messageTypeButton, 1);
234     _typeButtonGroup.addButton(ui.ctcpTypeButton, 2);
235     _strictnessButtonGroup.addButton(ui.dynamicStrictnessButton, 0);
236     _strictnessButtonGroup.addButton(ui.permanentStrictnessButton, 1);
237     _scopeButtonGroup.addButton(ui.globalScopeButton, 0);
238     _scopeButtonGroup.addButton(ui.networkScopeButton, 1);
239     _scopeButtonGroup.addButton(ui.channelScopeButton, 2);
240
241     ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
242
243     ui.ignoreRuleLineEdit->setText(item.contents());
244
245     if (item.type() == IgnoreListManager::MessageIgnore)
246         ui.messageTypeButton->setChecked(true);
247     else if (item.type() == IgnoreListManager::CtcpIgnore)
248         ui.ctcpTypeButton->setChecked(true);
249     else
250         ui.senderTypeButton->setChecked(true);
251
252     ui.isRegExCheckBox->setChecked(item.isRegEx());
253     ui.isActiveCheckBox->setChecked(item.isEnabled());
254
255     if (item.strictness() == IgnoreListManager::HardStrictness)
256         ui.permanentStrictnessButton->setChecked(true);
257     else
258         ui.dynamicStrictnessButton->setChecked(true);
259
260     switch (item.scope()) {
261     case IgnoreListManager::NetworkScope:
262         ui.networkScopeButton->setChecked(true);
263         ui.scopeRuleTextEdit->setEnabled(true);
264         break;
265     case IgnoreListManager::ChannelScope:
266         ui.channelScopeButton->setChecked(true);
267         ui.scopeRuleTextEdit->setEnabled(true);
268         break;
269     default:
270         ui.globalScopeButton->setChecked(true);
271         ui.scopeRuleTextEdit->setEnabled(false);
272     }
273
274     if (item.scope() == IgnoreListManager::GlobalScope)
275         ui.scopeRuleTextEdit->clear();
276     else
277         ui.scopeRuleTextEdit->setPlainText(item.scopeRule());
278
279     connect(ui.ignoreRuleLineEdit, &QLineEdit::textChanged, this, &IgnoreListEditDlg::widgetHasChanged);
280     connect(ui.scopeRuleTextEdit, &QPlainTextEdit::textChanged, this, &IgnoreListEditDlg::widgetHasChanged);
281     connect(&_typeButtonGroup, selectOverload<int>(&QButtonGroup::buttonClicked), this, &IgnoreListEditDlg::widgetHasChanged);
282     connect(&_strictnessButtonGroup, selectOverload<int>(&QButtonGroup::buttonClicked), this, &IgnoreListEditDlg::widgetHasChanged);
283     connect(&_scopeButtonGroup, selectOverload<int>(&QButtonGroup::buttonClicked), this, &IgnoreListEditDlg::widgetHasChanged);
284     connect(ui.isRegExCheckBox, &QCheckBox::stateChanged, this, &IgnoreListEditDlg::widgetHasChanged);
285     connect(ui.isActiveCheckBox, &QCheckBox::stateChanged, this, &IgnoreListEditDlg::widgetHasChanged);
286
287     connect(ui.buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, this, &IgnoreListEditDlg::aboutToAccept);
288     widgetHasChanged();
289 }
290
291 void IgnoreListEditDlg::widgetHasChanged()
292 {
293     if (ui.messageTypeButton->isChecked())
294         _clonedIgnoreListItem.setType(IgnoreListManager::MessageIgnore);
295     else if (ui.ctcpTypeButton->isChecked())
296         _clonedIgnoreListItem.setType(IgnoreListManager::CtcpIgnore);
297     else
298         _clonedIgnoreListItem.setType(IgnoreListManager::SenderIgnore);
299
300     if (ui.permanentStrictnessButton->isChecked())
301         _clonedIgnoreListItem.setStrictness(IgnoreListManager::HardStrictness);
302     else
303         _clonedIgnoreListItem.setStrictness(IgnoreListManager::SoftStrictness);
304
305     if (ui.networkScopeButton->isChecked()) {
306         _clonedIgnoreListItem.setScope(IgnoreListManager::NetworkScope);
307         ui.scopeRuleTextEdit->setEnabled(true);
308     }
309     else if (ui.channelScopeButton->isChecked()) {
310         _clonedIgnoreListItem.setScope(IgnoreListManager::ChannelScope);
311         ui.scopeRuleTextEdit->setEnabled(true);
312     }
313     else {
314         _clonedIgnoreListItem.setScope(IgnoreListManager::GlobalScope);
315         ui.scopeRuleTextEdit->setEnabled(false);
316     }
317
318     if (_clonedIgnoreListItem.scope() == IgnoreListManager::GlobalScope) {
319         _clonedIgnoreListItem.setScopeRule(QString());
320     }
321     else {
322         // Trim the resulting MultiWildcard expression
323         _clonedIgnoreListItem.setScopeRule(ExpressionMatch::trimMultiWildcardWhitespace(ui.scopeRuleTextEdit->toPlainText()));
324     }
325
326     _clonedIgnoreListItem.setContents(ui.ignoreRuleLineEdit->text());
327     _clonedIgnoreListItem.setIsRegEx(ui.isRegExCheckBox->isChecked());
328     _clonedIgnoreListItem.setIsEnabled(ui.isActiveCheckBox->isChecked());
329
330     if (!_clonedIgnoreListItem.contents().isEmpty() && _clonedIgnoreListItem != _ignoreListItem)
331         _hasChanged = true;
332     else
333         _hasChanged = false;
334     ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(_hasChanged);
335 }
336
337 void IgnoreListEditDlg::enableOkButton(bool state)
338 {
339     ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(state);
340 }