+ setSortingEnabled(true);
+ sortByColumn(0, Qt::AscendingOrder);
+
+#if defined Q_OS_MACOS || defined Q_OS_WIN
+ // afaik this is better on Mac and Windows
+ connect(this, &QAbstractItemView::activated, this, &BufferView::joinChannel, Qt::UniqueConnection);
+#else
+ connect(this, &QAbstractItemView::doubleClicked, this, &BufferView::joinChannel, Qt::UniqueConnection);
+#endif
+}
+
+void BufferView::setModel(QAbstractItemModel* model)
+{
+ delete selectionModel();
+
+ TreeViewTouch::setModel(model);
+ init();
+ // remove old Actions
+ QList<QAction*> oldactions = header()->actions();
+ foreach (QAction* action, oldactions) {
+ header()->removeAction(action);
+ action->deleteLater();
+ }
+
+ if (!model)
+ return;
+
+ QString sectionName;
+ QAction* showSection;
+ for (int i = 1; i < model->columnCount(); i++) {
+ sectionName = (model->headerData(i, Qt::Horizontal, Qt::DisplayRole)).toString();
+ showSection = new QAction(sectionName, header());
+ showSection->setCheckable(true);
+ showSection->setChecked(!isColumnHidden(i));
+ showSection->setProperty("column", i);
+ connect(showSection, &QAction::toggled, this, &BufferView::toggleHeader);
+ header()->addAction(showSection);
+ }
+
+ connect(model, &QAbstractItemModel::layoutChanged, this, &BufferView::on_layoutChanged);
+
+ // Make sure collapsation is correct after setting a model
+ // This might not be needed here, only in BufferView::setFilteredModel(). If issues arise, just
+ // move down to setFilteredModel (which calls this function).
+ setExpandedState();
+}
+
+void BufferView::setFilteredModel(QAbstractItemModel* model_, BufferViewConfig* config)
+{
+ auto* filter = qobject_cast<BufferViewFilter*>(model());
+ if (filter) {
+ filter->setConfig(config);
+ setConfig(config);
+ return;
+ }
+
+ if (model()) {
+ disconnect(this, nullptr, model(), nullptr);
+ disconnect(model(), nullptr, this, nullptr);
+ }
+
+ if (!model_) {
+ setModel(model_);
+ }
+ else {
+ auto* filter = new BufferViewFilter(model_, config);
+ setModel(filter);
+ connect(filter, &BufferViewFilter::configChanged, this, &BufferView::on_configChanged);
+ }
+ setConfig(config);
+}
+
+void BufferView::setConfig(BufferViewConfig* config)
+{
+ if (_config == config)
+ return;
+
+ if (_config) {
+ disconnect(_config, nullptr, this, nullptr);
+ }
+
+ _config = config;
+ if (config) {
+ connect(config, &BufferViewConfig::networkIdSet, this, &BufferView::setRootIndexForNetworkId);
+ setRootIndexForNetworkId(config->networkId());
+ }
+ else {
+ setIndentation(10);
+ setRootIndex(QModelIndex());
+ }
+}
+
+void BufferView::setRootIndexForNetworkId(const NetworkId& networkId)
+{
+ if (!networkId.isValid() || !model()) {
+ setIndentation(10);
+ setRootIndex(QModelIndex());
+ }
+ else {
+ setIndentation(5);
+ int networkCount = model()->rowCount();
+ QModelIndex child;
+ for (int i = 0; i < networkCount; i++) {
+ child = model()->index(i, 0);
+ if (networkId == model()->data(child, NetworkModel::NetworkIdRole).value<NetworkId>())
+ setRootIndex(child);
+ }
+ }
+}
+
+void BufferView::joinChannel(const QModelIndex& index)
+{
+ BufferInfo::Type bufferType = (BufferInfo::Type)index.data(NetworkModel::BufferTypeRole).value<int>();
+
+ if (bufferType != BufferInfo::ChannelBuffer)
+ return;
+
+ BufferInfo bufferInfo = index.data(NetworkModel::BufferInfoRole).value<BufferInfo>();
+
+ Client::userInput(bufferInfo, QString("/JOIN %1").arg(bufferInfo.bufferName()));
+}
+
+void BufferView::dropEvent(QDropEvent* event)
+{
+ QModelIndex index = indexAt(event->pos());
+
+ QRect indexRect = visualRect(index);
+ QPoint cursorPos = event->pos();
+
+ // check if we're really _on_ the item and not indicating a move to just above or below the item
+ // Magic margin number for this is from QAbstractItemViewPrivate::position()
+ const int margin = 2;
+ if (cursorPos.y() - indexRect.top() < margin || indexRect.bottom() - cursorPos.y() < margin)
+ return TreeViewTouch::dropEvent(event);
+
+ // If more than one buffer was being dragged, treat this as a rearrangement instead of a merge request
+ QList<QPair<NetworkId, BufferId>> bufferList = Client::networkModel()->mimeDataToBufferList(event->mimeData());
+ if (bufferList.count() != 1)
+ return TreeViewTouch::dropEvent(event);
+
+ // Get the Buffer ID of the buffer that was being dragged
+ BufferId bufferId2 = bufferList[0].second;
+
+ // Get the Buffer ID of the target buffer
+ BufferId bufferId1 = index.data(NetworkModel::BufferIdRole).value<BufferId>();
+
+ // If the source and target are the same buffer, this was an aborted rearrangement
+ if (bufferId1 == bufferId2)
+ return TreeViewTouch::dropEvent(event);
+
+ // Get index of buffer that was being dragged
+ QModelIndex index2 = Client::networkModel()->bufferIndex(bufferId2);
+
+ // If the buffer being dragged is a channel and we're still joined to it, treat this as a rearrangement
+ // This prevents us from being joined to a channel with no associated UI elements
+ if (index2.data(NetworkModel::BufferTypeRole) == BufferInfo::ChannelBuffer && index2.data(NetworkModel::ItemActiveRole) == true)
+ return TreeViewTouch::dropEvent(event);
+
+ // If the source buffer is not mergeable(AKA not a Channel and not a Query), try rearranging instead
+ if (index2.data(NetworkModel::BufferTypeRole) != BufferInfo::ChannelBuffer
+ && index2.data(NetworkModel::BufferTypeRole) != BufferInfo::QueryBuffer)
+ return TreeViewTouch::dropEvent(event);
+
+ // If the target buffer is not mergeable(AKA not a Channel and not a Query), try rearranging instead
+ if (index.data(NetworkModel::BufferTypeRole) != BufferInfo::ChannelBuffer
+ && index.data(NetworkModel::BufferTypeRole) != BufferInfo::QueryBuffer)
+ return TreeViewTouch::dropEvent(event);
+
+ // Confirm that the user really wants to merge the buffers before doing so
+ int res = QMessageBox::question(nullptr,
+ tr("Merge buffers permanently?"),
+ tr("Do you want to merge the buffer \"%1\" permanently into buffer \"%2\"?\n This cannot be reversed!")
+ .arg(Client::networkModel()->bufferName(bufferId2))
+ .arg(Client::networkModel()->bufferName(bufferId1)),
+ QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No);
+ if (res == QMessageBox::Yes) {
+ Client::mergeBuffersPermanently(bufferId1, bufferId2);
+ }
+}
+
+void BufferView::removeSelectedBuffers(bool permanently)
+{
+ if (!config())
+ return;
+
+ BufferId bufferId;
+ QSet<BufferId> removedRows;
+ foreach (QModelIndex index, selectionModel()->selectedIndexes()) {
+ if (index.data(NetworkModel::ItemTypeRole) != NetworkModel::BufferItemType)
+ continue;
+
+ bufferId = index.data(NetworkModel::BufferIdRole).value<BufferId>();
+ if (removedRows.contains(bufferId))
+ continue;
+
+ removedRows << bufferId;
+ }
+
+ foreach (BufferId bufferId, removedRows) {
+ if (permanently)
+ config()->requestRemoveBufferPermanently(bufferId);
+ else
+ config()->requestRemoveBuffer(bufferId);
+ }
+}
+
+void BufferView::rowsInserted(const QModelIndex& parent, int start, int end)
+{
+ TreeViewTouch::rowsInserted(parent, start, end);
+
+ // ensure that newly inserted network nodes are expanded per default
+ if (parent.data(NetworkModel::ItemTypeRole) != NetworkModel::NetworkItemType)
+ return;
+
+ setExpandedState(parent);
+}
+
+void BufferView::on_layoutChanged()
+{
+ int numNets = model()->rowCount(QModelIndex());
+ for (int row = 0; row < numNets; row++) {
+ QModelIndex networkIdx = model()->index(row, 0, QModelIndex());
+ setExpandedState(networkIdx);
+ }
+}
+
+void BufferView::on_configChanged()
+{
+ Q_ASSERT(model());
+
+ // Expand/collapse as needed
+ setExpandedState();
+
+ if (config()) {
+ // update selection to current one
+ Client::bufferModel()->synchronizeView(this);
+ }
+}
+
+void BufferView::setExpandedState()
+{
+ // Expand all active networks, collapse inactive ones... unless manually changed
+ QModelIndex networkIdx;
+ NetworkId networkId;
+ for (int row = 0; row < model()->rowCount(); row++) {
+ networkIdx = model()->index(row, 0);
+ if (model()->rowCount(networkIdx) == 0)
+ continue;
+
+ networkId = model()->data(networkIdx, NetworkModel::NetworkIdRole).value<NetworkId>();
+ if (!networkId.isValid())
+ continue;
+
+ setExpandedState(networkIdx);
+ }