File indexing completed on 2024-04-21 14:45:53
0001 /* 0002 SPDX-FileCopyrightText: 2004 Jasem Mutlaq <mutlaqja@ikarustech.com> 0003 0004 2006-03-03 Using CFITSIO, Porting to Qt4 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "fitsviewer.h" 0010 0011 #include "config-kstars.h" 0012 0013 #include "fitsdata.h" 0014 #include "fitsdebayer.h" 0015 #include "fitstab.h" 0016 #include "fitsview.h" 0017 #include "kstars.h" 0018 #include "ksutils.h" 0019 #include "Options.h" 0020 #ifdef HAVE_INDI 0021 #include "indi/indilistener.h" 0022 #endif 0023 0024 #include <KActionCollection> 0025 #include <KMessageBox> 0026 #include <KToolBar> 0027 #include <KNotifications/KStatusNotifierItem> 0028 0029 #ifndef KSTARS_LITE 0030 #include "fitshistogrameditor.h" 0031 #endif 0032 0033 #include <fits_debug.h> 0034 0035 #define INITIAL_W 785 0036 #define INITIAL_H 640 0037 0038 bool FITSViewer::m_BlinkBusy = false; 0039 0040 QStringList FITSViewer::filterTypes = 0041 QStringList() << I18N_NOOP("Auto Stretch") << I18N_NOOP("High Contrast") << I18N_NOOP("Equalize") 0042 << I18N_NOOP("High Pass") << I18N_NOOP("Median") << I18N_NOOP("Gaussian blur") 0043 << I18N_NOOP("Rotate Right") << I18N_NOOP("Rotate Left") << I18N_NOOP("Flip Horizontal") 0044 << I18N_NOOP("Flip Vertical"); 0045 0046 FITSViewer::FITSViewer(QWidget *parent) : KXmlGuiWindow(parent) 0047 { 0048 #ifdef Q_OS_OSX 0049 if (Options::independentWindowFITS()) 0050 setWindowFlags(Qt::Window); 0051 else 0052 { 0053 setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint); 0054 connect(QApplication::instance(), SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, 0055 SLOT(changeAlwaysOnTop(Qt::ApplicationState))); 0056 } 0057 #endif 0058 0059 // Since QSharedPointer is managing it, do not delete automatically. 0060 setAttribute(Qt::WA_DeleteOnClose, false); 0061 0062 fitsTabWidget = new QTabWidget(this); 0063 undoGroup = new QUndoGroup(this); 0064 0065 lastURL = QUrl(QDir::homePath()); 0066 0067 fitsTabWidget->setTabsClosable(true); 0068 0069 setWindowIcon(QIcon::fromTheme("kstars_fitsviewer")); 0070 0071 setCentralWidget(fitsTabWidget); 0072 0073 connect(fitsTabWidget, &QTabWidget::currentChanged, this, &FITSViewer::tabFocusUpdated); 0074 connect(fitsTabWidget, &QTabWidget::tabCloseRequested, this, &FITSViewer::closeTab); 0075 0076 //These two connections will enable or disable the scope button if a scope is available or not. 0077 //Of course this is also dependent on the presence of WCS data in the image. 0078 0079 #ifdef HAVE_INDI 0080 connect(INDIListener::Instance(), &INDIListener::newDevice, this, &FITSViewer::updateWCSFunctions); 0081 connect(INDIListener::Instance(), &INDIListener::newDevice, this, &FITSViewer::updateWCSFunctions); 0082 #endif 0083 0084 led.setColor(Qt::green); 0085 0086 fitsPosition.setAlignment(Qt::AlignCenter); 0087 fitsPosition.setMinimumWidth(100); 0088 fitsValue.setAlignment(Qt::AlignCenter); 0089 fitsValue.setMinimumWidth(40); 0090 0091 fitsWCS.setVisible(false); 0092 0093 statusBar()->insertPermanentWidget(FITS_CLIP, &fitsClip); 0094 statusBar()->insertPermanentWidget(FITS_HFR, &fitsHFR); 0095 statusBar()->insertPermanentWidget(FITS_WCS, &fitsWCS); 0096 statusBar()->insertPermanentWidget(FITS_VALUE, &fitsValue); 0097 statusBar()->insertPermanentWidget(FITS_POSITION, &fitsPosition); 0098 statusBar()->insertPermanentWidget(FITS_ZOOM, &fitsZoom); 0099 statusBar()->insertPermanentWidget(FITS_RESOLUTION, &fitsResolution); 0100 statusBar()->insertPermanentWidget(FITS_LED, &led); 0101 0102 QAction *action = actionCollection()->addAction("rotate_right", this, &FITSViewer::rotateCW); 0103 0104 action->setText(i18n("Rotate Right")); 0105 action->setIcon(QIcon::fromTheme("object-rotate-right")); 0106 0107 action = actionCollection()->addAction("rotate_left", this, &FITSViewer::rotateCCW); 0108 action->setText(i18n("Rotate Left")); 0109 action->setIcon(QIcon::fromTheme("object-rotate-left")); 0110 0111 action = actionCollection()->addAction("flip_horizontal", this, &FITSViewer::flipHorizontal); 0112 action->setText(i18n("Flip Horizontal")); 0113 action->setIcon( 0114 QIcon::fromTheme("object-flip-horizontal")); 0115 0116 action = actionCollection()->addAction("flip_vertical", this, &FITSViewer::flipVertical); 0117 action->setText(i18n("Flip Vertical")); 0118 action->setIcon(QIcon::fromTheme("object-flip-vertical")); 0119 0120 action = actionCollection()->addAction("image_histogram"); 0121 action->setText(i18n("Histogram")); 0122 connect(action, &QAction::triggered, this, &FITSViewer::histoFITS); 0123 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_T)); 0124 0125 action->setIcon(QIcon(":/icons/histogram.png")); 0126 0127 action = KStandardAction::open(this, &FITSViewer::openFile, actionCollection()); 0128 action->setIcon(QIcon::fromTheme("document-open")); 0129 0130 action = actionCollection()->addAction("blink"); 0131 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_O + Qt::AltModifier)); 0132 action->setText(i18n("Open/Blink Directory")); 0133 connect(action, &QAction::triggered, this, &FITSViewer::blink); 0134 0135 saveFileAction = KStandardAction::save(this, &FITSViewer::saveFile, actionCollection()); 0136 saveFileAction->setIcon(QIcon::fromTheme("document-save")); 0137 0138 saveFileAsAction = KStandardAction::saveAs(this, &FITSViewer::saveFileAs, actionCollection()); 0139 saveFileAsAction->setIcon( 0140 QIcon::fromTheme("document-save_as")); 0141 0142 action = actionCollection()->addAction("fits_header"); 0143 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_H)); 0144 action->setIcon(QIcon::fromTheme("document-properties")); 0145 action->setText(i18n("FITS Header")); 0146 connect(action, &QAction::triggered, this, &FITSViewer::headerFITS); 0147 0148 action = actionCollection()->addAction("fits_debayer"); 0149 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_D)); 0150 action->setIcon(QIcon::fromTheme("view-preview")); 0151 action->setText(i18n("Debayer...")); 0152 connect(action, &QAction::triggered, this, &FITSViewer::debayerFITS); 0153 0154 action = KStandardAction::close(this, &FITSViewer::close, actionCollection()); 0155 action->setIcon(QIcon::fromTheme("window-close")); 0156 0157 action = KStandardAction::copy(this, &FITSViewer::copyFITS, actionCollection()); 0158 action->setIcon(QIcon::fromTheme("edit-copy")); 0159 0160 action = KStandardAction::zoomIn(this, &FITSViewer::ZoomIn, actionCollection()); 0161 action->setIcon(QIcon::fromTheme("zoom-in")); 0162 0163 action = KStandardAction::zoomOut(this, &FITSViewer::ZoomOut, actionCollection()); 0164 action->setIcon(QIcon::fromTheme("zoom-out")); 0165 0166 action = KStandardAction::actualSize(this, &FITSViewer::ZoomDefault, actionCollection()); 0167 action->setIcon(QIcon::fromTheme("zoom-fit-best")); 0168 0169 QAction *kundo = KStandardAction::undo(undoGroup, &QUndoGroup::undo, actionCollection()); 0170 kundo->setIcon(QIcon::fromTheme("edit-undo")); 0171 0172 QAction *kredo = KStandardAction::redo(undoGroup, &QUndoGroup::redo, actionCollection()); 0173 kredo->setIcon(QIcon::fromTheme("edit-redo")); 0174 0175 connect(undoGroup, &QUndoGroup::canUndoChanged, kundo, &QAction::setEnabled); 0176 connect(undoGroup, &QUndoGroup::canRedoChanged, kredo, &QAction::setEnabled); 0177 0178 action = actionCollection()->addAction("image_stats"); 0179 action->setIcon(QIcon::fromTheme("view-statistics")); 0180 action->setText(i18n("Statistics")); 0181 connect(action, &QAction::triggered, this, &FITSViewer::statFITS); 0182 0183 action = actionCollection()->addAction("image_roi_stats"); 0184 0185 roiActionMenu = new KActionMenu(QIcon(":/icons/select_stat"), "Selection Statistics", action ); 0186 roiActionMenu->setText(i18n("&Selection Statistics")); 0187 roiActionMenu->setDelayed(false); 0188 roiActionMenu->addSeparator(); 0189 connect(roiActionMenu, &QAction::triggered, this, &FITSViewer::toggleSelectionMode); 0190 0191 KToggleAction *ksa = actionCollection()->add<KToggleAction>("100x100"); 0192 ksa->setText("100x100"); 0193 ksa->setCheckable(false); 0194 roiActionMenu->addAction(ksa); 0195 ksa = actionCollection()->add<KToggleAction>("50x50"); 0196 ksa->setText("50x50"); 0197 ksa->setCheckable(false); 0198 roiActionMenu->addAction(ksa); 0199 ksa = actionCollection()->add<KToggleAction>("25x25"); 0200 ksa->setText("25x25"); 0201 ksa->setCheckable(false); 0202 roiActionMenu->addAction(ksa); 0203 ksa = actionCollection()->add<KToggleAction>("CustomRoi"); 0204 ksa->setText("Custom"); 0205 ksa->setCheckable(false); 0206 roiActionMenu->addAction(ksa); 0207 0208 action->setMenu(roiActionMenu->menu()); 0209 action->setIcon(QIcon(":/icons/select_stat")); 0210 action->setCheckable(true); 0211 0212 connect(roiActionMenu->menu()->actions().at(1), &QAction::triggered, this, [this] { ROIFixedSize(100); }); 0213 connect(roiActionMenu->menu()->actions().at(2), &QAction::triggered, this, [this] { ROIFixedSize(50); }); 0214 connect(roiActionMenu->menu()->actions().at(3), &QAction::triggered, this, [this] { ROIFixedSize(25); }); 0215 connect(roiActionMenu->menu()->actions().at(4), &QAction::triggered, this, [this] { customROIInputWindow();}); 0216 connect(action, &QAction::triggered, this, &FITSViewer::toggleSelectionMode); 0217 0218 action = actionCollection()->addAction("view_crosshair"); 0219 action->setIcon(QIcon::fromTheme("crosshairs")); 0220 action->setText(i18n("Show Cross Hairs")); 0221 action->setCheckable(true); 0222 connect(action, &QAction::triggered, this, &FITSViewer::toggleCrossHair); 0223 0224 action = actionCollection()->addAction("view_clipping"); 0225 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_L)); 0226 action->setIcon(QIcon::fromTheme("media-record")); 0227 action->setText(i18n("Show Clipping")); 0228 action->setCheckable(true); 0229 connect(action, &QAction::triggered, this, &FITSViewer::toggleClipping); 0230 0231 action = actionCollection()->addAction("view_pixel_grid"); 0232 action->setIcon(QIcon::fromTheme("map-flat")); 0233 action->setText(i18n("Show Pixel Gridlines")); 0234 action->setCheckable(true); 0235 connect(action, &QAction::triggered, this, &FITSViewer::togglePixelGrid); 0236 0237 action = actionCollection()->addAction("view_eq_grid"); 0238 action->setIcon(QIcon::fromTheme("kstars_grid")); 0239 action->setText(i18n("Show Equatorial Gridlines")); 0240 action->setCheckable(true); 0241 action->setDisabled(true); 0242 connect(action, &QAction::triggered, this, &FITSViewer::toggleEQGrid); 0243 0244 action = actionCollection()->addAction("view_objects"); 0245 action->setIcon(QIcon::fromTheme("help-hint")); 0246 action->setText(i18n("Show Objects in Image")); 0247 action->setCheckable(true); 0248 action->setDisabled(true); 0249 connect(action, &QAction::triggered, this, &FITSViewer::toggleObjects); 0250 0251 action = actionCollection()->addAction("view_hips_overlay"); 0252 action->setIcon(QIcon::fromTheme("pixelate")); 0253 action->setText(i18n("Show HiPS Overlay")); 0254 action->setCheckable(true); 0255 action->setDisabled(true); 0256 connect(action, &QAction::triggered, this, &FITSViewer::toggleHiPSOverlay); 0257 0258 action = actionCollection()->addAction("center_telescope"); 0259 action->setIcon(QIcon(":/icons/center_telescope.svg")); 0260 action->setText(i18n("Center Telescope\n*No Telescopes Detected*")); 0261 action->setDisabled(true); 0262 action->setCheckable(true); 0263 connect(action, &QAction::triggered, this, &FITSViewer::centerTelescope); 0264 0265 action = actionCollection()->addAction("view_zoom_fit"); 0266 action->setIcon(QIcon::fromTheme("zoom-fit-width")); 0267 action->setText(i18n("Zoom To Fit")); 0268 connect(action, &QAction::triggered, this, &FITSViewer::ZoomToFit); 0269 0270 action = actionCollection()->addAction("next_tab"); 0271 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Tab)); 0272 action->setText(i18n("Next Tab")); 0273 connect(action, &QAction::triggered, this, &FITSViewer::nextTab); 0274 0275 action = actionCollection()->addAction("previous_tab"); 0276 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Tab + Qt::ShiftModifier)); 0277 action->setText(i18n("Previous Tab")); 0278 connect(action, &QAction::triggered, this, &FITSViewer::previousTab); 0279 0280 action = actionCollection()->addAction("next_blink"); 0281 actionCollection()->setDefaultShortcut(action, QKeySequence(QKeySequence::SelectNextWord)); 0282 action->setText(i18n("Next Blink Image")); 0283 connect(action, &QAction::triggered, this, &FITSViewer::nextBlink); 0284 0285 action = actionCollection()->addAction("previous_blink"); 0286 actionCollection()->setDefaultShortcut(action, QKeySequence(QKeySequence::SelectPreviousWord)); 0287 action->setText(i18n("Previous Blink Image")); 0288 connect(action, &QAction::triggered, this, &FITSViewer::previousBlink); 0289 0290 action = actionCollection()->addAction("zoom_all_in"); 0291 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Plus + Qt::AltModifier)); 0292 action->setText(i18n("Zoom all tabs in")); 0293 connect(action, &QAction::triggered, this, &FITSViewer::ZoomAllIn); 0294 0295 action = actionCollection()->addAction("zoom_all_out"); 0296 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Minus + Qt::AltModifier)); 0297 action->setText(i18n("Zoom all tabs out")); 0298 connect(action, &QAction::triggered, this, &FITSViewer::ZoomAllOut); 0299 0300 action = actionCollection()->addAction("mark_stars"); 0301 action->setIcon(QIcon::fromTheme("glstarbase", QIcon(":/icons/glstarbase.png"))); 0302 action->setText(i18n("Mark Stars")); 0303 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_A)); 0304 action->setCheckable(true); 0305 connect(action, &QAction::triggered, this, &FITSViewer::toggleStars); 0306 0307 #ifdef HAVE_DATAVISUALIZATION 0308 action = actionCollection()->addAction("toggle_3D_graph"); 0309 action->setIcon(QIcon::fromTheme("star_profile", QIcon(":/icons/star_profile.svg"))); 0310 action->setText(i18n("View 3D Graph")); 0311 action->setCheckable(true); 0312 connect(action, &QAction::triggered, this, &FITSViewer::toggle3DGraph); 0313 #endif 0314 0315 0316 int filterCounter = 1; 0317 0318 for (auto &filter : FITSViewer::filterTypes) 0319 { 0320 action = actionCollection()->addAction(QString("filter%1").arg(filterCounter)); 0321 action->setText(i18n(filter.toUtf8().constData())); 0322 connect(action, &QAction::triggered, this, [this, filterCounter] { applyFilter(filterCounter);}); 0323 filterCounter++; 0324 } 0325 0326 this->setAttribute(Qt::WA_AlwaysShowToolTips); 0327 /* Create GUI */ 0328 createGUI("fitsviewerui.rc"); 0329 0330 setWindowTitle(i18nc("@title:window", "KStars FITS Viewer")); 0331 0332 /* initially resize in accord with KDE rules */ 0333 show(); 0334 resize(INITIAL_W, INITIAL_H); 0335 } 0336 0337 void FITSViewer::changeAlwaysOnTop(Qt::ApplicationState state) 0338 { 0339 if (isVisible()) 0340 { 0341 if (state == Qt::ApplicationActive) 0342 setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint); 0343 else 0344 setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint); 0345 show(); 0346 } 0347 } 0348 0349 FITSViewer::~FITSViewer() 0350 { 0351 } 0352 0353 void FITSViewer::closeEvent(QCloseEvent * /*event*/) 0354 { 0355 KStars *ks = KStars::Instance(); 0356 0357 if (ks) 0358 { 0359 QAction *a = KStars::Instance()->actionCollection()->action("show_fits_viewer"); 0360 QList<FITSViewer *> viewers = KStars::Instance()->findChildren<FITSViewer *>(); 0361 0362 if (a && viewers.count() == 1) 0363 { 0364 a->setEnabled(false); 0365 a->setChecked(false); 0366 } 0367 } 0368 0369 emit terminated(); 0370 } 0371 0372 void FITSViewer::hideEvent(QHideEvent * /*event*/) 0373 { 0374 KStars *ks = KStars::Instance(); 0375 0376 if (ks) 0377 { 0378 QAction *a = KStars::Instance()->actionCollection()->action("show_fits_viewer"); 0379 if (a) 0380 { 0381 QList<FITSViewer *> viewers = KStars::Instance()->findChildren<FITSViewer *>(); 0382 0383 if (viewers.count() <= 1) 0384 a->setChecked(false); 0385 } 0386 } 0387 } 0388 0389 void FITSViewer::showEvent(QShowEvent * /*event*/) 0390 { 0391 QAction *a = KStars::Instance()->actionCollection()->action("show_fits_viewer"); 0392 if (a) 0393 { 0394 a->setEnabled(true); 0395 a->setChecked(true); 0396 } 0397 } 0398 0399 0400 namespace 0401 { 0402 QString HFRStatusString(const QSharedPointer<FITSData> &data) 0403 { 0404 const double hfrValue = data->getHFR(); 0405 if (hfrValue <= 0.0) return QString(""); 0406 if (data->getSkyBackground().starsDetected > 0) 0407 return 0408 i18np("HFR:%2 Ecc:%3 %1 star.", "HFR:%2 Ecc:%3 %1 stars.", 0409 data->getSkyBackground().starsDetected, 0410 QString::number(hfrValue, 'f', 2), 0411 QString::number(data->getEccentricity(), 'f', 2)); 0412 else 0413 return 0414 i18np("HFR:%2, %1 star.", "HFR:%2, %1 stars.", 0415 data->getDetectedStars(), 0416 QString::number(hfrValue, 'f', 2)); 0417 } 0418 0419 QString HFRClipString(FITSView* view) 0420 { 0421 if (view->isClippingShown()) 0422 { 0423 const int numClipped = view->getNumClipped(); 0424 if (numClipped < 0) 0425 return QString("Clip:failed"); 0426 else 0427 return QString("Clip:%1").arg(view->getNumClipped()); 0428 } 0429 return ""; 0430 } 0431 } // namespace 0432 0433 bool FITSViewer::addFITSCommon(const QSharedPointer<FITSTab> &tab, const QUrl &imageName, 0434 FITSMode mode, const QString &previewText) 0435 { 0436 int tabIndex = fitsTabWidget->indexOf(tab.get()); 0437 if (tabIndex != -1) 0438 return false; 0439 0440 if (!imageName.isValid()) 0441 lastURL = QUrl(imageName.url(QUrl::RemoveFilename)); 0442 0443 QApplication::restoreOverrideCursor(); 0444 tab->setPreviewText(previewText); 0445 0446 // Connect tab signals 0447 tab->disconnect(this); 0448 connect(tab.get(), &FITSTab::newStatus, this, &FITSViewer::updateStatusBar); 0449 connect(tab.get(), &FITSTab::changeStatus, this, &FITSViewer::updateTabStatus); 0450 connect(tab.get(), &FITSTab::debayerToggled, this, &FITSViewer::setDebayerAction); 0451 // Connect tab view signals 0452 connect(tab->getView().get(), &FITSView::actionUpdated, this, &FITSViewer::updateAction); 0453 connect(tab->getView().get(), &FITSView::wcsToggled, this, &FITSViewer::updateWCSFunctions); 0454 connect(tab->getView().get(), &FITSView::starProfileWindowClosed, this, &FITSViewer::starProfileButtonOff); 0455 0456 switch (mode) 0457 { 0458 case FITS_NORMAL: 0459 fitsTabWidget->addTab(tab.get(), previewText.isEmpty() ? imageName.fileName() : previewText); 0460 break; 0461 0462 case FITS_CALIBRATE: 0463 fitsTabWidget->addTab(tab.get(), i18n("Calibrate")); 0464 break; 0465 0466 case FITS_FOCUS: 0467 fitsTabWidget->addTab(tab.get(), i18n("Focus")); 0468 break; 0469 0470 case FITS_GUIDE: 0471 fitsTabWidget->addTab(tab.get(), i18n("Guide")); 0472 break; 0473 0474 case FITS_ALIGN: 0475 fitsTabWidget->addTab(tab.get(), i18n("Align")); 0476 break; 0477 0478 case FITS_UNKNOWN: 0479 break; 0480 } 0481 0482 saveFileAction->setEnabled(true); 0483 saveFileAsAction->setEnabled(true); 0484 0485 undoGroup->addStack(tab->getUndoStack()); 0486 0487 fitsMap[fitsID] = tab; 0488 0489 fitsTabWidget->setCurrentWidget(tab.get()); 0490 0491 actionCollection()->action("fits_debayer")->setEnabled(tab->getView()->imageData()->hasDebayer()); 0492 0493 tab->tabPositionUpdated(); 0494 0495 tab->setUID(fitsID); 0496 0497 led.setColor(Qt::green); 0498 0499 if (tab->shouldComputeHFR()) 0500 updateStatusBar(HFRStatusString(tab->getView()->imageData()), FITS_HFR); 0501 else 0502 updateStatusBar("", FITS_HFR); 0503 updateStatusBar(i18n("Ready."), FITS_MESSAGE); 0504 0505 updateStatusBar(HFRClipString(tab->getView().get()), FITS_CLIP); 0506 0507 tab->getView()->setCursorMode(FITSView::dragCursor); 0508 0509 actionCollection()->action("next_blink")->setEnabled(tab->blinkFilenames().size() > 1); 0510 actionCollection()->action("previous_blink")->setEnabled(tab->blinkFilenames().size() > 1); 0511 0512 updateWCSFunctions(); 0513 0514 return true; 0515 } 0516 0517 void FITSViewer::loadFiles() 0518 { 0519 if (m_urls.size() == 0) 0520 return; 0521 0522 const QUrl imageName = m_urls[0]; 0523 m_urls.pop_front(); 0524 0525 // Make sure we don't have it open already, if yes, switch to it 0526 QString fpath = imageName.toLocalFile(); 0527 for (auto tab : m_Tabs) 0528 { 0529 const QString cpath = tab->getCurrentURL()->path(); 0530 if (fpath == cpath) 0531 { 0532 fitsTabWidget->setCurrentWidget(tab.get()); 0533 if (m_urls.size() > 0) 0534 loadFiles(); 0535 return; 0536 } 0537 } 0538 0539 led.setColor(Qt::yellow); 0540 QApplication::setOverrideCursor(Qt::WaitCursor); 0541 0542 QSharedPointer<FITSTab> tab(new FITSTab(this)); 0543 0544 m_Tabs.push_back(tab); 0545 0546 connect(tab.get(), &FITSTab::failed, this, [ this ](const QString & errorMessage) 0547 { 0548 QApplication::restoreOverrideCursor(); 0549 led.setColor(Qt::red); 0550 m_Tabs.removeLast(); 0551 emit failed(errorMessage); 0552 if (m_Tabs.size() == 0) 0553 { 0554 // Close FITS Viewer and let KStars know it is no longer needed in memory. 0555 close(); 0556 } 0557 0558 if (m_urls.size() > 0) 0559 loadFiles(); 0560 }); 0561 0562 connect(tab.get(), &FITSTab::loaded, this, [ = ]() 0563 { 0564 if (addFITSCommon(m_Tabs.last(), imageName, FITS_NORMAL, "")) 0565 emit loaded(fitsID++); 0566 else 0567 m_Tabs.removeLast(); 0568 0569 if (m_urls.size() > 0) 0570 loadFiles(); 0571 }); 0572 0573 tab->loadFile(imageName, FITS_NORMAL, FITS_NONE); 0574 } 0575 0576 void FITSViewer::loadFile(const QUrl &imageName, FITSMode mode, FITSScale filter, const QString &previewText) 0577 { 0578 led.setColor(Qt::yellow); 0579 QApplication::setOverrideCursor(Qt::WaitCursor); 0580 0581 QSharedPointer<FITSTab> tab(new FITSTab(this)); 0582 0583 m_Tabs.push_back(tab); 0584 0585 connect(tab.get(), &FITSTab::failed, this, [ this ](const QString & errorMessage) 0586 { 0587 QApplication::restoreOverrideCursor(); 0588 led.setColor(Qt::red); 0589 m_Tabs.removeLast(); 0590 emit failed(errorMessage); 0591 if (m_Tabs.size() == 0) 0592 { 0593 // Close FITS Viewer and let KStars know it is no longer needed in memory. 0594 close(); 0595 } 0596 }); 0597 0598 connect(tab.get(), &FITSTab::loaded, this, [ = ]() 0599 { 0600 if (addFITSCommon(m_Tabs.last(), imageName, mode, previewText)) 0601 emit loaded(fitsID++); 0602 else 0603 m_Tabs.removeLast(); 0604 }); 0605 0606 tab->loadFile(imageName, mode, filter); 0607 } 0608 0609 bool FITSViewer::loadData(const QSharedPointer<FITSData> &data, const QUrl &imageName, int *tab_uid, FITSMode mode, 0610 FITSScale filter, const QString &previewText) 0611 { 0612 led.setColor(Qt::yellow); 0613 QApplication::setOverrideCursor(Qt::WaitCursor); 0614 0615 QSharedPointer<FITSTab> tab(new FITSTab(this)); 0616 0617 m_Tabs.push_back(tab); 0618 0619 if (!tab->loadData(data, mode, filter)) 0620 { 0621 auto errorMessage = tab->getView()->imageData()->getLastError(); 0622 QApplication::restoreOverrideCursor(); 0623 led.setColor(Qt::red); 0624 m_Tabs.removeLast(); 0625 emit failed(errorMessage); 0626 if (m_Tabs.size() == 0) 0627 { 0628 // Close FITS Viewer and let KStars know it is no longer needed in memory. 0629 close(); 0630 } 0631 return false; 0632 } 0633 0634 if (!addFITSCommon(tab, imageName, mode, previewText)) 0635 { 0636 m_Tabs.removeLast(); 0637 return false; 0638 } 0639 0640 *tab_uid = fitsID++; 0641 return true; 0642 } 0643 0644 bool FITSViewer::removeFITS(int fitsUID) 0645 { 0646 auto tab = fitsMap.value(fitsUID); 0647 0648 if (tab.isNull()) 0649 { 0650 qCWarning(KSTARS_FITS) << "Cannot find tab with UID " << fitsUID << " in the FITS Viewer"; 0651 return false; 0652 } 0653 0654 int index = m_Tabs.indexOf(tab); 0655 0656 if (index >= 0) 0657 { 0658 closeTab(index); 0659 return true; 0660 } 0661 0662 return false; 0663 } 0664 0665 void FITSViewer::updateFile(const QUrl &imageName, int fitsUID, FITSScale filter) 0666 { 0667 static bool updateBusy = false; 0668 if (updateBusy) 0669 return; 0670 updateBusy = true; 0671 0672 auto tab = fitsMap.value(fitsUID); 0673 0674 if (tab.isNull()) 0675 { 0676 QString message = i18n("Cannot find tab with UID %1 in the FITS Viewer", fitsUID); 0677 emit failed(message); 0678 updateBusy = false; 0679 return; 0680 } 0681 0682 if (tab->isVisible()) 0683 led.setColor(Qt::yellow); 0684 0685 // On tab load success 0686 auto conn = std::make_shared<QMetaObject::Connection>(); 0687 *conn = connect(tab.get(), &FITSTab::loaded, this, [ = ]() 0688 { 0689 if (updateFITSCommon(tab, imageName)) 0690 { 0691 QObject::disconnect(*conn); 0692 emit loaded(tab->getUID()); 0693 updateBusy = false; 0694 } 0695 }); 0696 0697 auto conn2 = std::make_shared<QMetaObject::Connection>(); 0698 *conn2 = connect(tab.get(), &FITSTab::failed, this, [ = ](const QString & errorMessage) 0699 { 0700 Q_UNUSED(errorMessage); 0701 QObject::disconnect(*conn2); 0702 updateBusy = false; 0703 }); 0704 0705 tab->loadFile(imageName, tab->getView()->getMode(), filter); 0706 } 0707 0708 bool FITSViewer::updateFITSCommon(const QSharedPointer<FITSTab> &tab, const QUrl &imageName) 0709 { 0710 // On tab load success 0711 int tabIndex = fitsTabWidget->indexOf(tab.get()); 0712 if (tabIndex == -1) 0713 return false; 0714 0715 if (tab->getView()->getMode() == FITS_NORMAL) 0716 { 0717 if ((imageName.path().startsWith(QLatin1String("/tmp")) || 0718 imageName.path().contains("/Temp")) && 0719 Options::singlePreviewFITS()) 0720 fitsTabWidget->setTabText(tabIndex, 0721 tab->getPreviewText().isEmpty() ? i18n("Preview") : tab->getPreviewText()); 0722 else 0723 fitsTabWidget->setTabText(tabIndex, imageName.fileName()); 0724 } 0725 0726 tab->getUndoStack()->clear(); 0727 0728 if (tab->isVisible()) 0729 led.setColor(Qt::green); 0730 0731 if (tab->shouldComputeHFR()) 0732 updateStatusBar(HFRStatusString(tab->getView()->imageData()), FITS_HFR); 0733 else 0734 updateStatusBar("", FITS_HFR); 0735 0736 updateStatusBar(HFRClipString(tab->getView().get()), FITS_CLIP); 0737 0738 actionCollection()->action("next_blink")->setEnabled(tab->blinkFilenames().size() > 1); 0739 actionCollection()->action("previous_blink")->setEnabled(tab->blinkFilenames().size() > 1); 0740 0741 return true; 0742 } 0743 0744 bool FITSViewer::updateData(const QSharedPointer<FITSData> &data, const QUrl &imageName, int fitsUID, int *tab_uid, 0745 FITSScale filter, FITSMode mode) 0746 { 0747 auto tab = fitsMap.value(fitsUID); 0748 0749 if (tab.isNull()) 0750 return false; 0751 0752 if (mode != FITS_UNKNOWN) 0753 tab->getView()->updateMode(mode); 0754 0755 if (tab->isVisible()) 0756 led.setColor(Qt::yellow); 0757 0758 if (!tab->loadData(data, tab->getView()->getMode(), filter)) 0759 return false; 0760 0761 if (!updateFITSCommon(tab, imageName)) 0762 return false; 0763 0764 *tab_uid = tab->getUID(); 0765 return true; 0766 } 0767 0768 void FITSViewer::tabFocusUpdated(int currentIndex) 0769 { 0770 if (currentIndex < 0 || m_Tabs.empty()) 0771 return; 0772 0773 m_Tabs[currentIndex]->tabPositionUpdated(); 0774 0775 auto view = m_Tabs[currentIndex]->getView(); 0776 0777 view->toggleStars(markStars); 0778 0779 if (isVisible()) 0780 view->updateFrame(); 0781 0782 if (m_Tabs[currentIndex]->shouldComputeHFR()) 0783 updateStatusBar(HFRStatusString(view->imageData()), FITS_HFR); 0784 else 0785 updateStatusBar("", FITS_HFR); 0786 0787 updateStatusBar(HFRClipString(m_Tabs[currentIndex]->getView().get()), FITS_CLIP); 0788 0789 if (view->imageData()->hasDebayer()) 0790 { 0791 actionCollection()->action("fits_debayer")->setEnabled(true); 0792 0793 if (debayerDialog) 0794 { 0795 BayerParams param; 0796 view->imageData()->getBayerParams(¶m); 0797 debayerDialog->setBayerParams(¶m); 0798 } 0799 } 0800 else 0801 actionCollection()->action("fits_debayer")->setEnabled(false); 0802 0803 updateStatusBar("", FITS_WCS); 0804 connect(view.get(), &FITSView::starProfileWindowClosed, this, &FITSViewer::starProfileButtonOff); 0805 QSharedPointer<FITSView> currentView; 0806 if (getCurrentView(currentView)) 0807 { 0808 updateButtonStatus("toggle_3D_graph", i18n("currentView 3D Graph"), currentView->isStarProfileShown()); 0809 updateButtonStatus("view_crosshair", i18n("Cross Hairs"), currentView->isCrosshairShown()); 0810 updateButtonStatus("view_clipping", i18n("Clipping"), currentView->isClippingShown()); 0811 updateButtonStatus("view_eq_grid", i18n("Equatorial Gridlines"), currentView->isEQGridShown()); 0812 updateButtonStatus("view_objects", i18n("Objects in Image"), currentView->areObjectsShown()); 0813 updateButtonStatus("view_pixel_grid", i18n("Pixel Gridlines"), currentView->isPixelGridShown()); 0814 updateButtonStatus("view_hips_overlay", i18n("HiPS Overlay"), currentView->isHiPSOverlayShown()); 0815 } 0816 0817 actionCollection()->action("next_blink")->setEnabled(m_Tabs[currentIndex]->blinkFilenames().size() > 1); 0818 actionCollection()->action("previous_blink")->setEnabled(m_Tabs[currentIndex]->blinkFilenames().size() > 1); 0819 0820 updateScopeButton(); 0821 updateWCSFunctions(); 0822 } 0823 0824 void FITSViewer::starProfileButtonOff() 0825 { 0826 updateButtonStatus("toggle_3D_graph", i18n("View 3D Graph"), false); 0827 } 0828 0829 0830 QList<QString> findAllImagesBelowDir(const QDir &topDir) 0831 { 0832 QList<QString> result; 0833 QList<QString> nameFilter = { "*" }; 0834 QDir::Filters filter = QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Files | QDir::NoSymLinks; 0835 0836 QList<QDir> dirs; 0837 dirs.push_back(topDir); 0838 0839 QRegularExpression re(".*(fits|fits.fz|fit|fts|xisf|jpg|jpeg|png|gif|bmp|cr2|cr3|crw|nef|raf|dng|arw|orf)$"); 0840 while (!dirs.empty()) 0841 { 0842 auto dir = dirs.back(); 0843 dirs.removeLast(); 0844 auto list = dir.entryInfoList( nameFilter, filter ); 0845 foreach( const QFileInfo &entry, list) 0846 { 0847 if( entry.isDir() ) 0848 dirs.push_back(entry.filePath()); 0849 else 0850 { 0851 const QString suffix = entry.completeSuffix(); 0852 QRegularExpressionMatch match = re.match(suffix); 0853 if (match.hasMatch()) 0854 result.append(entry.absoluteFilePath()); 0855 } 0856 } 0857 } 0858 return result; 0859 } 0860 0861 void FITSViewer::blink() 0862 { 0863 if (m_BlinkBusy) 0864 return; 0865 m_BlinkBusy = true; 0866 QFileDialog dialog(KStars::Instance(), i18nc("@title:window", "Blink Top Directory")); 0867 dialog.setFileMode(QFileDialog::Directory); 0868 dialog.setDirectoryUrl(lastURL); 0869 0870 if (!dialog.exec()) 0871 { 0872 m_BlinkBusy = false; 0873 return; 0874 } 0875 QStringList selected = dialog.selectedFiles(); 0876 if (selected.size() < 1) 0877 { 0878 m_BlinkBusy = false; 0879 return; 0880 } 0881 QString topDir = selected[0]; 0882 0883 auto allImages = findAllImagesBelowDir(QDir(topDir)); 0884 if (allImages.size() == 0) 0885 { 0886 m_BlinkBusy = false; 0887 return; 0888 } 0889 0890 const QUrl imageName(QUrl::fromLocalFile(allImages[0])); 0891 0892 led.setColor(Qt::yellow); 0893 QApplication::setOverrideCursor(Qt::WaitCursor); 0894 0895 QSharedPointer<FITSTab> tab(new FITSTab(this)); 0896 0897 int tabIndex = m_Tabs.size(); 0898 if (allImages.size() > 1) 0899 { 0900 m_Tabs.push_back(tab); 0901 tab->initBlink(allImages); 0902 tab->setBlinkUpto(1); 0903 } 0904 QString tabName = QString("%1/%2 %3") 0905 .arg(1).arg(allImages.size()).arg(QFileInfo(allImages[0]).fileName()); 0906 connect(tab.get(), &FITSTab::failed, this, [ this ](const QString & errorMessage) 0907 { 0908 Q_UNUSED(errorMessage); 0909 QObject::sender()->disconnect(this); 0910 QApplication::restoreOverrideCursor(); 0911 led.setColor(Qt::red); 0912 m_BlinkBusy = false; 0913 }, Qt::UniqueConnection); 0914 0915 connect(tab.get(), &FITSTab::loaded, this, [ = ]() 0916 { 0917 QObject::sender()->disconnect(this); 0918 addFITSCommon(m_Tabs.last(), imageName, FITS_NORMAL, ""); 0919 //fitsTabWidget->tabBar()->setTabTextColor(tabIndex, Qt::red); 0920 fitsTabWidget->setTabText(tabIndex, tabName); 0921 m_BlinkBusy = false; 0922 }, Qt::UniqueConnection); 0923 0924 actionCollection()->action("next_blink")->setEnabled(allImages.size() > 1); 0925 actionCollection()->action("previous_blink")->setEnabled(allImages.size() > 1); 0926 0927 tab->loadFile(imageName, FITS_NORMAL, FITS_NONE); 0928 } 0929 0930 0931 void FITSViewer::changeBlink(bool increment) 0932 { 0933 if (m_Tabs.empty() || m_BlinkBusy) 0934 return; 0935 0936 m_BlinkBusy = true; 0937 const int tabIndex = fitsTabWidget->currentIndex(); 0938 if (tabIndex >= m_Tabs.count() || tabIndex < 0) 0939 { 0940 m_BlinkBusy = false; 0941 return; 0942 } 0943 auto tab = m_Tabs[tabIndex]; 0944 const QList<QString> &filenames = tab->blinkFilenames(); 0945 if (filenames.size() <= 1) 0946 { 0947 m_BlinkBusy = false; 0948 return; 0949 } 0950 0951 int blinkIndex = tab->blinkUpto() + (increment ? 1 : -1); 0952 if (blinkIndex >= filenames.size()) 0953 blinkIndex = 0; 0954 else if (blinkIndex < 0) 0955 blinkIndex = filenames.size() - 1; 0956 0957 QString nextFilename = filenames[blinkIndex]; 0958 QString tabName = QString("%1/%2 %3") 0959 .arg(blinkIndex + 1).arg(filenames.size()).arg(QFileInfo(nextFilename).fileName()); 0960 tab->disconnect(this); 0961 connect(tab.get(), &FITSTab::failed, this, [ this, nextFilename ](const QString & errorMessage) 0962 { 0963 Q_UNUSED(errorMessage); 0964 QObject::sender()->disconnect(this); 0965 QApplication::restoreOverrideCursor(); 0966 led.setColor(Qt::red); 0967 m_BlinkBusy = false; 0968 }, Qt::UniqueConnection); 0969 0970 connect(tab.get(), &FITSTab::loaded, this, [ = ]() 0971 { 0972 QObject::sender()->disconnect(this); 0973 updateFITSCommon(tab, QUrl::fromLocalFile(nextFilename)); 0974 fitsTabWidget->setTabText(tabIndex, tabName); 0975 m_BlinkBusy = false; 0976 }, Qt::UniqueConnection); 0977 0978 tab->setBlinkUpto(blinkIndex); 0979 tab->loadFile(QUrl::fromLocalFile(nextFilename), FITS_NORMAL, FITS_NONE); 0980 } 0981 0982 void FITSViewer::nextBlink() 0983 { 0984 changeBlink(true); 0985 } 0986 0987 void FITSViewer::previousBlink() 0988 { 0989 changeBlink(false); 0990 } 0991 0992 void FITSViewer::openFile() 0993 { 0994 QFileDialog dialog(KStars::Instance(), i18nc("@title:window", "Open Image")); 0995 dialog.setFileMode(QFileDialog::ExistingFiles); 0996 dialog.setDirectoryUrl(lastURL); 0997 dialog.setNameFilter("Images (*.fits *.fits.fz *.fit *.fts *.xisf " 0998 "*.jpg *.jpeg *.png *.gif *.bmp " 0999 "*.cr2 *.cr3 *.crw *.nef *.raf *.dng *.arw *.orf)"); 1000 if (!dialog.exec()) 1001 return; 1002 m_urls = dialog.selectedUrls(); 1003 if (m_urls.size() < 1) 1004 return; 1005 // Protect against, e.g. opening 1000 tabs. Not sure what the right number is. 1006 constexpr int MAX_NUM_OPENS = 40; 1007 if (m_urls.size() > MAX_NUM_OPENS) 1008 return; 1009 1010 lastURL = QUrl(m_urls[0].url(QUrl::RemoveFilename)); 1011 loadFiles(); 1012 } 1013 1014 void FITSViewer::saveFile() 1015 { 1016 m_Tabs[fitsTabWidget->currentIndex()]->saveFile(); 1017 } 1018 1019 void FITSViewer::saveFileAs() 1020 { 1021 if (m_Tabs.empty()) 1022 return; 1023 1024 if (m_Tabs[fitsTabWidget->currentIndex()]->saveFileAs() && 1025 m_Tabs[fitsTabWidget->currentIndex()]->getView()->getMode() == FITS_NORMAL) 1026 fitsTabWidget->setTabText(fitsTabWidget->currentIndex(), 1027 m_Tabs[fitsTabWidget->currentIndex()]->getCurrentURL()->fileName()); 1028 } 1029 1030 void FITSViewer::copyFITS() 1031 { 1032 if (m_Tabs.empty()) 1033 return; 1034 1035 m_Tabs[fitsTabWidget->currentIndex()]->copyFITS(); 1036 } 1037 1038 void FITSViewer::histoFITS() 1039 { 1040 if (m_Tabs.empty()) 1041 return; 1042 1043 m_Tabs[fitsTabWidget->currentIndex()]->histoFITS(); 1044 } 1045 1046 void FITSViewer::statFITS() 1047 { 1048 if (m_Tabs.empty()) 1049 return; 1050 1051 m_Tabs[fitsTabWidget->currentIndex()]->statFITS(); 1052 } 1053 1054 void FITSViewer::rotateCW() 1055 { 1056 applyFilter(FITS_ROTATE_CW); 1057 } 1058 1059 void FITSViewer::rotateCCW() 1060 { 1061 applyFilter(FITS_ROTATE_CCW); 1062 } 1063 1064 void FITSViewer::flipHorizontal() 1065 { 1066 applyFilter(FITS_MOUNT_FLIP_H); 1067 } 1068 1069 void FITSViewer::flipVertical() 1070 { 1071 applyFilter(FITS_MOUNT_FLIP_V); 1072 } 1073 1074 void FITSViewer::headerFITS() 1075 { 1076 if (m_Tabs.empty()) 1077 return; 1078 1079 m_Tabs[fitsTabWidget->currentIndex()]->headerFITS(); 1080 } 1081 1082 void FITSViewer::debayerFITS() 1083 { 1084 if (debayerDialog == nullptr) 1085 { 1086 debayerDialog = new FITSDebayer(this); 1087 } 1088 1089 QSharedPointer<FITSView> view; 1090 if (getCurrentView(view)) 1091 { 1092 BayerParams param; 1093 view->imageData()->getBayerParams(¶m); 1094 debayerDialog->setBayerParams(¶m); 1095 debayerDialog->show(); 1096 } 1097 } 1098 1099 void FITSViewer::updateStatusBar(const QString &msg, FITSBar id) 1100 { 1101 switch (id) 1102 { 1103 case FITS_POSITION: 1104 fitsPosition.setText(msg); 1105 break; 1106 case FITS_RESOLUTION: 1107 fitsResolution.setText(msg); 1108 break; 1109 case FITS_ZOOM: 1110 fitsZoom.setText(msg); 1111 break; 1112 case FITS_WCS: 1113 fitsWCS.setVisible(true); 1114 fitsWCS.setText(msg); 1115 break; 1116 case FITS_VALUE: 1117 fitsValue.setText(msg); 1118 break; 1119 case FITS_HFR: 1120 fitsHFR.setText(msg); 1121 break; 1122 case FITS_CLIP: 1123 fitsClip.setText(msg); 1124 break; 1125 case FITS_MESSAGE: 1126 statusBar()->showMessage(msg); 1127 break; 1128 1129 default: 1130 break; 1131 } 1132 } 1133 1134 void FITSViewer::ZoomAllIn() 1135 { 1136 if (m_Tabs.empty()) 1137 return; 1138 1139 // Could add code to not call View::updateFrame for these 1140 for (int i = 0; i < fitsTabWidget->count(); ++i) 1141 if (i != fitsTabWidget->currentIndex()) 1142 m_Tabs[i]->ZoomIn(); 1143 1144 m_Tabs[fitsTabWidget->currentIndex()]->ZoomIn(); 1145 } 1146 1147 void FITSViewer::ZoomAllOut() 1148 { 1149 if (m_Tabs.empty()) 1150 return; 1151 1152 // Could add code to not call View::updateFrame for these 1153 for (int i = 0; i < fitsTabWidget->count(); ++i) 1154 if (i != fitsTabWidget->currentIndex()) 1155 m_Tabs[i]->ZoomOut(); 1156 1157 m_Tabs[fitsTabWidget->currentIndex()]->ZoomOut(); 1158 } 1159 1160 void FITSViewer::ZoomIn() 1161 { 1162 if (m_Tabs.empty()) 1163 return; 1164 1165 m_Tabs[fitsTabWidget->currentIndex()]->ZoomIn(); 1166 } 1167 1168 void FITSViewer::ZoomOut() 1169 { 1170 if (m_Tabs.empty()) 1171 return; 1172 1173 m_Tabs[fitsTabWidget->currentIndex()]->ZoomOut(); 1174 } 1175 1176 void FITSViewer::ZoomDefault() 1177 { 1178 if (m_Tabs.empty()) 1179 return; 1180 1181 m_Tabs[fitsTabWidget->currentIndex()]->ZoomDefault(); 1182 } 1183 1184 void FITSViewer::ZoomToFit() 1185 { 1186 if (m_Tabs.empty()) 1187 return; 1188 1189 QSharedPointer<FITSView> currentView; 1190 if (getCurrentView(currentView)) 1191 currentView->ZoomToFit(); 1192 } 1193 1194 void FITSViewer::updateAction(const QString &name, bool enable) 1195 { 1196 QAction *toolAction = actionCollection()->action(name); 1197 1198 if (toolAction != nullptr) 1199 toolAction->setEnabled(enable); 1200 } 1201 1202 void FITSViewer::updateTabStatus(bool clean, const QUrl &imageURL) 1203 { 1204 if (m_Tabs.empty() || (fitsTabWidget->currentIndex() >= m_Tabs.size())) 1205 return; 1206 1207 if (m_Tabs[fitsTabWidget->currentIndex()]->getView()->getMode() != FITS_NORMAL) 1208 return; 1209 1210 //QString tabText = fitsImages[fitsTab->currentIndex()]->getCurrentURL()->fileName(); 1211 1212 QString tabText = imageURL.isEmpty() ? fitsTabWidget->tabText(fitsTabWidget->currentIndex()) : imageURL.fileName(); 1213 1214 fitsTabWidget->setTabText(fitsTabWidget->currentIndex(), clean ? tabText.remove('*') : tabText + '*'); 1215 } 1216 1217 void FITSViewer::closeTab(int index) 1218 { 1219 if (m_Tabs.empty()) 1220 return; 1221 1222 auto tab = m_Tabs[index]; 1223 1224 int UID = tab->getUID(); 1225 1226 fitsMap.remove(UID); 1227 m_Tabs.removeOne(tab); 1228 1229 if (m_Tabs.empty()) 1230 { 1231 saveFileAction->setEnabled(false); 1232 saveFileAsAction->setEnabled(false); 1233 } 1234 1235 emit closed(UID); 1236 } 1237 1238 /** 1239 This is helper function to make it really easy to make the update the state of toggle buttons 1240 that either show or hide information in the Current view. This method would get called both 1241 when one of them gets pushed and also when tabs are switched. 1242 */ 1243 1244 void FITSViewer::updateButtonStatus(const QString &action, const QString &item, bool showing) 1245 { 1246 QAction *a = actionCollection()->action(action); 1247 if (a == nullptr) 1248 return; 1249 1250 if (showing) 1251 { 1252 a->setText(i18n("Hide %1", item)); 1253 a->setChecked(true); 1254 } 1255 else 1256 { 1257 a->setText(i18n("Show %1", item)); 1258 a->setChecked(false); 1259 } 1260 } 1261 1262 /** 1263 This is a method that either enables or disables the WCS based features in the Current View. 1264 */ 1265 1266 void FITSViewer::updateWCSFunctions() 1267 { 1268 QSharedPointer<FITSView> currentView; 1269 if (!getCurrentView(currentView)) 1270 return; 1271 1272 if (currentView->imageHasWCS()) 1273 { 1274 actionCollection()->action("view_eq_grid")->setDisabled(false); 1275 actionCollection()->action("view_eq_grid")->setText(i18n("Show Equatorial Gridlines")); 1276 actionCollection()->action("view_objects")->setDisabled(false); 1277 actionCollection()->action("view_objects")->setText(i18n("Show Objects in Image")); 1278 if (currentView->isTelescopeActive()) 1279 { 1280 actionCollection()->action("center_telescope")->setDisabled(false); 1281 actionCollection()->action("center_telescope")->setText(i18n("Center Telescope\n*Ready*")); 1282 } 1283 else 1284 { 1285 actionCollection()->action("center_telescope")->setDisabled(true); 1286 actionCollection()->action("center_telescope")->setText(i18n("Center Telescope\n*No Telescopes Detected*")); 1287 } 1288 actionCollection()->action("view_hips_overlay")->setDisabled(false); 1289 } 1290 else 1291 { 1292 actionCollection()->action("view_eq_grid")->setDisabled(true); 1293 actionCollection()->action("view_eq_grid")->setText(i18n("Show Equatorial Gridlines\n*No WCS Info*")); 1294 actionCollection()->action("center_telescope")->setDisabled(true); 1295 actionCollection()->action("center_telescope")->setText(i18n("Center Telescope\n*No WCS Info*")); 1296 actionCollection()->action("view_objects")->setDisabled(true); 1297 actionCollection()->action("view_objects")->setText(i18n("Show Objects in Image\n*No WCS Info*")); 1298 actionCollection()->action("view_hips_overlay")->setDisabled(true); 1299 } 1300 } 1301 1302 void FITSViewer::updateScopeButton() 1303 { 1304 QSharedPointer<FITSView> currentView; 1305 if (!getCurrentView(currentView)) 1306 return; 1307 1308 if (currentView->getCursorMode() == FITSView::scopeCursor) 1309 { 1310 actionCollection()->action("center_telescope")->setChecked(true); 1311 } 1312 else 1313 { 1314 actionCollection()->action("center_telescope")->setChecked(false); 1315 } 1316 } 1317 1318 void FITSViewer::ROIFixedSize(int s) 1319 { 1320 if (m_Tabs.empty()) 1321 return; 1322 1323 QSharedPointer<FITSView> currentView; 1324 if (getCurrentView(currentView)) 1325 { 1326 if(!currentView->isSelectionRectShown()) 1327 { 1328 toggleSelectionMode(); 1329 updateButtonStatus("image_roi_stats", i18n("Selection Rectangle"), currentView->isSelectionRectShown()); 1330 } 1331 currentView->processRectangleFixed(s); 1332 } 1333 } 1334 1335 void FITSViewer::customROIInputWindow() 1336 { 1337 if(m_Tabs.empty()) 1338 return; 1339 1340 QSharedPointer<FITSView> currentView; 1341 if (getCurrentView(currentView)) 1342 { 1343 if(!currentView->isSelectionRectShown()) 1344 return; 1345 1346 int mh = currentView->imageData()->height(); 1347 int mw = currentView->imageData()->width(); 1348 1349 if(mh % 2) 1350 mh++; 1351 if(mw % 2) 1352 mw++; 1353 1354 QDialog customRoiDialog; 1355 QFormLayout form(&customRoiDialog); 1356 QDialogButtonBox buttonBox(QDialogButtonBox:: Ok | QDialogButtonBox:: Cancel, Qt::Horizontal, &customRoiDialog); 1357 1358 form.addRow(new QLabel(i18n("Size"))); 1359 1360 QLineEdit wle(&customRoiDialog); 1361 QLineEdit hle(&customRoiDialog); 1362 1363 wle.setValidator(new QIntValidator(1, mw, &wle)); 1364 hle.setValidator(new QIntValidator(1, mh, &hle)); 1365 1366 form.addRow(i18n("Width"), &wle); 1367 form.addRow(i18n("Height"), &hle); 1368 form.addRow(&buttonBox); 1369 1370 connect(&buttonBox, &QDialogButtonBox::accepted, &customRoiDialog, &QDialog::accept); 1371 connect(&buttonBox, &QDialogButtonBox::rejected, &customRoiDialog, &QDialog::reject); 1372 1373 if(customRoiDialog.exec() == QDialog::Accepted) 1374 { 1375 QPoint resetCenter = currentView->getSelectionRegion().center(); 1376 int newheight = hle.text().toInt(); 1377 int newwidth = wle.text().toInt(); 1378 1379 newheight = qMin(newheight, mh) ; 1380 newheight = qMax(newheight, 1) ; 1381 newwidth = qMin(newwidth, mw); 1382 newwidth = qMax(newwidth, 1); 1383 1384 QPoint topLeft = resetCenter; 1385 QPoint botRight = resetCenter; 1386 1387 topLeft.setX((topLeft.x() - newwidth / 2)); 1388 topLeft.setY((topLeft.y() - newheight / 2)); 1389 botRight.setX((botRight.x() + newwidth / 2)); 1390 botRight.setY((botRight.y() + newheight / 2)); 1391 1392 emit currentView->setRubberBand(QRect(topLeft, botRight)); 1393 currentView->processRectangle(topLeft, botRight, true); 1394 } 1395 } 1396 } 1397 /** 1398 This method either enables or disables the scope mouse mode so you can slew your scope to coordinates 1399 just by clicking the mouse on a spot in the image. 1400 */ 1401 1402 void FITSViewer::centerTelescope() 1403 { 1404 QSharedPointer<FITSView> currentView; 1405 if (!getCurrentView(currentView)) 1406 return; 1407 1408 currentView->setScopeButton(actionCollection()->action("center_telescope")); 1409 if (currentView->getCursorMode() == FITSView::scopeCursor) 1410 { 1411 currentView->setCursorMode(currentView->lastMouseMode); 1412 } 1413 else 1414 { 1415 currentView->lastMouseMode = currentView->getCursorMode(); 1416 currentView->setCursorMode(FITSView::scopeCursor); 1417 } 1418 updateScopeButton(); 1419 } 1420 1421 void FITSViewer::toggleCrossHair() 1422 { 1423 if (m_Tabs.empty()) 1424 return; 1425 1426 QSharedPointer<FITSView> currentView; 1427 if (!getCurrentView(currentView)) 1428 return; 1429 1430 currentView->toggleCrosshair(); 1431 updateButtonStatus("view_crosshair", i18n("Cross Hairs"), currentView->isCrosshairShown()); 1432 } 1433 1434 void FITSViewer::toggleClipping() 1435 { 1436 if (m_Tabs.empty()) 1437 return; 1438 1439 QSharedPointer<FITSView> currentView; 1440 if (!getCurrentView(currentView)) 1441 return; 1442 currentView->toggleClipping(); 1443 if (!currentView->isClippingShown()) 1444 fitsClip.clear(); 1445 updateButtonStatus("view_clipping", i18n("Clipping"), currentView->isClippingShown()); 1446 } 1447 1448 void FITSViewer::toggleEQGrid() 1449 { 1450 if (m_Tabs.empty()) 1451 return; 1452 1453 QSharedPointer<FITSView> currentView; 1454 if (!getCurrentView(currentView)) 1455 return; 1456 1457 currentView->toggleEQGrid(); 1458 updateButtonStatus("view_eq_grid", i18n("Equatorial Gridlines"), currentView->isEQGridShown()); 1459 } 1460 1461 void FITSViewer::toggleHiPSOverlay() 1462 { 1463 if (m_Tabs.empty()) 1464 return; 1465 1466 QSharedPointer<FITSView> currentView; 1467 if (!getCurrentView(currentView)) 1468 return; 1469 1470 currentView->toggleHiPSOverlay(); 1471 updateButtonStatus("view_hips_overlay", i18n("HiPS Overlay"), currentView->isHiPSOverlayShown()); 1472 } 1473 1474 void FITSViewer::toggleSelectionMode() 1475 { 1476 if (m_Tabs.empty()) 1477 return; 1478 1479 QSharedPointer<FITSView> currentView; 1480 if (!getCurrentView(currentView)) 1481 return; 1482 1483 currentView->toggleSelectionMode(); 1484 updateButtonStatus("image_roi_stats", i18n("Selection Rectangle"), currentView->isSelectionRectShown()); 1485 } 1486 1487 void FITSViewer::toggleObjects() 1488 { 1489 if (m_Tabs.empty()) 1490 return; 1491 1492 QSharedPointer<FITSView> currentView; 1493 if (!getCurrentView(currentView)) 1494 return; 1495 1496 currentView->toggleObjects(); 1497 updateButtonStatus("view_objects", i18n("Objects in Image"), currentView->areObjectsShown()); 1498 } 1499 1500 void FITSViewer::togglePixelGrid() 1501 { 1502 if (m_Tabs.empty()) 1503 return; 1504 1505 QSharedPointer<FITSView> currentView; 1506 if (!getCurrentView(currentView)) 1507 return; 1508 1509 currentView->togglePixelGrid(); 1510 updateButtonStatus("view_pixel_grid", i18n("Pixel Gridlines"), currentView->isPixelGridShown()); 1511 } 1512 1513 void FITSViewer::toggle3DGraph() 1514 { 1515 if (m_Tabs.empty()) 1516 return; 1517 1518 QSharedPointer<FITSView> currentView; 1519 if (!getCurrentView(currentView)) 1520 return; 1521 1522 currentView->toggleStarProfile(); 1523 updateButtonStatus("toggle_3D_graph", i18n("View 3D Graph"), currentView->isStarProfileShown()); 1524 } 1525 1526 void FITSViewer::nextTab() 1527 { 1528 if (m_Tabs.empty()) 1529 return; 1530 1531 int index = fitsTabWidget->currentIndex() + 1; 1532 if (index >= m_Tabs.count() || index < 0) 1533 index = 0; 1534 fitsTabWidget->setCurrentIndex(index); 1535 1536 actionCollection()->action("next_blink")->setEnabled(m_Tabs[index]->blinkFilenames().size() > 1); 1537 actionCollection()->action("previous_blink")->setEnabled(m_Tabs[index]->blinkFilenames().size() > 1); 1538 } 1539 1540 void FITSViewer::previousTab() 1541 { 1542 if (m_Tabs.empty()) 1543 return; 1544 1545 int index = fitsTabWidget->currentIndex() - 1; 1546 if (index >= m_Tabs.count() || index < 0) 1547 index = m_Tabs.count() - 1; 1548 fitsTabWidget->setCurrentIndex(index); 1549 1550 actionCollection()->action("next_blink")->setEnabled(m_Tabs[index]->blinkFilenames().size() > 1); 1551 actionCollection()->action("previous_blink")->setEnabled(m_Tabs[index]->blinkFilenames().size() > 1); 1552 1553 } 1554 1555 void FITSViewer::toggleStars() 1556 { 1557 if (markStars) 1558 { 1559 markStars = false; 1560 actionCollection()->action("mark_stars")->setText(i18n("Mark Stars")); 1561 } 1562 else 1563 { 1564 markStars = true; 1565 actionCollection()->action("mark_stars")->setText(i18n("Unmark Stars")); 1566 } 1567 1568 for (auto tab : m_Tabs) 1569 { 1570 tab->getView()->toggleStars(markStars); 1571 tab->getView()->updateFrame(); 1572 } 1573 } 1574 1575 void FITSViewer::applyFilter(int ftype) 1576 { 1577 if (m_Tabs.empty()) 1578 return; 1579 1580 QApplication::setOverrideCursor(Qt::WaitCursor); 1581 updateStatusBar(i18n("Processing %1...", filterTypes[ftype - 1]), FITS_MESSAGE); 1582 qApp->processEvents(); 1583 m_Tabs[fitsTabWidget->currentIndex()]->getHistogram()->applyFilter(static_cast<FITSScale>(ftype)); 1584 qApp->processEvents(); 1585 m_Tabs[fitsTabWidget->currentIndex()]->getView()->updateFrame(); 1586 QApplication::restoreOverrideCursor(); 1587 updateStatusBar(i18n("Ready."), FITS_MESSAGE); 1588 } 1589 1590 bool FITSViewer::getView(int fitsUID, QSharedPointer<FITSView> &view) 1591 { 1592 auto tab = fitsMap.value(fitsUID); 1593 if (tab) 1594 { 1595 view = tab->getView(); 1596 return true; 1597 } 1598 return false; 1599 1600 } 1601 1602 bool FITSViewer::getCurrentView(QSharedPointer<FITSView> &view) 1603 { 1604 if (m_Tabs.empty() || fitsTabWidget->currentIndex() >= m_Tabs.count()) 1605 return false; 1606 1607 view = m_Tabs[fitsTabWidget->currentIndex()]->getView(); 1608 return true; 1609 } 1610 1611 void FITSViewer::setDebayerAction(bool enable) 1612 { 1613 actionCollection()->addAction("fits_debayer")->setEnabled(enable); 1614 }