/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "clangtoolsdiagnosticview.h" #include "clangtoolsdiagnosticmodel.h" #include "clangtoolsprojectsettings.h" #include "clangtoolsutils.h" #include #include #include #include #include #include #include #include #include using namespace Debugger; namespace ClangTools { namespace Internal { // A header view that puts a check box in front of a given column. class HeaderWithCheckBoxInColumn : public QHeaderView { Q_OBJECT public: HeaderWithCheckBoxInColumn(Qt::Orientation orientation, int column = 0, QWidget *parent = nullptr) : QHeaderView(orientation, parent) , m_column(column) { setDefaultAlignment(Qt::AlignLeft); } void setState(QFlags newState) { state = newState; } protected: void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const override { painter->save(); QHeaderView::paintSection(painter, rect, logicalIndex); painter->restore(); if (logicalIndex == m_column) { QStyleOptionButton option; const int side = sizeHint().height(); option.rect = QRect(rect.left() + 1, 1, side - 3, side - 3); option.state = state; painter->save(); const int shift = side - 2; painter->translate(QPoint(shift, 0)); QHeaderView::paintSection(painter, rect.adjusted(0, 0, -shift, 0), logicalIndex); painter->restore(); style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &option, painter); } } void mouseReleaseEvent(QMouseEvent *event) override { const int x = event->localPos().x(); const int columnX = sectionPosition(m_column); const bool isWithinCheckBox = x > columnX && x < columnX + sizeHint().height() - 3; if (isWithinCheckBox) { state = (state != QStyle::State_On) ? QStyle::State_On : QStyle::State_Off; viewport()->update(); emit checkBoxClicked(state == QStyle::State_On); return; // Avoid changing sort order } QHeaderView::mouseReleaseEvent(event); } signals: void checkBoxClicked(bool checked); private: const int m_column = 0; QFlags state = QStyle::State_Off; }; static QString getBaseStyleName() { QStyle *style = QApplication::style(); if (auto proxyStyle = qobject_cast(style)) style = proxyStyle->baseStyle(); return style->objectName(); } // Paints the check box indicator disabled if requested by client. class DiagnosticViewStyle : public ManhattanStyle { public: DiagnosticViewStyle(const QString &baseStyleName = getBaseStyleName()) : ManhattanStyle(baseStyleName) {} void setPaintCheckBoxDisabled(bool paintDisabledCheckbox) { m_paintCheckBoxDisabled = paintDisabledCheckbox; } void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = nullptr) const final { if (element == QStyle::PE_IndicatorCheckBox && m_paintCheckBoxDisabled) { if (const QStyleOptionButton *o = qstyleoption_cast( option)) { QStyleOptionButton myOption = *o; myOption.palette.setCurrentColorGroup(QPalette::Disabled); ManhattanStyle::drawPrimitive(element, &myOption, painter, widget); return; } } ManhattanStyle::drawPrimitive(element, option, painter, widget); } private: bool m_paintCheckBoxDisabled = false; }; // A delegate that allows to paint a disabled check box (indicator). // This is useful if the rest of the item should not be disabled. class DiagnosticViewDelegate : public QStyledItemDelegate { Q_OBJECT public: DiagnosticViewDelegate(DiagnosticViewStyle *style) : m_style(style) {} void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const final { const bool paintDisabled = !index.data(ClangToolsDiagnosticModel::CheckBoxEnabledRole) .toBool(); if (paintDisabled) m_style->setPaintCheckBoxDisabled(true); QStyledItemDelegate::paint(painter, option, index); if (paintDisabled) m_style->setPaintCheckBoxDisabled(false); } private: DiagnosticViewStyle *m_style = nullptr; }; DiagnosticView::DiagnosticView(QWidget *parent) : Debugger::DetailedErrorView(parent) , m_style(new DiagnosticViewStyle) , m_delegate(new DiagnosticViewDelegate(m_style.get())) { m_suppressAction = new QAction(tr("Suppress This Diagnostic"), this); connect(m_suppressAction, &QAction::triggered, this, &DiagnosticView::suppressCurrentDiagnostic); installEventFilter(this); setStyle(m_style.get()); setItemDelegate(m_delegate.get()); } DiagnosticView::~DiagnosticView() = default; void DiagnosticView::suppressCurrentDiagnostic() { const QModelIndexList indexes = selectionModel()->selectedRows(); QTC_ASSERT(indexes.count() == 1, return); const Diagnostic diag = model()->data(indexes.first(), ClangToolsDiagnosticModel::DiagnosticRole) .value(); QTC_ASSERT(diag.isValid(), return); // If the original project was closed, we work directly on the filter model, otherwise // we go via the project settings. auto * const filterModel = static_cast(model()); ProjectExplorer::Project * const project = filterModel->project(); if (project) { Utils::FilePath filePath = Utils::FilePath::fromString(diag.location.filePath); const Utils::FilePath relativeFilePath = filePath.relativeChildPath(project->projectDirectory()); if (!relativeFilePath.isEmpty()) filePath = relativeFilePath; const SuppressedDiagnostic supDiag(filePath, diag.description, diag.issueContextKind, diag.issueContext, diag.explainingSteps.count()); ClangToolsProjectSettingsManager::getSettings(project)->addSuppressedDiagnostic(supDiag); } else { filterModel->addSuppressedDiagnostic(SuppressedDiagnostic(diag)); } } void DiagnosticView::goNext() { const QModelIndex currentIndex = selectionModel()->currentIndex(); selectIndex(getIndex(currentIndex, Next)); } void DiagnosticView::goBack() { const QModelIndex currentIndex = selectionModel()->currentIndex(); selectIndex(getIndex(currentIndex, Previous)); } QModelIndex DiagnosticView::getIndex(const QModelIndex &index, Direction direction) const { const QModelIndex parentIndex = index.parent(); QModelIndex followingTopIndex = index; if (parentIndex.isValid()) { // Use direct sibling for level 2 and 3 items const QModelIndex followingIndex = index.sibling(index.row() + direction, 0); if (followingIndex.isValid()) return followingIndex; // First/Last level 3 item? Continue on level 2 item. if (parentIndex.parent().isValid()) return direction == Previous ? parentIndex : getIndex(parentIndex, direction); followingTopIndex = getTopLevelIndex(parentIndex, direction); } // Skip top level items without children while (!model()->hasChildren(followingTopIndex)) followingTopIndex = getTopLevelIndex(followingTopIndex, direction); // Select first/last level 2 item const int row = direction == Next ? 0 : model()->rowCount(followingTopIndex) - 1; return model()->index(row, 0, followingTopIndex); } QModelIndex DiagnosticView::getTopLevelIndex(const QModelIndex &index, Direction direction) const { QModelIndex following = index.sibling(index.row() + direction, 0); if (following.isValid()) return following; const int row = direction == Next ? 0 : model()->rowCount() - 1; return model()->index(row, 0); } QList DiagnosticView::customActions() const { return {m_suppressAction}; } bool DiagnosticView::eventFilter(QObject *watched, QEvent *event) { switch (event->type()) { case QEvent::KeyRelease: { const int key = static_cast(event)->key(); switch (key) { case Qt::Key_Return: case Qt::Key_Enter: openEditorForCurrentIndex(); } return true; } default: return QObject::eventFilter(watched, event); } } void DiagnosticView::mouseDoubleClickEvent(QMouseEvent *event) { openEditorForCurrentIndex(); Debugger::DetailedErrorView::mouseDoubleClickEvent(event); } void DiagnosticView::setSelectedFixItsCount(int fixItsCount) { if (m_ignoreSetSelectedFixItsCount) return; auto checkBoxHeader = static_cast(header()); checkBoxHeader->setState(fixItsCount ? (QStyle::State_NoChange | QStyle::State_On | QStyle::State_Off) : QStyle::State_Off); checkBoxHeader->viewport()->update(); } void DiagnosticView::openEditorForCurrentIndex() { const QVariant v = model()->data(currentIndex(), Debugger::DetailedErrorView::LocationRole); const auto loc = v.value(); if (loc.isValid()) Core::EditorManager::openEditorAt(loc.filePath, loc.line, loc.column - 1); } void DiagnosticView::setModel(QAbstractItemModel *theProxyModel) { const auto proxyModel = static_cast(theProxyModel); Debugger::DetailedErrorView::setModel(proxyModel); auto *header = new HeaderWithCheckBoxInColumn(Qt::Horizontal, DiagnosticView::DiagnosticColumn, this); connect(header, &HeaderWithCheckBoxInColumn::checkBoxClicked, this, [=](bool checked) { m_ignoreSetSelectedFixItsCount = true; for (int i = 0, count = proxyModel->rowCount(); i < count; ++i) { const QModelIndex filePathItemIndex = proxyModel->index(i, 0); for (int j = 0, count = proxyModel->rowCount(filePathItemIndex); j < count; ++j) { const QModelIndex proxyIndex = proxyModel->index(j, 0, filePathItemIndex); const QModelIndex diagnosticItemIndex = proxyModel->mapToSource(proxyIndex); auto item = static_cast(diagnosticItemIndex.internalPointer()); item->setData(DiagnosticView::DiagnosticColumn, checked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); } } m_ignoreSetSelectedFixItsCount = false; }); setHeader(header); header->setSectionResizeMode(DiagnosticView::DiagnosticColumn, QHeaderView::Stretch); const int columnWidth = header->sectionSizeHint(DiagnosticView::DiagnosticColumn); const int checkboxWidth = header->height(); header->setMinimumSectionSize(columnWidth + 1.2 * checkboxWidth); } } // namespace Internal } // namespace ClangTools #include "clangtoolsdiagnosticview.moc"