src: Yearly copyright bump
[quassel.git] / src / qtui / settingspages / bufferviewsettingspage.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2019 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 "bufferviewsettingspage.h"
22
23 #include <utility>
24
25 #include <QMessageBox>
26
27 #include "buffermodel.h"
28 #include "bufferviewconfig.h"
29 #include "bufferviewfilter.h"
30 #include "client.h"
31 #include "clientbufferviewmanager.h"
32 #include "icon.h"
33 #include "network.h"
34 #include "networkmodel.h"
35 #include "util.h"
36
37 BufferViewSettingsPage::BufferViewSettingsPage(QWidget* parent)
38     : SettingsPage(tr("Interface"), tr("Custom Chat Lists"), parent)
39 {
40     ui.setupUi(this);
41     // Hide the hide inactive networks feature on older cores (which won't save the setting)
42     if (!Client::isCoreFeatureEnabled(Quassel::Feature::HideInactiveNetworks))
43         ui.hideInactiveNetworks->hide();
44
45     ui.renameBufferView->setIcon(icon::get("edit-rename"));
46     ui.addBufferView->setIcon(icon::get("list-add"));
47     ui.deleteBufferView->setIcon(icon::get("edit-delete"));
48
49     reset();
50
51     ui.bufferViewList->setSortingEnabled(true);
52     ui.settingsGroupBox->setEnabled(false);
53     ui.bufferViewPreview->setEnabled(false);
54
55     coreConnectionStateChanged(Client::isConnected());  // need a core connection!
56     connect(Client::instance(), &Client::coreConnectionStateChanged, this, &BufferViewSettingsPage::coreConnectionStateChanged);
57     connect(ui.bufferViewList->selectionModel(),
58             &QItemSelectionModel::selectionChanged,
59             this,
60             &BufferViewSettingsPage::bufferViewSelectionChanged);
61
62     connect(ui.onlyStatusBuffers, &QAbstractButton::clicked, this, &BufferViewSettingsPage::widgetHasChanged);
63     connect(ui.onlyChannelBuffers, &QAbstractButton::clicked, this, &BufferViewSettingsPage::widgetHasChanged);
64     connect(ui.onlyQueryBuffers, &QAbstractButton::clicked, this, &BufferViewSettingsPage::widgetHasChanged);
65     connect(ui.addNewBuffersAutomatically, &QAbstractButton::clicked, this, &BufferViewSettingsPage::widgetHasChanged);
66     connect(ui.sortAlphabetically, &QAbstractButton::clicked, this, &BufferViewSettingsPage::widgetHasChanged);
67     connect(ui.hideInactiveBuffers, &QAbstractButton::clicked, this, &BufferViewSettingsPage::widgetHasChanged);
68     connect(ui.hideInactiveNetworks, &QAbstractButton::clicked, this, &BufferViewSettingsPage::widgetHasChanged);
69     connect(ui.networkSelector, selectOverload<int>(&QComboBox::currentIndexChanged), this, &BufferViewSettingsPage::widgetHasChanged);
70     connect(ui.minimumActivitySelector, selectOverload<int>(&QComboBox::currentIndexChanged), this, &BufferViewSettingsPage::widgetHasChanged);
71     connect(ui.showSearch, &QAbstractButton::clicked, this, &BufferViewSettingsPage::widgetHasChanged);
72
73     connect(ui.networkSelector, selectOverload<int>(&QComboBox::currentIndexChanged), this, &BufferViewSettingsPage::enableStatusBuffers);
74 }
75
76 BufferViewSettingsPage::~BufferViewSettingsPage()
77 {
78     reset();
79 }
80
81 void BufferViewSettingsPage::reset()
82 {
83     ui.bufferViewList->clear();
84     ui.deleteBufferView->setEnabled(false);
85
86     QHash<BufferViewConfig*, BufferViewConfig*>::iterator changedConfigIter = _changedBufferViews.begin();
87     QHash<BufferViewConfig*, BufferViewConfig*>::iterator changedConfigIterEnd = _changedBufferViews.end();
88     BufferViewConfig* config;
89     while (changedConfigIter != changedConfigIterEnd) {
90         config = changedConfigIter.value();
91         changedConfigIter = _changedBufferViews.erase(changedConfigIter);
92         config->deleteLater();
93     }
94
95     QList<BufferViewConfig*>::iterator newConfigIter = _newBufferViews.begin();
96     QList<BufferViewConfig*>::iterator newConfigIterEnd = _newBufferViews.end();
97     while (newConfigIter != newConfigIterEnd) {
98         config = *newConfigIter;
99         newConfigIter = _newBufferViews.erase(newConfigIter);
100         config->deleteLater();
101     }
102
103     _deleteBufferViews.clear();
104
105     _useBufferViewHint = false;
106
107     setChangedState(false);
108 }
109
110 void BufferViewSettingsPage::load()
111 {
112     bool useBufferViewHint = _useBufferViewHint;
113     int bufferViewHint = _bufferViewHint;
114     reset();
115
116     if (!Client::bufferViewManager())
117         return;
118
119     const QList<BufferViewConfig*> bufferViewConfigs = Client::bufferViewManager()->bufferViewConfigs();
120     foreach (BufferViewConfig* bufferViewConfig, bufferViewConfigs) {
121         addBufferView(bufferViewConfig);
122     }
123
124     _ignoreWidgetChanges = true;
125     // load network selector
126     ui.networkSelector->clear();
127     ui.networkSelector->addItem(tr("All"));
128     ui.networkSelector->setItemData(0, qVariantFromValue<NetworkId>(NetworkId()));
129     const Network* net;
130     foreach (NetworkId netId, Client::networkIds()) {
131         net = Client::network(netId);
132         ui.networkSelector->addItem(net->networkName());
133         ui.networkSelector->setItemData(ui.networkSelector->count() - 1, qVariantFromValue<NetworkId>(net->networkId()));
134     }
135     _ignoreWidgetChanges = false;
136
137     if (!useBufferViewHint || !selectBufferViewById(bufferViewHint))
138         ui.bufferViewList->setCurrentRow(0);
139 }
140
141 void BufferViewSettingsPage::save()
142 {
143     setEnabled(false);
144
145     BufferViewConfig* currentConfig = bufferView(ui.bufferViewList->currentRow());
146     if (currentConfig) {
147         _useBufferViewHint = true;
148         _bufferViewHint = currentConfig->bufferViewId();
149     }
150
151     QVariantList newConfigs;
152     QVariantList deleteConfigs;
153     QVariantList changedConfigs;
154
155     foreach (int bufferId, _deleteBufferViews) {
156         deleteConfigs << bufferId;
157     }
158     _deleteBufferViews.clear();
159     if (Client::bufferViewManager()) {
160         Client::bufferViewManager()->requestDeleteBufferViews(deleteConfigs);
161     }
162
163     QHash<BufferViewConfig*, BufferViewConfig*>::iterator changedConfigIter = _changedBufferViews.begin();
164     QHash<BufferViewConfig*, BufferViewConfig*>::iterator changedConfigIterEnd = _changedBufferViews.end();
165     BufferViewConfig *config, *changedConfig;
166     while (changedConfigIter != changedConfigIterEnd) {
167         config = changedConfigIter.key();
168         changedConfig = changedConfigIter.value();
169         changedConfigIter = _changedBufferViews.erase(changedConfigIter);
170         config->requestUpdate(changedConfig->toVariantMap());
171         changedConfig->deleteLater();
172     }
173
174     QList<BufferViewConfig*>::iterator newConfigIter = _newBufferViews.begin();
175     QList<BufferViewConfig*>::iterator newConfigIterEnd = _newBufferViews.end();
176     while (newConfigIter != newConfigIterEnd) {
177         config = *newConfigIter;
178         newConfigIter = _newBufferViews.erase(newConfigIter);
179         newConfigs << config->toVariantMap();
180         config->deleteLater();
181     }
182     if (Client::bufferViewManager()) {
183         Client::bufferViewManager()->requestCreateBufferViews(newConfigs);
184     }
185
186     load();
187     setEnabled(true);
188 }
189
190 void BufferViewSettingsPage::coreConnectionStateChanged(bool state)
191 {
192     setEnabled(state);
193     if (state) {
194         load();
195         connect(Client::bufferViewManager(),
196                 selectOverload<int>(&BufferViewManager::bufferViewConfigAdded),
197                 this,
198                 selectOverload<int>(&BufferViewSettingsPage::addBufferView));
199     }
200     else {
201         reset();
202     }
203 }
204
205 void BufferViewSettingsPage::addBufferView(BufferViewConfig* config)
206 {
207     auto* item = new QListWidgetItem(config->bufferViewName(), ui.bufferViewList);
208     item->setData(Qt::UserRole, qVariantFromValue<QObject*>(qobject_cast<QObject*>(config)));
209     connect(config, &SyncableObject::updatedRemotely, this, &BufferViewSettingsPage::updateBufferView);
210     connect(config, &QObject::destroyed, this, &BufferViewSettingsPage::bufferViewDeleted);
211     ui.deleteBufferView->setEnabled(ui.bufferViewList->count() > 1);
212 }
213
214 void BufferViewSettingsPage::addBufferView(int bufferViewId)
215 {
216     // we are informed about a new bufferview from Client::bufferViewManager()
217     Q_ASSERT(Client::bufferViewManager());
218     addBufferView(Client::bufferViewManager()->bufferViewConfig(bufferViewId));
219     selectBufferViewById(bufferViewId);
220 }
221
222 void BufferViewSettingsPage::bufferViewDeleted()
223 {
224     auto* config = static_cast<BufferViewConfig*>(sender());
225     QObject* obj;
226     for (int i = 0; i < ui.bufferViewList->count(); i++) {
227         obj = ui.bufferViewList->item(i)->data(Qt::UserRole).value<QObject*>();
228         if (config == static_cast<BufferViewConfig*>(obj)) {
229             QListWidgetItem* item = ui.bufferViewList->takeItem(i);
230             delete item;
231             break;
232         }
233     }
234     ui.deleteBufferView->setEnabled(ui.bufferViewList->count() > 1);
235 }
236
237 void BufferViewSettingsPage::newBufferView(const QString& bufferViewName)
238 {
239     // id's of newly created bufferviews are negative (-1, -2... -n)
240     int fakeId = -1 * (_newBufferViews.count() + 1);
241     auto* config = new BufferViewConfig(fakeId);
242     config->setBufferViewName(bufferViewName);
243     config->setInitialized();
244     QList<BufferId> bufferIds;
245     if (config->addNewBuffersAutomatically()) {
246         if (config->sortAlphabetically()) {
247             bufferIds = Client::networkModel()->allBufferIdsSorted();
248         }
249         else {
250             bufferIds = Client::networkModel()->allBufferIds();
251             qSort(bufferIds);
252             config->setProperty("OriginalBufferList", toVariantList<BufferId>(bufferIds));
253         }
254     }
255     config->setBufferList(bufferIds);
256
257     _newBufferViews << config;
258     addBufferView(config);
259     ui.bufferViewList->setCurrentRow(listPos(config));
260 }
261
262 int BufferViewSettingsPage::listPos(BufferViewConfig* config)
263 {
264     QObject* obj;
265     for (int i = 0; i < ui.bufferViewList->count(); i++) {
266         obj = ui.bufferViewList->item(i)->data(Qt::UserRole).value<QObject*>();
267         if (config == qobject_cast<BufferViewConfig*>(obj))
268             return i;
269     }
270     return -1;
271 }
272
273 BufferViewConfig* BufferViewSettingsPage::bufferView(int listPos)
274 {
275     if (listPos < ui.bufferViewList->count() && listPos >= 0) {
276         auto* obj = ui.bufferViewList->item(listPos)->data(Qt::UserRole).value<QObject*>();
277         return qobject_cast<BufferViewConfig*>(obj);
278     }
279     else {
280         return nullptr;
281     }
282 }
283
284 bool BufferViewSettingsPage::selectBufferViewById(int bufferViewId)
285 {
286     BufferViewConfig* config;
287     for (int i = 0; i < ui.bufferViewList->count(); i++) {
288         config = qobject_cast<BufferViewConfig*>(ui.bufferViewList->item(i)->data(Qt::UserRole).value<QObject*>());
289         if (config && config->bufferViewId() == bufferViewId) {
290             ui.bufferViewList->setCurrentRow(i);
291             return true;
292         }
293     }
294     return false;
295 }
296
297 void BufferViewSettingsPage::updateBufferView()
298 {
299     auto* config = qobject_cast<BufferViewConfig*>(sender());
300     if (!config)
301         return;
302
303     int itemPos = listPos(config);
304     if (itemPos == -1) {
305         qWarning() << "BufferViewSettingsPage::updateBufferView(): view is unknown:" << config->bufferViewId();
306         return;
307     }
308     ui.bufferViewList->item(itemPos)->setText(config->bufferViewName());
309     if (itemPos == ui.bufferViewList->currentRow())
310         loadConfig(config);
311 }
312
313 void BufferViewSettingsPage::enableStatusBuffers(int networkIdx)
314 {
315     // we don't show a status buffer if we show multiple networks as selecting
316     // the network is the same as selecting the status buffer.
317     ui.onlyStatusBuffers->setEnabled(networkIdx != 0);
318 }
319
320 void BufferViewSettingsPage::on_addBufferView_clicked()
321 {
322     if (!Client::bufferViewManager())
323         return;
324
325     QStringList existing;
326     foreach (BufferViewConfig* bufferConfig, Client::bufferViewManager()->bufferViewConfigs()) {
327         existing << bufferConfig->bufferViewName();
328     }
329
330     BufferViewEditDlg dlg(QString(), existing, this);
331     if (dlg.exec() == QDialog::Accepted) {
332         newBufferView(dlg.bufferViewName());
333         setChangedState(true);
334     }
335 }
336
337 void BufferViewSettingsPage::on_renameBufferView_clicked()
338 {
339     if (ui.bufferViewList->selectedItems().isEmpty())
340         return;
341
342     if (!Client::bufferViewManager())
343         return;
344
345     BufferViewConfig* config = bufferView(ui.bufferViewList->currentRow());
346     if (!config)
347         return;
348
349     QStringList existing;
350     foreach (BufferViewConfig* bufferConfig, Client::bufferViewManager()->bufferViewConfigs()) {
351         existing << bufferConfig->bufferViewName();
352     }
353
354     BufferViewEditDlg dlg(config->bufferViewName(), existing, this);
355     if (dlg.exec() == QDialog::Accepted) {
356         BufferViewConfig* changedConfig = cloneConfig(config);
357         changedConfig->setBufferViewName(dlg.bufferViewName());
358         ui.bufferViewList->item(listPos(config))->setText(dlg.bufferViewName());
359         setChangedState(true);
360     }
361 }
362
363 void BufferViewSettingsPage::on_deleteBufferView_clicked()
364 {
365     if (ui.bufferViewList->selectedItems().isEmpty())
366         return;
367
368     QListWidgetItem* currentItem = ui.bufferViewList->item(ui.bufferViewList->currentRow());
369     QString viewName = currentItem->text();
370     int viewId = bufferView(ui.bufferViewList->currentRow())->bufferViewId();
371     int ret = QMessageBox::question(this,
372                                     tr("Delete Chat List?"),
373                                     tr("Do you really want to delete the chat list \"%1\"?").arg(viewName),
374                                     QMessageBox::Yes | QMessageBox::No,
375                                     QMessageBox::No);
376
377     if (ret == QMessageBox::Yes) {
378         ui.bufferViewList->removeItemWidget(currentItem);
379         auto* config = qobject_cast<BufferViewConfig*>(currentItem->data(Qt::UserRole).value<QObject*>());
380         delete currentItem;
381         if (viewId >= 0) {
382             _deleteBufferViews << viewId;
383             setChangedState(true);
384         }
385         else if (config) {
386             QList<BufferViewConfig*>::iterator iter = _newBufferViews.begin();
387             while (iter != _newBufferViews.end()) {
388                 if (*iter == config) {
389                     iter = _newBufferViews.erase(iter);
390                     break;
391                 }
392                 else {
393                     ++iter;
394                 }
395             }
396             delete config;
397             if (_deleteBufferViews.isEmpty() && _changedBufferViews.isEmpty() && _newBufferViews.isEmpty())
398                 setChangedState(false);
399         }
400     }
401 }
402
403 void BufferViewSettingsPage::bufferViewSelectionChanged(const QItemSelection& current, const QItemSelection& previous)
404 {
405     Q_UNUSED(previous)
406
407     if (!current.isEmpty()) {
408         ui.settingsGroupBox->setEnabled(true);
409         ui.bufferViewPreview->setEnabled(true);
410
411         loadConfig(configForDisplay(bufferView(ui.bufferViewList->currentRow())));
412     }
413     else {
414         ui.settingsGroupBox->setEnabled(false);
415         ui.bufferViewPreview->setEnabled(false);
416     }
417 }
418
419 void BufferViewSettingsPage::loadConfig(BufferViewConfig* config)
420 {
421     if (!config)
422         return;
423
424     _ignoreWidgetChanges = true;
425     ui.onlyStatusBuffers->setChecked(BufferInfo::StatusBuffer & config->allowedBufferTypes());
426     ui.onlyChannelBuffers->setChecked(BufferInfo::ChannelBuffer & config->allowedBufferTypes());
427     ui.onlyQueryBuffers->setChecked(BufferInfo::QueryBuffer & config->allowedBufferTypes());
428     ui.addNewBuffersAutomatically->setChecked(config->addNewBuffersAutomatically());
429     ui.sortAlphabetically->setChecked(config->sortAlphabetically());
430     ui.hideInactiveBuffers->setChecked(config->hideInactiveBuffers());
431     ui.hideInactiveNetworks->setChecked(config->hideInactiveNetworks());
432     ui.showSearch->setChecked(config->showSearch());
433
434     int networkIndex = 0;
435     for (int i = 0; i < ui.networkSelector->count(); i++) {
436         if (ui.networkSelector->itemData(i).value<NetworkId>() == config->networkId()) {
437             networkIndex = i;
438             break;
439         }
440     }
441     ui.networkSelector->setCurrentIndex(networkIndex);
442
443     int activityIndex = 0;
444     int minimumActivity = config->minimumActivity();
445     while (minimumActivity) {
446         activityIndex++;
447         minimumActivity = minimumActivity >> 1;
448     }
449     ui.minimumActivitySelector->setCurrentIndex(activityIndex);
450
451     ui.bufferViewPreview->setFilteredModel(Client::bufferModel(), config);
452
453     _ignoreWidgetChanges = false;
454 }
455
456 void BufferViewSettingsPage::saveConfig(BufferViewConfig* config)
457 {
458     if (!config)
459         return;
460
461     int allowedBufferTypes = 0;
462     if (ui.onlyStatusBuffers->isChecked())
463         allowedBufferTypes |= BufferInfo::StatusBuffer;
464     if (ui.onlyChannelBuffers->isChecked())
465         allowedBufferTypes |= BufferInfo::ChannelBuffer;
466     if (ui.onlyQueryBuffers->isChecked())
467         allowedBufferTypes |= BufferInfo::QueryBuffer;
468     config->setAllowedBufferTypes(allowedBufferTypes);
469
470     config->setAddNewBuffersAutomatically(ui.addNewBuffersAutomatically->isChecked());
471     config->setSortAlphabetically(ui.sortAlphabetically->isChecked());
472     config->setHideInactiveBuffers(ui.hideInactiveBuffers->isChecked());
473     config->setHideInactiveNetworks(ui.hideInactiveNetworks->isChecked());
474     config->setNetworkId(ui.networkSelector->itemData(ui.networkSelector->currentIndex()).value<NetworkId>());
475     config->setShowSearch(ui.showSearch->isChecked());
476
477     int minimumActivity = 0;
478     if (ui.minimumActivitySelector->currentIndex() > 0)
479         minimumActivity = 1 << (ui.minimumActivitySelector->currentIndex() - 1);
480     config->setMinimumActivity(minimumActivity);
481
482     QList<BufferId> bufferIds = fromVariantList<BufferId>(config->property("OriginalBufferList").toList());
483     if (config->sortAlphabetically())
484         Client::networkModel()->sortBufferIds(bufferIds);
485
486     if (!_newBufferViews.contains(config) || config->addNewBuffersAutomatically())
487         config->setBufferList(bufferIds);
488 }
489
490 void BufferViewSettingsPage::widgetHasChanged()
491 {
492     if (_ignoreWidgetChanges)
493         return;
494     setChangedState(testHasChanged());
495 }
496
497 bool BufferViewSettingsPage::testHasChanged()
498 {
499     saveConfig(cloneConfig(bufferView(ui.bufferViewList->currentRow())));
500
501     if (!_newBufferViews.isEmpty())
502         return true;
503
504     bool changed = false;
505     QHash<BufferViewConfig*, BufferViewConfig*>::iterator iter = _changedBufferViews.begin();
506     QHash<BufferViewConfig*, BufferViewConfig*>::iterator iterEnd = _changedBufferViews.end();
507     while (iter != iterEnd) {
508         if (&(iter.key()) == &(iter.value())) {
509             iter.value()->deleteLater();
510             iter = _changedBufferViews.erase(iter);
511         }
512         else {
513             changed = true;
514             ++iter;
515         }
516     }
517     return changed;
518 }
519
520 BufferViewConfig* BufferViewSettingsPage::cloneConfig(BufferViewConfig* config)
521 {
522     if (!config || config->bufferViewId() < 0)
523         return config;
524
525     if (_changedBufferViews.contains(config))
526         return _changedBufferViews[config];
527
528     auto* changedConfig = new BufferViewConfig(-1, this);
529     changedConfig->fromVariantMap(config->toVariantMap());
530     changedConfig->setInitialized();
531     _changedBufferViews[config] = changedConfig;
532     connect(config, &BufferViewConfig::bufferAdded, changedConfig, &BufferViewConfig::addBuffer);
533     connect(config, &BufferViewConfig::bufferMoved, changedConfig, &BufferViewConfig::moveBuffer);
534     connect(config, &BufferViewConfig::bufferRemoved, changedConfig, &BufferViewConfig::removeBuffer);
535     // connect(config, &BufferViewConfig::addBufferRequested, changedConfig, &BufferViewConfig::addBuffer);
536     // connect(config, &BufferViewConfig::moveBufferRequested, changedConfig, &BufferViewConfig::moveBuffer);
537     // connect(config, &BufferViewconfig::removeBufferRequested, changedConfig, &BufferViewConfig::removeBuffer);
538
539     changedConfig->setProperty("OriginalBufferList", toVariantList<BufferId>(config->bufferList()));
540     // if this is the currently displayed view we have to change the config of the preview filter
541     auto* filter = qobject_cast<BufferViewFilter*>(ui.bufferViewPreview->model());
542     if (filter && filter->config() == config)
543         filter->setConfig(changedConfig);
544     ui.bufferViewPreview->setConfig(changedConfig);
545
546     return changedConfig;
547 }
548
549 BufferViewConfig* BufferViewSettingsPage::configForDisplay(BufferViewConfig* config)
550 {
551     if (_changedBufferViews.contains(config))
552         return _changedBufferViews[config];
553     else
554         return config;
555 }
556
557 /**************************************************************************
558  * BufferViewEditDlg
559  *************************************************************************/
560 BufferViewEditDlg::BufferViewEditDlg(const QString& old, QStringList exist, QWidget* parent)
561     : QDialog(parent)
562     , existing(std::move(exist))
563 {
564     ui.setupUi(this);
565
566     if (old.isEmpty()) {
567         // new buffer
568         setWindowTitle(tr("Add Chat List"));
569         on_bufferViewEdit_textChanged("");  // disable ok button
570     }
571     else {
572         ui.bufferViewEdit->setText(old);
573     }
574 }
575
576 void BufferViewEditDlg::on_bufferViewEdit_textChanged(const QString& text)
577 {
578     ui.buttonBox->button(QDialogButtonBox::Ok)->setDisabled(text.isEmpty() || existing.contains(text));
579 }