File indexing completed on 2024-05-05 07:44:13
0001 /* Artificial Horizon UI test 0002 SPDX-FileCopyrightText: 2021 Hy Murveit <hy@murveit.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "test_artificial_horizon.h" 0008 0009 #if defined(HAVE_INDI) 0010 0011 #include <QStandardItemModel> 0012 0013 #include "artificialhorizoncomponent.h" 0014 #include "kstars_ui_tests.h" 0015 #include "horizonmanager.h" 0016 #include "linelist.h" 0017 #include "skycomponents/skymapcomposite.h" 0018 #include "skymap.h" 0019 #include "test_ekos.h" 0020 0021 TestArtificialHorizon::TestArtificialHorizon(QObject *parent) : QObject(parent) 0022 { 0023 } 0024 0025 void TestArtificialHorizon::initTestCase() 0026 { 0027 // HACK: Reset clock to initial conditions 0028 KHACK_RESET_EKOS_TIME(); 0029 } 0030 0031 void TestArtificialHorizon::cleanupTestCase() 0032 { 0033 } 0034 0035 void TestArtificialHorizon::init() 0036 { 0037 0038 } 0039 0040 void TestArtificialHorizon::cleanup() 0041 { 0042 0043 } 0044 0045 void TestArtificialHorizon::testArtificialHorizon_data() 0046 { 0047 0048 } 0049 0050 namespace 0051 { 0052 0053 void skyClick(SkyMap *sky, int x, int y) 0054 { 0055 QTest::mouseClick(sky->viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(x, y), 100); 0056 } 0057 0058 // Returns the QModelIndex for the nth point in the mth region 0059 QModelIndex pointIndex(QStandardItemModel *model, int region, int point) 0060 { 0061 QModelIndex badIndex; 0062 if (model == nullptr) 0063 return badIndex; 0064 auto reg = model->item(region, 0); 0065 if (reg == nullptr) 0066 return badIndex; 0067 auto child = reg->child(point, 1); 0068 if (child == nullptr) 0069 return badIndex; 0070 return child->index(); 0071 } 0072 0073 // Returns the QModelIndex for the nth region . 0074 QModelIndex regionIndex(QStandardItemModel *model, int region) 0075 { 0076 QModelIndex badIndex; 0077 if (model == nullptr) 0078 return badIndex; 0079 QModelIndex idx = model->index(region, 0); 0080 return idx; 0081 } 0082 0083 // Simulates a left mouse clock on the given view. 0084 // The click is centered vertically, and offset by leftOffset from the left size of the view. 0085 bool clickView(QAbstractItemView *view, const QModelIndex &idx, int leftOffset) 0086 { 0087 if (!idx.isValid()) 0088 return false; 0089 QPoint itemPtCenter = view->visualRect(idx).center(); 0090 if (itemPtCenter.isNull()) 0091 return false; 0092 QPoint itemPtLeft = itemPtCenter; 0093 itemPtLeft.setX(view->visualRect(idx).left() + leftOffset); 0094 QTest::mouseClick(view->viewport(), Qt::LeftButton, Qt::NoModifier, itemPtLeft); 0095 return true; 0096 } 0097 0098 // Simulates a click on the enable checkbox for the region view. 0099 bool clickEnableRegion(QAbstractItemView *view, int region) 0100 { 0101 QStandardItemModel *model = static_cast<QStandardItemModel*>(view->model()); 0102 const QModelIndex index = regionIndex(model, region); 0103 if (!index.isValid()) return false; 0104 // The checkbox is on the far left, so left offset is 5. 0105 return clickView(view, index, 5); 0106 } 0107 0108 // Simulates a click on the nth region, in the region view. 0109 bool clickSelectRegion(QAbstractItemView *view, int region) 0110 { 0111 QStandardItemModel *model = static_cast<QStandardItemModel*>(view->model()); 0112 const QModelIndex index = regionIndex(model, region); 0113 if (!index.isValid()) return false; 0114 // Clicks near the middle of the box (left offset of 50). 0115 return clickView(view, index, 50); 0116 } 0117 0118 // Simulates a click on the nth point, in the point list view. 0119 bool clickSelectPoint(QAbstractItemView *view, int region, int point) 0120 { 0121 QStandardItemModel *model = static_cast<QStandardItemModel*>(view->model()); 0122 const QModelIndex index = pointIndex(model, region, point); 0123 // Clicks near the middle of he box (left offset of 50). 0124 return clickView(view, index, 50); 0125 } 0126 0127 #if 0 0128 // Debugging printout. Prints the list of az/alt points in a region. 0129 bool printAzAlt(QStandardItemModel *model, int region) 0130 { 0131 if (model->rowCount() <= region) 0132 return false; 0133 0134 const auto reg = model->item(region); 0135 const int numPoints = reg->rowCount(); 0136 for (int i = 0; i < numPoints; ++i) 0137 { 0138 QStandardItem *azItem = reg->child(i, 1); 0139 QStandardItem *altItem = reg->child(i, 2); 0140 0141 const dms az = dms::fromString(azItem->data(Qt::DisplayRole).toString(), true); 0142 const dms alt = dms::fromString(altItem->data(Qt::DisplayRole).toString(), true); 0143 0144 fprintf(stderr, "az %f alt %f\n", az.Degrees(), alt.Degrees()); 0145 } 0146 return true; 0147 } 0148 #endif 0149 0150 } // namespace 0151 0152 // Returns the nth region. 0153 QStandardItem *TestArtificialHorizon::getRegion(int region) 0154 { 0155 return m_Model->item(region); 0156 } 0157 0158 0159 // Creates a list of SkyPoints corresponding to the points in the nth region. 0160 QList<SkyPoint> TestArtificialHorizon::getRegionPoints(int region) 0161 { 0162 const auto reg = getRegion(region); 0163 const int numPoints = reg->rowCount(); 0164 QList<SkyPoint> pts; 0165 for (int i = 0; i < numPoints; ++i) 0166 { 0167 QStandardItem *azItem = reg->child(i, 1); 0168 QStandardItem *altItem = reg->child(i, 2); 0169 const dms az = dms::fromString(azItem->data(Qt::DisplayRole).toString(), true); 0170 const dms alt = dms::fromString(altItem->data(Qt::DisplayRole).toString(), true); 0171 SkyPoint p; 0172 p.setAz(az); 0173 p.setAlt(alt); 0174 pts.append(p); 0175 } 0176 return pts; 0177 } 0178 0179 // Returns true if the SkyList contains the same az/alt points as the region. 0180 bool TestArtificialHorizon::compareLivePreview(int region, SkyList *previewPoints) 0181 { 0182 if (m_Model->rowCount() <= region) 0183 return false; 0184 QList<SkyPoint> regionPoints = getRegionPoints(region); 0185 if (previewPoints->size() != regionPoints.size()) 0186 return false; 0187 for (int i = 0; i < regionPoints.size(); ++i) 0188 { 0189 if (previewPoints->at(i)->az().Degrees() != regionPoints[i].az().Degrees() || 0190 previewPoints->at(i)->alt().Degrees() != regionPoints[i].alt().Degrees()) 0191 return false; 0192 } 0193 return true; 0194 } 0195 0196 // Checks for a testing bug where all az/alt points were repeated. 0197 bool TestArtificialHorizon::checkForRepeatedAzAlt(int region) 0198 { 0199 if (m_Model->rowCount() <= region) 0200 return false; 0201 const auto reg = getRegion(region); 0202 const int numPoints = reg->rowCount(); 0203 double azKeep = 0, altKeep = 0; 0204 for (int i = 0; i < numPoints; ++i) 0205 { 0206 QStandardItem *azItem = reg->child(i, 1); 0207 QStandardItem *altItem = reg->child(i, 2); 0208 0209 const dms az = dms::fromString(azItem->data(Qt::DisplayRole).toString(), true); 0210 const dms alt = dms::fromString(altItem->data(Qt::DisplayRole).toString(), true); 0211 0212 if (i == 0) 0213 { 0214 azKeep = az.Degrees(); 0215 altKeep = alt.Degrees(); 0216 } 0217 else 0218 { 0219 if (az.Degrees() == azKeep || altKeep == alt.Degrees()) 0220 { 0221 fprintf(stderr, "Repeated point in Region %d pt %d: %f %f\n", region, i, az.Degrees(), alt.Degrees()); 0222 return false; 0223 } 0224 } 0225 } 0226 return true; 0227 } 0228 0229 void TestArtificialHorizon::testArtificialHorizon() 0230 { 0231 // Open the Artificial Horizon menu and instantiate the interface. 0232 KStars::Instance()->slotHorizonManager(); 0233 SkyMap *skymap = KStars::Instance()->map(); 0234 0235 ArtificialHorizonComponent *horizonComponent = KStarsData::Instance()->skyComposite()->artificialHorizon(); 0236 0237 // Region buttons 0238 KTRY_AH_GADGET(QPushButton, addRegionB); 0239 KTRY_AH_GADGET(QPushButton, removeRegionB); 0240 KTRY_AH_GADGET(QPushButton, toggleCeilingB); 0241 KTRY_AH_GADGET(QPushButton, saveB); 0242 // Points buttons 0243 KTRY_AH_GADGET(QPushButton, addPointB); 0244 KTRY_AH_GADGET(QPushButton, removePointB); 0245 KTRY_AH_GADGET(QPushButton, clearPointsB); 0246 KTRY_AH_GADGET(QPushButton, selectPointsB); 0247 // Views 0248 KTRY_AH_GADGET(QTableView, pointsList); 0249 KTRY_AH_GADGET(QListView, regionsList); 0250 0251 // This is the underlying data structure for the entire UI. 0252 m_Model = static_cast<QStandardItemModel*>(regionsList->model()); 0253 0254 // There shouldn't be any regions at the start. 0255 QVERIFY(regionsList->model()->rowCount() == 0); 0256 0257 // There should be a live preview. 0258 QVERIFY(!horizonComponent->livePreview.get()); 0259 0260 // Add a region. 0261 KTRY_AH_CLICK(addRegionB); 0262 0263 // There should now be one region, it has no points, is checked, and named "Region 1". 0264 QVERIFY(regionsList->currentIndex().row() == 0); 0265 QVERIFY(getRegion(0)->rowCount() == 0); 0266 QVERIFY(getRegion(0)->checkState() == Qt::Checked); 0267 QVERIFY(m_Model->index(0, 0).data( Qt::DisplayRole ).toString() == QString("Region 1")); 0268 0269 // Check we can toggle on and off "enable" with a mouse click. 0270 QVERIFY(clickEnableRegion(regionsList, 0)); 0271 QVERIFY(getRegion(0)->checkState() == Qt::Unchecked); 0272 QVERIFY(clickEnableRegion(regionsList, 0)); 0273 QVERIFY(getRegion(0)->checkState() == Qt::Checked); 0274 0275 // Mouse-click entry of points shouldn't be enabled yet. 0276 QVERIFY(!selectPointsB->isChecked()); 0277 0278 // Enable mouse-click entry of points 0279 KTRY_AH_CLICK(selectPointsB); 0280 QVERIFY(selectPointsB->isChecked()); 0281 0282 // Add 5 points to the region by clicking on the skymap. 0283 skyClick(skymap, 200, 200); 0284 skyClick(skymap, 250, 250); 0285 skyClick(skymap, 300, 300); 0286 skyClick(skymap, 350, 350); 0287 skyClick(skymap, 400, 400); 0288 0289 // Make sure there are 5 points now for region 0. 0290 QVERIFY(5 == getRegion(0)->rowCount()); 0291 QVERIFY(checkForRepeatedAzAlt(0)); 0292 0293 // Turn this region into a ceiling, check it was noted, and turn that off. 0294 QVERIFY(!getRegion(0)->data(Qt::UserRole).toBool()); 0295 KTRY_AH_CLICK(toggleCeilingB); 0296 QVERIFY(getRegion(0)->data(Qt::UserRole).toBool()); 0297 KTRY_AH_CLICK(toggleCeilingB); 0298 0299 // Add a 2nd region. This also turns of mouse-entry of points. 0300 KTRY_AH_CLICK(addRegionB); 0301 QVERIFY(!selectPointsB->isChecked()); 0302 0303 // The new region shouldn't have any points, but the first should still have 5. 0304 QVERIFY(5 == getRegion(0)->rowCount()); 0305 QVERIFY(0 == getRegion(1)->rowCount()); 0306 0307 // Add 2 points to the 2nd region. 0308 KTRY_AH_CLICK(selectPointsB); 0309 skyClick(skymap, 400, 400); 0310 skyClick(skymap, 450, 450); 0311 QVERIFY(5 == getRegion(0)->rowCount()); 0312 QVERIFY(2 == getRegion(1)->rowCount()); 0313 QVERIFY(checkForRepeatedAzAlt(0)); 0314 QVERIFY(checkForRepeatedAzAlt(1)); 0315 0316 // Make sure the live preview reflects the points in the 2nd region. 0317 QVERIFY(horizonComponent->livePreview.get()); 0318 QVERIFY(compareLivePreview(1, horizonComponent->livePreview->points())); 0319 0320 // The 2nd region should still be the active one. 0321 QVERIFY(1 == regionsList->currentIndex().row()); 0322 0323 // Select the first region, and make sure it becomes active. 0324 QVERIFY(clickSelectRegion(regionsList, 0)); 0325 QVERIFY(0 == regionsList->currentIndex().row()); 0326 0327 // Keep these points for later comparison. 0328 QList<SkyPoint> pointsA = getRegionPoints(0); 0329 0330 // Click on the skymap to add a new point to the first region. 0331 skyClick(skymap, 450, 450); 0332 // The 1st region should now have one more point. 0333 QVERIFY(6 == getRegion(0)->rowCount()); 0334 QVERIFY(2 == getRegion(1)->rowCount()); 0335 0336 // The point should have been appended to the end of the 1st region. 0337 QList<SkyPoint> pointsB = getRegionPoints(0); 0338 QVERIFY(pointsA.size() + 1 == pointsB.size()); 0339 for (int i = 0; i < pointsA.size(); i++) 0340 QVERIFY(pointsA[i] == pointsB[i]); 0341 0342 QVERIFY(checkForRepeatedAzAlt(0)); 0343 QVERIFY(checkForRepeatedAzAlt(1)); 0344 0345 // Make sure the live preview now reflects the points in the 1st region. 0346 QVERIFY(horizonComponent->livePreview.get()); 0347 QVERIFY(compareLivePreview(0, horizonComponent->livePreview->points())); 0348 0349 // Select the 3rd point in the 1st region 0350 QVERIFY(clickSelectPoint(pointsList, 0, 2)); 0351 QVERIFY(2 == pointsList->currentIndex().row()); 0352 0353 // Copy the points for later comparison. 0354 pointsA = getRegionPoints(0); 0355 0356 // Insert a point after the (just selected) 3rd point by clicking on the SkyMap. 0357 skyClick(skymap, 375, 330); 0358 QVERIFY(7 == getRegion(0)->rowCount()); 0359 QVERIFY(2 == getRegion(1)->rowCount()); 0360 0361 // The new point should have been place in the middle. 0362 pointsB = getRegionPoints(0); 0363 QVERIFY(pointsA.size() + 1 == pointsB.size()); 0364 for (int i = 0; i < 3; i++) 0365 QVERIFY(pointsA[i] == pointsB[i]); 0366 for (int i = 3; i < pointsA.size(); i++) 0367 QVERIFY(pointsA[i] == pointsB[i + 1]); 0368 0369 QVERIFY(checkForRepeatedAzAlt(0)); 0370 QVERIFY(checkForRepeatedAzAlt(1)); 0371 0372 // Select the 5th point in the 1st region and delete it. 0373 QVERIFY(clickSelectPoint(pointsList, 0, 4)); 0374 QVERIFY(4 == pointsList->currentIndex().row()); 0375 KTRY_AH_CLICK(removePointB); 0376 0377 // There should now be one less point in the 1st region. 0378 QVERIFY(6 == getRegion(0)->rowCount()); 0379 QVERIFY(2 == getRegion(1)->rowCount()); 0380 0381 QVERIFY(checkForRepeatedAzAlt(0)); 0382 QVERIFY(checkForRepeatedAzAlt(1)); 0383 0384 // Clear all the points in the (currently selected) 1st region. 0385 KTRY_AH_CLICK(clearPointsB); 0386 QVERIFY(0 == getRegion(0)->rowCount()); 0387 QVERIFY(2 == getRegion(1)->rowCount()); 0388 0389 // Remove the original 1st region. The 2nd region becomes the 1st one. 0390 pointsA = getRegionPoints(1); 0391 KTRY_AH_CLICK(removeRegionB); 0392 QVERIFY(2 == getRegion(0)->rowCount()); 0393 pointsB = getRegionPoints(0); 0394 QVERIFY(pointsA == pointsB); 0395 0396 QVERIFY(checkForRepeatedAzAlt(0)); 0397 0398 // Apply, then close the Artificial Horizon menu. 0399 0400 // Equivalent to clicking the apply button. 0401 KStars::Instance()->m_HorizonManager->slotSaveChanges(); 0402 // same as clicking X to close the window. 0403 KStars::Instance()->m_HorizonManager->close(); 0404 0405 // Should no longer have a live preview. 0406 QVERIFY(!horizonComponent->livePreview.get()); 0407 0408 // Re-open the menu. 0409 KStars::Instance()->slotHorizonManager(); 0410 0411 // The 2-point region should still be there. 0412 QVERIFY(regionsList->model()->rowCount() == 1); 0413 QVERIFY(2 == getRegion(0)->rowCount()); 0414 0415 // There should be a live preview again. 0416 QVERIFY(horizonComponent->livePreview.get()); 0417 QVERIFY(compareLivePreview(0, horizonComponent->livePreview->points())); 0418 0419 // This section tests to make sure that, when a region is enabled, 0420 // the horizon component's isVisible() method reflects the values 0421 // of the region. Will test using the 2 points of the saved region above. 0422 0423 // Get the approximate azimuth and altitude at the midpoint of the 2-point region. 0424 pointsA = getRegionPoints(0); 0425 QVERIFY(2 == pointsA.size()); 0426 const double az = (pointsA[0].az().Degrees() + pointsA[1].az().Degrees()) / 2.0; 0427 const double alt = (pointsA[0].alt().Degrees() + pointsA[1].alt().Degrees()) / 2.0; 0428 0429 // Make sure the region is enabled. 0430 const auto state = getRegion(0)->checkState(); 0431 if (state != Qt::Checked) 0432 { 0433 QVERIFY(clickEnableRegion(regionsList, 0)); 0434 QVERIFY(getRegion(0)->checkState() == Qt::Checked); 0435 } 0436 0437 // Same as clicking the apply button. 0438 KStars::Instance()->m_HorizonManager->slotSaveChanges(); 0439 0440 // Verify that isVisible() roughly reflects the region's limit. 0441 QVERIFY(horizonComponent->getHorizon().isVisible(az, alt + 5)); 0442 QVERIFY(!horizonComponent->getHorizon().isVisible(az, alt - 5)); 0443 0444 // Turn the line into a ceiling line. The visibility should be reversed. 0445 KTRY_AH_CLICK(toggleCeilingB); 0446 KStars::Instance()->m_HorizonManager->slotSaveChanges(); 0447 QVERIFY(!horizonComponent->getHorizon().isVisible(az, alt + 5)); 0448 QVERIFY(horizonComponent->getHorizon().isVisible(az, alt - 5)); 0449 // and turn ceiling off for that line, and the original visibility returns. 0450 KTRY_AH_CLICK(toggleCeilingB); 0451 KStars::Instance()->m_HorizonManager->slotSaveChanges(); 0452 QVERIFY(horizonComponent->getHorizon().isVisible(az, alt + 5)); 0453 QVERIFY(!horizonComponent->getHorizon().isVisible(az, alt - 5)); 0454 0455 // Now Disable the constraint. 0456 QVERIFY(clickEnableRegion(regionsList, 0)); 0457 QVERIFY(getRegion(0)->checkState() == Qt::Unchecked); 0458 // Same as clicking the apply button. 0459 KStars::Instance()->m_HorizonManager->slotSaveChanges(); 0460 // The constraint should be at -90 when not enabled. 0461 QVERIFY(horizonComponent->getHorizon().isVisible(az, -89)); 0462 0463 // Finally enable the region again, click apply, and exit the module. 0464 // The constraint should still be there, even with the menu closed. 0465 QVERIFY(clickEnableRegion(regionsList, 0)); 0466 QVERIFY(getRegion(0)->checkState() == Qt::Checked); 0467 KStars::Instance()->m_HorizonManager->slotSaveChanges(); 0468 KStars::Instance()->m_HorizonManager->close(); 0469 QVERIFY(horizonComponent->getHorizon().isVisible(az, alt + 5)); 0470 QVERIFY(!horizonComponent->getHorizon().isVisible(az, alt - 5)); 0471 } 0472 0473 QTEST_KSTARS_MAIN(TestArtificialHorizon) 0474 0475 #endif // HAVE_INDI