Kstars

fitsviewer.cpp
1/*
2 SPDX-FileCopyrightText: 2004 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 2006-03-03 Using CFITSIO, Porting to Qt4
5
6 SPDX-License-Identifier: GPL-2.0-or-later
7*/
8
9#include "fitsviewer.h"
10
11#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
12#include <QtGui/QUndoGroup>
13#endif
14
15#include "QtWidgets/qmenu.h"
16#include "QtWidgets/qstatusbar.h"
17#include "config-kstars.h"
18
19#include "fitsdata.h"
20#include "fitsdebayer.h"
21#include "fitstab.h"
22#include "fitsview.h"
23#include "kstars.h"
24#include "ksutils.h"
25#include "Options.h"
26#ifdef HAVE_INDI
27#include "indi/indilistener.h"
28#endif
29
30#include <KActionCollection>
31#include <QFileDialog>
32#include <KMessageBox>
33#include <KToolBar>
34#include <knotification.h>
35
36#include <fits_debug.h>
37
38#define INITIAL_W 785
39#define INITIAL_H 640
40
41bool FITSViewer::m_BlinkBusy = false;
42
43QList<KLocalizedString> FITSViewer::filterTypes = {ki18n("Auto Stretch"), ki18n("High Contrast"), ki18n("Equalize"),
44 ki18n("High Pass"), ki18n("Median"), ki18n("Gaussian blur"), ki18n("Rotate Right"), ki18n("Rotate Left"), ki18n("Flip Horizontal"),
45 ki18n("Flip Vertical")
46 };
47
49{
50#ifdef Q_OS_MACOS
51 if (Options::independentWindowFITS())
53 else
54 {
56 connect(QApplication::instance(), SIGNAL(applicationStateChanged(Qt::ApplicationState)), this,
57 SLOT(changeAlwaysOnTop(Qt::ApplicationState)));
58 }
59#endif
60
61 // Since QSharedPointer is managing it, do not delete automatically.
63
64 fitsTabWidget = new QTabWidget(this);
65 undoGroup = new QUndoGroup(this);
66
67 lastURL = QUrl(QDir::homePath());
68
69 fitsTabWidget->setTabsClosable(true);
70
71 setWindowIcon(QIcon::fromTheme("kstars_fitsviewer"));
72
73 setCentralWidget(fitsTabWidget);
74
75 connect(fitsTabWidget, &QTabWidget::currentChanged, this, &FITSViewer::tabFocusUpdated);
76 connect(fitsTabWidget, &QTabWidget::tabCloseRequested, this, &FITSViewer::closeTab);
77
78 //These two connections will enable or disable the scope button if a scope is available or not.
79 //Of course this is also dependent on the presence of WCS data in the image.
80
81#ifdef HAVE_INDI
82 connect(INDIListener::Instance(), &INDIListener::newDevice, this, &FITSViewer::updateWCSFunctions);
83 connect(INDIListener::Instance(), &INDIListener::newDevice, this, &FITSViewer::updateWCSFunctions);
84#endif
85
87
88 fitsPosition.setAlignment(Qt::AlignCenter);
89 fitsPosition.setMinimumWidth(100);
91 fitsValue.setMinimumWidth(40);
92
93 fitsWCS.setVisible(false);
94
95 statusBar()->insertPermanentWidget(FITS_CLIP, &fitsClip);
96 statusBar()->insertPermanentWidget(FITS_HFR, &fitsHFR);
97 statusBar()->insertPermanentWidget(FITS_WCS, &fitsWCS);
98 statusBar()->insertPermanentWidget(FITS_VALUE, &fitsValue);
99 statusBar()->insertPermanentWidget(FITS_POSITION, &fitsPosition);
100 statusBar()->insertPermanentWidget(FITS_ZOOM, &fitsZoom);
101 statusBar()->insertPermanentWidget(FITS_RESOLUTION, &fitsResolution);
102 statusBar()->insertPermanentWidget(FITS_LED, &led);
103
104 QAction *action = actionCollection()->addAction("rotate_right", this, &FITSViewer::rotateCW);
105
106 action->setText(i18n("Rotate Right"));
107 action->setIcon(QIcon::fromTheme("object-rotate-right"));
108
109 action = actionCollection()->addAction("rotate_left", this, &FITSViewer::rotateCCW);
110 action->setText(i18n("Rotate Left"));
111 action->setIcon(QIcon::fromTheme("object-rotate-left"));
112
113 action = actionCollection()->addAction("flip_horizontal", this, &FITSViewer::flipHorizontal);
114 action->setText(i18n("Flip Horizontal"));
116 QIcon::fromTheme("object-flip-horizontal"));
117
118 action = actionCollection()->addAction("flip_vertical", this, &FITSViewer::flipVertical);
119 action->setText(i18n("Flip Vertical"));
120 action->setIcon(QIcon::fromTheme("object-flip-vertical"));
121
122 action = actionCollection()->addAction("image_histogram");
123 action->setText(i18n("Histogram"));
124 connect(action, &QAction::triggered, this, &FITSViewer::histoFITS);
126
127 action->setIcon(QIcon(":/icons/histogram.png"));
128
129 action = KStandardAction::open(this, &FITSViewer::openFile, actionCollection());
130 action->setIcon(QIcon::fromTheme("document-open"));
131
132 action = actionCollection()->addAction("blink");
134 action->setText(i18n("Open/Blink Directory"));
135 connect(action, &QAction::triggered, this, &FITSViewer::blink);
136
137 saveFileAction = KStandardAction::save(this, &FITSViewer::saveFile, actionCollection());
138 saveFileAction->setIcon(QIcon::fromTheme("document-save"));
139
140 saveFileAsAction = KStandardAction::saveAs(this, &FITSViewer::saveFileAs, actionCollection());
141 saveFileAsAction->setIcon(
142 QIcon::fromTheme("document-save_as"));
143
144 action = actionCollection()->addAction("fits_header");
146 action->setIcon(QIcon::fromTheme("document-properties"));
147 action->setText(i18n("FITS Header"));
148 connect(action, &QAction::triggered, this, &FITSViewer::headerFITS);
149
150 action = actionCollection()->addAction("fits_debayer");
152 action->setIcon(QIcon::fromTheme("view-preview"));
153 action->setText(i18n("Debayer..."));
154 connect(action, &QAction::triggered, this, &FITSViewer::debayerFITS);
155
157 action->setIcon(QIcon::fromTheme("window-close"));
158
159 action = KStandardAction::copy(this, &FITSViewer::copyFITS, actionCollection());
160 action->setIcon(QIcon::fromTheme("edit-copy"));
161
162 action = KStandardAction::zoomIn(this, &FITSViewer::ZoomIn, actionCollection());
163 action->setIcon(QIcon::fromTheme("zoom-in"));
164
165 action = KStandardAction::zoomOut(this, &FITSViewer::ZoomOut, actionCollection());
166 action->setIcon(QIcon::fromTheme("zoom-out"));
167
168 action = KStandardAction::actualSize(this, &FITSViewer::ZoomDefault, actionCollection());
169 action->setIcon(QIcon::fromTheme("zoom-fit-best"));
170
172 kundo->setIcon(QIcon::fromTheme("edit-undo"));
173
175 kredo->setIcon(QIcon::fromTheme("edit-redo"));
176
179
180 action = actionCollection()->addAction("image_stats");
181 action->setIcon(QIcon::fromTheme("view-statistics"));
182 action->setText(i18n("Statistics"));
183 connect(action, &QAction::triggered, this, &FITSViewer::statFITS);
184
185 action = actionCollection()->addAction("image_roi_stats");
186
187 roiActionMenu = new KActionMenu(QIcon(":/icons/select_stat"), "Selection Statistics", action );
188 roiActionMenu->setText(i18n("&Selection Statistics"));
190 roiActionMenu->addSeparator();
191 connect(roiActionMenu, &QAction::triggered, this, &FITSViewer::toggleSelectionMode);
192
193 KToggleAction *ksa = actionCollection()->add<KToggleAction>("100x100");
194 ksa->setText("100x100");
195 ksa->setCheckable(false);
196 roiActionMenu->addAction(ksa);
197 ksa = actionCollection()->add<KToggleAction>("50x50");
198 ksa->setText("50x50");
199 ksa->setCheckable(false);
200 roiActionMenu->addAction(ksa);
201 ksa = actionCollection()->add<KToggleAction>("25x25");
202 ksa->setText("25x25");
203 ksa->setCheckable(false);
204 roiActionMenu->addAction(ksa);
205 ksa = actionCollection()->add<KToggleAction>("CustomRoi");
206 ksa->setText("Custom");
207 ksa->setCheckable(false);
208 roiActionMenu->addAction(ksa);
209
210 action->setMenu(roiActionMenu->menu());
211 action->setIcon(QIcon(":/icons/select_stat"));
212 action->setCheckable(true);
213
214 connect(roiActionMenu->menu()->actions().at(1), &QAction::triggered, this, [this] { ROIFixedSize(100); });
215 connect(roiActionMenu->menu()->actions().at(2), &QAction::triggered, this, [this] { ROIFixedSize(50); });
216 connect(roiActionMenu->menu()->actions().at(3), &QAction::triggered, this, [this] { ROIFixedSize(25); });
217 connect(roiActionMenu->menu()->actions().at(4), &QAction::triggered, this, [this] { customROIInputWindow();});
218 connect(action, &QAction::triggered, this, &FITSViewer::toggleSelectionMode);
219
220 action = actionCollection()->addAction("view_crosshair");
221 action->setIcon(QIcon::fromTheme("crosshairs"));
222 action->setText(i18n("Show Cross Hairs"));
223 action->setCheckable(true);
224 connect(action, &QAction::triggered, this, &FITSViewer::toggleCrossHair);
225
226 action = actionCollection()->addAction("view_clipping");
228 action->setIcon(QIcon::fromTheme("media-record"));
229 action->setText(i18n("Show Clipping"));
230 action->setCheckable(true);
231 connect(action, &QAction::triggered, this, &FITSViewer::toggleClipping);
232
233 action = actionCollection()->addAction("view_pixel_grid");
234 action->setIcon(QIcon::fromTheme("map-flat"));
235 action->setText(i18n("Show Pixel Gridlines"));
236 action->setCheckable(true);
237 connect(action, &QAction::triggered, this, &FITSViewer::togglePixelGrid);
238
239 action = actionCollection()->addAction("view_eq_grid");
240 action->setIcon(QIcon::fromTheme("kstars_grid"));
241 action->setText(i18n("Show Equatorial Gridlines"));
242 action->setCheckable(true);
243 action->setDisabled(true);
244 connect(action, &QAction::triggered, this, &FITSViewer::toggleEQGrid);
245
246 action = actionCollection()->addAction("view_objects");
247 action->setIcon(QIcon::fromTheme("help-hint"));
248 action->setText(i18n("Show Objects in Image"));
249 action->setCheckable(true);
250 action->setDisabled(true);
251 connect(action, &QAction::triggered, this, &FITSViewer::toggleObjects);
252
253 action = actionCollection()->addAction("view_hips_overlay");
254 action->setIcon(QIcon::fromTheme("pixelate"));
255 action->setText(i18n("Show HiPS Overlay"));
256 action->setCheckable(true);
257 action->setDisabled(true);
258 connect(action, &QAction::triggered, this, &FITSViewer::toggleHiPSOverlay);
259
260 action = actionCollection()->addAction("center_telescope");
261 action->setIcon(QIcon(":/icons/center_telescope.svg"));
262 action->setText(i18n("Center Telescope\n*No Telescopes Detected*"));
263 action->setDisabled(true);
264 action->setCheckable(true);
266
267 action = actionCollection()->addAction("view_zoom_fit");
268 action->setIcon(QIcon::fromTheme("zoom-fit-width"));
269 action->setText(i18n("Zoom To Fit"));
270 connect(action, &QAction::triggered, this, &FITSViewer::ZoomToFit);
271
272 action = actionCollection()->addAction("view_fit_page");
273 action->setIcon(QIcon::fromTheme("zoom-original"));
274 action->setText(i18n("Fit Page to Zoom"));
275 connect(action, &QAction::triggered, this, &FITSViewer::FitToZoom);
276
277 action = actionCollection()->addAction("next_tab");
279 action->setText(i18n("Next Tab"));
280 connect(action, &QAction::triggered, this, &FITSViewer::nextTab);
281
282 action = actionCollection()->addAction("previous_tab");
284 action->setText(i18n("Previous Tab"));
285 connect(action, &QAction::triggered, this, &FITSViewer::previousTab);
286
287 action = actionCollection()->addAction("next_blink");
289 action->setText(i18n("Next Blink Image"));
290 connect(action, &QAction::triggered, this, &FITSViewer::nextBlink);
291
292 action = actionCollection()->addAction("previous_blink");
294 action->setText(i18n("Previous Blink Image"));
295 connect(action, &QAction::triggered, this, &FITSViewer::previousBlink);
296
297 action = actionCollection()->addAction("zoom_all_in");
299 action->setText(i18n("Zoom all tabs in"));
300 connect(action, &QAction::triggered, this, &FITSViewer::ZoomAllIn);
301
302 action = actionCollection()->addAction("zoom_all_out");
304 action->setText(i18n("Zoom all tabs out"));
305 connect(action, &QAction::triggered, this, &FITSViewer::ZoomAllOut);
306
307 action = actionCollection()->addAction("mark_stars");
308 action->setIcon(QIcon::fromTheme("glstarbase", QIcon(":/icons/glstarbase.png")));
309 action->setText(i18n("Mark Stars"));
311 action->setCheckable(true);
312 connect(action, &QAction::triggered, this, &FITSViewer::toggleStars);
313
314#ifdef HAVE_DATAVISUALIZATION
315 action = actionCollection()->addAction("toggle_3D_graph");
316 action->setIcon(QIcon::fromTheme("star_profile", QIcon(":/icons/star_profile.svg")));
317 action->setText(i18n("View 3D Graph"));
318 action->setCheckable(true);
319 connect(action, &QAction::triggered, this, &FITSViewer::toggle3DGraph);
320#endif
321
322
323 int filterCounter = 1;
324
325 for (auto &filter : FITSViewer::filterTypes)
326 {
327 action = actionCollection()->addAction(QString("filter%1").arg(filterCounter));
328 action->setText(i18n(filter.toString().toUtf8().constData()));
329 connect(action, &QAction::triggered, this, [this, filterCounter] { applyFilter(filterCounter);});
330 filterCounter++;
331 }
332
334 /* Create GUI */
335 createGUI("fitsviewerui.rc");
336
337 setWindowTitle(i18nc("@title:window", "KStars FITS Viewer"));
338
339 /* initially resize in accord with KDE rules */
340 show();
341 resize(INITIAL_W, INITIAL_H);
342}
343
344void FITSViewer::changeAlwaysOnTop(Qt::ApplicationState state)
345{
346 if (isVisible())
347 {
348 if (state == Qt::ApplicationActive)
350 else
352 show();
353 }
354}
355
356FITSViewer::~FITSViewer()
357{
358}
359
360void FITSViewer::closeEvent(QCloseEvent * /*event*/)
361{
362 KStars *ks = KStars::Instance();
363
364 if (ks)
365 {
366 QAction *a = KStars::Instance()->actionCollection()->action("show_fits_viewer");
368
369 if (a && viewers.count() == 1)
370 {
371 a->setEnabled(false);
372 a->setChecked(false);
373 }
374 }
375
376 emit terminated();
377}
378
379void FITSViewer::hideEvent(QHideEvent * /*event*/)
380{
381 KStars *ks = KStars::Instance();
382
383 if (ks)
384 {
385 QAction *a = KStars::Instance()->actionCollection()->action("show_fits_viewer");
386 if (a)
387 {
389
390 if (viewers.count() <= 1)
391 a->setChecked(false);
392 }
393 }
394}
395
396void FITSViewer::showEvent(QShowEvent * /*event*/)
397{
398 QAction *a = KStars::Instance()->actionCollection()->action("show_fits_viewer");
399 if (a)
400 {
401 a->setEnabled(true);
402 a->setChecked(true);
403 }
404}
405
406
407namespace
408{
409QString HFRStatusString(const QSharedPointer<FITSData> &data)
410{
411 const double hfrValue = data->getHFR();
412 if (hfrValue <= 0.0) return QString("");
413 if (data->getSkyBackground().starsDetected > 0)
414 return
415 i18np("HFR:%2 Ecc:%3 %1 star.", "HFR:%2 Ecc:%3 %1 stars.",
416 data->getSkyBackground().starsDetected,
417 QString::number(hfrValue, 'f', 2),
418 QString::number(data->getEccentricity(), 'f', 2));
419 else
420 return
421 i18np("HFR:%2, %1 star.", "HFR:%2, %1 stars.",
422 data->getDetectedStars(),
423 QString::number(hfrValue, 'f', 2));
424}
425
426QString HFRClipString(FITSView* view)
427{
428 if (view->isClippingShown())
429 {
430 const int numClipped = view->getNumClipped();
431 if (numClipped < 0)
432 return QString("Clip:failed");
433 else
434 return QString("Clip:%1").arg(view->getNumClipped());
435 }
436 return "";
437}
438} // namespace
439
440bool FITSViewer::addFITSCommon(const QSharedPointer<FITSTab> &tab, const QUrl &imageName,
441 FITSMode mode, const QString &previewText)
442{
443 int tabIndex = fitsTabWidget->indexOf(tab.get());
444 if (tabIndex != -1)
445 return false;
446
447 if (!imageName.isValid())
448 lastURL = QUrl(imageName.url(QUrl::RemoveFilename));
449
451 tab->setPreviewText(previewText);
452
453 // Connect tab signals
454 tab->disconnect(this);
455 connect(tab.get(), &FITSTab::newStatus, this, &FITSViewer::updateStatusBar);
456 connect(tab.get(), &FITSTab::changeStatus, this, &FITSViewer::updateTabStatus);
457 connect(tab.get(), &FITSTab::debayerToggled, this, &FITSViewer::setDebayerAction);
458 // Connect tab view signals
459 connect(tab->getView().get(), &FITSView::actionUpdated, this, &FITSViewer::updateAction);
460 connect(tab->getView().get(), &FITSView::wcsToggled, this, &FITSViewer::updateWCSFunctions);
461 connect(tab->getView().get(), &FITSView::starProfileWindowClosed, this, &FITSViewer::starProfileButtonOff);
462
463 switch (mode)
464 {
465 case FITS_NORMAL:
466 case FITS_CALIBRATE:
467 fitsTabWidget->addTab(tab.get(), previewText.isEmpty() ? imageName.fileName() : previewText);
468 break;
469
470 case FITS_FOCUS:
471 fitsTabWidget->addTab(tab.get(), i18n("Focus"));
472 break;
473
474 case FITS_GUIDE:
475 fitsTabWidget->addTab(tab.get(), i18n("Guide"));
476 break;
477
478 case FITS_ALIGN:
479 fitsTabWidget->addTab(tab.get(), i18n("Align"));
480 break;
481
482 case FITS_UNKNOWN:
483 break;
484 }
485
486 saveFileAction->setEnabled(true);
487 saveFileAsAction->setEnabled(true);
488
489 undoGroup->addStack(tab->getUndoStack());
490
491 fitsMap[fitsID] = tab;
492
493 fitsTabWidget->setCurrentWidget(tab.get());
494
495 actionCollection()->action("fits_debayer")->setEnabled(tab->getView()->imageData()->hasDebayer());
496
497 tab->tabPositionUpdated();
498
499 tab->setUID(fitsID);
500
501 led.setColor(Qt::green);
502
503 if (tab->shouldComputeHFR())
504 updateStatusBar(HFRStatusString(tab->getView()->imageData()), FITS_HFR);
505 else
506 updateStatusBar("", FITS_HFR);
507 updateStatusBar(i18n("Ready."), FITS_MESSAGE);
508
509 updateStatusBar(HFRClipString(tab->getView().get()), FITS_CLIP);
510
511 tab->getView()->setCursorMode(FITSView::dragCursor);
512
513 actionCollection()->action("next_blink")->setEnabled(tab->blinkFilenames().size() > 1);
514 actionCollection()->action("previous_blink")->setEnabled(tab->blinkFilenames().size() > 1);
515
517
518 return true;
519}
520
521void FITSViewer::loadFiles()
522{
523 if (m_urls.size() == 0)
524 return;
525
526 const QUrl imageName = m_urls[0];
527 m_urls.pop_front();
528
529 // Make sure we don't have it open already, if yes, switch to it
530 QString fpath = imageName.toLocalFile();
531 for (auto tab : m_Tabs)
532 {
533 const QString cpath = tab->getCurrentURL()->path();
534 if (fpath == cpath)
535 {
536 fitsTabWidget->setCurrentWidget(tab.get());
537 if (m_urls.size() > 0)
538 loadFiles();
539 return;
540 }
541 }
542
543 led.setColor(Qt::yellow);
545
546 QSharedPointer<FITSTab> tab(new FITSTab(this));
547
548 m_Tabs.push_back(tab);
549
550 connect(tab.get(), &FITSTab::failed, this, [ this ](const QString & errorMessage)
551 {
552 QApplication::restoreOverrideCursor();
553 led.setColor(Qt::red);
554 m_Tabs.removeLast();
555 emit failed(errorMessage);
556 if (m_Tabs.size() == 0)
557 {
558 // Close FITS Viewer and let KStars know it is no longer needed in memory.
559 close();
560 }
561
562 if (m_urls.size() > 0)
563 loadFiles();
564 });
565
566 connect(tab.get(), &FITSTab::loaded, this, [ = ]()
567 {
568 if (addFITSCommon(m_Tabs.last(), imageName, FITS_NORMAL, ""))
569 emit loaded(fitsID++);
570 else
571 m_Tabs.removeLast();
572
573 if (m_urls.size() > 0)
574 loadFiles();
575 });
576
577 tab->loadFile(imageName, FITS_NORMAL, FITS_NONE);
578}
579
580int FITSViewer::loadFile(const QUrl &imageName, FITSMode mode, FITSScale filter, const QString &previewText)
581{
582 led.setColor(Qt::yellow);
584
585 QSharedPointer<FITSTab> tab(new FITSTab(this));
586
587 m_Tabs.push_back(tab);
588 const int id = fitsID;
589
590 connect(tab.get(), &FITSTab::failed, this, [ this ](const QString & errorMessage)
591 {
592 QApplication::restoreOverrideCursor();
593 led.setColor(Qt::red);
594 m_Tabs.removeLast();
595 emit failed(errorMessage);
596 if (m_Tabs.size() == 0)
597 {
598 // Close FITS Viewer and let KStars know it is no longer needed in memory.
599 close();
600 }
601 });
602
603 connect(tab.get(), &FITSTab::loaded, this, [ = ]()
604 {
605 if (addFITSCommon(m_Tabs.last(), imageName, mode, previewText))
606 emit loaded(fitsID++);
607 else
608 m_Tabs.removeLast();
609 });
610
611 tab->loadFile(imageName, mode, filter);
612 return id;
613}
614
615bool FITSViewer::loadData(const QSharedPointer<FITSData> &data, const QUrl &imageName, int *tab_uid, FITSMode mode,
616 FITSScale filter, const QString &previewText)
617{
618 led.setColor(Qt::yellow);
620
621 QSharedPointer<FITSTab> tab(new FITSTab(this));
622
623 m_Tabs.push_back(tab);
624
625 if (!tab->loadData(data, mode, filter))
626 {
627 auto errorMessage = tab->getView()->imageData()->getLastError();
629 led.setColor(Qt::red);
630 m_Tabs.removeLast();
631 emit failed(errorMessage);
632 if (m_Tabs.size() == 0)
633 {
634 // Close FITS Viewer and let KStars know it is no longer needed in memory.
635 close();
636 }
637 return false;
638 }
639
640 if (!addFITSCommon(tab, imageName, mode, previewText))
641 {
642 m_Tabs.removeLast();
643 return false;
644 }
645
646 *tab_uid = fitsID++;
647 return true;
648}
649
650bool FITSViewer::removeFITS(int fitsUID)
651{
652 auto tab = fitsMap.value(fitsUID);
653
654 if (tab.isNull())
655 {
656 qCWarning(KSTARS_FITS) << "Cannot find tab with UID " << fitsUID << " in the FITS Viewer";
657 return false;
658 }
659
660 int index = m_Tabs.indexOf(tab);
661
662 if (index >= 0)
663 {
664 closeTab(index);
665 return true;
666 }
667
668 return false;
669}
670
671void FITSViewer::updateFile(const QUrl &imageName, int fitsUID, FITSScale filter)
672{
673 static bool updateBusy = false;
674 if (updateBusy)
675 return;
676 updateBusy = true;
677
678 auto tab = fitsMap.value(fitsUID);
679
680 if (tab.isNull())
681 {
682 QString message = i18n("Cannot find tab with UID %1 in the FITS Viewer", fitsUID);
683 emit failed(message);
684 updateBusy = false;
685 return;
686 }
687
688 if (tab->isVisible())
689 led.setColor(Qt::yellow);
690
691 // On tab load success
692 auto conn = std::make_shared<QMetaObject::Connection>();
693 *conn = connect(tab.get(), &FITSTab::loaded, this, [ = ]()
694 {
695 if (updateFITSCommon(tab, imageName))
696 {
697 QObject::disconnect(*conn);
698 emit loaded(tab->getUID());
699 updateBusy = false;
700 }
701 });
702
703 auto conn2 = std::make_shared<QMetaObject::Connection>();
704 *conn2 = connect(tab.get(), &FITSTab::failed, this, [ = ](const QString & errorMessage)
705 {
706 Q_UNUSED(errorMessage);
707 QObject::disconnect(*conn2);
708 updateBusy = false;
709 });
710
711 tab->loadFile(imageName, tab->getView()->getMode(), filter);
712}
713
714bool FITSViewer::updateFITSCommon(const QSharedPointer<FITSTab> &tab, const QUrl &imageName, const QString tabTitle)
715{
716 // On tab load success
717 int tabIndex = fitsTabWidget->indexOf(tab.get());
718 if (tabIndex == -1)
719 return false;
720
721 if (tabTitle != "")
722 fitsTabWidget->setTabText(tabIndex, tabTitle);
723 else if (tab->getView()->getMode() == FITS_NORMAL)
724 {
725 if ((imageName.fileName() == "Preview" ||
726 imageName.path().startsWith(QLatin1String("/tmp")) ||
727 imageName.path().contains("/Temp")) &&
728 Options::singlePreviewFITS())
729 fitsTabWidget->setTabText(tabIndex,
730 tab->getPreviewText().isEmpty() ? i18n("Preview") : tab->getPreviewText());
731 else if (tab->getPreviewText() != i18n("Preview") || imageName.fileName().size() > 0)
732 fitsTabWidget->setTabText(tabIndex, imageName.fileName());
733 }
734 else if (imageName.isValid())
735 fitsTabWidget->setTabText(tabIndex, imageName.fileName());
736
737 tab->getUndoStack()->clear();
738
739 if (tab->isVisible())
740 led.setColor(Qt::green);
741
742 if (tab->shouldComputeHFR())
743 updateStatusBar(HFRStatusString(tab->getView()->imageData()), FITS_HFR);
744 else
745 updateStatusBar("", FITS_HFR);
746
747 updateStatusBar(HFRClipString(tab->getView().get()), FITS_CLIP);
748
749 actionCollection()->action("next_blink")->setEnabled(tab->blinkFilenames().size() > 1);
750 actionCollection()->action("previous_blink")->setEnabled(tab->blinkFilenames().size() > 1);
751
752 return true;
753}
754
755bool FITSViewer::updateData(const QSharedPointer<FITSData> &data, const QUrl &imageName, int fitsUID, int *tab_uid,
756 FITSMode mode, FITSScale filter, const QString &tabTitle)
757{
758 auto tab = fitsMap.value(fitsUID);
759
760 if (tab.isNull())
761 return false;
762
763 if (mode != FITS_UNKNOWN)
764 tab->getView()->updateMode(mode);
765
766 if (tab->isVisible())
767 led.setColor(Qt::yellow);
768
769 if (!tab->loadData(data, tab->getView()->getMode(), filter))
770 return false;
771
772 if (!updateFITSCommon(tab, imageName, tabTitle))
773 return false;
774
775 *tab_uid = tab->getUID();
776 return true;
777}
778
779void FITSViewer::tabFocusUpdated(int currentIndex)
780{
781 if (currentIndex < 0 || m_Tabs.empty())
782 return;
783
784 m_Tabs[currentIndex]->tabPositionUpdated();
785
786 auto view = m_Tabs[currentIndex]->getView();
787
788 view->toggleStars(markStars);
789
790 if (isVisible())
791 view->updateFrame();
792
793 if (m_Tabs[currentIndex]->shouldComputeHFR())
794 updateStatusBar(HFRStatusString(view->imageData()), FITS_HFR);
795 else
796 updateStatusBar("", FITS_HFR);
797
798 updateStatusBar(HFRClipString(m_Tabs[currentIndex]->getView().get()), FITS_CLIP);
799
800 if (view->imageData()->hasDebayer())
801 {
802 actionCollection()->action("fits_debayer")->setEnabled(true);
803
804 if (debayerDialog)
805 {
806 BayerParams param;
807 view->imageData()->getBayerParams(&param);
808 debayerDialog->setBayerParams(&param);
809 }
810 }
811 else
812 actionCollection()->action("fits_debayer")->setEnabled(false);
813
814 updateStatusBar("", FITS_WCS);
815 connect(view.get(), &FITSView::starProfileWindowClosed, this, &FITSViewer::starProfileButtonOff);
816 QSharedPointer<FITSView> currentView;
817 if (getCurrentView(currentView))
818 {
819 updateButtonStatus("toggle_3D_graph", i18n("currentView 3D Graph"), currentView->isStarProfileShown());
820 updateButtonStatus("view_crosshair", i18n("Cross Hairs"), currentView->isCrosshairShown());
821 updateButtonStatus("view_clipping", i18n("Clipping"), currentView->isClippingShown());
822 updateButtonStatus("view_eq_grid", i18n("Equatorial Gridlines"), currentView->isEQGridShown());
823 updateButtonStatus("view_objects", i18n("Objects in Image"), currentView->areObjectsShown());
824 updateButtonStatus("view_pixel_grid", i18n("Pixel Gridlines"), currentView->isPixelGridShown());
825 updateButtonStatus("view_hips_overlay", i18n("HiPS Overlay"), currentView->isHiPSOverlayShown());
826 }
827
828 actionCollection()->action("next_blink")->setEnabled(m_Tabs[currentIndex]->blinkFilenames().size() > 1);
829 actionCollection()->action("previous_blink")->setEnabled(m_Tabs[currentIndex]->blinkFilenames().size() > 1);
830
831 updateScopeButton();
833}
834
835void FITSViewer::starProfileButtonOff()
836{
837 updateButtonStatus("toggle_3D_graph", i18n("View 3D Graph"), false);
838}
839
840
841QList<QString> findAllImagesBelowDir(const QDir &topDir)
842{
843 QList<QString> result;
844 QList<QString> nameFilter = { "*" };
846
847 QList<QDir> dirs;
848 dirs.push_back(topDir);
849
850 QRegularExpression re(".*(fits|fits.fz|fit|fts|xisf|jpg|jpeg|png|gif|bmp|cr2|cr3|crw|nef|raf|dng|arw|orf)$");
851 while (!dirs.empty())
852 {
853 auto dir = dirs.back();
854 dirs.removeLast();
855 auto list = dir.entryInfoList( nameFilter, filter );
856 foreach( const QFileInfo &entry, list)
857 {
858 if( entry.isDir() )
859 dirs.push_back(entry.filePath());
860 else
861 {
862 const QString suffix = entry.completeSuffix();
863 QRegularExpressionMatch match = re.match(suffix);
864 if (match.hasMatch())
865 result.append(entry.absoluteFilePath());
866 }
867 }
868 }
869 return result;
870}
871
872void FITSViewer::blink()
873{
874 if (m_BlinkBusy)
875 return;
876 m_BlinkBusy = true;
877 QFileDialog dialog(KStars::Instance(), i18nc("@title:window", "Blink Top Directory"));
878 dialog.setFileMode(QFileDialog::Directory);
879 dialog.setDirectoryUrl(lastURL);
880
881 if (!dialog.exec())
882 {
883 m_BlinkBusy = false;
884 return;
885 }
886 QStringList selected = dialog.selectedFiles();
887 if (selected.size() < 1)
888 {
889 m_BlinkBusy = false;
890 return;
891 }
892 QString topDir = selected[0];
893
894 auto allImages = findAllImagesBelowDir(QDir(topDir));
895 if (allImages.size() == 0)
896 {
897 m_BlinkBusy = false;
898 return;
899 }
900
901 const QUrl imageName(QUrl::fromLocalFile(allImages[0]));
902
903 led.setColor(Qt::yellow);
905
906 QSharedPointer<FITSTab> tab(new FITSTab(this));
907
908 int tabIndex = m_Tabs.size();
909 if (allImages.size() > 1)
910 {
911 m_Tabs.push_back(tab);
912 tab->initBlink(allImages);
913 tab->setBlinkUpto(1);
914 }
915 QString tabName = QString("%1/%2 %3")
916 .arg(1).arg(allImages.size()).arg(QFileInfo(allImages[0]).fileName());
917 connect(tab.get(), &FITSTab::failed, this, [ this ](const QString & errorMessage)
918 {
919 Q_UNUSED(errorMessage);
920 QObject::sender()->disconnect(this);
921 QApplication::restoreOverrideCursor();
922 led.setColor(Qt::red);
923 m_BlinkBusy = false;
925
926 connect(tab.get(), &FITSTab::loaded, this, [ = ]()
927 {
928 QObject::sender()->disconnect(this);
929 addFITSCommon(m_Tabs.last(), imageName, FITS_NORMAL, "");
930 //fitsTabWidget->tabBar()->setTabTextColor(tabIndex, Qt::red);
931 fitsTabWidget->setTabText(tabIndex, tabName);
932 m_BlinkBusy = false;
934
935 actionCollection()->action("next_blink")->setEnabled(allImages.size() > 1);
936 actionCollection()->action("previous_blink")->setEnabled(allImages.size() > 1);
937
938 tab->loadFile(imageName, FITS_NORMAL, FITS_NONE);
939}
940
941
942void FITSViewer::changeBlink(bool increment)
943{
944 if (m_Tabs.empty() || m_BlinkBusy)
945 return;
946
947 m_BlinkBusy = true;
948 const int tabIndex = fitsTabWidget->currentIndex();
949 if (tabIndex >= m_Tabs.count() || tabIndex < 0)
950 {
951 m_BlinkBusy = false;
952 return;
953 }
954 auto tab = m_Tabs[tabIndex];
955 const QList<QString> &filenames = tab->blinkFilenames();
956 if (filenames.size() <= 1)
957 {
958 m_BlinkBusy = false;
959 return;
960 }
961
962 int blinkIndex = tab->blinkUpto() + (increment ? 1 : -1);
963 if (blinkIndex >= filenames.size())
964 blinkIndex = 0;
965 else if (blinkIndex < 0)
966 blinkIndex = filenames.size() - 1;
967
968 QString nextFilename = filenames[blinkIndex];
969 QString tabName = QString("%1/%2 %3")
970 .arg(blinkIndex + 1).arg(filenames.size()).arg(QFileInfo(nextFilename).fileName());
971 tab->disconnect(this);
972 connect(tab.get(), &FITSTab::failed, this, [ this, nextFilename ](const QString & errorMessage)
973 {
974 Q_UNUSED(errorMessage);
975 QObject::sender()->disconnect(this);
976 QApplication::restoreOverrideCursor();
977 led.setColor(Qt::red);
978 m_BlinkBusy = false;
980
981 connect(tab.get(), &FITSTab::loaded, this, [ = ]()
982 {
983 QObject::sender()->disconnect(this);
984 updateFITSCommon(tab, QUrl::fromLocalFile(nextFilename));
985 fitsTabWidget->setTabText(tabIndex, tabName);
986 m_BlinkBusy = false;
988
989 tab->setBlinkUpto(blinkIndex);
990 tab->loadFile(QUrl::fromLocalFile(nextFilename), FITS_NORMAL, FITS_NONE);
991}
992
993void FITSViewer::nextBlink()
994{
995 changeBlink(true);
996}
997
998void FITSViewer::previousBlink()
999{
1000 changeBlink(false);
1001}
1002
1003void FITSViewer::openFile()
1004{
1005 QFileDialog dialog(KStars::Instance(), i18nc("@title:window", "Open Image"));
1006 dialog.setFileMode(QFileDialog::ExistingFiles);
1007 dialog.setDirectoryUrl(lastURL);
1008 dialog.setNameFilter("Images (*.fits *.fits.fz *.fit *.fts *.xisf "
1009 "*.jpg *.jpeg *.png *.gif *.bmp "
1010 "*.cr2 *.cr3 *.crw *.nef *.raf *.dng *.arw *.orf)");
1011 if (!dialog.exec())
1012 return;
1013 m_urls = dialog.selectedUrls();
1014 if (m_urls.size() < 1)
1015 return;
1016 // Protect against, e.g. opening 1000 tabs. Not sure what the right number is.
1017 constexpr int MAX_NUM_OPENS = 40;
1018 if (m_urls.size() > MAX_NUM_OPENS)
1019 return;
1020
1021 lastURL = QUrl(m_urls[0].url(QUrl::RemoveFilename));
1022 loadFiles();
1023}
1024
1025void FITSViewer::saveFile()
1026{
1027 m_Tabs[fitsTabWidget->currentIndex()]->saveFile();
1028}
1029
1030void FITSViewer::saveFileAs()
1031{
1032 if (m_Tabs.empty())
1033 return;
1034
1035 if (m_Tabs[fitsTabWidget->currentIndex()]->saveFileAs() &&
1036 m_Tabs[fitsTabWidget->currentIndex()]->getView()->getMode() == FITS_NORMAL)
1037 fitsTabWidget->setTabText(fitsTabWidget->currentIndex(),
1038 m_Tabs[fitsTabWidget->currentIndex()]->getCurrentURL()->fileName());
1039}
1040
1041void FITSViewer::copyFITS()
1042{
1043 if (m_Tabs.empty())
1044 return;
1045
1046 m_Tabs[fitsTabWidget->currentIndex()]->copyFITS();
1047}
1048
1049void FITSViewer::histoFITS()
1050{
1051 if (m_Tabs.empty())
1052 return;
1053
1054 m_Tabs[fitsTabWidget->currentIndex()]->histoFITS();
1055}
1056
1057void FITSViewer::statFITS()
1058{
1059 if (m_Tabs.empty())
1060 return;
1061
1062 m_Tabs[fitsTabWidget->currentIndex()]->statFITS();
1063}
1064
1065void FITSViewer::rotateCW()
1066{
1067 applyFilter(FITS_ROTATE_CW);
1068}
1069
1070void FITSViewer::rotateCCW()
1071{
1072 applyFilter(FITS_ROTATE_CCW);
1073}
1074
1075void FITSViewer::flipHorizontal()
1076{
1077 applyFilter(FITS_MOUNT_FLIP_H);
1078}
1079
1080void FITSViewer::flipVertical()
1081{
1082 applyFilter(FITS_MOUNT_FLIP_V);
1083}
1084
1085void FITSViewer::headerFITS()
1086{
1087 if (m_Tabs.empty())
1088 return;
1089
1090 m_Tabs[fitsTabWidget->currentIndex()]->headerFITS();
1091}
1092
1093void FITSViewer::debayerFITS()
1094{
1095 if (debayerDialog == nullptr)
1096 {
1097 debayerDialog = new FITSDebayer(this);
1098 }
1099
1101 if (getCurrentView(view))
1102 {
1103 BayerParams param;
1104 view->imageData()->getBayerParams(&param);
1105 debayerDialog->setBayerParams(&param);
1106 debayerDialog->show();
1107 }
1108}
1109
1110void FITSViewer::updateStatusBar(const QString &msg, FITSBar id)
1111{
1112 switch (id)
1113 {
1114 case FITS_POSITION:
1115 fitsPosition.setText(msg);
1116 break;
1117 case FITS_RESOLUTION:
1118 fitsResolution.setText(msg);
1119 break;
1120 case FITS_ZOOM:
1121 fitsZoom.setText(msg);
1122 break;
1123 case FITS_WCS:
1124 fitsWCS.setVisible(true);
1125 fitsWCS.setText(msg);
1126 break;
1127 case FITS_VALUE:
1128 fitsValue.setText(msg);
1129 break;
1130 case FITS_HFR:
1131 fitsHFR.setText(msg);
1132 break;
1133 case FITS_CLIP:
1134 fitsClip.setText(msg);
1135 break;
1136 case FITS_MESSAGE:
1137 statusBar()->showMessage(msg);
1138 break;
1139
1140 default:
1141 break;
1142 }
1143}
1144
1145void FITSViewer::ZoomAllIn()
1146{
1147 if (m_Tabs.empty())
1148 return;
1149
1150 // Could add code to not call View::updateFrame for these
1151 for (int i = 0; i < fitsTabWidget->count(); ++i)
1152 if (i != fitsTabWidget->currentIndex())
1153 m_Tabs[i]->ZoomIn();
1154
1155 m_Tabs[fitsTabWidget->currentIndex()]->ZoomIn();
1156}
1157
1158void FITSViewer::ZoomAllOut()
1159{
1160 if (m_Tabs.empty())
1161 return;
1162
1163 // Could add code to not call View::updateFrame for these
1164 for (int i = 0; i < fitsTabWidget->count(); ++i)
1165 if (i != fitsTabWidget->currentIndex())
1166 m_Tabs[i]->ZoomOut();
1167
1168 m_Tabs[fitsTabWidget->currentIndex()]->ZoomOut();
1169}
1170
1171void FITSViewer::ZoomIn()
1172{
1173 if (m_Tabs.empty())
1174 return;
1175
1176 m_Tabs[fitsTabWidget->currentIndex()]->ZoomIn();
1177}
1178
1179void FITSViewer::ZoomOut()
1180{
1181 if (m_Tabs.empty())
1182 return;
1183
1184 m_Tabs[fitsTabWidget->currentIndex()]->ZoomOut();
1185}
1186
1187void FITSViewer::ZoomDefault()
1188{
1189 if (m_Tabs.empty())
1190 return;
1191
1192 m_Tabs[fitsTabWidget->currentIndex()]->ZoomDefault();
1193}
1194
1195void FITSViewer::ZoomToFit()
1196{
1197 if (m_Tabs.empty())
1198 return;
1199
1200 QSharedPointer<FITSView> currentView;
1201 if (getCurrentView(currentView))
1202 currentView->ZoomToFit();
1203}
1204
1205// Adjust the (top-level) window size to fit the current zoom, so that there
1206// are no scroll bars and minimal empty space in the view. Note, if the zoom
1207// is such that it would be larger than the display size than this can't be
1208// accomplished and the windowing will do its best.
1209void FITSViewer::FitToZoom()
1210{
1211 if (m_Tabs.empty())
1212 return;
1213
1214 QSharedPointer<FITSView> currentView;
1215 if (!getCurrentView(currentView))
1216 return;
1217
1218 const double zoom = currentView->getCurrentZoom() * .01;
1219 const int w = zoom * currentView->imageData()->width();
1220 const int h = zoom * currentView->imageData()->height();
1221 const int extraW = width() - currentView->width();
1222 const int extraH = height() - currentView->height();
1223 // These 4s seem to be needed to make sure we don't wind up with scroll bars.
1224 // Not sure why, would be great to figure out how to remove them.
1225 const int wNeeded = w + extraW + 4;
1226 const int hNeeded = h + extraH + 4;
1227 resize(wNeeded, hNeeded);
1228}
1229
1230void FITSViewer::updateAction(const QString &name, bool enable)
1231{
1232 QAction *toolAction = actionCollection()->action(name);
1233
1234 if (toolAction != nullptr)
1235 toolAction->setEnabled(enable);
1236}
1237
1238void FITSViewer::updateTabStatus(bool clean, const QUrl &imageURL)
1239{
1240 if (m_Tabs.empty() || (fitsTabWidget->currentIndex() >= m_Tabs.size()))
1241 return;
1242
1243 if (m_Tabs[fitsTabWidget->currentIndex()]->getView()->getMode() != FITS_NORMAL)
1244 return;
1245
1246 //QString tabText = fitsImages[fitsTab->currentIndex()]->getCurrentURL()->fileName();
1247
1248 QString tabText = imageURL.isEmpty() ? fitsTabWidget->tabText(fitsTabWidget->currentIndex()) : imageURL.fileName();
1249
1250 fitsTabWidget->setTabText(fitsTabWidget->currentIndex(), clean ? tabText.remove('*') : tabText + '*');
1251}
1252
1253void FITSViewer::closeTab(int index)
1254{
1255 if (m_Tabs.empty())
1256 return;
1257
1258 auto tab = m_Tabs[index];
1259
1260 int UID = tab->getUID();
1261
1262 fitsMap.remove(UID);
1263 m_Tabs.removeOne(tab);
1264
1265 if (m_Tabs.empty())
1266 {
1267 saveFileAction->setEnabled(false);
1268 saveFileAsAction->setEnabled(false);
1269 }
1270
1271 emit closed(UID);
1272}
1273
1274/**
1275 This is helper function to make it really easy to make the update the state of toggle buttons
1276 that either show or hide information in the Current view. This method would get called both
1277 when one of them gets pushed and also when tabs are switched.
1278 */
1279
1280void FITSViewer::updateButtonStatus(const QString &action, const QString &item, bool showing)
1281{
1283 if (a == nullptr)
1284 return;
1285
1286 if (showing)
1287 {
1288 a->setText(i18n("Hide %1", item));
1289 a->setChecked(true);
1290 }
1291 else
1292 {
1293 a->setText(i18n("Show %1", item));
1294 a->setChecked(false);
1295 }
1296}
1297
1298/**
1299This is a method that either enables or disables the WCS based features in the Current View.
1300 */
1301
1303{
1304 QSharedPointer<FITSView> currentView;
1305 if (!getCurrentView(currentView))
1306 return;
1307
1308 if (currentView->imageHasWCS())
1309 {
1310 actionCollection()->action("view_eq_grid")->setDisabled(false);
1311 actionCollection()->action("view_eq_grid")->setText(i18n("Show Equatorial Gridlines"));
1312 actionCollection()->action("view_objects")->setDisabled(false);
1313 actionCollection()->action("view_objects")->setText(i18n("Show Objects in Image"));
1314 if (currentView->isTelescopeActive())
1315 {
1316 actionCollection()->action("center_telescope")->setDisabled(false);
1317 actionCollection()->action("center_telescope")->setText(i18n("Center Telescope\n*Ready*"));
1318 }
1319 else
1320 {
1321 actionCollection()->action("center_telescope")->setDisabled(true);
1322 actionCollection()->action("center_telescope")->setText(i18n("Center Telescope\n*No Telescopes Detected*"));
1323 }
1324 actionCollection()->action("view_hips_overlay")->setDisabled(false);
1325 }
1326 else
1327 {
1328 actionCollection()->action("view_eq_grid")->setDisabled(true);
1329 actionCollection()->action("view_eq_grid")->setText(i18n("Show Equatorial Gridlines\n*No WCS Info*"));
1330 actionCollection()->action("center_telescope")->setDisabled(true);
1331 actionCollection()->action("center_telescope")->setText(i18n("Center Telescope\n*No WCS Info*"));
1332 actionCollection()->action("view_objects")->setDisabled(true);
1333 actionCollection()->action("view_objects")->setText(i18n("Show Objects in Image\n*No WCS Info*"));
1334 actionCollection()->action("view_hips_overlay")->setDisabled(true);
1335 }
1336}
1337
1338void FITSViewer::updateScopeButton()
1339{
1340 QSharedPointer<FITSView> currentView;
1341 if (!getCurrentView(currentView))
1342 return;
1343
1344 if (currentView->getCursorMode() == FITSView::scopeCursor)
1345 {
1346 actionCollection()->action("center_telescope")->setChecked(true);
1347 }
1348 else
1349 {
1350 actionCollection()->action("center_telescope")->setChecked(false);
1351 }
1352}
1353
1354void FITSViewer::ROIFixedSize(int s)
1355{
1356 if (m_Tabs.empty())
1357 return;
1358
1359 QSharedPointer<FITSView> currentView;
1360 if (getCurrentView(currentView))
1361 {
1362 if(!currentView->isSelectionRectShown())
1363 {
1364 toggleSelectionMode();
1365 updateButtonStatus("image_roi_stats", i18n("Selection Rectangle"), currentView->isSelectionRectShown());
1366 }
1367 currentView->processRectangleFixed(s);
1368 }
1369}
1370
1371void FITSViewer::customROIInputWindow()
1372{
1373 if(m_Tabs.empty())
1374 return;
1375
1376 QSharedPointer<FITSView> currentView;
1377 if (getCurrentView(currentView))
1378 {
1379 if(!currentView->isSelectionRectShown())
1380 return;
1381
1382 int mh = currentView->imageData()->height();
1383 int mw = currentView->imageData()->width();
1384
1385 if(mh % 2)
1386 mh++;
1387 if(mw % 2)
1388 mw++;
1389
1390 QDialog customRoiDialog;
1391 QFormLayout form(&customRoiDialog);
1393
1394 form.addRow(new QLabel(i18n("Size")));
1395
1396 QLineEdit wle(&customRoiDialog);
1397 QLineEdit hle(&customRoiDialog);
1398
1399 wle.setValidator(new QIntValidator(1, mw, &wle));
1400 hle.setValidator(new QIntValidator(1, mh, &hle));
1401
1402 form.addRow(i18n("Width"), &wle);
1403 form.addRow(i18n("Height"), &hle);
1404 form.addRow(&buttonBox);
1405
1406 connect(&buttonBox, &QDialogButtonBox::accepted, &customRoiDialog, &QDialog::accept);
1407 connect(&buttonBox, &QDialogButtonBox::rejected, &customRoiDialog, &QDialog::reject);
1408
1409 if(customRoiDialog.exec() == QDialog::Accepted)
1410 {
1411 QPoint resetCenter = currentView->getSelectionRegion().center();
1412 int newheight = hle.text().toInt();
1413 int newwidth = wle.text().toInt();
1414
1415 newheight = qMin(newheight, mh) ;
1416 newheight = qMax(newheight, 1) ;
1417 newwidth = qMin(newwidth, mw);
1418 newwidth = qMax(newwidth, 1);
1419
1420 QPoint topLeft = resetCenter;
1421 QPoint botRight = resetCenter;
1422
1423 topLeft.setX((topLeft.x() - newwidth / 2));
1424 topLeft.setY((topLeft.y() - newheight / 2));
1425 botRight.setX((botRight.x() + newwidth / 2));
1426 botRight.setY((botRight.y() + newheight / 2));
1427
1428 emit currentView->setRubberBand(QRect(topLeft, botRight));
1429 currentView->processRectangle(topLeft, botRight, true);
1430 }
1431 }
1432}
1433/**
1434 This method either enables or disables the scope mouse mode so you can slew your scope to coordinates
1435 just by clicking the mouse on a spot in the image.
1436 */
1437
1439{
1440 QSharedPointer<FITSView> currentView;
1441 if (!getCurrentView(currentView))
1442 return;
1443
1444 currentView->setScopeButton(actionCollection()->action("center_telescope"));
1445 if (currentView->getCursorMode() == FITSView::scopeCursor)
1446 {
1447 currentView->setCursorMode(currentView->lastMouseMode);
1448 }
1449 else
1450 {
1451 currentView->lastMouseMode = currentView->getCursorMode();
1452 currentView->setCursorMode(FITSView::scopeCursor);
1453 }
1454 updateScopeButton();
1455}
1456
1457void FITSViewer::toggleCrossHair()
1458{
1459 if (m_Tabs.empty())
1460 return;
1461
1462 QSharedPointer<FITSView> currentView;
1463 if (!getCurrentView(currentView))
1464 return;
1465
1466 currentView->toggleCrosshair();
1467 updateButtonStatus("view_crosshair", i18n("Cross Hairs"), currentView->isCrosshairShown());
1468}
1469
1470void FITSViewer::toggleClipping()
1471{
1472 if (m_Tabs.empty())
1473 return;
1474
1475 QSharedPointer<FITSView> currentView;
1476 if (!getCurrentView(currentView))
1477 return;
1478 currentView->toggleClipping();
1479 if (!currentView->isClippingShown())
1480 fitsClip.clear();
1481 updateButtonStatus("view_clipping", i18n("Clipping"), currentView->isClippingShown());
1482}
1483
1484void FITSViewer::toggleEQGrid()
1485{
1486 if (m_Tabs.empty())
1487 return;
1488
1489 QSharedPointer<FITSView> currentView;
1490 if (!getCurrentView(currentView))
1491 return;
1492
1493 currentView->toggleEQGrid();
1494 updateButtonStatus("view_eq_grid", i18n("Equatorial Gridlines"), currentView->isEQGridShown());
1495}
1496
1497void FITSViewer::toggleHiPSOverlay()
1498{
1499 if (m_Tabs.empty())
1500 return;
1501
1502 QSharedPointer<FITSView> currentView;
1503 if (!getCurrentView(currentView))
1504 return;
1505
1506 currentView->toggleHiPSOverlay();
1507 updateButtonStatus("view_hips_overlay", i18n("HiPS Overlay"), currentView->isHiPSOverlayShown());
1508}
1509
1510void FITSViewer::toggleSelectionMode()
1511{
1512 if (m_Tabs.empty())
1513 return;
1514
1515 QSharedPointer<FITSView> currentView;
1516 if (!getCurrentView(currentView))
1517 return;
1518
1519 currentView->toggleSelectionMode();
1520 updateButtonStatus("image_roi_stats", i18n("Selection Rectangle"), currentView->isSelectionRectShown());
1521}
1522
1523void FITSViewer::toggleObjects()
1524{
1525 if (m_Tabs.empty())
1526 return;
1527
1528 QSharedPointer<FITSView> currentView;
1529 if (!getCurrentView(currentView))
1530 return;
1531
1532 currentView->toggleObjects();
1533 updateButtonStatus("view_objects", i18n("Objects in Image"), currentView->areObjectsShown());
1534}
1535
1536void FITSViewer::togglePixelGrid()
1537{
1538 if (m_Tabs.empty())
1539 return;
1540
1541 QSharedPointer<FITSView> currentView;
1542 if (!getCurrentView(currentView))
1543 return;
1544
1545 currentView->togglePixelGrid();
1546 updateButtonStatus("view_pixel_grid", i18n("Pixel Gridlines"), currentView->isPixelGridShown());
1547}
1548
1549void FITSViewer::toggle3DGraph()
1550{
1551 if (m_Tabs.empty())
1552 return;
1553
1554 QSharedPointer<FITSView> currentView;
1555 if (!getCurrentView(currentView))
1556 return;
1557
1558 currentView->toggleStarProfile();
1559 updateButtonStatus("toggle_3D_graph", i18n("View 3D Graph"), currentView->isStarProfileShown());
1560}
1561
1562void FITSViewer::nextTab()
1563{
1564 if (m_Tabs.empty())
1565 return;
1566
1567 int index = fitsTabWidget->currentIndex() + 1;
1568 if (index >= m_Tabs.count() || index < 0)
1569 index = 0;
1570 fitsTabWidget->setCurrentIndex(index);
1571
1572 actionCollection()->action("next_blink")->setEnabled(m_Tabs[index]->blinkFilenames().size() > 1);
1573 actionCollection()->action("previous_blink")->setEnabled(m_Tabs[index]->blinkFilenames().size() > 1);
1574}
1575
1576void FITSViewer::previousTab()
1577{
1578 if (m_Tabs.empty())
1579 return;
1580
1581 int index = fitsTabWidget->currentIndex() - 1;
1582 if (index >= m_Tabs.count() || index < 0)
1583 index = m_Tabs.count() - 1;
1584 fitsTabWidget->setCurrentIndex(index);
1585
1586 actionCollection()->action("next_blink")->setEnabled(m_Tabs[index]->blinkFilenames().size() > 1);
1587 actionCollection()->action("previous_blink")->setEnabled(m_Tabs[index]->blinkFilenames().size() > 1);
1588
1589}
1590
1591void FITSViewer::toggleStars()
1592{
1593 if (markStars)
1594 {
1595 markStars = false;
1596 actionCollection()->action("mark_stars")->setText(i18n("Mark Stars"));
1597 }
1598 else
1599 {
1600 markStars = true;
1601 actionCollection()->action("mark_stars")->setText(i18n("Unmark Stars"));
1602 }
1603
1604 for (auto tab : m_Tabs)
1605 {
1606 tab->getView()->toggleStars(markStars);
1607 tab->getView()->updateFrame();
1608 }
1609}
1610
1611void FITSViewer::applyFilter(int ftype)
1612{
1613 if (m_Tabs.empty())
1614 return;
1615
1617 updateStatusBar(i18n("Processing %1...", filterTypes[ftype - 1]), FITS_MESSAGE);
1618 qApp->processEvents();
1619 m_Tabs[fitsTabWidget->currentIndex()]->getHistogram()->applyFilter(static_cast<FITSScale>(ftype));
1620 qApp->processEvents();
1621 m_Tabs[fitsTabWidget->currentIndex()]->getView()->updateFrame();
1623 updateStatusBar(i18n("Ready."), FITS_MESSAGE);
1624}
1625
1626bool FITSViewer::getView(int fitsUID, QSharedPointer<FITSView> &view)
1627{
1628 auto tab = fitsMap.value(fitsUID);
1629 if (tab)
1630 {
1631 view = tab->getView();
1632 return true;
1633 }
1634 return false;
1635
1636}
1637
1638bool FITSViewer::getCurrentView(QSharedPointer<FITSView> &view)
1639{
1640 if (m_Tabs.empty() || fitsTabWidget->currentIndex() >= m_Tabs.count())
1641 return false;
1642
1643 view = m_Tabs[fitsTabWidget->currentIndex()]->getView();
1644 return true;
1645}
1646
1647void FITSViewer::setDebayerAction(bool enable)
1648{
1649 actionCollection()->addAction("fits_debayer")->setEnabled(enable);
1650}
1651
1652bool FITSViewer::tabExists(int fitsUID)
1653{
1654 auto tab = fitsMap.value(fitsUID);
1655 return (!tab.isNull() && tab.data() != nullptr);
1656}
The FITSTab class holds information on the current view (drawing area) in addition to the undo/redo s...
Definition fitstab.h:51
Primary window to view monochrome and color FITS images.
Definition fitsviewer.h:54
FITSViewer(QWidget *parent)
Constructor.
void centerTelescope()
This method either enables or disables the scope mouse mode so you can slew your scope to coordinates...
void updateWCSFunctions()
This is a method that either enables or disables the WCS based features in the Current View.
Q_INVOKABLE QAction * action(const QString &name) const
ActionType * add(const QString &name, const QObject *receiver=nullptr, const char *member=nullptr)
QAction * addAction(const QString &name, const QObject *receiver=nullptr, const char *member=nullptr)
static void setDefaultShortcut(QAction *action, const QKeySequence &shortcut)
void setPopupMode(QToolButton::ToolButtonPopupMode popupMode)
void addAction(QAction *action)
void setColor(const QColor &color)
This is the main window for KStars.
Definition kstars.h:89
static KStars * Instance()
Definition kstars.h:121
virtual KActionCollection * actionCollection() const
virtual QAction * action(const QDomElement &element) const
void createGUI(const QString &xmlfile=QString())
KLocalizedString KI18N_EXPORT ki18n(const char *text)
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
KCALUTILS_EXPORT QString errorMessage(const KCalendarCore::Exception &exception)
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
KIOCORE_EXPORT QString dir(const QString &fileClass)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
QAction * zoomIn(const QObject *recvr, const char *slot, QObject *parent)
QAction * close(const QObject *recvr, const char *slot, QObject *parent)
QAction * undo(const QObject *recvr, const char *slot, QObject *parent)
QAction * copy(const QObject *recvr, const char *slot, QObject *parent)
QAction * zoomOut(const QObject *recvr, const char *slot, QObject *parent)
QAction * redo(const QObject *recvr, const char *slot, QObject *parent)
QAction * save(const QObject *recvr, const char *slot, QObject *parent)
QAction * open(const QObject *recvr, const char *slot, QObject *parent)
QAction * actualSize(const QObject *recvr, const char *slot, QObject *parent)
QAction * zoom(const QObject *recvr, const char *slot, QObject *parent)
QAction * saveAs(const QObject *recvr, const char *slot, QObject *parent)
void setCheckable(bool)
void setChecked(bool)
void setEnabled(bool)
void setIcon(const QIcon &icon)
QMenu * menu() const const
void setDisabled(bool b)
void setMenu(QMenu *menu)
void setText(const QString &text)
void triggered(bool checked)
QCoreApplication * instance()
virtual void accept()
virtual int exec()
virtual void reject()
QString homePath()
QString absoluteFilePath() const const
QString completeSuffix() const const
QString filePath() const const
bool isDir() const const
void restoreOverrideCursor()
void setOverrideCursor(const QCursor &cursor)
QIcon fromTheme(const QString &name)
void setAlignment(Qt::Alignment)
void clear()
void setText(const QString &)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
reference back()
qsizetype count() const const
bool empty() const const
void pop_front()
void push_back(parameter_type value)
void removeLast()
qsizetype size() const const
void setCentralWidget(QWidget *widget)
QStatusBar * statusBar() const const
size_type remove(const Key &key)
T value(const Key &key, const T &defaultValue) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QList< T > findChildren(Qt::FindChildOptions options) const const
void setX(int x)
void setY(int y)
int x() const const
int y() const const
T * data() const const
T * get() const const
bool isNull() const const
int insertPermanentWidget(int index, QWidget *widget, int stretch)
void showMessage(const QString &message, int timeout)
QString arg(Args &&... args) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString number(double n, char format, int precision)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
AlignCenter
ApplicationState
UniqueConnection
WaitCursor
AltModifier
Horizontal
WA_DeleteOnClose
int addTab(QWidget *page, const QIcon &icon, const QString &label)
void currentChanged(int index)
int indexOf(const QWidget *w) const const
void setCurrentWidget(QWidget *widget)
void setTabText(int index, const QString &label)
void tabCloseRequested(int index)
QString tabText(int index) const const
void setTabsClosable(bool closeable)
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void addStack(QUndoStack *stack)
void canRedoChanged(bool canRedo)
void canUndoChanged(bool canUndo)
void redo()
void undo()
RemoveFilename
QString fileName(ComponentFormattingOptions options) const const
QUrl fromLocalFile(const QString &localFile)
bool isEmpty() const const
bool isValid() const const
QString path(ComponentFormattingOptions options) const const
QString toLocalFile() const const
QString url(FormattingOptions options) const const
QList< QAction * > actions() const const
bool close()
void setMinimumWidth(int minw)
void setAttribute(Qt::WidgetAttribute attribute, bool on)
void show()
void resize(const QSize &)
virtual void setVisible(bool visible)
void setWindowFlags(Qt::WindowFlags type)
void setWindowIcon(const QIcon &icon)
void setWindowTitle(const QString &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:04:46 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.