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#include "config-kstars.h"
12
13#include "fitsdata.h"
14#include "fitsdebayer.h"
15#include "fitstab.h"
16#include "fitsview.h"
17#include "kstars.h"
18#include "ksutils.h"
19#include "Options.h"
20#ifdef HAVE_INDI
21#include "indi/indilistener.h"
22#endif
23
24#include <KActionCollection>
25#include <KMessageBox>
26#include <KToolBar>
27#include <KNotifications/KStatusNotifierItem>
28
29#ifndef KSTARS_LITE
30#include "fitshistogrameditor.h"
31#endif
32
33#include <fits_debug.h>
34
35#define INITIAL_W 785
36#define INITIAL_H 640
37
38bool FITSViewer::m_BlinkBusy = false;
39
40QStringList FITSViewer::filterTypes =
41 QStringList() << I18N_NOOP("Auto Stretch") << I18N_NOOP("High Contrast") << I18N_NOOP("Equalize")
42 << I18N_NOOP("High Pass") << I18N_NOOP("Median") << I18N_NOOP("Gaussian blur")
43 << I18N_NOOP("Rotate Right") << I18N_NOOP("Rotate Left") << I18N_NOOP("Flip Horizontal")
44 << I18N_NOOP("Flip Vertical");
45
47{
48#ifdef Q_OS_OSX
49 if (Options::independentWindowFITS())
51 else
52 {
54 connect(QApplication::instance(), SIGNAL(applicationStateChanged(Qt::ApplicationState)), this,
55 SLOT(changeAlwaysOnTop(Qt::ApplicationState)));
56 }
57#endif
58
59 // Since QSharedPointer is managing it, do not delete automatically.
61
62 fitsTabWidget = new QTabWidget(this);
63 undoGroup = new QUndoGroup(this);
64
65 lastURL = QUrl(QDir::homePath());
66
67 fitsTabWidget->setTabsClosable(true);
68
69 setWindowIcon(QIcon::fromTheme("kstars_fitsviewer"));
70
71 setCentralWidget(fitsTabWidget);
72
73 connect(fitsTabWidget, &QTabWidget::currentChanged, this, &FITSViewer::tabFocusUpdated);
74 connect(fitsTabWidget, &QTabWidget::tabCloseRequested, this, &FITSViewer::closeTab);
75
76 //These two connections will enable or disable the scope button if a scope is available or not.
77 //Of course this is also dependent on the presence of WCS data in the image.
78
79#ifdef HAVE_INDI
80 connect(INDIListener::Instance(), &INDIListener::newDevice, this, &FITSViewer::updateWCSFunctions);
81 connect(INDIListener::Instance(), &INDIListener::newDevice, this, &FITSViewer::updateWCSFunctions);
82#endif
83
85
86 fitsPosition.setAlignment(Qt::AlignCenter);
87 fitsPosition.setMinimumWidth(100);
89 fitsValue.setMinimumWidth(40);
90
91 fitsWCS.setVisible(false);
92
93 statusBar()->insertPermanentWidget(FITS_CLIP, &fitsClip);
94 statusBar()->insertPermanentWidget(FITS_HFR, &fitsHFR);
95 statusBar()->insertPermanentWidget(FITS_WCS, &fitsWCS);
96 statusBar()->insertPermanentWidget(FITS_VALUE, &fitsValue);
97 statusBar()->insertPermanentWidget(FITS_POSITION, &fitsPosition);
98 statusBar()->insertPermanentWidget(FITS_ZOOM, &fitsZoom);
99 statusBar()->insertPermanentWidget(FITS_RESOLUTION, &fitsResolution);
100 statusBar()->insertPermanentWidget(FITS_LED, &led);
101
102 QAction *action = actionCollection()->addAction("rotate_right", this, &FITSViewer::rotateCW);
103
104 action->setText(i18n("Rotate Right"));
105 action->setIcon(QIcon::fromTheme("object-rotate-right"));
106
107 action = actionCollection()->addAction("rotate_left", this, &FITSViewer::rotateCCW);
108 action->setText(i18n("Rotate Left"));
109 action->setIcon(QIcon::fromTheme("object-rotate-left"));
110
111 action = actionCollection()->addAction("flip_horizontal", this, &FITSViewer::flipHorizontal);
112 action->setText(i18n("Flip Horizontal"));
113 action->setIcon(
114 QIcon::fromTheme("object-flip-horizontal"));
115
116 action = actionCollection()->addAction("flip_vertical", this, &FITSViewer::flipVertical);
117 action->setText(i18n("Flip Vertical"));
118 action->setIcon(QIcon::fromTheme("object-flip-vertical"));
119
120 action = actionCollection()->addAction("image_histogram");
121 action->setText(i18n("Histogram"));
122 connect(action, &QAction::triggered, this, &FITSViewer::histoFITS);
123 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_T));
124
125 action->setIcon(QIcon(":/icons/histogram.png"));
126
127 action = KStandardAction::open(this, &FITSViewer::openFile, actionCollection());
128 action->setIcon(QIcon::fromTheme("document-open"));
129
130 action = actionCollection()->addAction("blink");
132 action->setText(i18n("Open/Blink Directory"));
133 connect(action, &QAction::triggered, this, &FITSViewer::blink);
134
135 saveFileAction = KStandardAction::save(this, &FITSViewer::saveFile, actionCollection());
136 saveFileAction->setIcon(QIcon::fromTheme("document-save"));
137
138 saveFileAsAction = KStandardAction::saveAs(this, &FITSViewer::saveFileAs, actionCollection());
139 saveFileAsAction->setIcon(
140 QIcon::fromTheme("document-save_as"));
141
142 action = actionCollection()->addAction("fits_header");
143 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_H));
144 action->setIcon(QIcon::fromTheme("document-properties"));
145 action->setText(i18n("FITS Header"));
146 connect(action, &QAction::triggered, this, &FITSViewer::headerFITS);
147
148 action = actionCollection()->addAction("fits_debayer");
149 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_D));
150 action->setIcon(QIcon::fromTheme("view-preview"));
151 action->setText(i18n("Debayer..."));
152 connect(action, &QAction::triggered, this, &FITSViewer::debayerFITS);
153
155 action->setIcon(QIcon::fromTheme("window-close"));
156
157 action = KStandardAction::copy(this, &FITSViewer::copyFITS, actionCollection());
158 action->setIcon(QIcon::fromTheme("edit-copy"));
159
160 action = KStandardAction::zoomIn(this, &FITSViewer::ZoomIn, actionCollection());
161 action->setIcon(QIcon::fromTheme("zoom-in"));
162
163 action = KStandardAction::zoomOut(this, &FITSViewer::ZoomOut, actionCollection());
164 action->setIcon(QIcon::fromTheme("zoom-out"));
165
166 action = KStandardAction::actualSize(this, &FITSViewer::ZoomDefault, actionCollection());
167 action->setIcon(QIcon::fromTheme("zoom-fit-best"));
168
170 kundo->setIcon(QIcon::fromTheme("edit-undo"));
171
173 kredo->setIcon(QIcon::fromTheme("edit-redo"));
174
177
178 action = actionCollection()->addAction("image_stats");
179 action->setIcon(QIcon::fromTheme("view-statistics"));
180 action->setText(i18n("Statistics"));
181 connect(action, &QAction::triggered, this, &FITSViewer::statFITS);
182
183 action = actionCollection()->addAction("image_roi_stats");
184
185 roiActionMenu = new KActionMenu(QIcon(":/icons/select_stat"), "Selection Statistics", action );
186 roiActionMenu->setText(i18n("&Selection Statistics"));
187 roiActionMenu->setDelayed(false);
188 roiActionMenu->addSeparator();
189 connect(roiActionMenu, &QAction::triggered, this, &FITSViewer::toggleSelectionMode);
190
191 KToggleAction *ksa = actionCollection()->add<KToggleAction>("100x100");
192 ksa->setText("100x100");
193 ksa->setCheckable(false);
194 roiActionMenu->addAction(ksa);
195 ksa = actionCollection()->add<KToggleAction>("50x50");
196 ksa->setText("50x50");
197 ksa->setCheckable(false);
198 roiActionMenu->addAction(ksa);
199 ksa = actionCollection()->add<KToggleAction>("25x25");
200 ksa->setText("25x25");
201 ksa->setCheckable(false);
202 roiActionMenu->addAction(ksa);
203 ksa = actionCollection()->add<KToggleAction>("CustomRoi");
204 ksa->setText("Custom");
205 ksa->setCheckable(false);
206 roiActionMenu->addAction(ksa);
207
208 action->setMenu(roiActionMenu->menu());
209 action->setIcon(QIcon(":/icons/select_stat"));
210 action->setCheckable(true);
211
212 connect(roiActionMenu->menu()->actions().at(1), &QAction::triggered, this, [this] { ROIFixedSize(100); });
213 connect(roiActionMenu->menu()->actions().at(2), &QAction::triggered, this, [this] { ROIFixedSize(50); });
214 connect(roiActionMenu->menu()->actions().at(3), &QAction::triggered, this, [this] { ROIFixedSize(25); });
215 connect(roiActionMenu->menu()->actions().at(4), &QAction::triggered, this, [this] { customROIInputWindow();});
216 connect(action, &QAction::triggered, this, &FITSViewer::toggleSelectionMode);
217
218 action = actionCollection()->addAction("view_crosshair");
219 action->setIcon(QIcon::fromTheme("crosshairs"));
220 action->setText(i18n("Show Cross Hairs"));
221 action->setCheckable(true);
222 connect(action, &QAction::triggered, this, &FITSViewer::toggleCrossHair);
223
224 action = actionCollection()->addAction("view_clipping");
225 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_L));
226 action->setIcon(QIcon::fromTheme("media-record"));
227 action->setText(i18n("Show Clipping"));
228 action->setCheckable(true);
229 connect(action, &QAction::triggered, this, &FITSViewer::toggleClipping);
230
231 action = actionCollection()->addAction("view_pixel_grid");
232 action->setIcon(QIcon::fromTheme("map-flat"));
233 action->setText(i18n("Show Pixel Gridlines"));
234 action->setCheckable(true);
235 connect(action, &QAction::triggered, this, &FITSViewer::togglePixelGrid);
236
237 action = actionCollection()->addAction("view_eq_grid");
238 action->setIcon(QIcon::fromTheme("kstars_grid"));
239 action->setText(i18n("Show Equatorial Gridlines"));
240 action->setCheckable(true);
241 action->setDisabled(true);
242 connect(action, &QAction::triggered, this, &FITSViewer::toggleEQGrid);
243
244 action = actionCollection()->addAction("view_objects");
245 action->setIcon(QIcon::fromTheme("help-hint"));
246 action->setText(i18n("Show Objects in Image"));
247 action->setCheckable(true);
248 action->setDisabled(true);
249 connect(action, &QAction::triggered, this, &FITSViewer::toggleObjects);
250
251 action = actionCollection()->addAction("view_hips_overlay");
252 action->setIcon(QIcon::fromTheme("pixelate"));
253 action->setText(i18n("Show HiPS Overlay"));
254 action->setCheckable(true);
255 action->setDisabled(true);
256 connect(action, &QAction::triggered, this, &FITSViewer::toggleHiPSOverlay);
257
258 action = actionCollection()->addAction("center_telescope");
259 action->setIcon(QIcon(":/icons/center_telescope.svg"));
260 action->setText(i18n("Center Telescope\n*No Telescopes Detected*"));
261 action->setDisabled(true);
262 action->setCheckable(true);
264
265 action = actionCollection()->addAction("view_zoom_fit");
266 action->setIcon(QIcon::fromTheme("zoom-fit-width"));
267 action->setText(i18n("Zoom To Fit"));
268 connect(action, &QAction::triggered, this, &FITSViewer::ZoomToFit);
269
270 action = actionCollection()->addAction("next_tab");
271 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Tab));
272 action->setText(i18n("Next Tab"));
273 connect(action, &QAction::triggered, this, &FITSViewer::nextTab);
274
275 action = actionCollection()->addAction("previous_tab");
277 action->setText(i18n("Previous Tab"));
278 connect(action, &QAction::triggered, this, &FITSViewer::previousTab);
279
280 action = actionCollection()->addAction("next_blink");
282 action->setText(i18n("Next Blink Image"));
283 connect(action, &QAction::triggered, this, &FITSViewer::nextBlink);
284
285 action = actionCollection()->addAction("previous_blink");
287 action->setText(i18n("Previous Blink Image"));
288 connect(action, &QAction::triggered, this, &FITSViewer::previousBlink);
289
290 action = actionCollection()->addAction("zoom_all_in");
292 action->setText(i18n("Zoom all tabs in"));
293 connect(action, &QAction::triggered, this, &FITSViewer::ZoomAllIn);
294
295 action = actionCollection()->addAction("zoom_all_out");
297 action->setText(i18n("Zoom all tabs out"));
298 connect(action, &QAction::triggered, this, &FITSViewer::ZoomAllOut);
299
300 action = actionCollection()->addAction("mark_stars");
301 action->setIcon(QIcon::fromTheme("glstarbase", QIcon(":/icons/glstarbase.png")));
302 action->setText(i18n("Mark Stars"));
303 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_A));
304 action->setCheckable(true);
305 connect(action, &QAction::triggered, this, &FITSViewer::toggleStars);
306
307#ifdef HAVE_DATAVISUALIZATION
308 action = actionCollection()->addAction("toggle_3D_graph");
309 action->setIcon(QIcon::fromTheme("star_profile", QIcon(":/icons/star_profile.svg")));
310 action->setText(i18n("View 3D Graph"));
311 action->setCheckable(true);
312 connect(action, &QAction::triggered, this, &FITSViewer::toggle3DGraph);
313#endif
314
315
316 int filterCounter = 1;
317
318 for (auto &filter : FITSViewer::filterTypes)
319 {
320 action = actionCollection()->addAction(QString("filter%1").arg(filterCounter));
321 action->setText(i18n(filter.toUtf8().constData()));
322 connect(action, &QAction::triggered, this, [this, filterCounter] { applyFilter(filterCounter);});
324 }
325
327 /* Create GUI */
328 createGUI("fitsviewerui.rc");
329
330 setWindowTitle(i18nc("@title:window", "KStars FITS Viewer"));
331
332 /* initially resize in accord with KDE rules */
333 show();
334 resize(INITIAL_W, INITIAL_H);
335}
336
337void FITSViewer::changeAlwaysOnTop(Qt::ApplicationState state)
338{
339 if (isVisible())
340 {
341 if (state == Qt::ApplicationActive)
343 else
345 show();
346 }
347}
348
349FITSViewer::~FITSViewer()
350{
351}
352
353void FITSViewer::closeEvent(QCloseEvent * /*event*/)
354{
356
357 if (ks)
358 {
359 QAction *a = KStars::Instance()->actionCollection()->action("show_fits_viewer");
361
362 if (a && viewers.count() == 1)
363 {
364 a->setEnabled(false);
365 a->setChecked(false);
366 }
367 }
368
369 emit terminated();
370}
371
372void FITSViewer::hideEvent(QHideEvent * /*event*/)
373{
375
376 if (ks)
377 {
378 QAction *a = KStars::Instance()->actionCollection()->action("show_fits_viewer");
379 if (a)
380 {
382
383 if (viewers.count() <= 1)
384 a->setChecked(false);
385 }
386 }
387}
388
389void FITSViewer::showEvent(QShowEvent * /*event*/)
390{
391 QAction *a = KStars::Instance()->actionCollection()->action("show_fits_viewer");
392 if (a)
393 {
394 a->setEnabled(true);
395 a->setChecked(true);
396 }
397}
398
399
400namespace
401{
403{
404 const double hfrValue = data->getHFR();
405 if (hfrValue <= 0.0) return QString("");
406 if (data->getSkyBackground().starsDetected > 0)
407 return
408 i18np("HFR:%2 Ecc:%3 %1 star.", "HFR:%2 Ecc:%3 %1 stars.",
409 data->getSkyBackground().starsDetected,
410 QString::number(hfrValue, 'f', 2),
411 QString::number(data->getEccentricity(), 'f', 2));
412 else
413 return
414 i18np("HFR:%2, %1 star.", "HFR:%2, %1 stars.",
415 data->getDetectedStars(),
416 QString::number(hfrValue, 'f', 2));
417}
418
419QString HFRClipString(FITSView* view)
420{
421 if (view->isClippingShown())
422 {
423 const int numClipped = view->getNumClipped();
424 if (numClipped < 0)
425 return QString("Clip:failed");
426 else
427 return QString("Clip:%1").arg(view->getNumClipped());
428 }
429 return "";
430}
431} // namespace
432
433bool FITSViewer::addFITSCommon(const QSharedPointer<FITSTab> &tab, const QUrl &imageName,
434 FITSMode mode, const QString &previewText)
435{
436 int tabIndex = fitsTabWidget->indexOf(tab.get());
437 if (tabIndex != -1)
438 return false;
439
440 if (!imageName.isValid())
441 lastURL = QUrl(imageName.url(QUrl::RemoveFilename));
442
444 tab->setPreviewText(previewText);
445
446 // Connect tab signals
447 tab->disconnect(this);
448 connect(tab.get(), &FITSTab::newStatus, this, &FITSViewer::updateStatusBar);
449 connect(tab.get(), &FITSTab::changeStatus, this, &FITSViewer::updateTabStatus);
450 connect(tab.get(), &FITSTab::debayerToggled, this, &FITSViewer::setDebayerAction);
451 // Connect tab view signals
452 connect(tab->getView().get(), &FITSView::actionUpdated, this, &FITSViewer::updateAction);
453 connect(tab->getView().get(), &FITSView::wcsToggled, this, &FITSViewer::updateWCSFunctions);
454 connect(tab->getView().get(), &FITSView::starProfileWindowClosed, this, &FITSViewer::starProfileButtonOff);
455
456 switch (mode)
457 {
458 case FITS_NORMAL:
459 fitsTabWidget->addTab(tab.get(), previewText.isEmpty() ? imageName.fileName() : previewText);
460 break;
461
462 case FITS_CALIBRATE:
463 fitsTabWidget->addTab(tab.get(), i18n("Calibrate"));
464 break;
465
466 case FITS_FOCUS:
467 fitsTabWidget->addTab(tab.get(), i18n("Focus"));
468 break;
469
470 case FITS_GUIDE:
471 fitsTabWidget->addTab(tab.get(), i18n("Guide"));
472 break;
473
474 case FITS_ALIGN:
475 fitsTabWidget->addTab(tab.get(), i18n("Align"));
476 break;
477
478 case FITS_UNKNOWN:
479 break;
480 }
481
482 saveFileAction->setEnabled(true);
483 saveFileAsAction->setEnabled(true);
484
485 undoGroup->addStack(tab->getUndoStack());
486
487 fitsMap[fitsID] = tab;
488
489 fitsTabWidget->setCurrentWidget(tab.get());
490
491 actionCollection()->action("fits_debayer")->setEnabled(tab->getView()->imageData()->hasDebayer());
492
493 tab->tabPositionUpdated();
494
495 tab->setUID(fitsID);
496
497 led.setColor(Qt::green);
498
499 if (tab->shouldComputeHFR())
500 updateStatusBar(HFRStatusString(tab->getView()->imageData()), FITS_HFR);
501 else
502 updateStatusBar("", FITS_HFR);
503 updateStatusBar(i18n("Ready."), FITS_MESSAGE);
504
505 updateStatusBar(HFRClipString(tab->getView().get()), FITS_CLIP);
506
507 tab->getView()->setCursorMode(FITSView::dragCursor);
508
509 actionCollection()->action("next_blink")->setEnabled(tab->blinkFilenames().size() > 1);
510 actionCollection()->action("previous_blink")->setEnabled(tab->blinkFilenames().size() > 1);
511
513
514 return true;
515}
516
517void FITSViewer::loadFiles()
518{
519 if (m_urls.size() == 0)
520 return;
521
522 const QUrl imageName = m_urls[0];
523 m_urls.pop_front();
524
525 // Make sure we don't have it open already, if yes, switch to it
526 QString fpath = imageName.toLocalFile();
527 for (auto tab : m_Tabs)
528 {
529 const QString cpath = tab->getCurrentURL()->path();
530 if (fpath == cpath)
531 {
532 fitsTabWidget->setCurrentWidget(tab.get());
533 if (m_urls.size() > 0)
534 loadFiles();
535 return;
536 }
537 }
538
539 led.setColor(Qt::yellow);
541
542 QSharedPointer<FITSTab> tab(new FITSTab(this));
543
544 m_Tabs.push_back(tab);
545
546 connect(tab.get(), &FITSTab::failed, this, [ this ](const QString & errorMessage)
547 {
548 QApplication::restoreOverrideCursor();
549 led.setColor(Qt::red);
550 m_Tabs.removeLast();
551 emit failed(errorMessage);
552 if (m_Tabs.size() == 0)
553 {
554 // Close FITS Viewer and let KStars know it is no longer needed in memory.
555 close();
556 }
557
558 if (m_urls.size() > 0)
559 loadFiles();
560 });
561
562 connect(tab.get(), &FITSTab::loaded, this, [ = ]()
563 {
564 if (addFITSCommon(m_Tabs.last(), imageName, FITS_NORMAL, ""))
565 emit loaded(fitsID++);
566 else
567 m_Tabs.removeLast();
568
569 if (m_urls.size() > 0)
570 loadFiles();
571 });
572
573 tab->loadFile(imageName, FITS_NORMAL, FITS_NONE);
574}
575
576void FITSViewer::loadFile(const QUrl &imageName, FITSMode mode, FITSScale filter, const QString &previewText)
577{
578 led.setColor(Qt::yellow);
580
581 QSharedPointer<FITSTab> tab(new FITSTab(this));
582
583 m_Tabs.push_back(tab);
584
585 connect(tab.get(), &FITSTab::failed, this, [ this ](const QString & errorMessage)
586 {
587 QApplication::restoreOverrideCursor();
588 led.setColor(Qt::red);
589 m_Tabs.removeLast();
590 emit failed(errorMessage);
591 if (m_Tabs.size() == 0)
592 {
593 // Close FITS Viewer and let KStars know it is no longer needed in memory.
594 close();
595 }
596 });
597
598 connect(tab.get(), &FITSTab::loaded, this, [ = ]()
599 {
600 if (addFITSCommon(m_Tabs.last(), imageName, mode, previewText))
601 emit loaded(fitsID++);
602 else
603 m_Tabs.removeLast();
604 });
605
606 tab->loadFile(imageName, mode, filter);
607}
608
609bool FITSViewer::loadData(const QSharedPointer<FITSData> &data, const QUrl &imageName, int *tab_uid, FITSMode mode,
610 FITSScale filter, const QString &previewText)
611{
612 led.setColor(Qt::yellow);
614
615 QSharedPointer<FITSTab> tab(new FITSTab(this));
616
617 m_Tabs.push_back(tab);
618
619 if (!tab->loadData(data, mode, filter))
620 {
621 auto errorMessage = tab->getView()->imageData()->getLastError();
623 led.setColor(Qt::red);
624 m_Tabs.removeLast();
625 emit failed(errorMessage);
626 if (m_Tabs.size() == 0)
627 {
628 // Close FITS Viewer and let KStars know it is no longer needed in memory.
629 close();
630 }
631 return false;
632 }
633
634 if (!addFITSCommon(tab, imageName, mode, previewText))
635 {
636 m_Tabs.removeLast();
637 return false;
638 }
639
640 *tab_uid = fitsID++;
641 return true;
642}
643
644bool FITSViewer::removeFITS(int fitsUID)
645{
646 auto tab = fitsMap.value(fitsUID);
647
648 if (tab.isNull())
649 {
650 qCWarning(KSTARS_FITS) << "Cannot find tab with UID " << fitsUID << " in the FITS Viewer";
651 return false;
652 }
653
654 int index = m_Tabs.indexOf(tab);
655
656 if (index >= 0)
657 {
658 closeTab(index);
659 return true;
660 }
661
662 return false;
663}
664
665void FITSViewer::updateFile(const QUrl &imageName, int fitsUID, FITSScale filter)
666{
667 static bool updateBusy = false;
668 if (updateBusy)
669 return;
670 updateBusy = true;
671
672 auto tab = fitsMap.value(fitsUID);
673
674 if (tab.isNull())
675 {
676 QString message = i18n("Cannot find tab with UID %1 in the FITS Viewer", fitsUID);
677 emit failed(message);
678 updateBusy = false;
679 return;
680 }
681
682 if (tab->isVisible())
683 led.setColor(Qt::yellow);
684
685 // On tab load success
686 auto conn = std::make_shared<QMetaObject::Connection>();
687 *conn = connect(tab.get(), &FITSTab::loaded, this, [ = ]()
688 {
689 if (updateFITSCommon(tab, imageName))
690 {
691 QObject::disconnect(*conn);
692 emit loaded(tab->getUID());
693 updateBusy = false;
694 }
695 });
696
697 auto conn2 = std::make_shared<QMetaObject::Connection>();
698 *conn2 = connect(tab.get(), &FITSTab::failed, this, [ = ](const QString & errorMessage)
699 {
700 Q_UNUSED(errorMessage);
701 QObject::disconnect(*conn2);
702 updateBusy = false;
703 });
704
705 tab->loadFile(imageName, tab->getView()->getMode(), filter);
706}
707
708bool FITSViewer::updateFITSCommon(const QSharedPointer<FITSTab> &tab, const QUrl &imageName)
709{
710 // On tab load success
711 int tabIndex = fitsTabWidget->indexOf(tab.get());
712 if (tabIndex == -1)
713 return false;
714
715 if (tab->getView()->getMode() == FITS_NORMAL)
716 {
717 if ((imageName.path().startsWith(QLatin1String("/tmp")) ||
718 imageName.path().contains("/Temp")) &&
719 Options::singlePreviewFITS())
720 fitsTabWidget->setTabText(tabIndex,
721 tab->getPreviewText().isEmpty() ? i18n("Preview") : tab->getPreviewText());
722 else
723 fitsTabWidget->setTabText(tabIndex, imageName.fileName());
724 }
725
726 tab->getUndoStack()->clear();
727
728 if (tab->isVisible())
729 led.setColor(Qt::green);
730
731 if (tab->shouldComputeHFR())
732 updateStatusBar(HFRStatusString(tab->getView()->imageData()), FITS_HFR);
733 else
734 updateStatusBar("", FITS_HFR);
735
736 updateStatusBar(HFRClipString(tab->getView().get()), FITS_CLIP);
737
738 actionCollection()->action("next_blink")->setEnabled(tab->blinkFilenames().size() > 1);
739 actionCollection()->action("previous_blink")->setEnabled(tab->blinkFilenames().size() > 1);
740
741 return true;
742}
743
744bool FITSViewer::updateData(const QSharedPointer<FITSData> &data, const QUrl &imageName, int fitsUID, int *tab_uid,
745 FITSScale filter, FITSMode mode)
746{
747 auto tab = fitsMap.value(fitsUID);
748
749 if (tab.isNull())
750 return false;
751
752 if (mode != FITS_UNKNOWN)
753 tab->getView()->updateMode(mode);
754
755 if (tab->isVisible())
756 led.setColor(Qt::yellow);
757
758 if (!tab->loadData(data, tab->getView()->getMode(), filter))
759 return false;
760
761 if (!updateFITSCommon(tab, imageName))
762 return false;
763
764 *tab_uid = tab->getUID();
765 return true;
766}
767
768void FITSViewer::tabFocusUpdated(int currentIndex)
769{
770 if (currentIndex < 0 || m_Tabs.empty())
771 return;
772
773 m_Tabs[currentIndex]->tabPositionUpdated();
774
775 auto view = m_Tabs[currentIndex]->getView();
776
777 view->toggleStars(markStars);
778
779 if (isVisible())
780 view->updateFrame();
781
782 if (m_Tabs[currentIndex]->shouldComputeHFR())
783 updateStatusBar(HFRStatusString(view->imageData()), FITS_HFR);
784 else
785 updateStatusBar("", FITS_HFR);
786
787 updateStatusBar(HFRClipString(m_Tabs[currentIndex]->getView().get()), FITS_CLIP);
788
789 if (view->imageData()->hasDebayer())
790 {
791 actionCollection()->action("fits_debayer")->setEnabled(true);
792
793 if (debayerDialog)
794 {
795 BayerParams param;
796 view->imageData()->getBayerParams(&param);
797 debayerDialog->setBayerParams(&param);
798 }
799 }
800 else
801 actionCollection()->action("fits_debayer")->setEnabled(false);
802
803 updateStatusBar("", FITS_WCS);
804 connect(view.get(), &FITSView::starProfileWindowClosed, this, &FITSViewer::starProfileButtonOff);
805 QSharedPointer<FITSView> currentView;
806 if (getCurrentView(currentView))
807 {
808 updateButtonStatus("toggle_3D_graph", i18n("currentView 3D Graph"), currentView->isStarProfileShown());
809 updateButtonStatus("view_crosshair", i18n("Cross Hairs"), currentView->isCrosshairShown());
810 updateButtonStatus("view_clipping", i18n("Clipping"), currentView->isClippingShown());
811 updateButtonStatus("view_eq_grid", i18n("Equatorial Gridlines"), currentView->isEQGridShown());
812 updateButtonStatus("view_objects", i18n("Objects in Image"), currentView->areObjectsShown());
813 updateButtonStatus("view_pixel_grid", i18n("Pixel Gridlines"), currentView->isPixelGridShown());
814 updateButtonStatus("view_hips_overlay", i18n("HiPS Overlay"), currentView->isHiPSOverlayShown());
815 }
816
817 actionCollection()->action("next_blink")->setEnabled(m_Tabs[currentIndex]->blinkFilenames().size() > 1);
818 actionCollection()->action("previous_blink")->setEnabled(m_Tabs[currentIndex]->blinkFilenames().size() > 1);
819
820 updateScopeButton();
822}
823
824void FITSViewer::starProfileButtonOff()
825{
826 updateButtonStatus("toggle_3D_graph", i18n("View 3D Graph"), false);
827}
828
829
830QList<QString> findAllImagesBelowDir(const QDir &topDir)
831{
832 QList<QString> result;
833 QList<QString> nameFilter = { "*" };
835
837 dirs.push_back(topDir);
838
839 QRegularExpression re(".*(fits|fits.fz|fit|fts|xisf|jpg|jpeg|png|gif|bmp|cr2|cr3|crw|nef|raf|dng|arw|orf)$");
840 while (!dirs.empty())
841 {
842 auto dir = dirs.back();
843 dirs.removeLast();
844 auto list = dir.entryInfoList( nameFilter, filter );
845 foreach( const QFileInfo &entry, list)
846 {
847 if( entry.isDir() )
848 dirs.push_back(entry.filePath());
849 else
850 {
851 const QString suffix = entry.completeSuffix();
852 QRegularExpressionMatch match = re.match(suffix);
853 if (match.hasMatch())
854 result.append(entry.absoluteFilePath());
855 }
856 }
857 }
858 return result;
859}
860
861void FITSViewer::blink()
862{
863 if (m_BlinkBusy)
864 return;
865 m_BlinkBusy = true;
866 QFileDialog dialog(KStars::Instance(), i18nc("@title:window", "Blink Top Directory"));
867 dialog.setFileMode(QFileDialog::Directory);
868 dialog.setDirectoryUrl(lastURL);
869
870 if (!dialog.exec())
871 {
872 m_BlinkBusy = false;
873 return;
874 }
875 QStringList selected = dialog.selectedFiles();
876 if (selected.size() < 1)
877 {
878 m_BlinkBusy = false;
879 return;
880 }
881 QString topDir = selected[0];
882
883 auto allImages = findAllImagesBelowDir(QDir(topDir));
884 if (allImages.size() == 0)
885 {
886 m_BlinkBusy = false;
887 return;
888 }
889
890 const QUrl imageName(QUrl::fromLocalFile(allImages[0]));
891
892 led.setColor(Qt::yellow);
894
895 QSharedPointer<FITSTab> tab(new FITSTab(this));
896
897 int tabIndex = m_Tabs.size();
898 if (allImages.size() > 1)
899 {
900 m_Tabs.push_back(tab);
901 tab->initBlink(allImages);
902 tab->setBlinkUpto(1);
903 }
904 QString tabName = QString("%1/%2 %3")
905 .arg(1).arg(allImages.size()).arg(QFileInfo(allImages[0]).fileName());
906 connect(tab.get(), &FITSTab::failed, this, [ this ](const QString & errorMessage)
907 {
908 Q_UNUSED(errorMessage);
909 QObject::sender()->disconnect(this);
910 QApplication::restoreOverrideCursor();
911 led.setColor(Qt::red);
912 m_BlinkBusy = false;
914
915 connect(tab.get(), &FITSTab::loaded, this, [ = ]()
916 {
917 QObject::sender()->disconnect(this);
918 addFITSCommon(m_Tabs.last(), imageName, FITS_NORMAL, "");
919 //fitsTabWidget->tabBar()->setTabTextColor(tabIndex, Qt::red);
920 fitsTabWidget->setTabText(tabIndex, tabName);
921 m_BlinkBusy = false;
923
924 actionCollection()->action("next_blink")->setEnabled(allImages.size() > 1);
925 actionCollection()->action("previous_blink")->setEnabled(allImages.size() > 1);
926
927 tab->loadFile(imageName, FITS_NORMAL, FITS_NONE);
928}
929
930
931void FITSViewer::changeBlink(bool increment)
932{
933 if (m_Tabs.empty() || m_BlinkBusy)
934 return;
935
936 m_BlinkBusy = true;
937 const int tabIndex = fitsTabWidget->currentIndex();
938 if (tabIndex >= m_Tabs.count() || tabIndex < 0)
939 {
940 m_BlinkBusy = false;
941 return;
942 }
943 auto tab = m_Tabs[tabIndex];
944 const QList<QString> &filenames = tab->blinkFilenames();
945 if (filenames.size() <= 1)
946 {
947 m_BlinkBusy = false;
948 return;
949 }
950
951 int blinkIndex = tab->blinkUpto() + (increment ? 1 : -1);
952 if (blinkIndex >= filenames.size())
953 blinkIndex = 0;
954 else if (blinkIndex < 0)
955 blinkIndex = filenames.size() - 1;
956
958 QString tabName = QString("%1/%2 %3")
959 .arg(blinkIndex + 1).arg(filenames.size()).arg(QFileInfo(nextFilename).fileName());
960 tab->disconnect(this);
961 connect(tab.get(), &FITSTab::failed, this, [ this, nextFilename ](const QString & errorMessage)
962 {
963 Q_UNUSED(errorMessage);
964 QObject::sender()->disconnect(this);
965 QApplication::restoreOverrideCursor();
966 led.setColor(Qt::red);
967 m_BlinkBusy = false;
969
970 connect(tab.get(), &FITSTab::loaded, this, [ = ]()
971 {
972 QObject::sender()->disconnect(this);
973 updateFITSCommon(tab, QUrl::fromLocalFile(nextFilename));
974 fitsTabWidget->setTabText(tabIndex, tabName);
975 m_BlinkBusy = false;
977
978 tab->setBlinkUpto(blinkIndex);
979 tab->loadFile(QUrl::fromLocalFile(nextFilename), FITS_NORMAL, FITS_NONE);
980}
981
982void FITSViewer::nextBlink()
983{
984 changeBlink(true);
985}
986
987void FITSViewer::previousBlink()
988{
989 changeBlink(false);
990}
991
992void FITSViewer::openFile()
993{
994 QFileDialog dialog(KStars::Instance(), i18nc("@title:window", "Open Image"));
995 dialog.setFileMode(QFileDialog::ExistingFiles);
996 dialog.setDirectoryUrl(lastURL);
997 dialog.setNameFilter("Images (*.fits *.fits.fz *.fit *.fts *.xisf "
998 "*.jpg *.jpeg *.png *.gif *.bmp "
999 "*.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
1014void FITSViewer::saveFile()
1015{
1016 m_Tabs[fitsTabWidget->currentIndex()]->saveFile();
1017}
1018
1019void 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
1030void FITSViewer::copyFITS()
1031{
1032 if (m_Tabs.empty())
1033 return;
1034
1035 m_Tabs[fitsTabWidget->currentIndex()]->copyFITS();
1036}
1037
1038void FITSViewer::histoFITS()
1039{
1040 if (m_Tabs.empty())
1041 return;
1042
1043 m_Tabs[fitsTabWidget->currentIndex()]->histoFITS();
1044}
1045
1046void FITSViewer::statFITS()
1047{
1048 if (m_Tabs.empty())
1049 return;
1050
1051 m_Tabs[fitsTabWidget->currentIndex()]->statFITS();
1052}
1053
1054void FITSViewer::rotateCW()
1055{
1056 applyFilter(FITS_ROTATE_CW);
1057}
1058
1059void FITSViewer::rotateCCW()
1060{
1061 applyFilter(FITS_ROTATE_CCW);
1062}
1063
1064void FITSViewer::flipHorizontal()
1065{
1066 applyFilter(FITS_MOUNT_FLIP_H);
1067}
1068
1069void FITSViewer::flipVertical()
1070{
1071 applyFilter(FITS_MOUNT_FLIP_V);
1072}
1073
1074void FITSViewer::headerFITS()
1075{
1076 if (m_Tabs.empty())
1077 return;
1078
1079 m_Tabs[fitsTabWidget->currentIndex()]->headerFITS();
1080}
1081
1082void FITSViewer::debayerFITS()
1083{
1084 if (debayerDialog == nullptr)
1085 {
1086 debayerDialog = new FITSDebayer(this);
1087 }
1088
1090 if (getCurrentView(view))
1091 {
1092 BayerParams param;
1093 view->imageData()->getBayerParams(&param);
1094 debayerDialog->setBayerParams(&param);
1095 debayerDialog->show();
1096 }
1097}
1098
1099void 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
1134void 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
1147void 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
1160void FITSViewer::ZoomIn()
1161{
1162 if (m_Tabs.empty())
1163 return;
1164
1165 m_Tabs[fitsTabWidget->currentIndex()]->ZoomIn();
1166}
1167
1168void FITSViewer::ZoomOut()
1169{
1170 if (m_Tabs.empty())
1171 return;
1172
1173 m_Tabs[fitsTabWidget->currentIndex()]->ZoomOut();
1174}
1175
1176void FITSViewer::ZoomDefault()
1177{
1178 if (m_Tabs.empty())
1179 return;
1180
1181 m_Tabs[fitsTabWidget->currentIndex()]->ZoomDefault();
1182}
1183
1184void FITSViewer::ZoomToFit()
1185{
1186 if (m_Tabs.empty())
1187 return;
1188
1189 QSharedPointer<FITSView> currentView;
1190 if (getCurrentView(currentView))
1191 currentView->ZoomToFit();
1192}
1193
1194void 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
1202void 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
1217void 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
1244void 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/**
1263This is a method that either enables or disables the WCS based features in the Current View.
1264 */
1265
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
1302void 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
1318void 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
1335void 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
1357
1358 form.addRow(new QLabel(i18n("Size")));
1359
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
1372
1374 {
1375 QPoint resetCenter = currentView->getSelectionRegion().center();
1376 int newheight = hle.text().toInt();
1377 int newwidth = wle.text().toInt();
1378
1380 newheight = qMax(newheight, 1) ;
1382 newwidth = qMax(newwidth, 1);
1383
1384 QPoint topLeft = 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
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
1421void 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
1434void 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
1448void 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
1461void 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
1474void 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
1487void 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
1500void 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
1513void 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
1526void 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
1540void 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
1555void 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
1575void FITSViewer::applyFilter(int ftype)
1576{
1577 if (m_Tabs.empty())
1578 return;
1579
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();
1587 updateStatusBar(i18n("Ready."), FITS_MESSAGE);
1588}
1589
1590bool 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
1602bool 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
1611void FITSViewer::setDebayerAction(bool enable)
1612{
1613 actionCollection()->addAction("fits_debayer")->setEnabled(enable);
1614}
The FITSTab class holds information on the current view (drawing area) in addition to the undo/redo s...
Definition fitstab.h:46
Primary window to view monochrome and color FITS images.
Definition fitsviewer.h:50
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.
void addAction(QAction *action)
void setColor(const QColor &color)
This is the main window for KStars.
Definition kstars.h:91
static KStars * Instance()
Definition kstars.h:123
virtual KActionCollection * actionCollection() const
virtual QAction * action(const QDomElement &element) const
void createGUI(const QString &xmlfile=QString())
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 * saveAs(const QObject *recvr, const char *slot, QObject *parent)
void setChecked(bool)
void setEnabled(bool)
void setIcon(const QIcon &icon)
QMenu * menu() const const
void setText(const QString &text)
void triggered(bool checked)
QCoreApplication * instance()
virtual void accept()
virtual void reject()
typedef Filters
QString homePath()
QString absoluteFilePath() const const
QString completeSuffix() const const
QString filePath() const const
bool isDir() const const
Int toInt() const const
void restoreOverrideCursor()
void setOverrideCursor(const QCursor &cursor)
QIcon fromTheme(const QString &name)
void setAlignment(Qt::Alignment)
void clear()
void setText(const QString &)
const_reference at(qsizetype i) const const
qsizetype count() const const
bool empty() const const
qsizetype indexOf(const AT &value, qsizetype from) const const
void pop_front()
void push_back(parameter_type value)
void removeLast()
bool removeOne(const AT &t)
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)
void setX(int x)
void setY(int y)
int x() const const
int y() 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)
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 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 Tue Mar 26 2024 11:19:03 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.