X-Git-Url: https://git.quassel-irc.org/?a=blobdiff_plain;f=src%2Fuisupport%2Fbufferview.cpp;h=620c2627bc95d90617fc378453475743c4bf6db6;hb=8582c2ad5708a1972c85bea1cf8d81ad3ece4814;hp=f002405c61fa0a0b387f254ae385655c82ef9656;hpb=3545504d0588f79ac92a7702c4cf78dc0adff05f;p=quassel.git diff --git a/src/uisupport/bufferview.cpp b/src/uisupport/bufferview.cpp index f002405c..620c2627 100644 --- a/src/uisupport/bufferview.cpp +++ b/src/uisupport/bufferview.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005-2016 by the Quassel Project * + * Copyright (C) 2005-2018 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -56,7 +56,7 @@ BufferView::BufferView(QWidget *parent) setSelectionMode(QAbstractItemView::ExtendedSelection); QAbstractItemDelegate *oldDelegate = itemDelegate(); - BufferViewDelegate *tristateDelegate = new BufferViewDelegate(this); + auto *tristateDelegate = new BufferViewDelegate(this); setItemDelegate(tristateDelegate); delete oldDelegate; } @@ -69,13 +69,13 @@ void BufferView::init() hideColumn(2); setIndentation(10); - expandAll(); + // New entries will be expanded automatically when added; no need to call expandAll() header()->hide(); // nobody seems to use this anyway - // breaks with Qt 4.8 - if (QString("4.8.0") > qVersion()) // FIXME breaks with Qt versions >= 4.10! - setAnimated(true); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + setAnimated(true); // FIXME This is to workaround bug #663 setUniformRowHeights(true); @@ -89,14 +89,13 @@ void BufferView::init() setSortingEnabled(true); sortByColumn(0, Qt::AscendingOrder); - // activated() fails on X11 and Qtopia at least -#if defined Q_WS_QWS || defined Q_WS_X11 - disconnect(this, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(joinChannel(QModelIndex))); - connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(joinChannel(QModelIndex))); -#else +#if defined Q_OS_MACOS || defined Q_OS_WIN // afaik this is better on Mac and Windows disconnect(this, SIGNAL(activated(QModelIndex)), this, SLOT(joinChannel(QModelIndex))); connect(this, SIGNAL(activated(QModelIndex)), SLOT(joinChannel(QModelIndex))); +#else + disconnect(this, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(joinChannel(QModelIndex))); + connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(joinChannel(QModelIndex))); #endif } @@ -130,12 +129,17 @@ void BufferView::setModel(QAbstractItemModel *model) } connect(model, SIGNAL(layoutChanged()), this, SLOT(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) { - BufferViewFilter *filter = qobject_cast(model()); + auto *filter = qobject_cast(model()); if (filter) { filter->setConfig(config); setConfig(config); @@ -143,15 +147,15 @@ void BufferView::setFilteredModel(QAbstractItemModel *model_, BufferViewConfig * } if (model()) { - disconnect(this, 0, model(), 0); - disconnect(model(), 0, this, 0); + disconnect(this, nullptr, model(), nullptr); + disconnect(model(), nullptr, this, nullptr); } if (!model_) { setModel(model_); } else { - BufferViewFilter *filter = new BufferViewFilter(model_, config); + auto *filter = new BufferViewFilter(model_, config); setModel(filter); connect(filter, SIGNAL(configChanged()), this, SLOT(on_configChanged())); } @@ -165,7 +169,7 @@ void BufferView::setConfig(BufferViewConfig *config) return; if (_config) { - disconnect(_config, 0, this, 0); + disconnect(_config, nullptr, this, nullptr); } _config = config; @@ -212,16 +216,6 @@ void BufferView::joinChannel(const QModelIndex &index) } -void BufferView::keyPressEvent(QKeyEvent *event) -{ - if (event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete) { - event->accept(); - removeSelectedBuffers(); - } - TreeViewTouch::keyPressEvent(event); -} - - void BufferView::dropEvent(QDropEvent *event) { QModelIndex index = indexAt(event->pos()); @@ -230,28 +224,45 @@ void BufferView::dropEvent(QDropEvent *event) 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 > 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; - if (index.data(NetworkModel::ItemTypeRole) != NetworkModel::BufferItemType) + // Get the Buffer ID of the target buffer + BufferId bufferId1 = index.data(NetworkModel::BufferIdRole).value(); + + // 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 (index.data(NetworkModel::BufferTypeRole) != BufferInfo::QueryBuffer) + //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); - BufferId bufferId1 = index.data(NetworkModel::BufferIdRole).value(); - if (bufferId1 == bufferId2) + // 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); - int res = QMessageBox::question(0, tr("Merge buffers permanently?"), + // 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) { @@ -313,7 +324,19 @@ void BufferView::on_configChanged() { Q_ASSERT(model()); - // expand all active networks... collapse inactive ones... unless manually changed + // 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++) { @@ -327,11 +350,6 @@ void BufferView::on_configChanged() setExpandedState(networkIdx); } - - if (config()) { - // update selection to current one - Client::bufferModel()->synchronizeView(this); - } } @@ -374,15 +392,9 @@ void BufferView::setExpandedState(const QModelIndex &networkIdx) storeExpandedState(networkIdx); // this call is needed to keep track of the isActive state } -#if QT_VERSION < 0x050000 -void BufferView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) -{ - TreeViewTouch::dataChanged(topLeft, bottomRight); -#else void BufferView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { TreeViewTouch::dataChanged(topLeft, bottomRight, roles); -#endif // determine how many items have been changed and if any of them is a networkitem // which just swichted from active to inactive or vice versa @@ -398,7 +410,7 @@ void BufferView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bott void BufferView::toggleHeader(bool checked) { - QAction *action = qobject_cast(sender()); + auto *action = qobject_cast(sender()); header()->setSectionHidden((action->property("column")).toInt(), !checked); } @@ -435,7 +447,7 @@ void BufferView::addActionsToMenu(QMenu *contextMenu, const QModelIndex &index) void BufferView::addFilterActions(QMenu *contextMenu, const QModelIndex &index) { - BufferViewFilter *filter = qobject_cast(model()); + auto *filter = qobject_cast(model()); if (filter) { QList filterActions = filter->actions(index); if (!filterActions.isEmpty()) { @@ -481,6 +493,8 @@ void BufferView::changeBuffer(Direction direction) QModelIndex currentIndex = selectionModel()->currentIndex(); QModelIndex resultingIndex; + QModelIndex lastNetIndex = model()->index(model()->rowCount() - 1, 0, QModelIndex()); + if (currentIndex.parent().isValid()) { //If we are a child node just switch among siblings unless it's the first/last child resultingIndex = currentIndex.sibling(currentIndex.row() + direction, 0); @@ -497,6 +511,8 @@ void BufferView::changeBuffer(Direction direction) //If we have a toplevel node, try and get an adjacent child if (direction == Backward) { QModelIndex newParent = currentIndex.sibling(currentIndex.row() - 1, 0); + if (currentIndex.row() == 0) + newParent = lastNetIndex; if (model()->hasChildren(newParent)) resultingIndex = newParent.child(model()->rowCount(newParent) - 1, 0); else @@ -510,13 +526,41 @@ void BufferView::changeBuffer(Direction direction) } } - if (!resultingIndex.isValid()) - return; + if (!resultingIndex.isValid()) { + if (direction == Forward) + resultingIndex = model()->index(0, 0, QModelIndex()); + else + resultingIndex = lastNetIndex.child(model()->rowCount(lastNetIndex) - 1, 0); + } selectionModel()->setCurrentIndex(resultingIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); selectionModel()->select(resultingIndex, QItemSelectionModel::ClearAndSelect); } +void BufferView::selectFirstBuffer() +{ + int networksCount = model()->rowCount(QModelIndex()); + if (networksCount == 0) { + return; + } + + QModelIndex bufferIndex; + for (int row = 0; row < networksCount; row++) { + QModelIndex networkIndex = model()->index(row, 0, QModelIndex()); + int childCount = model()->rowCount(networkIndex); + if (childCount > 0) { + bufferIndex = model()->index(0, 0, networkIndex); + break; + } + } + + if (!bufferIndex.isValid()) { + return; + } + + selectionModel()->setCurrentIndex(bufferIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + selectionModel()->select(bufferIndex, QItemSelectionModel::ClearAndSelect); +} void BufferView::wheelEvent(QWheelEvent *event) { @@ -539,19 +583,12 @@ void BufferView::hideCurrentBuffer() //The check above means we won't be looking at a network, which should always be the first row, so we can just go backwards. changeBuffer(Backward); - /*if(removedRows.contains(bufferId)) - continue; - - removedRows << bufferId;*/ - /*if(permanently) - config()->requestRemoveBufferPermanently(bufferId); - else*/ config()->requestRemoveBuffer(bufferId); } -void BufferView::filterTextChanged(QString filterString) +void BufferView::filterTextChanged(const QString& filterString) { - BufferViewFilter *filter = qobject_cast(model()); + auto *filter = qobject_cast(model()); if (!filter) { return; } @@ -568,19 +605,74 @@ QSize BufferView::sizeHint() const return TreeViewTouch::sizeHint(); if (model()->rowCount() == 0) - return QSize(120, 50); + return {120, 50}; int columnSize = 0; for (int i = 0; i < model()->columnCount(); i++) { if (!isColumnHidden(i)) columnSize += sizeHintForColumn(i); } - return QSize(columnSize, 50); + return {columnSize, 50}; +} + + +void BufferView::changeHighlight(BufferView::Direction direction) +{ + // If for some weird reason we get a new delegate + auto delegate = qobject_cast(itemDelegate(_currentHighlight)); + if (delegate) { + delegate->currentHighlight = QModelIndex(); + } + + QModelIndex newIndex = _currentHighlight; + if (!newIndex.isValid()) { + newIndex = model()->index(0, 0); + } + + if (direction == Backward) { + newIndex = indexBelow(newIndex); + } else { + newIndex = indexAbove(newIndex); + } + + if (!newIndex.isValid()) { + return; + } + + _currentHighlight = newIndex; + + delegate = qobject_cast(itemDelegate(_currentHighlight)); + if (delegate) { + delegate->currentHighlight = _currentHighlight; + } + viewport()->update(); +} + +void BufferView::selectHighlighted() +{ + if (_currentHighlight.isValid()) { + selectionModel()->setCurrentIndex(_currentHighlight, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + selectionModel()->select(_currentHighlight, QItemSelectionModel::ClearAndSelect); + } else { + selectFirstBuffer(); + } + + clearHighlight(); } +void BufferView::clearHighlight() +{ + // If for some weird reason we get a new delegate + auto delegate = qobject_cast(itemDelegate(_currentHighlight)); + if (delegate) { + delegate->currentHighlight = QModelIndex(); + } + _currentHighlight = QModelIndex(); + viewport()->update(); +} // **************************************** -// BufferViewDelgate +// BufferViewDelegate // **************************************** class ColorsChangedEvent : public QEvent { @@ -616,16 +708,16 @@ bool BufferViewDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, c if (!value.isValid()) return QStyledItemDelegate::editorEvent(event, model, option, index); - QStyleOptionViewItemV4 viewOpt(option); + QStyleOptionViewItem viewOpt(option); initStyleOption(&viewOpt, index); QRect checkRect = viewOpt.widget->style()->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &viewOpt, viewOpt.widget); - QMouseEvent *me = static_cast(event); + auto *me = static_cast(event); if (me->button() != Qt::LeftButton || !checkRect.contains(me->pos())) return QStyledItemDelegate::editorEvent(event, model, option, index); - Qt::CheckState state = static_cast(value.toInt()); + auto state = static_cast(value.toInt()); if (state == Qt::Unchecked) state = Qt::PartiallyChecked; else if (state == Qt::PartiallyChecked) @@ -642,7 +734,7 @@ bool BufferViewDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, c // ============================== BufferViewDock::BufferViewDock(BufferViewConfig *config, QWidget *parent) : QDockWidget(parent), - _childWidget(0), + _childWidget(nullptr), _widget(new QWidget(parent)), _filterEdit(new QLineEdit(parent)), _active(false), @@ -658,12 +750,26 @@ BufferViewDock::BufferViewDock(BufferViewConfig *config, QWidget *parent) _widget->setLayout(new QVBoxLayout); _widget->layout()->setSpacing(0); _widget->layout()->setContentsMargins(0, 0, 0, 0); - _filterEdit->setVisible(config->showSearch()); // hide it here, so we don't flicker or somesuch + + // We need to potentially hide it early, so it doesn't flicker + _filterEdit->setVisible(config->showSearch()); + _filterEdit->setFocusPolicy(Qt::ClickFocus); + _filterEdit->installEventFilter(this); _filterEdit->setPlaceholderText(tr("Search...")); + connect(_filterEdit, SIGNAL(returnPressed()), SLOT(onFilterReturnPressed())); + _widget->layout()->addWidget(_filterEdit); QDockWidget::setWidget(_widget); } +void BufferViewDock::setLocked(bool locked) { + if (locked) { + setFeatures(nullptr); + } + else { + setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); + } +} void BufferViewDock::updateTitle() { @@ -675,10 +781,33 @@ void BufferViewDock::updateTitle() void BufferViewDock::configChanged() { - _filterEdit->setVisible(config()->showSearch()); + if (_filterEdit->isVisible() != config()->showSearch()) { + _filterEdit->setVisible(config()->showSearch()); + _filterEdit->clear(); + } +} - if (!_filterEdit->isVisible()) { - _filterEdit->setText(QLatin1String("")); +void BufferViewDock::onFilterReturnPressed() +{ + if (_oldFocusItem) { + _oldFocusItem->setFocus(); + _oldFocusItem = nullptr; + } + + if (!config()->showSearch()) { + _filterEdit->setVisible(false); + } + + BufferView *view = bufferView(); + if (!view) { + return; + } + + if (!_filterEdit->text().isEmpty()) { + view->selectHighlighted(); + _filterEdit->clear(); + } else { + view->clearHighlight(); } } @@ -687,11 +816,58 @@ void BufferViewDock::setActive(bool active) if (active != isActive()) { _active = active; updateTitle(); - if (active) + if (active) { raise(); // for tabbed docks + } } } +bool BufferViewDock::eventFilter(QObject *object, QEvent *event) +{ + if (object != _filterEdit) { + return false; + } + + if (event->type() == QEvent::FocusOut) { + if (!config()->showSearch() && _filterEdit->text().isEmpty()) { + _filterEdit->setVisible(false); + return true; + } + } else if (event->type() == QEvent::KeyRelease) { + auto keyEvent = static_cast(event); + + BufferView *view = bufferView(); + if (!view) { + return false; + } + + switch (keyEvent->key()) { + case Qt::Key_Escape: { + _filterEdit->clear(); + + if (!_oldFocusItem) { + return false; + } + + _oldFocusItem->setFocus(); + _oldFocusItem = nullptr; + return true; + } + case Qt::Key_Down: + view->changeHighlight(BufferView::Backward); + return true; + case Qt::Key_Up: + view->changeHighlight(BufferView::Forward); + return true; + default: + break; + } + + return false; + } + + return false; +} void BufferViewDock::bufferViewRenamed(const QString &newName) { @@ -718,7 +894,7 @@ BufferViewConfig *BufferViewDock::config() const { BufferView *view = bufferView(); if (!view) - return 0; + return nullptr; else return view->config(); } @@ -730,3 +906,24 @@ void BufferViewDock::setWidget(QWidget *newWidget) connect(_filterEdit, SIGNAL(textChanged(QString)), bufferView(), SLOT(filterTextChanged(QString))); } + +void BufferViewDock::activateFilter() +{ + if (!_filterEdit->isVisible()) { + _filterEdit->setVisible(true); + } + + _oldFocusItem = qApp->focusWidget(); + + _filterEdit->setFocus(); +} + + +void BufferViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItem newOption = option; + if (index == currentHighlight) { + newOption.state |= QStyle::State_HasFocus; + } + QStyledItemDelegate::paint(painter, newOption, index); +}