1 /***************************************************************************
2 * Copyright (C) 2005-2019 by the Quassel Project *
3 * devel@quassel-irc.org *
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. *
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. *
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 ***************************************************************************/
21 #include "bufferviewfilter.h"
23 #include <QApplication>
27 #include "bufferinfo.h"
28 #include "buffermodel.h"
29 #include "buffersettings.h"
31 #include "clientbufferviewconfig.h"
32 #include "graphicalui.h"
33 #include "networkmodel.h"
36 /*****************************************
37 * The Filter for the Tree View
38 *****************************************/
39 BufferViewFilter::BufferViewFilter(QAbstractItemModel* model, BufferViewConfig* config)
40 : QSortFilterProxyModel(model)
42 , _sortOrder(Qt::AscendingOrder)
43 , _showServerQueries(false)
45 , _enableEditMode(tr("Show / Hide Chats"), this)
48 setSourceModel(model);
50 setDynamicSortFilter(true);
51 // Sort case-insensitively (primarily for network names; channels/nicks handled elsewhere)
52 setSortCaseSensitivity(Qt::CaseInsensitive);
54 _enableEditMode.setCheckable(true);
55 _enableEditMode.setChecked(_editMode);
56 connect(&_enableEditMode, &QAction::toggled, this, &BufferViewFilter::enableEditMode);
58 BufferSettings defaultSettings;
59 defaultSettings.notify("ServerNoticesTarget", this, &BufferViewFilter::showServerQueriesChanged);
60 showServerQueriesChanged();
63 void BufferViewFilter::setConfig(BufferViewConfig* config)
65 if (_config == config)
69 disconnect(_config, nullptr, this, nullptr);
80 if (config->isInitialized()) {
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);
91 void BufferViewFilter::configInitialized()
96 connect(config(), &BufferViewConfig::configChanged, this, &QSortFilterProxyModel::invalidate);
98 disconnect(config(), &SyncableObject::initDone, this, &BufferViewFilter::configInitialized);
100 setObjectName(config()->bufferViewName());
103 emit configChanged();
106 void BufferViewFilter::showServerQueriesChanged()
108 BufferSettings bufferSettings;
110 bool showQueries = (bufferSettings.serverNoticesTarget() & BufferSettings::DefaultBuffer);
111 if (_showServerQueries != showQueries) {
112 _showServerQueries = showQueries;
117 QList<QAction*> BufferViewFilter::actions(const QModelIndex& index)
120 QList<QAction*> actionList;
121 actionList << &_enableEditMode;
125 void BufferViewFilter::setFilterString(const QString string)
128 _filterString = string;
130 enableEditMode(!string.isEmpty());
133 void BufferViewFilter::enableEditMode(bool enable)
135 if (_editMode == enable) {
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))
149 config()->requestRemoveBuffer(*iter);
151 for (iter = _toRemove.constBegin(); iter != _toRemove.constEnd(); ++iter) {
152 if (config()->removedBuffers().contains(*iter))
154 config()->requestRemoveBufferPermanently(*iter);
158 _toTempRemove.clear();
164 Qt::ItemFlags BufferViewFilter::flags(const QModelIndex& index) const
166 QModelIndex source_index = mapToSource(index);
167 Qt::ItemFlags flags = sourceModel()->flags(source_index);
169 BufferInfo::Type bufferType = (BufferInfo::Type)sourceModel()->data(source_index, NetworkModel::BufferTypeRole).toInt();
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;
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;
190 bool BufferViewFilter::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
192 if (!config() || !NetworkModel::mimeContainsBufferList(data))
193 return QSortFilterProxyModel::dropMimeData(data, action, row, column, parent);
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>();
200 QList<QPair<NetworkId, BufferId>> bufferList = NetworkModel::mimeDataToBufferList(data);
204 for (int i = 0; i < bufferList.count(); i++) {
205 networkId = bufferList[i].first;
206 bufferId = bufferList[i].second;
207 if (droppedNetworkId == networkId) {
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)
219 if (_sortOrder == Qt::AscendingOrder)
220 pos = config()->bufferList().count();
225 if (config()->bufferList().contains(bufferId) && !config()->sortAlphabetically()) {
226 if (config()->bufferList().indexOf(bufferId) < pos)
228 auto* clientConf = qobject_cast<ClientBufferViewConfig*>(config());
229 if (!clientConf || !clientConf->isLocked())
230 config()->requestMoveBuffer(bufferId, pos);
233 config()->requestAddBuffer(bufferId, pos);
243 void BufferViewFilter::sort(int column, Qt::SortOrder order)
246 QSortFilterProxyModel::sort(column, order);
249 void BufferViewFilter::addBuffer(const BufferId& bufferId) const
251 if (!config() || config()->bufferList().contains(bufferId))
254 int pos = config()->bufferList().count();
256 for (int i = 0; i < config()->bufferList().count(); i++) {
257 if (config() && config()->sortAlphabetically())
258 lt = bufferIdLessThan(bufferId, config()->bufferList()[i]);
260 lt = bufferId < config()->bufferList()[i];
267 config()->requestAddBuffer(bufferId, pos);
270 void BufferViewFilter::addBuffers(const QList<BufferId>& bufferIds) const
275 QList<BufferId> bufferList = config()->bufferList();
276 foreach (BufferId bufferId, bufferIds) {
277 if (bufferList.contains(bufferId))
280 int pos = bufferList.count();
282 for (int i = 0; i < bufferList.count(); i++) {
283 if (config() && config()->sortAlphabetically())
284 lt = bufferIdLessThan(bufferId, bufferList[i]);
286 lt = bufferId < config()->bufferList()[i];
290 bufferList.insert(pos, bufferId);
294 config()->requestAddBuffer(bufferId, pos);
298 bool BufferViewFilter::filterAcceptBuffer(const QModelIndex& source_bufferIndex) const
300 // no config -> "all buffers" -> accept everything
304 BufferId bufferId = sourceModel()->data(source_bufferIndex, NetworkModel::BufferIdRole).value<BufferId>();
305 Q_ASSERT(bufferId.isValid());
307 int activityLevel = sourceModel()->data(source_bufferIndex, NetworkModel::BufferActivityRole).toInt();
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.
318 // note: adding the buffer to the valid list does not temper with the following filters ("show only channels" and stuff)
322 if (config()->networkId().isValid()
323 && config()->networkId() != sourceModel()->data(source_bufferIndex, NetworkModel::NetworkIdRole).value<NetworkId>())
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))
333 if (bufferType & BufferInfo::QueryBuffer && !_showServerQueries
334 && sourceModel()->data(source_bufferIndex, Qt::DisplayRole).toString().contains('.')) {
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)) {
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>())
354 if (config()->hideInactiveBuffers() && !sourceModel()->data(source_bufferIndex, NetworkModel::ItemActiveRole).toBool()
355 && activityLevel <= BufferInfo::OtherActivity)
358 if (config()->minimumActivity() > activityLevel)
364 bool BufferViewFilter::filterAcceptNetwork(const QModelIndex& source_index) const
369 if (config()->hideInactiveNetworks() && !(sourceModel()->data(source_index, NetworkModel::ItemActiveRole).toBool())) {
373 if (!config()->networkId().isValid()) {
377 return config()->networkId() == sourceModel()->data(source_index, NetworkModel::NetworkIdRole).value<NetworkId>();
381 bool BufferViewFilter::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
383 QModelIndex child = sourceModel()->index(source_row, 0, source_parent);
385 if (!child.isValid()) {
386 qWarning() << "filterAcceptsRow has been called with an invalid Child";
390 NetworkModel::ItemType childType = (NetworkModel::ItemType)sourceModel()->data(child, NetworkModel::ItemTypeRole).toInt();
392 case NetworkModel::NetworkItemType:
393 return filterAcceptNetwork(child);
394 case NetworkModel::BufferItemType:
395 return filterAcceptBuffer(child);
401 bool BufferViewFilter::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const
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;
407 case NetworkModel::NetworkItemType:
408 return networkLessThan(source_left, source_right);
409 case NetworkModel::BufferItemType:
410 return bufferLessThan(source_left, source_right);
412 return QSortFilterProxyModel::lessThan(source_left, source_right);
416 bool BufferViewFilter::bufferLessThan(const QModelIndex& source_left, const QModelIndex& source_right) const
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);
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);
440 // Otherwise, do the normal sorting (sorting happens within each priority bracket)
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;
452 return bufferIdLessThan(leftBufferId, rightBufferId);
455 bool BufferViewFilter::networkLessThan(const QModelIndex& source_left, const QModelIndex& source_right) const
457 // NetworkId leftNetworkId = sourceModel()->data(source_left, NetworkModel::NetworkIdRole).value<NetworkId>();
458 // NetworkId rightNetworkId = sourceModel()->data(source_right, NetworkModel::NetworkIdRole).value<NetworkId>();
460 return QSortFilterProxyModel::lessThan(source_left, source_right);
463 QVariant BufferViewFilter::data(const QModelIndex& index, int role) const
467 case Qt::ForegroundRole:
468 case Qt::BackgroundRole:
469 case Qt::DecorationRole:
470 if ((config() && config()->disableDecoration()))
472 return GraphicalUi::uiStyle()->bufferViewItemData(mapToSource(index), role);
473 case Qt::CheckStateRole:
474 return checkedState(index);
476 return QSortFilterProxyModel::data(index, role);
480 QVariant BufferViewFilter::checkedState(const QModelIndex& index) const
482 if (!_editMode || !config())
485 QModelIndex source_index = mapToSource(index);
486 if (source_index == QModelIndex() || sourceModel()->data(source_index, NetworkModel::ItemTypeRole) == NetworkModel::NetworkItemType)
489 BufferId bufferId = sourceModel()->data(source_index, NetworkModel::BufferIdRole).value<BufferId>();
490 if (_toAdd.contains(bufferId))
493 if (_toTempRemove.contains(bufferId))
494 return Qt::PartiallyChecked;
496 if (_toRemove.contains(bufferId))
497 return Qt::Unchecked;
499 if (config()->bufferList().contains(bufferId))
502 if (config()->temporarilyRemovedBuffers().contains(bufferId))
503 return Qt::PartiallyChecked;
505 return Qt::Unchecked;
508 bool BufferViewFilter::setData(const QModelIndex& index, const QVariant& value, int role)
511 case Qt::CheckStateRole:
512 return setCheckedState(index, Qt::CheckState(value.toInt()));
514 return QSortFilterProxyModel::setData(index, value, role);
518 bool BufferViewFilter::setCheckedState(const QModelIndex& index, Qt::CheckState state)
520 QModelIndex source_index = mapToSource(index);
521 BufferId bufferId = sourceModel()->data(source_index, NetworkModel::BufferIdRole).value<BufferId>();
522 if (!bufferId.isValid())
527 _toAdd.remove(bufferId);
528 _toTempRemove.remove(bufferId);
529 _toRemove << bufferId;
531 case Qt::PartiallyChecked:
532 _toAdd.remove(bufferId);
533 _toTempRemove << bufferId;
534 _toRemove.remove(bufferId);
538 _toTempRemove.remove(bufferId);
539 _toRemove.remove(bufferId);
544 emit dataChanged(index, index);
548 bool BufferViewFilter::bufferIdLessThan(const BufferId& left, const BufferId& right)
550 Q_CHECK_PTR(Client::networkModel());
551 if (!Client::networkModel())
554 QModelIndex leftIndex = Client::networkModel()->bufferIndex(left);
555 QModelIndex rightIndex = Client::networkModel()->bufferIndex(right);
557 int leftType = Client::networkModel()->data(leftIndex, NetworkModel::BufferTypeRole).toInt();
558 int rightType = Client::networkModel()->data(rightIndex, NetworkModel::BufferTypeRole).toInt();
560 if (leftType != rightType)
561 return leftType < rightType;
563 return QString::compare(Client::networkModel()->data(leftIndex, Qt::DisplayRole).toString(),
564 Client::networkModel()->data(rightIndex, Qt::DisplayRole).toString(),