1 /* KDevelop xUnit plugin
3 * Copyright 2006 Ernst Huber <qxrunner@systest.ch>
4 * Copyright 2008 Manuel Breugelmans <mbr.nxi@gmail.com>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 #include "runnerwindow.h"
24 #include "ui_runnerwindow.h"
26 #include "interfaces/iproject.h"
28 #include "resultsmodel.h"
29 #include "resultsproxymodel.h"
30 #include "runnermodel.h"
31 #include "runnerproxymodel.h"
32 #include "selectionmanager.h"
33 #include "verbosemanager.h"
34 #include "verbosetoggle.h"
35 #include "selectiontoggle.h"
36 #include "testexecutor.h"
38 #include <ktexteditor/cursor.h>
39 #include "interfaces/icore.h"
40 #include "interfaces/idocumentcontroller.h"
44 #include "resultswidget.h"
46 #include <QMessageBox>
47 #include <QCloseEvent>
50 #include <KActionMenu>
51 #include <KSelectAction>
54 #include <KColorScheme>
57 #include <QHeaderView>
59 using KDevelop::IProject
;
60 Q_DECLARE_METATYPE(KDevelop::IProject
*)
62 static void initVeritasResource()
64 Q_INIT_RESOURCE(qxrunner
);
67 using KDevelop::ICore
;
68 using KDevelop::IDocumentController
;
70 using Veritas::RunnerWindow
;
71 using Veritas::RunnerModel
;
72 using Veritas::RunnerProxyModel
;
73 using Veritas::ResultsModel
;
74 using Veritas::ResultsProxyModel
;
75 using Veritas::VerboseManager
;
76 using Veritas::TestExecutor
;
79 const Ui::RunnerWindow
* RunnerWindow::ui() const
84 RunnerWindow::RunnerWindow(ResultsModel
* rmodel
, QWidget
* parent
, Qt::WFlags flags
)
85 : QWidget(parent
, flags
), m_executor(0), m_isRunning(false)
87 initVeritasResource();
88 m_ui
= new Ui::RunnerWindow
;
90 m_results
= new ResultsWidget();
91 runnerView()->setRootIsDecorated(false);
92 runnerView()->setUniformRowHeights(true);
96 ui()->progressRun
->setTextVisible(false);
97 ui()->progressRun
->show();
100 // Disable user interaction while there is no data.
101 enableItemActions(false);
103 initItemStatistics();
105 runnerView()->setMouseTracking(true);
107 ResultsProxyModel
* rproxy
= new ResultsProxyModel(this);
108 rproxy
->setSourceModel(rmodel
);
109 int filter
= Veritas::RunError
| Veritas::RunFatal
| Veritas::RunInfo
;
110 rproxy
->setFilter(filter
); // also updates the view
111 resultsView()->setModel(rproxy
);
112 m_results
->setResizeMode();
114 m_selection
= new SelectionManager(runnerView());
115 SelectionToggle
* selectionToggle
= new SelectionToggle(runnerView()->viewport());
116 m_selection
->setButton(selectionToggle
);
117 m_verbose
= new VerboseManager(runnerView());
118 VerboseToggle
* verboseToggle
= new VerboseToggle(runnerView()->viewport());
119 m_verbose
->setButton(verboseToggle
);
121 QPixmap refresh
= KIconLoader::global()->loadIcon("view-refresh", KIconLoader::Small
);
122 m_ui
->actionReload
->setIcon(refresh
);
123 QPixmap run
= KIconLoader::global()->loadIcon("arrow-right", KIconLoader::Small
);
124 m_ui
->actionStart
->setIcon(run
);
125 QPixmap stop
= KIconLoader::global()->loadIcon("window-close", KIconLoader::Small
);
126 m_ui
->actionStop
->setIcon(stop
);
127 QPixmap select
= KIconLoader::global()->loadIcon("list-add", KIconLoader::Small
);
128 m_ui
->actionSelectAll
->setIcon(select
);
129 QPixmap deselect
= KIconLoader::global()->loadIcon("list-remove", KIconLoader::Small
);
130 m_ui
->actionUnselectAll
->setIcon(deselect
);
132 runnerView()->setStyleSheet(
138 connect(runnerView(), SIGNAL(clicked(QModelIndex
)),
139 SLOT(expandOrCollapse(QModelIndex
)));
141 const char* whatsthis
= "xTest runner. First select a project from the rightmost dropdown box. Next, load the test tree by clicking on the green circular arrow icon. Run your tests with a click on the leftmost green arrow icon.";
142 setWhatsThis( i18n(whatsthis
) );
143 resultsView()->setWhatsThis( i18n(whatsthis
) );
145 runnerView()->setSelectionMode(QAbstractItemView::SingleSelection
);
146 runnerView()->setSelectionBehavior(QAbstractItemView::SelectRows
);
149 // helper for RunnerWindow(...)
150 void RunnerWindow::addProjectMenu()
152 KSelectAction
*m
= new KSelectAction(i18n("Project"), this);
153 m
->setToolTip(i18n("Select project"));
154 m
->setToolBarMode(KSelectAction::MenuMode
);
155 m
->setEditable(true);
156 m_ui
->runnerToolBar
->addSeparator();
157 m_ui
->runnerToolBar
->addAction(m
);
161 void RunnerWindow::addProjectToPopup(IProject
* proj
)
163 kDebug() << "Adding project to popup " << proj
->name();
164 QAction
* p
= new QAction(proj
->name(), this);
168 m_projectPopup
->addAction(p
);
169 m_project2action
[proj
] = p
;
172 void RunnerWindow::rmProjectFromPopup(IProject
* proj
)
174 if (m_project2action
.contains(proj
)) {
175 QAction
* p
= m_project2action
[proj
];
176 m_projectPopup
->removeAction(p
);
177 m_project2action
.remove(proj
);
183 /*! functor that counts the selected leaf tests */
184 class SelectedLeafCount
187 SelectedLeafCount() : result(0) {}
188 void operator()(Test
* t
) {
189 if ((t
->childCount() == 0) && t
->internal()->isChecked()) {
198 void RunnerWindow::resetProgressBar() const
200 ui()->progressRun
->setValue(0);
201 ui()->progressRun
->update();
203 SelectedLeafCount slf
;
204 traverseTree(runnerModel()->rootItem(), slf
);
205 if (slf
.result
== 0) slf
.result
++; // 0 results in an indeterminate progressbar, not good
206 ui()->progressRun
->setMaximum(slf
.result
);
208 ui()->progressRun
->setMaximum(1);
212 // helper for RunnerWindow(...)
213 void RunnerWindow::initItemStatistics()
217 displayNumExceptions(0);
220 // helper for RunnerWindow(...)
221 void RunnerWindow::connectFocusStuff()
223 // To keep the views synchronized when there are highlighted rows
224 // which get clicked again..
225 connect(runnerView(), SIGNAL(pressed(const QModelIndex
&)),
226 SLOT(scrollToHighlightedRows()));
227 connect(resultsView(), SIGNAL(pressed(const QModelIndex
&)),
228 SLOT(scrollToHighlightedRows()));
231 KSelectAction
* RunnerWindow::projectPopup() const
233 return m_projectPopup
;
236 // helper for RunnerWindow(...) ctor
237 void RunnerWindow::connectActions()
240 connect(m_ui
->actionExit
, SIGNAL(triggered(bool)), SLOT(close()));
242 m_ui
->actionStart
->setShortcut(QKeySequence(tr("Ctrl+R")));
243 connect(m_ui
->actionStart
, SIGNAL(triggered(bool)), SLOT(runItems()));
244 m_ui
->actionStop
->setShortcut(QKeySequence(tr("Ctrl+K")));
245 connect(m_ui
->actionStop
, SIGNAL(triggered(bool)), SLOT(stopItems()));
247 m_ui
->actionSelectAll
->setShortcut(QKeySequence(tr("Ctrl+A")));
248 connect(m_ui
->actionSelectAll
, SIGNAL(triggered(bool)),
249 this, SLOT(selectAll()));
250 m_ui
->actionUnselectAll
->setShortcut(QKeySequence(tr("Ctrl+U")));
251 connect(m_ui
->actionUnselectAll
, SIGNAL(triggered(bool)),
252 this, SLOT(unselectAll()));
253 m_ui
->actionExpandAll
->setShortcut(QKeySequence(tr("Ctrl++")));
254 connect(m_ui
->actionExpandAll
, SIGNAL(triggered(bool)),
255 runnerView(), SLOT(expandAll()));
256 m_ui
->actionCollapseAll
->setShortcut(QKeySequence(tr("Ctrl+-")));
257 connect(m_ui
->actionCollapseAll
, SIGNAL(triggered(bool)),
258 runnerView(), SLOT(collapseAll()));
261 void RunnerWindow::unselectAll()
263 runnerModel()->uncheckAll();
265 runnerView()->viewport()->update();
268 void RunnerWindow::selectAll()
270 runnerModel()->checkAll();
272 runnerView()->viewport()->update();
275 RunnerWindow::~RunnerWindow()
277 // Deleting the model is left to the owner of the model instance.
278 if (m_selection
) delete m_selection
;
279 if (m_verbose
) delete m_verbose
;
284 if (runnerModel()) delete runnerModel();
288 // helper for setModel(RunnerModel*)
289 void RunnerWindow::stopPreviousModel()
291 RunnerModel
* prevModel
= runnerModel();
293 if (m_executor
) m_executor
->stop();
295 RunnerProxyModel
* m1
= runnerProxyModel();
296 runnerView()->setModel(0);
297 runnerView()->reset();
300 resultsModel()->clear();
301 m_results
->setResizeMode();
302 prevModel
->disconnect();
307 // helper for setModel(RunnerModel*)
308 void RunnerWindow::initProxyModels(RunnerModel
* model
)
310 // Proxy models for the views with the source model as their parent
311 // to have the proxies deleted when the model gets deleted.
312 RunnerProxyModel
* proxy
= new RunnerProxyModel(model
);
313 proxy
->setSourceModel(model
);
314 runnerView()->setModel(proxy
);
315 m_stopWatch
= QTime();
318 // helper for setModel(RunnerModel*)
319 void RunnerWindow::connectItemStatistics(RunnerModel
* model
)
322 connect(model
, SIGNAL(numTotalChanged(int)),
323 SLOT(displayNumTotal(int)));
324 connect(model
, SIGNAL(numSelectedChanged(int)),
325 SLOT(displayNumSelected(int)));
326 connect(model
, SIGNAL(numErrorsChanged(int)),
327 SLOT(displayNumErrors(int)));
328 connect(model
, SIGNAL(numFatalsChanged(int)),
329 SLOT(displayNumFatals(int)));
330 connect(model
, SIGNAL(numExceptionsChanged(int)),
331 SLOT(displayNumExceptions(int)));
333 model
->countItems(); // this fires the signals connected above.
336 // helper for setModel(RunnerModel*)
337 void RunnerWindow::connectProgressIndicators(RunnerModel
* model
)
339 // Get notified of items run.
340 connect(model
, SIGNAL(numStartedChanged(int)),
341 SLOT(displayProgress(int)));
342 connect(model
, SIGNAL(numCompletedChanged(int)),
343 SLOT(displayNumCompleted(int)));
344 connect(model
, SIGNAL(allItemsCompleted()),
345 SLOT(displayCompleted()));
346 connect(model
, SIGNAL(itemCompleted(QModelIndex
)),
347 resultsModel(), SLOT(addResult(QModelIndex
)));
350 void RunnerWindow::setModel(RunnerModel
* model
)
353 m_selection
->reset();
356 // No user interaction without a model or an empty one
357 enableItemActions(false);
360 initProxyModels(model
);
361 if (model
->rowCount() == 0) {
362 enableItemActions(false);
365 connectItemStatistics(model
);
367 connectProgressIndicators(model
);
368 enableItemActions(true);
369 m_ui
->actionStop
->setDisabled(true);
371 connect(m_selection
, SIGNAL(selectionChanged()),
372 runnerModel(), SLOT(countItems()));
374 // set top row higlighted
375 runnerView()->setCurrentIndex(runnerProxyModel()->index(0, 0));
377 enableTestSync(true);
378 m_verbose
->makeConnections();
379 m_selection
->makeConnections();
380 runnerView()->resizeColumnToContents(0);
384 void RunnerWindow::displayProgress(int numItems
) const
386 // Display only when there are selected items
387 if (ui()->progressRun
->maximum() > 0) {
388 ui()->progressRun
->setValue(numItems
);
392 void RunnerWindow::displayCompleted() const
394 if (!m_isRunning
) return;
395 ui()->progressRun
->setValue(ui()->progressRun
->maximum());
396 enableControlsAfterRunning();
402 void RunnerWindow::displayNumTotal(int numItems
) const
407 void RunnerWindow::displayNumSelected(int numItems
) const
409 if (numItems
== 0) numItems
++;
413 void RunnerWindow::displayNumCompleted(int numItems
) const
415 ui()->labelNumRun
->setText(QString().setNum(numItems
));
419 void RunnerWindow::setGreenBar() const
421 QProgressBar
* bar
= ui()->progressRun
;
423 QString("QProgressBar {"
424 "border: 1px solid grey;"
425 "border-radius: 2px;"
426 "text-align: center;"
428 "QProgressBar::chunk {"
429 "background-color: #009700;"
434 void RunnerWindow::setRedBar() const
436 QProgressBar
* bar
= ui()->progressRun
;
438 QString("QProgressBar {"
439 "border: 1px solid grey;"
440 "border-radius: 2px;"
441 "text-align: center;"
443 "QProgressBar::chunk {"
444 "background-color: #DF1313;"
449 void RunnerWindow::displayNumErrors(int numItems
) const
451 if (numItems
> 0) setRedBar();
454 void RunnerWindow::displayNumFatals(int numItems
) const
456 if (numItems
> 0) setRedBar();
459 void RunnerWindow::displayNumExceptions(int numItems
) const
461 if (numItems
> 0) setRedBar();
466 inline Test
* testFromIndex(const QModelIndex
& index
)
468 return static_cast<Test
*>(index
.internalPointer());
472 void RunnerWindow::syncResultWithTest(const QItemSelection
& selected
,
473 const QItemSelection
& deselected
) const
475 Q_UNUSED(deselected
);
476 QModelIndexList indexes
= selected
.indexes();
477 if (indexes
.count() < 1 || !runnerProxyModel()->index(0, 0).isValid()) {
478 return; // Do nothing when there are no results or no runner item is selected.
481 enableResultSync(false); // Prevent circular reaction
482 QModelIndex testIndex
= runnerProxyModel()->mapToSource(indexes
.first());
483 if (testIndex
.isValid()) {
484 Test
* t
= testFromIndex(testIndex
);
485 resultsProxyModel()->setTestFilter(t
);
487 enableResultSync(true);
490 void RunnerWindow::syncTestWithResult(const QItemSelection
& selected
,
491 const QItemSelection
& deselected
) const
493 Q_UNUSED(deselected
);
494 QModelIndexList indexes
= selected
.indexes();
496 if (indexes
.count() < 1) {
497 return; // Do nothing when no result is selected.
500 // Determine the results model index.
501 QModelIndex resultIndex
;
502 resultIndex
= Utils::modelIndexFromProxy(resultsProxyModel(), indexes
.first());
504 if (resultIndex
.parent().isValid()) {
505 resultIndex
= resultIndex
.parent();
508 // Get the corresponding runner item index contained in the results model.
509 QModelIndex testItemIndex
;
510 testItemIndex
= resultsModel()->mapToTestIndex(resultIndex
);
512 enableTestSync(false); // prevent circular dependencies.
514 // Determine the proxy model index and highlight it.
515 QModelIndex currentIndex
;
516 currentIndex
= Utils::proxyIndexFromModel(runnerProxyModel(), testItemIndex
);
517 runnerView()->setCurrentIndex(currentIndex
);
519 scrollToHighlightedRows(); // Make the row in every tree view visible
520 // and expand corresponding parents.
521 enableTestSync(true); // Enable selection handler again.
524 void RunnerWindow::displayElapsed() const
526 if (m_stopWatch
.isValid()) {
527 int mili
= m_stopWatch
.elapsed();
528 QString elapsed
= QString("%1.%2").arg(int(mili
/1000)).arg(mili
%1000);
529 ui()->labelElapsed
->setText(elapsed
);
531 ui()->labelElapsed
->setText("0.000");
535 void RunnerWindow::scrollToHighlightedRows() const
538 if (runnerView()->selectionMode() == QAbstractItemView::NoSelection
||
539 resultsView()->selectionMode() == QAbstractItemView::NoSelection
) {
540 return; // No relevance when selections not allowed.
543 // Note: It's important not to use the current index but work with the
544 // selection instead due to the fact that these indexes might not be the same.
547 QModelIndexList indexes
;
548 indexes
= runnerView()->selectionModel()->selectedIndexes();
550 if (indexes
.count() > 0) {
551 index
= indexes
.first();
553 if (index
.isValid()) {
554 runnerView()->scrollTo(index
);
557 index
= QModelIndex();
558 indexes
= resultsView()->selectionModel()->selectedIndexes();
560 if (indexes
.count() > 0) {
561 index
= indexes
.first();
563 if (index
.isValid()) {
564 resultsView()->scrollTo(index
);
566 // Try to highlight a result.
567 syncResultWithTest(runnerView()->selectionModel()->selection(),
568 runnerView()->selectionModel()->selection());
572 void RunnerWindow::runItems()
574 if (m_isRunning
|| !runnerModel()->rootItem()) {
581 displayNumCompleted(0);
582 ui()->labelElapsed
->setText("0.000");
584 disableControlsBeforeRunning();
585 resultsModel()->clear();
586 runnerModel()->clearTree();
587 runnerView()->viewport()->update(); // the icons have changed
588 runnerModel()->initCounters();
590 if (m_executor
) delete m_executor
;
591 m_executor
= new TestExecutor
;
592 m_executor
->setRoot(runnerModel()->rootItem());
593 connect(m_executor
, SIGNAL(allDone()), SLOT(displayCompleted()));
598 void RunnerWindow::stopItems()
600 if (!runnerModel() || !m_isRunning
) return;
601 m_ui
->actionStop
->setDisabled(true);
602 if (m_executor
) m_executor
->stop();
604 enableControlsAfterRunning();
608 void RunnerWindow::disableControlsBeforeRunning()
610 enableItemActions(false);
612 m_ui
->actionStop
->setEnabled(true);
613 m_projectPopup
->setEnabled(false);
614 m_ui
->actionReload
->setEnabled(false);
615 runnerView()->setCursor(QCursor(Qt::BusyCursor
));
616 runnerView()->setFocus();
617 runnerView()->setSelectionMode(QAbstractItemView::NoSelection
);
618 resultsView()->setSelectionMode(QAbstractItemView::NoSelection
);
619 enableTestSync(false);
620 enableResultSync(false);
623 void RunnerWindow::enableControlsAfterRunning() const
625 enableItemActions(true);
627 m_ui
->actionStop
->setDisabled(true);
628 m_ui
->actionReload
->setEnabled(true);
629 m_projectPopup
->setEnabled(true);
630 runnerView()->setCursor(QCursor());
631 runnerView()->setFocus();
632 runnerView()->setSelectionMode(QAbstractItemView::SingleSelection
);
633 resultsView()->setSelectionMode(QAbstractItemView::SingleSelection
);
634 enableTestSync(true);
635 enableResultSync(true);
639 void RunnerWindow::enableItemActions(bool enable
) const
641 m_ui
->actionStart
->setEnabled(enable
);
642 m_ui
->actionStop
->setEnabled(enable
);
643 m_ui
->actionSelectAll
->setEnabled(enable
);
644 m_ui
->actionUnselectAll
->setEnabled(enable
);
645 m_ui
->actionExpandAll
->setEnabled(enable
);
646 m_ui
->actionCollapseAll
->setEnabled(enable
);
649 void RunnerWindow::enableTestSync(bool enable
) const
652 connect(runnerView()->selectionModel(),
653 SIGNAL(selectionChanged(QItemSelection
,QItemSelection
)),
654 SLOT(syncResultWithTest(QItemSelection
,QItemSelection
)));
656 disconnect(runnerView()->selectionModel(),
657 SIGNAL(selectionChanged(QItemSelection
,QItemSelection
)),
658 this, SLOT(syncResultWithTest(QItemSelection
,QItemSelection
)));
662 void RunnerWindow::enableToSource() const
664 connect(resultsView()->selectionModel(),
665 SIGNAL(selectionChanged(QItemSelection
,QItemSelection
)),
666 this, SLOT(jumpToSource(QItemSelection
,QItemSelection
)));
669 void RunnerWindow::jumpToSource(const QItemSelection
& selected
, const QItemSelection
& deselected
)
671 Q_UNUSED(deselected
);
672 QModelIndexList indexes
= selected
.indexes();
674 if (indexes
.count() < 1) {
675 return; // Do nothing when no result is selected.
678 // Determine the results model index.
679 QModelIndex resultIndex
;
680 resultIndex
= Utils::modelIndexFromProxy(resultsProxyModel(), indexes
.first());
681 Test
* t
= resultsModel()->testFromIndex(resultIndex
);
682 TestResult
* r
= t
->result();
684 KTextEditor::Cursor
range(r
->line() - 1, 0);
685 IDocumentController
* dc
= ICore::self()->documentController();
686 dc
->openDocument(KUrl(r
->file().pathOrUrl()), range
);
689 void RunnerWindow::enableResultSync(bool enable
) const
692 connect(resultsView()->selectionModel(),
693 SIGNAL(selectionChanged(QItemSelection
,QItemSelection
)),
694 SLOT(syncTestWithResult(QItemSelection
,QItemSelection
)));
696 disconnect(resultsView()->selectionModel(),
697 SIGNAL(selectionChanged(QItemSelection
,QItemSelection
)),
698 this, SLOT(syncTestWithResult(QItemSelection
,QItemSelection
)));
702 void RunnerWindow::displayStatusNum(QLabel
* labelForText
,
703 QLabel
* labelForPic
, int numItems
) const
705 labelForText
->setText(": " + QString().setNum(numItems
));
706 bool visible
= (numItems
> 0);
707 labelForText
->setVisible(visible
);
708 labelForPic
->setVisible(visible
);
711 void RunnerWindow::expandOrCollapse(const QModelIndex
& index
) const
713 runnerView()->isExpanded(index
) ?
714 runnerView()->collapse(index
) :
715 runnerView()->expand(index
);
718 ////////////////// GETTERS /////////////////////////////////////////////////////////////
720 QTreeView
* RunnerWindow::runnerView() const
722 return m_ui
->treeRunner
;
725 QTreeView
* RunnerWindow::resultsView() const
727 return m_results
->tree();
730 QWidget
* RunnerWindow::resultsWidget() const
735 RunnerModel
* RunnerWindow::runnerModel() const
737 RunnerProxyModel
* proxy
= runnerProxyModel();
739 qobject_cast
<RunnerModel
*>(proxy
->sourceModel()) :
743 RunnerProxyModel
* RunnerWindow::runnerProxyModel() const
745 return qobject_cast
<RunnerProxyModel
*>(runnerView()->model());
748 ResultsModel
* RunnerWindow::resultsModel() const
750 ResultsProxyModel
* proxy
= resultsProxyModel();
752 qobject_cast
<ResultsModel
*>(proxy
->sourceModel()) :
756 ResultsProxyModel
* RunnerWindow::resultsProxyModel() const
758 return qobject_cast
<ResultsProxyModel
*>(resultsView()->model());
761 VerboseManager
* RunnerWindow::verboseManager() const