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