File indexing completed on 2024-05-05 04:40:56
0001 /* 0002 SPDX-FileCopyrightText: 2007 Andreas Pakulat <apaku@gmx.de> 0003 SPDX-FileCopyrightText: 2007 Dukju Ahn <dukjuahn@gmail.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "outputwidget.h" 0009 0010 #include "standardoutputview.h" 0011 0012 #include <QAction> 0013 #include <QApplication> 0014 #include <QClipboard> 0015 #include <QIcon> 0016 #include <QLineEdit> 0017 #include <QRegularExpression> 0018 #include <QSortFilterProxyModel> 0019 #include <QStackedWidget> 0020 #include <QTabWidget> 0021 #include <QToolButton> 0022 #include <QTreeView> 0023 #include <QVBoxLayout> 0024 #include <QWidgetAction> 0025 0026 #include <KActionCollection> 0027 #include <KColorScheme> 0028 #include <KLocalizedString> 0029 #include <KStandardAction> 0030 #include <KToggleAction> 0031 0032 #include <outputview/ioutputviewmodel.h> 0033 #include <util/focusedtreeview.h> 0034 #include <util/expandablelineedit.h> 0035 0036 #include "outputmodel.h" 0037 #include "outputwidgetconfig.h" 0038 #include "toolviewdata.h" 0039 #include <debug.h> 0040 0041 namespace { 0042 QString validFilterInputToolTip() 0043 { 0044 return i18nc("@info:tooltip", "Enter a case-insensitive regular expression to filter the output view"); 0045 } 0046 } 0047 0048 OutputWidget::OutputWidget(QWidget* parent, const ToolViewData* tvdata) 0049 : QWidget(parent) 0050 , m_tabwidget(nullptr) 0051 , m_stackwidget(nullptr) 0052 , data(tvdata) 0053 , m_closeButton(nullptr) 0054 , m_closeOthersAction(nullptr) 0055 , m_nextAction(nullptr) 0056 , m_previousAction(nullptr) 0057 , m_activateOnSelect(nullptr) 0058 , m_focusOnSelect(nullptr) 0059 , m_filterInput(nullptr) 0060 , m_filterAction(nullptr) 0061 , m_outputWidgetConfig(nullptr) 0062 { 0063 setWindowTitle(i18nc("@title:window", "Output View")); 0064 setWindowIcon(tvdata->icon); 0065 auto* layout = new QVBoxLayout(this); 0066 layout->setContentsMargins(0, 0, 0, 0); 0067 if( data->type & KDevelop::IOutputView::MultipleView ) 0068 { 0069 m_tabwidget = new QTabWidget(this); 0070 layout->addWidget( m_tabwidget ); 0071 m_closeButton = new QToolButton( this ); 0072 connect( m_closeButton, &QToolButton::clicked, this, &OutputWidget::closeActiveView ); 0073 m_closeButton->setIcon( QIcon::fromTheme( QStringLiteral( "tab-close") ) ); 0074 m_closeButton->setToolTip(i18nc("@info:tooltip", "Close the currently active output view")); 0075 m_closeButton->setAutoRaise(true); 0076 0077 m_closeOthersAction = new QAction( this ); 0078 connect(m_closeOthersAction, &QAction::triggered, this, &OutputWidget::closeOtherViews); 0079 m_closeOthersAction->setIcon(QIcon::fromTheme(QStringLiteral("tab-close-other"))); 0080 m_closeOthersAction->setToolTip(i18nc("@info:tooltip", "Close all other output views")); 0081 m_closeOthersAction->setText( m_closeOthersAction->toolTip() ); 0082 addAction(m_closeOthersAction); 0083 0084 m_tabwidget->setCornerWidget(m_closeButton, Qt::TopRightCorner); 0085 m_tabwidget->setDocumentMode(true); 0086 } else if ( data->type == KDevelop::IOutputView::HistoryView ) 0087 { 0088 m_stackwidget = new QStackedWidget( this ); 0089 layout->addWidget( m_stackwidget ); 0090 0091 m_previousAction = new QAction(QIcon::fromTheme(QStringLiteral("arrow-left")), i18nc("@action", "Previous Output"), this); 0092 connect(m_previousAction, &QAction::triggered, this, &OutputWidget::previousOutput); 0093 addAction(m_previousAction); 0094 m_nextAction = new QAction(QIcon::fromTheme(QStringLiteral("arrow-right")), i18nc("@action", "Next Output"), this); 0095 connect(m_nextAction, &QAction::triggered, this, &OutputWidget::nextOutput); 0096 addAction(m_nextAction); 0097 } 0098 0099 m_activateOnSelect = new KToggleAction( QIcon(), i18nc("@action", "Select Activated Item"), this ); 0100 m_activateOnSelect->setChecked( true ); 0101 m_focusOnSelect = new KToggleAction( QIcon(), i18nc("@action", "Focus when Selecting Item"), this ); 0102 m_focusOnSelect->setChecked( false ); 0103 if( data->option & KDevelop::IOutputView::ShowItemsButton ) 0104 { 0105 addAction(m_activateOnSelect); 0106 addAction(m_focusOnSelect); 0107 } 0108 0109 QAction* action; 0110 action = new QAction(QIcon::fromTheme(QStringLiteral("text-wrap")), i18nc("@action", "Word Wrap"), this); 0111 action->setCheckable(true); 0112 connect(action, &QAction::toggled, this, &OutputWidget::setWordWrap); 0113 addAction(action); 0114 0115 auto *separator = new QAction(this); 0116 separator->setSeparator(true); 0117 addAction(separator); 0118 0119 action = new QAction(QIcon::fromTheme(QStringLiteral("go-first")), i18nc("@action", "First Item"), this); 0120 connect(action, &QAction::triggered, this, &OutputWidget::selectFirstItem); 0121 addAction(action); 0122 0123 action = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")), i18nc("@action", "Previous Item"), this); 0124 connect(action, &QAction::triggered, this, &OutputWidget::selectPreviousItem); 0125 addAction(action); 0126 0127 action = new QAction(QIcon::fromTheme(QStringLiteral("go-next")), i18nc("@action", "Next Item"), this); 0128 connect(action, &QAction::triggered, this, &OutputWidget::selectNextItem); 0129 addAction(action); 0130 0131 action = new QAction(QIcon::fromTheme(QStringLiteral("go-last")), i18nc("@action", "Last Item"), this); 0132 connect(action, &QAction::triggered, this, &OutputWidget::selectLastItem); 0133 addAction(action); 0134 0135 QAction* selectAllAction = KStandardAction::selectAll(this, SLOT(selectAll()), this); 0136 selectAllAction->setShortcut(QKeySequence()); //FIXME: why does CTRL-A conflict with Katepart (while CTRL-Cbelow doesn't) ? 0137 selectAllAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); 0138 addAction(selectAllAction); 0139 0140 QAction* copyAction = KStandardAction::copy(this, SLOT(copySelection()), this); 0141 copyAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); 0142 addAction(copyAction); 0143 0144 auto* clearAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-list")), i18nc("@action", "Clear"), this); 0145 connect(clearAction, &QAction::triggered, this, &OutputWidget::clearModel); 0146 addAction(clearAction); 0147 0148 if (data->type & KDevelop::IOutputView::MultipleView) { 0149 connect(m_tabwidget, &QTabWidget::currentChanged, this, &OutputWidget::currentViewChanged); 0150 } else if (data->type == KDevelop::IOutputView::HistoryView) { 0151 connect(m_stackwidget, &QStackedWidget::currentChanged, this, &OutputWidget::currentViewChanged); 0152 } 0153 0154 if( data->option & KDevelop::IOutputView::AddFilterAction ) 0155 { 0156 auto *separator = new QAction(this); 0157 separator->setSeparator(true); 0158 addAction(separator); 0159 0160 m_filterInput = new KExpandableLineEdit(this); 0161 m_filterInput->setPlaceholderText(i18nc("@info:placeholder", "Search...")); 0162 m_filterInput->setClearButtonEnabled(true); 0163 m_filterInput->setToolTip(validFilterInputToolTip()); 0164 m_filterAction = new QWidgetAction(this); 0165 m_filterAction->setText(m_filterInput->placeholderText()); 0166 connect(m_filterAction, &QAction::triggered, this, [this]() {m_filterInput->setFocus();}); 0167 m_filterAction->setDefaultWidget(m_filterInput); 0168 addAction(m_filterAction); 0169 0170 connect(m_filterInput, &QLineEdit::textEdited, 0171 this, &OutputWidget::outputFilter ); 0172 } 0173 0174 if (!data->configSubgroupName.isEmpty() 0175 && (data->type & KDevelop::IOutputView::MultipleView || data->type & KDevelop::IOutputView::HistoryView)) { 0176 m_outputWidgetConfig = new OutputWidgetConfig(data->configSubgroupName, data->title, this); 0177 connect(m_outputWidgetConfig, &OutputWidgetConfig::settingsChanged, this, [this]() { 0178 const auto maxViewCount = m_outputWidgetConfig->maxViewCount(); 0179 // don't close views when view limit is not enabled 0180 if (!maxViewCount.has_value()) { 0181 return; 0182 } 0183 if (data->type & KDevelop::IOutputView::MultipleView) { 0184 closeFirstViewsWhileTooMany(*m_tabwidget, *maxViewCount); 0185 } else { 0186 closeFirstViewsWhileTooMany(*m_stackwidget, *maxViewCount); 0187 } 0188 }); 0189 0190 QAction* const openConfigAction = KStandardAction::preferences( 0191 m_outputWidgetConfig, 0192 [this]() { 0193 m_outputWidgetConfig->openDialog(this); 0194 }, 0195 this); 0196 openConfigAction->setText( 0197 i18nc("@action %1: output type, e.g. Build or Run", "Configure %1 Output", data->title)); 0198 openConfigAction->setShortcut(QKeySequence()); 0199 openConfigAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); 0200 insertAction(separator, openConfigAction); 0201 } 0202 0203 addActions(data->actionList); 0204 0205 connect( data, &ToolViewData::outputAdded, 0206 this, &OutputWidget::addOutput ); 0207 0208 connect( this, &OutputWidget::outputRemoved, 0209 data->plugin, &StandardOutputView::outputRemoved ); 0210 0211 for (auto it = data->outputdata.keyBegin(), end =data->outputdata.keyEnd(); it != end; ++it) { 0212 int id = *it; 0213 changeModel( id ); 0214 changeDelegate( id ); 0215 } 0216 enableActions(); 0217 } 0218 0219 OutputWidget::~OutputWidget() 0220 { 0221 // Disconnect our widget to prevent updateFilter() slot calling from parent's destructor, 0222 // which leads to segfault since m_views hash will be destroyed before. 0223 if (m_tabwidget) { 0224 m_tabwidget->disconnect(this); 0225 } else if (m_stackwidget) { 0226 m_stackwidget->disconnect(this); 0227 } 0228 } 0229 0230 void OutputWidget::clearModel() 0231 { 0232 auto view = qobject_cast<QAbstractItemView*>(currentWidget()); 0233 if( !view || !view->isVisible()) 0234 return; 0235 0236 KDevelop::OutputModel *outputModel = nullptr; 0237 if (auto proxy = qobject_cast<QAbstractProxyModel*>(view->model())) { 0238 outputModel = qobject_cast<KDevelop::OutputModel*>(proxy->sourceModel()); 0239 } else { 0240 outputModel = qobject_cast<KDevelop::OutputModel*>(view->model()); 0241 } 0242 outputModel->clear(); 0243 } 0244 0245 void OutputWidget::addOutput( int id ) 0246 { 0247 QTreeView* listview = createListView(id); 0248 setCurrentWidget( listview ); 0249 connect( data->outputdata.value(id), &OutputData::modelChanged, this, &OutputWidget::changeModel); 0250 connect( data->outputdata.value(id), &OutputData::delegateChanged, this, &OutputWidget::changeDelegate); 0251 0252 enableActions(); 0253 } 0254 0255 void OutputWidget::setCurrentWidget( QTreeView* view ) 0256 { 0257 if( data->type & KDevelop::IOutputView::MultipleView ) 0258 { 0259 m_tabwidget->setCurrentWidget( view ); 0260 } else if( data->type & KDevelop::IOutputView::HistoryView ) 0261 { 0262 m_stackwidget->setCurrentWidget( view ); 0263 } 0264 } 0265 0266 void OutputWidget::changeDelegate( int id ) 0267 { 0268 const auto viewIt = m_views.constFind(id); 0269 const auto dataIt = data->outputdata.constFind(id); 0270 if (dataIt != data->outputdata.constEnd() && viewIt != m_views.constEnd()) { 0271 (*viewIt).view->setItemDelegate((*dataIt)->delegate); 0272 } else { 0273 addOutput(id); 0274 } 0275 } 0276 0277 void OutputWidget::changeModel( int id ) 0278 { 0279 const auto viewIt = m_views.constFind(id); 0280 const auto dataIt = data->outputdata.constFind(id); 0281 if (dataIt != data->outputdata.constEnd() && viewIt != m_views.constEnd()) { 0282 (*viewIt).view->setModel((*dataIt)->model); 0283 } 0284 else 0285 { 0286 addOutput( id ); 0287 } 0288 } 0289 0290 void OutputWidget::removeOutput( int id ) 0291 { 0292 const auto viewIt = m_views.constFind(id); 0293 if (data->outputdata.contains(id) && (viewIt != m_views.constEnd())) { 0294 auto view = viewIt->view; 0295 if( data->type & KDevelop::IOutputView::MultipleView || data->type & KDevelop::IOutputView::HistoryView ) 0296 { 0297 if( data->type & KDevelop::IOutputView::MultipleView ) 0298 { 0299 int idx = m_tabwidget->indexOf( view ); 0300 if (idx != -1) 0301 { 0302 m_tabwidget->removeTab( idx ); 0303 } 0304 } else 0305 { 0306 int idx = m_stackwidget->indexOf( view ); 0307 if (idx != -1) 0308 { 0309 m_stackwidget->removeWidget(view); 0310 } 0311 } 0312 } else { 0313 // KDevelop::IOutputView::OneView case 0314 // Do nothig here since our single view will be automatically removed from layout 0315 // during it's destroy 0316 } 0317 0318 m_views.erase(viewIt); 0319 // remove our view with proxy model which is view's child (see outputFilter() method). 0320 delete view; 0321 0322 emit outputRemoved( data->toolViewId, id ); 0323 } 0324 enableActions(); 0325 } 0326 0327 bool OutputWidget::closeView(const QWidget* view) 0328 { 0329 Q_ASSERT(view); 0330 Q_ASSERT(qobject_cast<const QAbstractItemView*>(view)); 0331 0332 const auto fvIt = constFindFilteredView(static_cast<const QAbstractItemView*>(view)); 0333 if (fvIt == m_views.cend()) { 0334 return false; 0335 } 0336 0337 const auto id = fvIt.key(); 0338 if (!(data->outputdata.value(id)->behaviour & KDevelop::IOutputView::AllowUserClose)) { 0339 return false; 0340 } 0341 0342 data->plugin->removeOutput(id); 0343 enableActions(); 0344 return true; 0345 } 0346 0347 template<class ViewContainer> 0348 void OutputWidget::closeFirstViewIfTooMany(const ViewContainer& viewContainer) 0349 { 0350 if (!m_outputWidgetConfig) { 0351 return; 0352 } 0353 const auto maxViewCount = m_outputWidgetConfig->maxViewCount(); 0354 Q_ASSERT(!maxViewCount.has_value() || *maxViewCount > 0); 0355 if (maxViewCount.has_value() && viewContainer.count() > *maxViewCount) { 0356 closeView(viewContainer.widget(0)); 0357 } 0358 } 0359 0360 template<class ViewContainer> 0361 void OutputWidget::closeFirstViewsWhileTooMany(const ViewContainer& viewContainer, int maxViewCount) 0362 { 0363 Q_ASSERT(maxViewCount > 0); 0364 while (viewContainer.count() > maxViewCount) { 0365 if (!closeView(viewContainer.widget(0))) { 0366 break; // Closing a view usually succeeds. Prevent endless loop if it fails. 0367 } 0368 } 0369 } 0370 0371 void OutputWidget::closeActiveView() 0372 { 0373 Q_ASSERT(m_tabwidget); 0374 if (const auto* view = m_tabwidget->currentWidget()) { 0375 closeView(view); 0376 } 0377 } 0378 0379 void OutputWidget::closeOtherViews() 0380 { 0381 Q_ASSERT(m_tabwidget); 0382 QWidget* widget = m_tabwidget->currentWidget(); 0383 if (!widget) 0384 return; 0385 0386 const auto ids = m_views.keys(); 0387 for (int id : ids) { 0388 if (m_views.value(id).view == widget) { 0389 continue; // leave the active view open 0390 } 0391 0392 OutputData* od = data->outputdata.value(id); 0393 if (od->behaviour & KDevelop::IOutputView::AllowUserClose) { 0394 data->plugin->removeOutput( id ); 0395 } 0396 } 0397 enableActions(); 0398 } 0399 0400 QWidget* OutputWidget::currentWidget() const 0401 { 0402 QWidget* widget; 0403 if( data->type & KDevelop::IOutputView::MultipleView ) 0404 { 0405 widget = m_tabwidget->currentWidget(); 0406 } else if( data->type & KDevelop::IOutputView::HistoryView ) 0407 { 0408 widget = m_stackwidget->currentWidget(); 0409 } else 0410 { 0411 widget = m_views.begin()->view; 0412 } 0413 return widget; 0414 } 0415 0416 KDevelop::IOutputViewModel *OutputWidget::outputViewModel() const 0417 { 0418 auto view = qobject_cast<QAbstractItemView*>(currentWidget()); 0419 if( !view || !view->isVisible()) 0420 return nullptr; 0421 0422 QAbstractItemModel *absmodel = view->model(); 0423 auto* iface = qobject_cast<KDevelop::IOutputViewModel*>(absmodel); 0424 if ( ! iface ) 0425 { 0426 // try if it's a proxy model? 0427 if ( auto* proxy = qobject_cast<QAbstractProxyModel*>(absmodel) ) 0428 { 0429 iface = qobject_cast<KDevelop::IOutputViewModel*>(proxy->sourceModel()); 0430 } 0431 } 0432 return iface; 0433 } 0434 0435 void OutputWidget::eventuallyDoFocus() 0436 { 0437 QWidget* widget = currentWidget(); 0438 if( m_focusOnSelect->isChecked() && !widget->hasFocus() ) { 0439 widget->setFocus( Qt::OtherFocusReason ); 0440 } 0441 } 0442 0443 QAbstractItemView *OutputWidget::outputView() const 0444 { 0445 return qobject_cast<QAbstractItemView*>(currentWidget()); 0446 } 0447 0448 void OutputWidget::activateIndex(const QModelIndex &index, QAbstractItemView *view, KDevelop::IOutputViewModel *iface) 0449 { 0450 if( ! index.isValid() ) 0451 return; 0452 QModelIndex sourceIndex = index; 0453 QModelIndex viewIndex = index; 0454 const auto fvIt = constFindFilteredView(view); 0455 if (fvIt != m_views.cend() && fvIt->proxyModel) { 0456 auto proxy = fvIt->proxyModel; 0457 if ( index.model() == proxy ) { 0458 // index is from the proxy, map it to the source 0459 sourceIndex = proxy->mapToSource(index); 0460 } else if (proxy == view->model()) { 0461 // index is from the source, map it to the proxy 0462 viewIndex = proxy->mapFromSource(index); 0463 } 0464 } 0465 0466 view->setCurrentIndex( viewIndex ); 0467 view->scrollTo( viewIndex ); 0468 0469 if( m_activateOnSelect->isChecked() ) { 0470 iface->activate( sourceIndex ); 0471 } 0472 } 0473 0474 void OutputWidget::selectFirstItem() 0475 { 0476 selectItem(First); 0477 } 0478 0479 void OutputWidget::selectNextItem() 0480 { 0481 selectItem(Next); 0482 } 0483 0484 void OutputWidget::selectPreviousItem() 0485 { 0486 selectItem(Previous); 0487 } 0488 0489 void OutputWidget::selectLastItem() 0490 { 0491 selectItem(Last); 0492 } 0493 0494 void OutputWidget::selectItem(SelectionMode selectionMode) 0495 { 0496 auto view = outputView(); 0497 auto iface = outputViewModel(); 0498 if ( ! view || ! iface ) 0499 return; 0500 eventuallyDoFocus(); 0501 0502 auto index = view->currentIndex(); 0503 const auto fvIt = constFindFilteredView(view); 0504 if (fvIt != m_views.cend() && fvIt->proxyModel) { 0505 auto proxy = fvIt->proxyModel; 0506 if ( index.model() == proxy ) { 0507 // index is from the proxy, map it to the source 0508 index = proxy->mapToSource(index); 0509 } 0510 } 0511 0512 QModelIndex newIndex; 0513 switch (selectionMode) { 0514 case First: 0515 newIndex = iface->firstHighlightIndex(); 0516 break; 0517 case Next: 0518 newIndex = iface->nextHighlightIndex( index ); 0519 break; 0520 case Previous: 0521 newIndex = iface->previousHighlightIndex( index ); 0522 break; 0523 case Last: 0524 newIndex = iface->lastHighlightIndex(); 0525 break; 0526 } 0527 0528 qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "old:" << index << "- new:" << newIndex; 0529 activateIndex(newIndex, view, iface); 0530 } 0531 0532 void OutputWidget::activate(const QModelIndex& index) 0533 { 0534 auto iface = outputViewModel(); 0535 auto view = outputView(); 0536 if( ! view || ! iface ) 0537 return; 0538 activateIndex(index, view, iface); 0539 } 0540 0541 QTreeView* OutputWidget::createListView(int id) 0542 { 0543 auto createHelper = [&]() -> QTreeView* { 0544 auto* listview = new KDevelop::FocusedTreeView(this); 0545 listview->setEditTriggers( QAbstractItemView::NoEditTriggers ); 0546 listview->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); //Always enable the scrollbar, so it doesn't flash around 0547 listview->setHeaderHidden(true); 0548 listview->setRootIsDecorated(false); 0549 listview->setUniformRowHeights(!m_wordWrap); 0550 listview->setWordWrap(m_wordWrap); 0551 listview->setSelectionMode( QAbstractItemView::ContiguousSelection ); 0552 0553 if (data->outputdata.value(id)->behaviour & KDevelop::IOutputView::AutoScroll) { 0554 listview->setAutoScrollAtEnd(true); 0555 } 0556 0557 connect(listview, &QTreeView::activated, this, &OutputWidget::activate); 0558 connect(listview, &QTreeView::clicked, this, &OutputWidget::activate); 0559 0560 return listview; 0561 }; 0562 0563 QTreeView* listview = nullptr; 0564 const auto viewIt = m_views.constFind(id); 0565 if (viewIt != m_views.constEnd()) { 0566 listview = viewIt->view; 0567 } else { 0568 bool newView = true; 0569 0570 if( data->type & KDevelop::IOutputView::MultipleView || data->type & KDevelop::IOutputView::HistoryView ) 0571 { 0572 qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "creating listview"; 0573 listview = createHelper(); 0574 0575 if( data->type & KDevelop::IOutputView::MultipleView ) 0576 { 0577 m_tabwidget->addTab(listview, data->outputdata.value(id)->title); 0578 closeFirstViewIfTooMany(*m_tabwidget); 0579 } else 0580 { 0581 const int index = m_stackwidget->addWidget(listview); 0582 m_stackwidget->setCurrentIndex(index); 0583 closeFirstViewIfTooMany(*m_stackwidget); 0584 } 0585 } else 0586 { 0587 if( m_views.isEmpty() ) 0588 { 0589 listview = createHelper(); 0590 layout()->addWidget(listview); 0591 } else 0592 { 0593 listview = m_views.cbegin().value().view; 0594 newView = false; 0595 } 0596 } 0597 m_views[id].view = listview; 0598 0599 changeModel( id ); 0600 changeDelegate( id ); 0601 0602 if (newView) 0603 listview->scrollToBottom(); 0604 } 0605 enableActions(); 0606 return listview; 0607 } 0608 0609 void OutputWidget::raiseOutput(int id) 0610 { 0611 const auto viewIt = m_views.constFind(id); 0612 if (viewIt != m_views.constEnd()) { 0613 auto view = viewIt->view; 0614 if( data->type & KDevelop::IOutputView::MultipleView ) 0615 { 0616 int idx = m_tabwidget->indexOf(view); 0617 if( idx >= 0 ) 0618 { 0619 m_tabwidget->setCurrentIndex( idx ); 0620 } 0621 } else if( data->type & KDevelop::IOutputView::HistoryView ) 0622 { 0623 int idx = m_stackwidget->indexOf(view); 0624 if( idx >= 0 ) 0625 { 0626 m_stackwidget->setCurrentIndex( idx ); 0627 } 0628 } 0629 } 0630 enableActions(); 0631 } 0632 0633 void OutputWidget::nextOutput() 0634 { 0635 if( m_stackwidget && m_stackwidget->currentIndex() < m_stackwidget->count()-1 ) 0636 { 0637 m_stackwidget->setCurrentIndex( m_stackwidget->currentIndex()+1 ); 0638 } 0639 enableActions(); 0640 } 0641 0642 void OutputWidget::previousOutput() 0643 { 0644 if( m_stackwidget && m_stackwidget->currentIndex() > 0 ) 0645 { 0646 m_stackwidget->setCurrentIndex( m_stackwidget->currentIndex()-1 ); 0647 } 0648 enableActions(); 0649 } 0650 0651 void OutputWidget::setWordWrap(bool enable) 0652 { 0653 m_wordWrap = enable; 0654 0655 auto* const widget = currentWidget(); 0656 if (!widget) { 0657 return; // this happens in test_standardoutputview when the last view is removed 0658 } 0659 auto* const view = qobject_cast<KDevelop::FocusedTreeView*>(widget); 0660 if (!view) { 0661 qCWarning(PLUGIN_STANDARDOUTPUTVIEW) << "current widget is not a FocusedTreeView:" << widget; 0662 return; 0663 } 0664 0665 if (view->wordWrap() == m_wordWrap) { 0666 Q_ASSERT(view->uniformRowHeights() == !m_wordWrap); 0667 return; // nothing changed 0668 } 0669 0670 view->setUniformRowHeights(!m_wordWrap); 0671 view->setWordWrap(m_wordWrap); 0672 view->fitColumns(); 0673 } 0674 0675 void OutputWidget::enableActions() 0676 { 0677 if( data->type == KDevelop::IOutputView::HistoryView ) 0678 { 0679 Q_ASSERT(m_stackwidget); 0680 Q_ASSERT(m_nextAction); 0681 Q_ASSERT(m_previousAction); 0682 m_previousAction->setEnabled( ( m_stackwidget->currentIndex() > 0 ) ); 0683 m_nextAction->setEnabled( ( m_stackwidget->currentIndex() < m_stackwidget->count() - 1 ) ); 0684 } 0685 } 0686 0687 void OutputWidget::scrollToIndex( const QModelIndex& idx ) 0688 { 0689 QWidget* w = currentWidget(); 0690 if( !w ) 0691 return; 0692 auto *view = static_cast<QAbstractItemView*>(w); 0693 view->scrollTo( idx ); 0694 } 0695 0696 void OutputWidget::copySelection() 0697 { 0698 QWidget* widget = currentWidget(); 0699 if( !widget ) 0700 return; 0701 auto* view = qobject_cast<QAbstractItemView*>(widget); 0702 if( !view ) 0703 return; 0704 0705 QClipboard *cb = QApplication::clipboard(); 0706 const QModelIndexList indexes = view->selectionModel()->selectedRows(); 0707 QStringList content; 0708 content.reserve(indexes.size()); 0709 for (const QModelIndex& index : indexes) { 0710 content += index.data().toString(); 0711 } 0712 cb->setText(content.join(QLatin1Char('\n'))); 0713 } 0714 0715 void OutputWidget::selectAll() 0716 { 0717 if (auto *view = qobject_cast<QAbstractItemView*>(currentWidget())) 0718 view->selectAll(); 0719 } 0720 0721 int OutputWidget::currentOutputIndex() 0722 { 0723 int index = 0; 0724 if( data->type & KDevelop::IOutputView::MultipleView ) 0725 { 0726 index = m_tabwidget->currentIndex(); 0727 } else if( data->type & KDevelop::IOutputView::HistoryView ) 0728 { 0729 index = m_stackwidget->currentIndex(); 0730 } 0731 return index; 0732 } 0733 0734 void OutputWidget::outputFilter(const QString& filter) 0735 { 0736 auto *view = qobject_cast<QAbstractItemView*>(currentWidget()); 0737 if( !view ) 0738 return; 0739 0740 auto fvIt = findFilteredView(view); 0741 auto proxyModel = qobject_cast<QSortFilterProxyModel*>(view->model()); 0742 if( !proxyModel ) 0743 { 0744 // create new proxy model and make view it's parent. This allows us destroy view and 0745 // it's model with "one shot" (see removeOutput() method). 0746 fvIt->proxyModel = proxyModel = new QSortFilterProxyModel(view); 0747 proxyModel->setDynamicSortFilter(true); 0748 proxyModel->setSourceModel(view->model()); 0749 view->setModel(proxyModel); 0750 } 0751 0752 // Don't capture anything as the captures are not used anyway. 0753 QRegularExpression regex(filter, QRegularExpression::CaseInsensitiveOption | QRegularExpression::DontCaptureOption); 0754 fvIt->filter = regex; // store away the original regex to be restored when the current tab/widget changes 0755 0756 if (!regex.isValid()) { 0757 // Setting an invalid regex as the model filter hides all output as expected, but also causes runtime warnings: 0758 // "QString::contains: invalid QRegularExpression object". 0759 // Set a valid regex that matches nothing to hide all output and avoid the warnings. 0760 static const QRegularExpression matchNothing(QStringLiteral("$z")); 0761 regex = matchNothing; 0762 } 0763 proxyModel->setFilterRegularExpression(regex); 0764 0765 updateFilterInputAppearance(constIterator(fvIt)); 0766 } 0767 0768 void OutputWidget::updateFilter(int index) 0769 { 0770 QWidget *view = (data->type & KDevelop::IOutputView::MultipleView) 0771 ? m_tabwidget->widget(index) : m_stackwidget->widget(index); 0772 const auto fvIt = constFindFilteredView(qobject_cast<QAbstractItemView*>(view)); 0773 0774 const QString filterText = fvIt == m_views.cend() ? QString{} : fvIt->filter.pattern(); 0775 if (filterText.isEmpty()) { 0776 m_filterInput->clear(); 0777 } else { 0778 m_filterInput->setText(filterText); 0779 } 0780 0781 updateFilterInputAppearance(fvIt); 0782 } 0783 0784 void OutputWidget::currentViewChanged(int index) 0785 { 0786 if (data->option & KDevelop::IOutputView::AddFilterAction) { 0787 updateFilter(index); 0788 } 0789 0790 // Update wordwrap status for new view 0791 setWordWrap(m_wordWrap); 0792 } 0793 0794 void OutputWidget::setTitle(int outputId, const QString& title) 0795 { 0796 auto fview = m_views.value(outputId, FilteredView{}); 0797 if (fview.view && (data->type & KDevelop::IOutputView::MultipleView)) { 0798 const int idx = m_tabwidget->indexOf(fview.view); 0799 if (idx >= 0) { 0800 m_tabwidget->setTabText(idx, title); 0801 } 0802 } 0803 } 0804 0805 auto OutputWidget::constIterator(FilteredViews::iterator it) -> FilteredViews::const_iterator 0806 { 0807 return static_cast<FilteredViews::const_iterator>(it); 0808 } 0809 0810 template<typename ForwardIt> 0811 ForwardIt OutputWidget::findFilteredView(ForwardIt first, ForwardIt last, const QAbstractItemView* view) 0812 { 0813 return std::find_if(first, last, [view](const FilteredView& filteredView) { 0814 return filteredView.view == view; 0815 }); 0816 } 0817 0818 auto OutputWidget::findFilteredView(const QAbstractItemView* view) -> FilteredViews::iterator 0819 { 0820 return findFilteredView(m_views.begin(), m_views.end(), view); 0821 } 0822 0823 auto OutputWidget::constFindFilteredView(const QAbstractItemView* view) const -> FilteredViews::const_iterator 0824 { 0825 return findFilteredView(m_views.cbegin(), m_views.cend(), view); 0826 } 0827 0828 void OutputWidget::updateFilterInputAppearance(FilteredViews::const_iterator currentView) 0829 { 0830 if (currentView == m_views.cend() || currentView->filter.isValid()) { 0831 m_filterInput->setPalette(QPalette{}); 0832 m_filterInput->setToolTip(validFilterInputToolTip()); 0833 } else { 0834 QPalette background(m_filterInput->palette()); 0835 KColorScheme::adjustBackground(background, KColorScheme::NegativeBackground); 0836 m_filterInput->setPalette(background); 0837 0838 m_filterInput->setToolTip( 0839 i18nc("@info:tooltip %1 - position in the pattern, %2 - textual description of the error", 0840 "Filter regular expression pattern error at offset %1: %2", currentView->filter.patternErrorOffset(), 0841 currentView->filter.errorString())); 0842 } 0843 } 0844 0845 #include "moc_outputwidget.cpp"