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