|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the test suite of the Qt Toolkit. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
15 ** |
|
16 ** GNU Lesser General Public License Usage |
|
17 ** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 ** General Public License version 2.1 as published by the Free Software |
|
19 ** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 ** packaging of this file. Please review the following information to |
|
21 ** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 ** |
|
24 ** In addition, as a special exception, Nokia gives you certain additional |
|
25 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 ** |
|
28 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
37 ** |
|
38 ** $QT_END_LICENSE$ |
|
39 ** |
|
40 ****************************************************************************/ |
|
41 |
|
42 #include <QCloseEvent> |
|
43 #include <QFileDialog> |
|
44 #include <QFileInfo> |
|
45 #include <QHeaderView> |
|
46 #include <QMenu> |
|
47 #include <QMessageBox> |
|
48 #include <QProcess> |
|
49 #include <QSettings> |
|
50 #include <QTextStream> |
|
51 |
|
52 #include "ASTItem.h" |
|
53 #include "FunctionSignaturesView.h" |
|
54 #include "Global.h" |
|
55 #include "TestCaseView.h" |
|
56 #include "TestResultView.h" |
|
57 #include "TestSuite.h" |
|
58 #include "TreeModel.h" |
|
59 #include "TreeSortFilter.h" |
|
60 #include "UserTestCase.h" |
|
61 |
|
62 #include "MainWindow.h" |
|
63 |
|
64 using namespace QPatternistSDK; |
|
65 |
|
66 MainWindow::MainWindow() : m_userTC(new UserTestCase()), |
|
67 m_currentTC(0) |
|
68 { |
|
69 setupUi(this); |
|
70 |
|
71 /* I want to do this in Designer.. */ |
|
72 testSuiteView->header()->setSortIndicator(0, Qt::AscendingOrder); |
|
73 testSuiteView->header()->setSortIndicatorShown(true); |
|
74 testSuiteView->header()->setClickable(true); |
|
75 |
|
76 setupActions(); |
|
77 |
|
78 QStringList suiteHeaders; |
|
79 suiteHeaders << QLatin1String("Name") |
|
80 << QLatin1String("Pass") |
|
81 << QLatin1String("Fail") |
|
82 << QLatin1String("Total"); |
|
83 |
|
84 TreeSortFilter *const proxy = new TreeSortFilter(this); |
|
85 connect(searchInput, SIGNAL(textChanged(const QString &)), |
|
86 proxy, SLOT(setFilterFixedString(const QString &))); |
|
87 |
|
88 proxy->setSourceModel(new TreeModel(suiteHeaders, this)); |
|
89 testSuiteView->setModel(proxy); |
|
90 |
|
91 /* --------- Test Result View ---------- */ |
|
92 testResultView = new TestResultView(this); |
|
93 testResultView->setAllowedAreas(Qt::AllDockWidgetAreas); |
|
94 addDockWidget(Qt::RightDockWidgetArea, testResultView); |
|
95 /* ----------------------------------- */ |
|
96 |
|
97 /* --------- Test Case View ---------- */ |
|
98 testCaseView = new TestCaseView(this); |
|
99 testCaseView->setAllowedAreas(Qt::AllDockWidgetAreas); |
|
100 addDockWidget(Qt::LeftDockWidgetArea, testCaseView); |
|
101 |
|
102 connect(this, SIGNAL(testCaseSelected(TestCase *const)), |
|
103 testCaseView, SLOT(displayTestCase(TestCase *const))); |
|
104 connect(this, SIGNAL(testCaseSelected(TestCase *const)), |
|
105 testResultView, SLOT(displayTestResult(TestCase *const))); |
|
106 connect(focusURI, SIGNAL(textChanged(const QString &)), |
|
107 m_userTC, SLOT(focusDocumentChanged(const QString &))); |
|
108 /* ----------------------------------- */ |
|
109 |
|
110 /* ----- Function Signature View ----- */ |
|
111 functionView = new FunctionSignaturesView(this); |
|
112 functionView->setAllowedAreas(Qt::AllDockWidgetAreas); |
|
113 addDockWidget(Qt::RightDockWidgetArea, functionView); |
|
114 /* ----------------------------------- */ |
|
115 |
|
116 /* Appears here, because the menu uses actions in the QDockWidgets. */ |
|
117 setupMenu(); |
|
118 |
|
119 readSettings(); |
|
120 |
|
121 /* Connect this after readSettings(), otherwise readSettings() triggers writeSettings(). */ |
|
122 connect(sourceTab, SIGNAL(currentChanged(int)), |
|
123 SLOT(writeSettings())); |
|
124 connect(testSuiteView, SIGNAL(clicked(const QModelIndex &)), |
|
125 SLOT(writeSettings())); |
|
126 connect(sourceInput, SIGNAL(textChanged()), |
|
127 SLOT(writeSettings())); |
|
128 } |
|
129 |
|
130 MainWindow::~MainWindow() |
|
131 { |
|
132 delete m_userTC; |
|
133 } |
|
134 |
|
135 QModelIndex MainWindow::sourceIndex(const QModelIndex &proxyIndex) const |
|
136 { |
|
137 return static_cast<TreeSortFilter *>(testSuiteView->model())->mapToSource(proxyIndex); |
|
138 } |
|
139 |
|
140 TreeModel *MainWindow::sourceModel() const |
|
141 { |
|
142 const TreeSortFilter *const proxy = static_cast<TreeSortFilter *>(testSuiteView->model()); |
|
143 return static_cast<TreeModel *>(proxy->sourceModel()); |
|
144 } |
|
145 |
|
146 void MainWindow::on_testSuiteView_clicked(const QModelIndex &index) |
|
147 { |
|
148 if(index.isValid()) |
|
149 { |
|
150 TestItem *const node = static_cast<TestItem *>(sourceIndex(index).internalPointer()); |
|
151 Q_ASSERT(node); |
|
152 |
|
153 if(node->isFinalNode()) |
|
154 { |
|
155 m_currentTC = static_cast<TestCase *>(node); |
|
156 testCaseSelected(m_currentTC); |
|
157 return; |
|
158 } |
|
159 } |
|
160 |
|
161 /* In all other cases: */ |
|
162 m_currentTC = 0; |
|
163 testCaseSelected(0); |
|
164 } |
|
165 |
|
166 void MainWindow::on_sourceInput_textChanged() |
|
167 { |
|
168 m_userTC->setSourceCode(sourceInput->toPlainText()); |
|
169 } |
|
170 |
|
171 void MainWindow::on_actionOpen_triggered() |
|
172 { |
|
173 const QString fileName(QFileDialog::getOpenFileName(this, |
|
174 QLatin1String("Open Test Suite Catalog"), |
|
175 m_previousOpenedCatalog.toLocalFile(), |
|
176 QLatin1String("Test Suite Catalog file (*.xml)"))); |
|
177 |
|
178 /* "If the user presses Cancel, it returns a null string." */ |
|
179 if(fileName.isNull()) |
|
180 return; |
|
181 |
|
182 m_currentSuiteType = TestSuite::XQuerySuite; |
|
183 openCatalog(QUrl::fromLocalFile(fileName), true, TestSuite::XQuerySuite); |
|
184 } |
|
185 |
|
186 void MainWindow::on_actionOpenXSLTSCatalog_triggered() |
|
187 { |
|
188 const QString fileName(QFileDialog::getOpenFileName(this, |
|
189 QLatin1String("Open Test Suite Catalog"), |
|
190 m_previousOpenedCatalog.toLocalFile(), |
|
191 QLatin1String("Test Suite Catalog file (*.xml)"))); |
|
192 |
|
193 /* "If the user presses Cancel, it returns a null string." */ |
|
194 if(fileName.isNull()) |
|
195 return; |
|
196 |
|
197 m_currentSuiteType = TestSuite::XsltSuite; |
|
198 openCatalog(QUrl::fromLocalFile(fileName), true, TestSuite::XsltSuite); |
|
199 } |
|
200 |
|
201 void MainWindow::on_actionOpenXSDTSCatalog_triggered() |
|
202 { |
|
203 const QString fileName(QFileDialog::getOpenFileName(this, |
|
204 QLatin1String("Open Test Suite Catalog"), |
|
205 m_previousOpenedCatalog.toLocalFile(), |
|
206 QLatin1String("Test Suite Catalog file (*.xml)"))); |
|
207 |
|
208 /* "If the user presses Cancel, it returns a null string." */ |
|
209 if(fileName.isNull()) |
|
210 return; |
|
211 |
|
212 m_currentSuiteType = TestSuite::XsdSuite; |
|
213 openCatalog(QUrl::fromLocalFile(fileName), true, TestSuite::XsdSuite); |
|
214 } |
|
215 |
|
216 void MainWindow::openCatalog(const QUrl &fileName, |
|
217 const bool reportError, |
|
218 const TestSuite::SuiteType suiteType) |
|
219 { |
|
220 setCurrentFile(fileName); |
|
221 m_previousOpenedCatalog = fileName; |
|
222 |
|
223 QString errorMsg; |
|
224 TestSuite *const loadedSuite = TestSuite::openCatalog(fileName, errorMsg, false, suiteType); |
|
225 |
|
226 if(!loadedSuite) |
|
227 { |
|
228 if(reportError) |
|
229 { |
|
230 QMessageBox::information(this, QLatin1String("Failed to load catalog file"), |
|
231 errorMsg, QMessageBox::Ok); |
|
232 } |
|
233 |
|
234 return; |
|
235 } |
|
236 |
|
237 TreeModel *const prevModel = sourceModel(); |
|
238 prevModel->setRoot(loadedSuite); |
|
239 m_currentTC = 0; |
|
240 |
|
241 testCaseCount->setText(QString::number(loadedSuite->resultSummary().second)); |
|
242 /* Switch to the tab containing the loaded test suite. */ |
|
243 sourceTab->setCurrentIndex(0); |
|
244 |
|
245 setWindowTitle(QCoreApplication::applicationName() + |
|
246 QLatin1String(" -- ") + |
|
247 QFileInfo(fileName.toLocalFile()).fileName()); |
|
248 |
|
249 /* @p reportError is set when not auto-loading on startup, and |
|
250 * we only want to save when the user opens from the GUI. */ |
|
251 if(reportError) |
|
252 writeSettings(); |
|
253 } |
|
254 |
|
255 void MainWindow::on_sourceTab_currentChanged(int index) |
|
256 { |
|
257 if(index == 1) |
|
258 { |
|
259 m_currentTC = m_userTC; |
|
260 testCaseSelected(m_userTC); |
|
261 } |
|
262 else |
|
263 on_testSuiteView_clicked(testSuiteView->currentIndex()); |
|
264 } |
|
265 |
|
266 void MainWindow::on_actionExecute_triggered() |
|
267 { |
|
268 Q_ASSERT(testCaseView); |
|
269 TestSuite *const ts = static_cast<TestSuite *>(sourceModel()->root()); |
|
270 |
|
271 const TestItem::ExecutionStage stage = compileOnly->isChecked() ? TestItem::CompileOnly |
|
272 : TestItem::CompileAndRun; |
|
273 |
|
274 m_userTC->setLanguage(isXSLT20->isChecked() ? QXmlQuery::XSLT20 : QXmlQuery::XQuery10); |
|
275 |
|
276 if(m_currentTC) |
|
277 { |
|
278 const TestResult::List rlist(m_currentTC->execute(stage, ts)); |
|
279 Q_ASSERT(rlist.count() == 1); |
|
280 const TestResult *const result = rlist.first(); |
|
281 Q_ASSERT(result); |
|
282 testResultView->displayTestResult(result); |
|
283 } |
|
284 else |
|
285 { |
|
286 const QModelIndexList indexes = testSuiteView->selectionModel()->selectedIndexes(); |
|
287 for (int i = 0; i < indexes.count(); ++i) { |
|
288 const QModelIndex source(sourceIndex(indexes.at(i))); |
|
289 |
|
290 TestItem *const ti = static_cast<TestItem *>(source.internalPointer()); |
|
291 if(!ti) |
|
292 return; |
|
293 |
|
294 /* ti is a TestGroup. It now executes its children, changed(TreeItem *) signals is |
|
295 * emitted which the view receives, and thus updates. */ |
|
296 ti->execute(stage, ts); |
|
297 } |
|
298 } |
|
299 } |
|
300 |
|
301 void MainWindow::readSettings() |
|
302 { |
|
303 QSettings settings; |
|
304 |
|
305 settings.beginGroup(QLatin1String("MainWindow")); |
|
306 restoreState(settings.value(QLatin1String("state")).toByteArray(), Global::versionNumber); |
|
307 resize(settings.value(QLatin1String("size"), QSize(400, 400)).toSize()); |
|
308 move(settings.value(QLatin1String("pos"), QPoint(200, 200)).toPoint()); |
|
309 m_previousOpenedCatalog = settings.value(QLatin1String("PreviousOpenedCatalogFile")).toUrl(); |
|
310 focusURI->setText(settings.value(QLatin1String("focusURI")).toString()); |
|
311 isXSLT20->setChecked(settings.value(QLatin1String("isXSLT20")).toBool()); |
|
312 compileOnly->setChecked(settings.value(QLatin1String("compileOnly")).toBool()); |
|
313 m_currentSuiteType = (TestSuite::SuiteType)settings.value(QLatin1String("PreviousSuiteType"), isXSLT20->isChecked() ? TestSuite::XsltSuite : TestSuite::XQuerySuite).toInt(); |
|
314 |
|
315 /* Open the previously opened catalog. */ |
|
316 if(!m_previousOpenedCatalog.isEmpty()) |
|
317 { |
|
318 openCatalog(m_previousOpenedCatalog, false, m_currentSuiteType); |
|
319 } |
|
320 |
|
321 sourceInput->setPlainText(settings.value(QLatin1String("sourceInput")).toString()); |
|
322 testResultView->resultViewSelection->setCurrentIndex( |
|
323 settings.value(QLatin1String("ResultViewMethod"), 0).toInt()); |
|
324 testResultView->outputStack->setCurrentIndex(settings.value( |
|
325 QLatin1String("ResultViewMethod"), 0).toInt()); |
|
326 |
|
327 /* Restore the selected test case/group. */ |
|
328 const QStringList rows(settings.value(QLatin1String("SelectedTestSuiteRow"), |
|
329 QString()) |
|
330 .toString().split(QLatin1Char(','))); |
|
331 |
|
332 if(!rows.isEmpty()) /* Ok, we have a selection. */ |
|
333 { |
|
334 QAbstractItemModel *const model = testSuiteView->model(); |
|
335 Q_ASSERT(model); |
|
336 QModelIndex p; |
|
337 |
|
338 for(int i = rows.count() - 1; i >= 0; --i) |
|
339 { |
|
340 const QModelIndex childIndex(model->index(rows.at(i).toInt(), 0 , p)); |
|
341 |
|
342 if(childIndex.isValid()) |
|
343 { |
|
344 testSuiteView->scrollTo(p); /* Work around for Qt issue #87575. */ |
|
345 p = childIndex; |
|
346 } |
|
347 } |
|
348 |
|
349 testSuiteView->scrollTo(p); /* Scrolls to it. */ |
|
350 testSuiteView->setCurrentIndex(p); /* Selects it. */ |
|
351 on_testSuiteView_clicked(p); /* Loads the test case in the Test Case View. */ |
|
352 } |
|
353 |
|
354 /* Do it here. In this way the user-entered test case gets selected, if that tab |
|
355 * was previously used. */ |
|
356 sourceTab->setCurrentIndex(settings.value(QLatin1String("SelectedTab"), 0).toInt()); |
|
357 on_sourceTab_currentChanged(sourceTab->currentIndex()); |
|
358 |
|
359 settings.endGroup(); |
|
360 } |
|
361 |
|
362 void MainWindow::writeSettings() |
|
363 { |
|
364 QSettings settings; |
|
365 |
|
366 settings.beginGroup(QLatin1String("MainWindow")); |
|
367 settings.setValue(QLatin1String("state"), saveState(Global::versionNumber)); |
|
368 settings.setValue(QLatin1String("pos"), pos()); |
|
369 settings.setValue(QLatin1String("size"), size()); |
|
370 settings.setValue(QLatin1String("sourceInput"), sourceInput->toPlainText()); |
|
371 settings.setValue(QLatin1String("PreviousOpenedCatalogFile"), m_previousOpenedCatalog); |
|
372 settings.setValue(QLatin1String("PreviousSuiteType"), m_currentSuiteType); |
|
373 settings.setValue(QLatin1String("SelectedTab"), sourceTab->currentIndex()); |
|
374 settings.setValue(QLatin1String("ResultViewMethod"), |
|
375 testResultView->resultViewSelection->currentIndex()); |
|
376 settings.setValue(QLatin1String("focusURI"), |
|
377 focusURI->text()); |
|
378 settings.setValue(QLatin1String("isXSLT20"), |
|
379 isXSLT20->isChecked()); |
|
380 settings.setValue(QLatin1String("compileOnly"), |
|
381 compileOnly->isChecked()); |
|
382 |
|
383 /* Store the selected test case/group. */ |
|
384 QModelIndex selected(sourceIndex(testSuiteView->currentIndex())); |
|
385 if(selected.isValid()) |
|
386 { |
|
387 QString result; |
|
388 |
|
389 do |
|
390 { |
|
391 result.append(QString::number(selected.row())); |
|
392 selected = selected.parent(); |
|
393 |
|
394 if(selected.isValid()) |
|
395 result.append(QLatin1Char(',')); |
|
396 else |
|
397 break; |
|
398 } |
|
399 while(true); |
|
400 |
|
401 settings.setValue(QLatin1String("SelectedTestSuiteRow"), result); |
|
402 } |
|
403 |
|
404 settings.endGroup(); |
|
405 } |
|
406 |
|
407 void MainWindow::setCurrentFile(const QUrl &f) |
|
408 { |
|
409 const QString fileName(f.toLocalFile()); |
|
410 QSettings settings; |
|
411 settings.beginGroup(QLatin1String("MainWindow")); |
|
412 QStringList files(settings.value(QLatin1String("RecentFileList")).toStringList()); |
|
413 |
|
414 files.removeAll(fileName); |
|
415 files.prepend(fileName); |
|
416 while(files.size() > MaximumRecentFiles) |
|
417 files.removeLast(); |
|
418 |
|
419 settings.setValue(QLatin1String("RecentFileList"), files); |
|
420 settings.endGroup(); |
|
421 |
|
422 updateRecentFileActions(); |
|
423 } |
|
424 |
|
425 void MainWindow::updateRecentFileActions() |
|
426 { |
|
427 QSettings settings; |
|
428 settings.beginGroup(QLatin1String("MainWindow")); |
|
429 const QStringList files(settings.value(QLatin1String("RecentFileList")).toStringList()); |
|
430 settings.endGroup(); |
|
431 |
|
432 const int numRecentFiles = qMin(files.size(), static_cast<int>(MaximumRecentFiles)); |
|
433 |
|
434 for(int i = 0; i < numRecentFiles; ++i) |
|
435 { |
|
436 const QString text(QString::fromLatin1("&%1 %2").arg(i + 1).arg(QFileInfo(files[i]).filePath())); |
|
437 m_recentFileActs[i]->setText(text); |
|
438 m_recentFileActs[i]->setData(QUrl::fromLocalFile(files[i])); |
|
439 m_recentFileActs[i]->setVisible(true); |
|
440 } |
|
441 |
|
442 for(int j = numRecentFiles; j < MaximumRecentFiles; ++j) |
|
443 m_recentFileActs[j]->setVisible(false); |
|
444 } |
|
445 |
|
446 void MainWindow::openRecentFile() |
|
447 { |
|
448 const QAction *const action = qobject_cast<QAction *>(sender()); |
|
449 if(action) |
|
450 openCatalog(action->data().toUrl(), true, TestSuite::XQuerySuite); |
|
451 } |
|
452 |
|
453 void MainWindow::closeEvent(QCloseEvent *ev) |
|
454 { |
|
455 writeSettings(); |
|
456 ev->accept(); |
|
457 } |
|
458 |
|
459 void MainWindow::setupActions() |
|
460 { |
|
461 connect(actionQuit, SIGNAL(triggered()), qApp, SLOT(closeAllWindows())); |
|
462 |
|
463 for(int i = 0; i < MaximumRecentFiles; ++i) |
|
464 { |
|
465 m_recentFileActs[i] = new QAction(this); |
|
466 m_recentFileActs[i]->setVisible(false); |
|
467 connect(m_recentFileActs[i], SIGNAL(triggered()), |
|
468 this, SLOT(openRecentFile())); |
|
469 } |
|
470 } |
|
471 |
|
472 void MainWindow::setupMenu() |
|
473 { |
|
474 QMenu *const menFile = findChild<QMenu *>(QLatin1String("menuFile")); |
|
475 Q_ASSERT(menFile); |
|
476 QAction *const actOpen = findChild<QAction *>(QLatin1String("actionExecute")); |
|
477 Q_ASSERT(actOpen); |
|
478 QMenu *const recent = new QMenu(QLatin1String("O&pen Recent"), this); |
|
479 |
|
480 menFile->insertMenu(actOpen, recent); |
|
481 menFile->insertSeparator(actOpen); |
|
482 |
|
483 for(int i = 0; i < MaximumRecentFiles; ++i) |
|
484 recent->addAction(m_recentFileActs[i]); |
|
485 |
|
486 updateRecentFileActions(); |
|
487 |
|
488 QMenu *const menWindows = findChild<QMenu *>(QLatin1String("menuWindows")); |
|
489 Q_ASSERT(menWindows); |
|
490 |
|
491 menWindows->addAction(testCaseView->toggleViewAction()); |
|
492 menWindows->addAction(testResultView->toggleViewAction()); |
|
493 menWindows->addAction(functionView->toggleViewAction()); |
|
494 } |
|
495 |
|
496 void MainWindow::on_actionRestart_triggered() |
|
497 { |
|
498 if(QProcess::startDetached(QCoreApplication::applicationFilePath())) |
|
499 QApplication::closeAllWindows(); |
|
500 else |
|
501 { |
|
502 QTextStream err(stderr); |
|
503 err << "Failed to start " << qPrintable(QCoreApplication::applicationFilePath()) << endl; |
|
504 } |
|
505 } |
|
506 |
|
507 |
|
508 // vim: et:ts=4:sw=4:sts=4 |