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