diff --git a/plugins/problemreporter/problemtreeview.cpp b/plugins/problemreporter/problemtreeview.cpp index 2ba6bdb4f8..e4aa84af46 100644 --- a/plugins/problemreporter/problemtreeview.cpp +++ b/plugins/problemreporter/problemtreeview.cpp @@ -1,255 +1,286 @@ /* * KDevelop Problem Reporter * * Copyright (c) 2006-2007 Hamish Rodda * Copyright 2006 Adam Treat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemtreeview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "problemreporterplugin.h" #include #include #include using namespace KDevelop; namespace { QString descriptionFromProblem(IProblem::Ptr problem) { QString text; const auto location = problem->finalLocation(); if (location.isValid()) { text += location.document.toUrl() .adjusted(QUrl::NormalizePathSegments) .toDisplayString(QUrl::PreferLocalFile); if (location.start().line() >= 0) { text += QLatin1Char(':') + QString::number(location.start().line() + 1); if (location.start().column() >= 0) { text += QLatin1Char(':') + QString::number(location.start().column() + 1); } } text += QLatin1String(": "); } text += problem->description(); if (!problem->explanation().isEmpty()) { text += QLatin1Char('\n') + problem->explanation(); } return text; } } namespace KDevelop { class ProblemTreeViewItemDelegate : public QItemDelegate { Q_OBJECT public: explicit ProblemTreeViewItemDelegate(QObject* parent = nullptr); void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; }; } ProblemTreeViewItemDelegate::ProblemTreeViewItemDelegate(QObject* parent) : QItemDelegate(parent) { } void ProblemTreeViewItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyleOptionViewItem newOption(option); newOption.textElideMode = index.column() == ProblemModel::File ? Qt::ElideMiddle : Qt::ElideRight; QItemDelegate::paint(painter, newOption, index); } ProblemTreeView::ProblemTreeView(QWidget* parent, QAbstractItemModel* itemModel) : QTreeView(parent) , m_proxy(new QSortFilterProxyModel(this)) { setObjectName(QStringLiteral("Problem Reporter Tree")); setWhatsThis(i18n("Problems")); setItemDelegate(new ProblemTreeViewItemDelegate(this)); setSelectionBehavior(QAbstractItemView::SelectRows); m_proxy->setSortRole(ProblemModel::SeverityRole); m_proxy->setDynamicSortFilter(true); m_proxy->sort(0, Qt::AscendingOrder); auto* problemModel = qobject_cast(itemModel); Q_ASSERT(problemModel); setModel(problemModel); header()->setStretchLastSection(false); if (!problemModel->features().testFlag(ProblemModel::ShowSource)) { hideColumn(ProblemModel::Source); } connect(this, &ProblemTreeView::clicked, this, &ProblemTreeView::itemActivated); connect(model(), &QAbstractItemModel::rowsInserted, this, &ProblemTreeView::changed); connect(model(), &QAbstractItemModel::rowsRemoved, this, &ProblemTreeView::changed); connect(model(), &QAbstractItemModel::modelReset, this, &ProblemTreeView::changed); m_proxy->setFilterKeyColumn(-1); m_proxy->setFilterCaseSensitivity(Qt::CaseInsensitive); } ProblemTreeView::~ProblemTreeView() { } void ProblemTreeView::openDocumentForCurrentProblem() { itemActivated(currentIndex()); } void ProblemTreeView::itemActivated(const QModelIndex& index) { if (!index.isValid()) return; KTextEditor::Cursor start; QUrl url; { // TODO: is this really necessary? DUChainReadLocker lock(DUChain::lock()); const auto problem = index.data(ProblemModel::ProblemRole).value(); if (!problem) return; url = problem->finalLocation().document.toUrl(); start = problem->finalLocation().start(); } if (QFile::exists(url.toLocalFile())) { ICore::self()->documentController()->openDocument(url, start); } } void ProblemTreeView::resizeColumns() { - for (int i = 0; i < model()->columnCount(); ++i) - resizeColumnToContents(i); + // Returns the approximate amount of characters in a column and + // a stretch factor. + auto sizeHint = [](int column) -> std::pair { + switch (column) { + case ProblemModel::Error: return {40, 20}; + case ProblemModel::Source: return {30, 1}; + case ProblemModel::File: return {30, 10}; + case ProblemModel::Line: return {15, 1}; + case ProblemModel::Column: return {15, 1}; + default: return {20, 0}; + } + }; + + auto charWidth = fontMetrics().averageCharWidth(); + + // Set each column to its minimum needed width, and figure out how much space is left. + int widths[ProblemModel::LastColumn]; + int remainPixels = header()->size().width(); + int totalWeight = 0; + for (int i = 0; i < ProblemModel::LastColumn; ++i) { + auto const [minWidth, weight] = sizeHint(i); + widths[i] = minWidth * charWidth; + remainPixels -= minWidth * charWidth; + totalWeight += weight; + } + + // Distribute according to stretch factor if there is any unoccupied space. + remainPixels = std::max(0, remainPixels); + for (int i = 0; i < ProblemModel::LastColumn; ++i) { + auto const [minWidth, weight] = sizeHint(i); + widths[i] += (remainPixels * weight) / totalWeight; + setColumnWidth(i, widths[i]); + } } void ProblemTreeView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles) { QTreeView::dataChanged(topLeft, bottomRight, roles); resizeColumns(); } void ProblemTreeView::reset() { QTreeView::reset(); resizeColumns(); } int ProblemTreeView::setFilter(const QString& filterText) { m_proxy->setFilterFixedString(filterText); return m_proxy->rowCount(); } ProblemModel* ProblemTreeView::model() const { return static_cast(m_proxy->sourceModel()); } void ProblemTreeView::setModel(QAbstractItemModel* model) { Q_ASSERT(qobject_cast(model)); m_proxy->setSourceModel(model); QTreeView::setModel(m_proxy); } void ProblemTreeView::contextMenuEvent(QContextMenuEvent* event) { QModelIndex index = indexAt(event->pos()); if (!index.isValid()) return; const auto problem = index.data(ProblemModel::ProblemRole).value(); if (!problem) { return; } QPointer m = new QMenu(this); m->addSection(i18n("Problem")); auto copyDescriptionAction = m->addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy Description")); connect(copyDescriptionAction, &QAction::triggered, this, [problem]() { QApplication::clipboard()->setText(descriptionFromProblem(problem), QClipboard::Clipboard); }); QExplicitlySharedDataPointer solution = problem->solutionAssistant(); if (solution && !solution->actions().isEmpty()) { QList actions; const auto solutionActions = solution->actions(); actions.reserve(solutionActions.size()); for (auto assistantAction : solutionActions) { auto action = assistantAction->toQAction(m.data()); action->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))); actions << action; } QString title = solution->title(); title = KDevelop::htmlToPlainText(title); title.replace(QLatin1String("'"), QLatin1String("\'")); m->addSection(i18n("Solve: %1", title)); m->addActions(actions); } m->exec(event->globalPos()); delete m; } void ProblemTreeView::showEvent(QShowEvent* event) { Q_UNUSED(event) resizeColumns(); } #include "problemtreeview.moc"