qa: Avoid deprecation warnings for QList/QSet conversions
[quassel.git] / src / uisupport / bufferviewfilter.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 "bufferviewfilter.h"
22
23 #include <QApplication>
24 #include <QBrush>
25 #include <QPalette>
26
27 #include "bufferinfo.h"
28 #include "buffermodel.h"
29 #include "buffersettings.h"
30 #include "client.h"
31 #include "clientbufferviewconfig.h"
32 #include "graphicalui.h"
33 #include "networkmodel.h"
34 #include "uistyle.h"
35 #include "util.h"
36
37 /*****************************************
38  * The Filter for the Tree View
39  *****************************************/
40 BufferViewFilter::BufferViewFilter(QAbstractItemModel* model, BufferViewConfig* config)
41     : QSortFilterProxyModel(model)
42     , _config(nullptr)
43     , _sortOrder(Qt::AscendingOrder)
44     , _showServerQueries(false)
45     , _editMode(false)
46     , _enableEditMode(tr("Show / Hide Chats"), this)
47 {
48     setConfig(config);
49     setSourceModel(model);
50
51     setDynamicSortFilter(true);
52     // Sort case-insensitively (primarily for network names; channels/nicks handled elsewhere)
53     setSortCaseSensitivity(Qt::CaseInsensitive);
54
55     _enableEditMode.setCheckable(true);
56     _enableEditMode.setChecked(_editMode);
57     connect(&_enableEditMode, &QAction::toggled, this, &BufferViewFilter::enableEditMode);
58
59     BufferSettings defaultSettings;
60     defaultSettings.notify("ServerNoticesTarget", this, &BufferViewFilter::showServerQueriesChanged);
61     showServerQueriesChanged();
62 }
63
64 void BufferViewFilter::setConfig(BufferViewConfig* config)
65 {
66     if (_config == config)
67         return;
68
69     if (_config) {
70         disconnect(_config, nullptr, this, nullptr);
71     }
72
73     _config = config;
74
75     if (!config) {
76         invalidate();
77         setObjectName("");
78         return;
79     }
80
81     if (config->isInitialized()) {
82         configInitialized();
83     }
84     else {
85         // we use a queued connection here since manipulating the connection list of a sending object
86         // doesn't seem to be such a good idea while executing a connected slots.
87         connect(config, &SyncableObject::initDone, this, &BufferViewFilter::configInitialized, Qt::QueuedConnection);
88         invalidate();
89     }
90 }
91
92 void BufferViewFilter::configInitialized()
93 {
94     if (!config())
95         return;
96
97     connect(config(), &BufferViewConfig::configChanged, this, &QSortFilterProxyModel::invalidate);
98
99     disconnect(config(), &SyncableObject::initDone, this, &BufferViewFilter::configInitialized);
100
101     setObjectName(config()->bufferViewName());
102
103     invalidate();
104     emit configChanged();
105 }
106
107 void BufferViewFilter::showServerQueriesChanged()
108 {
109     BufferSettings bufferSettings;
110
111     bool showQueries = (bufferSettings.serverNoticesTarget() & BufferSettings::DefaultBuffer);
112     if (_showServerQueries != showQueries) {
113         _showServerQueries = showQueries;
114         invalidate();
115     }
116 }
117
118 QList<QAction*> BufferViewFilter::actions(const QModelIndex& index)
119 {
120     Q_UNUSED(index)
121     QList<QAction*> actionList;
122     actionList << &_enableEditMode;
123     return actionList;
124 }
125
126 void BufferViewFilter::setFilterString(const QString string)
127 {
128     beginResetModel();
129     _filterString = string;
130     endResetModel();
131     enableEditMode(!string.isEmpty());
132 }
133
134 void BufferViewFilter::enableEditMode(bool enable)
135 {
136     if (_editMode == enable) {
137         return;
138     }
139     _editMode = enable;
140
141     if (!config())
142         return;
143
144     if (enable == false) {
145         addBuffers(_toAdd.values());
146         QSet<BufferId>::const_iterator iter;
147         for (iter = _toTempRemove.constBegin(); iter != _toTempRemove.constEnd(); ++iter) {
148             if (config()->temporarilyRemovedBuffers().contains(*iter))
149                 continue;
150             config()->requestRemoveBuffer(*iter);
151         }
152         for (iter = _toRemove.constBegin(); iter != _toRemove.constEnd(); ++iter) {
153             if (config()->removedBuffers().contains(*iter))
154                 continue;
155             config()->requestRemoveBufferPermanently(*iter);
156         }
157     }
158     _toAdd.clear();
159     _toTempRemove.clear();
160     _toRemove.clear();
161
162     invalidate();
163 }
164
165 Qt::ItemFlags BufferViewFilter::flags(const QModelIndex& index) const
166 {
167     QModelIndex source_index = mapToSource(index);
168     Qt::ItemFlags flags = sourceModel()->flags(source_index);
169     if (config()) {
170         BufferInfo::Type bufferType = (BufferInfo::Type)sourceModel()->data(source_index, NetworkModel::BufferTypeRole).toInt();
171
172         // We need Status Buffers to be a drop target, to allow for rearranging buffers.
173         // The Status Buffer "owns" the space between Channel/Query buffers in the tree.
174         // This DOES mean that it looks like you can merge a buffer into the Status buffer, but that is restricted in BufferView::dropEvent().
175         if (bufferType == BufferInfo::StatusBuffer) {
176             // But only if the layout isn't locked!
177             auto* clientConf = qobject_cast<ClientBufferViewConfig*>(config());
178             if (clientConf && !clientConf->isLocked()) {
179                 flags |= Qt::ItemIsDropEnabled;
180             }
181         }
182
183         // If we're in Edit Mode, everything except Status Buffers should be hideable.
184         if (_editMode && bufferType != BufferInfo::StatusBuffer) {
185             flags |= Qt::ItemIsUserCheckable | Qt::ItemIsTristate;
186         }
187     }
188     return flags;
189 }
190
191 bool BufferViewFilter::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
192 {
193     if (!config() || !NetworkModel::mimeContainsBufferList(data))
194         return QSortFilterProxyModel::dropMimeData(data, action, row, column, parent);
195
196     NetworkId droppedNetworkId;
197     QModelIndex source_parent = mapToSource(parent);
198     if (sourceModel()->data(source_parent, NetworkModel::ItemTypeRole) == NetworkModel::NetworkItemType)
199         droppedNetworkId = sourceModel()->data(source_parent, NetworkModel::NetworkIdRole).value<NetworkId>();
200
201     QList<QPair<NetworkId, BufferId>> bufferList = NetworkModel::mimeDataToBufferList(data);
202     BufferId bufferId;
203     NetworkId networkId;
204     int pos;
205     for (int i = 0; i < bufferList.count(); i++) {
206         networkId = bufferList[i].first;
207         bufferId = bufferList[i].second;
208         if (droppedNetworkId == networkId) {
209             if (row < 0)
210                 row = 0;
211
212             if (row < rowCount(parent)) {
213                 QModelIndex source_child = mapToSource(index(row, 0, parent));
214                 BufferId beforeBufferId = sourceModel()->data(source_child, NetworkModel::BufferIdRole).value<BufferId>();
215                 pos = config()->bufferList().indexOf(beforeBufferId);
216                 if (_sortOrder == Qt::DescendingOrder)
217                     pos++;
218             }
219             else {
220                 if (_sortOrder == Qt::AscendingOrder)
221                     pos = config()->bufferList().count();
222                 else
223                     pos = 0;
224             }
225
226             if (config()->bufferList().contains(bufferId) && !config()->sortAlphabetically()) {
227                 if (config()->bufferList().indexOf(bufferId) < pos)
228                     pos--;
229                 auto* clientConf = qobject_cast<ClientBufferViewConfig*>(config());
230                 if (!clientConf || !clientConf->isLocked())
231                     config()->requestMoveBuffer(bufferId, pos);
232             }
233             else {
234                 config()->requestAddBuffer(bufferId, pos);
235             }
236         }
237         else {
238             addBuffer(bufferId);
239         }
240     }
241     return true;
242 }
243
244 void BufferViewFilter::sort(int column, Qt::SortOrder order)
245 {
246     _sortOrder = order;
247     QSortFilterProxyModel::sort(column, order);
248 }
249
250 void BufferViewFilter::addBuffer(const BufferId& bufferId) const
251 {
252     if (!config() || config()->bufferList().contains(bufferId))
253         return;
254
255     int pos = config()->bufferList().count();
256     bool lt;
257     for (int i = 0; i < config()->bufferList().count(); i++) {
258         if (config() && config()->sortAlphabetically())
259             lt = bufferIdLessThan(bufferId, config()->bufferList()[i]);
260         else
261             lt = bufferId < config()->bufferList()[i];
262
263         if (lt) {
264             pos = i;
265             break;
266         }
267     }
268     config()->requestAddBuffer(bufferId, pos);
269 }
270
271 void BufferViewFilter::addBuffers(const QList<BufferId>& bufferIds) const
272 {
273     if (!config())
274         return;
275
276     QList<BufferId> bufferList = config()->bufferList();
277     foreach (BufferId bufferId, bufferIds) {
278         if (bufferList.contains(bufferId))
279             continue;
280
281         int pos = bufferList.count();
282         bool lt;
283         for (int i = 0; i < bufferList.count(); i++) {
284             if (config() && config()->sortAlphabetically())
285                 lt = bufferIdLessThan(bufferId, bufferList[i]);
286             else
287                 lt = bufferId < config()->bufferList()[i];
288
289             if (lt) {
290                 pos = i;
291                 bufferList.insert(pos, bufferId);
292                 break;
293             }
294         }
295         config()->requestAddBuffer(bufferId, pos);
296     }
297 }
298
299 bool BufferViewFilter::filterAcceptBuffer(const QModelIndex& source_bufferIndex) const
300 {
301     // no config -> "all buffers" -> accept everything
302     if (!config())
303         return true;
304
305     BufferId bufferId = sourceModel()->data(source_bufferIndex, NetworkModel::BufferIdRole).value<BufferId>();
306     Q_ASSERT(bufferId.isValid());
307
308     int activityLevel = sourceModel()->data(source_bufferIndex, NetworkModel::BufferActivityRole).toInt();
309
310     if (!config()->bufferList().contains(bufferId) && !_editMode) {
311         // add the buffer if...
312         if (config()->isInitialized() && !config()->removedBuffers().contains(bufferId)  // it hasn't been manually removed and either
313             && ((config()->addNewBuffersAutomatically()
314                  && !config()->temporarilyRemovedBuffers().contains(bufferId))  // is totally unknown to us (a new buffer)...
315                 || (config()->temporarilyRemovedBuffers().contains(bufferId)
316                     && activityLevel > BufferInfo::OtherActivity))) {  // or was just temporarily hidden and has a new message waiting for us.
317             addBuffer(bufferId);
318         }
319         // note: adding the buffer to the valid list does not temper with the following filters ("show only channels" and stuff)
320         return false;
321     }
322
323     if (config()->networkId().isValid()
324         && config()->networkId() != sourceModel()->data(source_bufferIndex, NetworkModel::NetworkIdRole).value<NetworkId>())
325         return false;
326
327     int allowedBufferTypes = config()->allowedBufferTypes();
328     if (!config()->networkId().isValid())
329         allowedBufferTypes &= ~BufferInfo::StatusBuffer;
330     int bufferType = sourceModel()->data(source_bufferIndex, NetworkModel::BufferTypeRole).toInt();
331     if (!(allowedBufferTypes & bufferType))
332         return false;
333
334     if (bufferType & BufferInfo::QueryBuffer && !_showServerQueries
335         && sourceModel()->data(source_bufferIndex, Qt::DisplayRole).toString().contains('.')) {
336         return false;
337     }
338
339     if (!_filterString.isEmpty()) {
340         const BufferInfo info = qvariant_cast<BufferInfo>(Client::bufferModel()->data(source_bufferIndex, NetworkModel::BufferInfoRole));
341         QString name = info.bufferName();
342         if (name.contains(_filterString, Qt::CaseInsensitive)) {
343             return true;
344         }
345         else {
346             return false;
347         }
348     }
349
350     // the following dynamic filters may not trigger if the buffer is currently selected.
351     QModelIndex currentIndex = Client::bufferModel()->standardSelectionModel()->currentIndex();
352     if (bufferId == Client::bufferModel()->data(currentIndex, NetworkModel::BufferIdRole).value<BufferId>())
353         return true;
354
355     if (config()->hideInactiveBuffers() && !sourceModel()->data(source_bufferIndex, NetworkModel::ItemActiveRole).toBool()
356         && activityLevel <= BufferInfo::OtherActivity)
357         return false;
358
359     if (config()->minimumActivity() > activityLevel)
360         return false;
361
362     return true;
363 }
364
365 bool BufferViewFilter::filterAcceptNetwork(const QModelIndex& source_index) const
366 {
367     if (!config())
368         return true;
369
370     if (config()->hideInactiveNetworks() && !(sourceModel()->data(source_index, NetworkModel::ItemActiveRole).toBool())) {
371         return false;
372     }
373
374     if (!config()->networkId().isValid()) {
375         return true;
376     }
377     else {
378         return config()->networkId() == sourceModel()->data(source_index, NetworkModel::NetworkIdRole).value<NetworkId>();
379     }
380 }
381
382 bool BufferViewFilter::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
383 {
384     QModelIndex child = sourceModel()->index(source_row, 0, source_parent);
385
386     if (!child.isValid()) {
387         qWarning() << "filterAcceptsRow has been called with an invalid Child";
388         return false;
389     }
390
391     NetworkModel::ItemType childType = (NetworkModel::ItemType)sourceModel()->data(child, NetworkModel::ItemTypeRole).toInt();
392     switch (childType) {
393     case NetworkModel::NetworkItemType:
394         return filterAcceptNetwork(child);
395     case NetworkModel::BufferItemType:
396         return filterAcceptBuffer(child);
397     default:
398         return false;
399     }
400 }
401
402 bool BufferViewFilter::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const
403 {
404     int leftItemType = sourceModel()->data(source_left, NetworkModel::ItemTypeRole).toInt();
405     int rightItemType = sourceModel()->data(source_right, NetworkModel::ItemTypeRole).toInt();
406     int itemType = leftItemType & rightItemType;
407     switch (itemType) {
408     case NetworkModel::NetworkItemType:
409         return networkLessThan(source_left, source_right);
410     case NetworkModel::BufferItemType:
411         return bufferLessThan(source_left, source_right);
412     default:
413         return QSortFilterProxyModel::lessThan(source_left, source_right);
414     }
415 }
416
417 bool BufferViewFilter::bufferLessThan(const QModelIndex& source_left, const QModelIndex& source_right) const
418 {
419     BufferId leftBufferId = sourceModel()->data(source_left, NetworkModel::BufferIdRole).value<BufferId>();
420     BufferId rightBufferId = sourceModel()->data(source_right, NetworkModel::BufferIdRole).value<BufferId>();
421     // If filtering, prioritize relevant items first
422     if (!_filterString.isEmpty()) {
423         // Get names of the buffers
424         QString leftBufferName = sourceModel()->data(source_left, NetworkModel::BufferInfoRole).value<BufferInfo>().bufferName();
425         QString rightBufferName = sourceModel()->data(source_right, NetworkModel::BufferInfoRole).value<BufferInfo>().bufferName();
426         // Check if there's any differences across types, most important first
427         if ((QString::compare(leftBufferName, _filterString, Qt::CaseInsensitive) == 0)
428             != (QString::compare(rightBufferName, _filterString, Qt::CaseInsensitive) == 0)) {
429             // One of these buffers is an exact match with the filter string, while the other isn't
430             // Prioritize whichever one is the exact match
431             // (If left buffer is exact, return true to set it as less than right)
432             return (QString::compare(leftBufferName, _filterString, Qt::CaseInsensitive) == 0);
433         }
434         else if (leftBufferName.startsWith(_filterString, Qt::CaseInsensitive)
435                  != rightBufferName.startsWith(_filterString, Qt::CaseInsensitive)) {
436             // One of these buffers starts with the filter string, while the other doesn't
437             // Prioritize whichever one starts with the filter string
438             // (If left buffer starts with, return true to set it as less than right)
439             return leftBufferName.startsWith(_filterString, Qt::CaseInsensitive);
440         }
441         // Otherwise, do the normal sorting (sorting happens within each priority bracket)
442     }
443     if (config()) {
444         int leftPos = config()->bufferList().indexOf(leftBufferId);
445         int rightPos = config()->bufferList().indexOf(rightBufferId);
446         if (leftPos == -1 && rightPos == -1)
447             return QSortFilterProxyModel::lessThan(source_left, source_right);
448         if (leftPos == -1 || rightPos == -1)
449             return !(leftPos < rightPos);
450         return leftPos < rightPos;
451     }
452     else
453         return bufferIdLessThan(leftBufferId, rightBufferId);
454 }
455
456 bool BufferViewFilter::networkLessThan(const QModelIndex& source_left, const QModelIndex& source_right) const
457 {
458     // NetworkId leftNetworkId = sourceModel()->data(source_left, NetworkModel::NetworkIdRole).value<NetworkId>();
459     // NetworkId rightNetworkId = sourceModel()->data(source_right, NetworkModel::NetworkIdRole).value<NetworkId>();
460
461     return QSortFilterProxyModel::lessThan(source_left, source_right);
462 }
463
464 QVariant BufferViewFilter::data(const QModelIndex& index, int role) const
465 {
466     switch (role) {
467     case Qt::FontRole:
468     case Qt::ForegroundRole:
469     case Qt::BackgroundRole:
470     case Qt::DecorationRole:
471         if ((config() && config()->disableDecoration()))
472             return QVariant();
473         return GraphicalUi::uiStyle()->bufferViewItemData(mapToSource(index), role);
474     case Qt::CheckStateRole:
475         return checkedState(index);
476     default:
477         return QSortFilterProxyModel::data(index, role);
478     }
479 }
480
481 QVariant BufferViewFilter::checkedState(const QModelIndex& index) const
482 {
483     if (!_editMode || !config())
484         return QVariant();
485
486     QModelIndex source_index = mapToSource(index);
487     if (source_index == QModelIndex() || sourceModel()->data(source_index, NetworkModel::ItemTypeRole) == NetworkModel::NetworkItemType)
488         return QVariant();
489
490     BufferId bufferId = sourceModel()->data(source_index, NetworkModel::BufferIdRole).value<BufferId>();
491     if (_toAdd.contains(bufferId))
492         return Qt::Checked;
493
494     if (_toTempRemove.contains(bufferId))
495         return Qt::PartiallyChecked;
496
497     if (_toRemove.contains(bufferId))
498         return Qt::Unchecked;
499
500     if (config()->bufferList().contains(bufferId))
501         return Qt::Checked;
502
503     if (config()->temporarilyRemovedBuffers().contains(bufferId))
504         return Qt::PartiallyChecked;
505
506     return Qt::Unchecked;
507 }
508
509 bool BufferViewFilter::setData(const QModelIndex& index, const QVariant& value, int role)
510 {
511     switch (role) {
512     case Qt::CheckStateRole:
513         return setCheckedState(index, Qt::CheckState(value.toInt()));
514     default:
515         return QSortFilterProxyModel::setData(index, value, role);
516     }
517 }
518
519 bool BufferViewFilter::setCheckedState(const QModelIndex& index, Qt::CheckState state)
520 {
521     QModelIndex source_index = mapToSource(index);
522     BufferId bufferId = sourceModel()->data(source_index, NetworkModel::BufferIdRole).value<BufferId>();
523     if (!bufferId.isValid())
524         return false;
525
526     switch (state) {
527     case Qt::Unchecked:
528         _toAdd.remove(bufferId);
529         _toTempRemove.remove(bufferId);
530         _toRemove << bufferId;
531         break;
532     case Qt::PartiallyChecked:
533         _toAdd.remove(bufferId);
534         _toTempRemove << bufferId;
535         _toRemove.remove(bufferId);
536         break;
537     case Qt::Checked:
538         _toAdd << bufferId;
539         _toTempRemove.remove(bufferId);
540         _toRemove.remove(bufferId);
541         break;
542     default:
543         return false;
544     }
545     emit dataChanged(index, index);
546     return true;
547 }
548
549 bool BufferViewFilter::bufferIdLessThan(const BufferId& left, const BufferId& right)
550 {
551     Q_CHECK_PTR(Client::networkModel());
552     if (!Client::networkModel())
553         return true;
554
555     QModelIndex leftIndex = Client::networkModel()->bufferIndex(left);
556     QModelIndex rightIndex = Client::networkModel()->bufferIndex(right);
557
558     int leftType = Client::networkModel()->data(leftIndex, NetworkModel::BufferTypeRole).toInt();
559     int rightType = Client::networkModel()->data(rightIndex, NetworkModel::BufferTypeRole).toInt();
560
561     if (leftType != rightType)
562         return leftType < rightType;
563     else
564         return QString::compare(Client::networkModel()->data(leftIndex, Qt::DisplayRole).toString(),
565                                 Client::networkModel()->data(rightIndex, Qt::DisplayRole).toString(),
566                                 Qt::CaseInsensitive)
567                < 0;
568 }