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("view_fit_page");
271 action->setIcon(QIcon::fromTheme("zoom-original"));
272 action->setText(i18n("Fit Page to Zoom"));
273 connect(action, &QAction::triggered, this, &FITSViewer::FitToZoom);
274
275 action = actionCollection()->addAction("next_tab");
276 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Tab));
277 action->setText(i18n("Next Tab"));
278 connect(action, &QAction::triggered, this, &FITSViewer::nextTab);
279
280 action = actionCollection()->addAction("previous_tab");
282 action->setText(i18n("Previous Tab"));
283 connect(action, &QAction::triggered, this, &FITSViewer::previousTab);
284
285 action = actionCollection()->addAction("next_blink");
287 action->setText(i18n("Next Blink Image"));
288 connect(action, &QAction::triggered, this, &FITSViewer::nextBlink);
289
290 action = actionCollection()->addAction("previous_blink");
292 action->setText(i18n("Previous Blink Image"));
293 connect(action, &QAction::triggered, this, &FITSViewer::previousBlink);
294
295 action = actionCollection()->addAction("zoom_all_in");
297 action->setText(i18n("Zoom all tabs in"));
298 connect(action, &QAction::triggered, this, &FITSViewer::ZoomAllIn);
299
300 action = actionCollection()->addAction("zoom_all_out");
302 action->setText(i18n("Zoom all tabs out"));
303 connect(action, &QAction::triggered, this, &FITSViewer::ZoomAllOut);
304
305 action = actionCollection()->addAction("mark_stars");
306 action->setIcon(QIcon::fromTheme("glstarbase", QIcon(":/icons/glstarbase.png")));
307 action->setText(i18n("Mark Stars"));
308 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_A));
309 action->setCheckable(true);
310 connect(action, &QAction::triggered, this, &FITSViewer::toggleStars);
311
312#ifdef HAVE_DATAVISUALIZATION
313 action = actionCollection()->addAction("toggle_3D_graph");
314 action->setIcon(QIcon::fromTheme("star_profile", QIcon(":/icons/star_profile.svg")));
315 action->setText(i18n("View 3D Graph"));
316 action->setCheckable(true);
317 connect(action, &QAction::triggered, this, &FITSViewer::toggle3DGraph);
318#endif
319
320
321 int filterCounter = 1;
322
323 for (auto &filter : FITSViewer::filterTypes)
324 {
325 action = actionCollection()->addAction(QString("filter%1").arg(filterCounter));
326 action->setText(i18n(filter.toUtf8().constData()));
327 connect(action, &QAction::triggered, this, [this, filterCounter] { applyFilter(filterCounter);});
329 }
330
332 /* Create GUI */
333 createGUI("fitsviewerui.rc");
334
335 setWindowTitle(i18nc("@title:window", "KStars FITS Viewer"));
336
337 /* initially resize in accord with KDE rules */
338 show();
339 resize(INITIAL_W, INITIAL_H);
340}
341
342void FITSViewer::changeAlwaysOnTop(Qt::ApplicationState state)
343{
344 if (isVisible())
345 {
346 if (state == Qt::ApplicationActive)
348 else
350 show();
351 }
352}
353
354FITSViewer::~FITSViewer()
355{
356}
357
358void FITSViewer::closeEvent(QCloseEvent * /*event*/)
359{
361
362 if (ks)
363 {
364 QAction *a = KStars::Instance()->actionCollection()->action("show_fits_viewer");
366
367 if (a && viewers.count() == 1)
368 {
369 a->setEnabled(false);
370 a->setChecked(false);
371 }
372 }
373
374 emit terminated();
375}
376
377void FITSViewer::hideEvent(QHideEvent * /*event*/)
378{
380
381 if (ks)
382 {
383 QAction *a = KStars::Instance()->actionCollection()->action("show_fits_viewer");
384 if (a)
385 {
387
388 if (viewers.count() <= 1)
389 a->setChecked(false);
390 }
391 }
392}
393
394void FITSViewer::showEvent(QShowEvent * /*event*/)
395{
396 QAction *a = KStars::Instance()->actionCollection()->action("show_fits_viewer");
397 if (a)
398 {
399 a->setEnabled(true);
400 a->setChecked(true);
401 }
402}
403
404
405namespace
406{
408{
409 const double hfrValue = data->getHFR();
410 if (hfrValue <= 0.0) return QString("");
411 if (data->getSkyBackground().starsDetected > 0)
412 return
413 i18np("HFR:%2 Ecc:%3 %1 star.", "HFR:%2 Ecc:%3 %1 stars.",
414 data->getSkyBackground().starsDetected,
415 QString::number(hfrValue, 'f', 2),
416 QString::number(data->getEccentricity(), 'f', 2));
417 else
418 return
419 i18np("HFR:%2, %1 star.", "HFR:%2, %1 stars.",
420 data->getDetectedStars(),
421 QString::number(hfrValue, 'f', 2));
422}
423
424QString HFRClipString(FITSView* view)
425{
426 if (view->isClippingShown())
427 {
428 const int numClipped = view->getNumClipped();
429 if (numClipped < 0)
430 return QString("Clip:failed");
431 else
432 return QString("Clip:%1").arg(view->getNumClipped());
433 }
434 return "";
435}
436} // namespace
437
438bool FITSViewer::addFITSCommon(const QSharedPointer<FITSTab> &tab, const QUrl &imageName,
439 FITSMode mode, const QString &previewText)
440{
441 int tabIndex = fitsTabWidget->indexOf(tab.get());
442 if (tabIndex != -1)
443 return false;
444
445 if (!imageName.isValid())
446 lastURL = QUrl(imageName.url(QUrl::RemoveFilename));
447
449 tab->setPreviewText(previewText);
450
451 // Connect tab signals
452 tab->disconnect(this);
453 connect(tab.get(), &FITSTab::newStatus, this, &FITSViewer::updateStatusBar);
454 connect(tab.get(), &FITSTab::changeStatus, this, &FITSViewer::updateTabStatus);
455 connect(tab.get(), &FITSTab::debayerToggled, this, &FITSViewer::setDebayerAction);
456 // Connect tab view signals
457 connect(tab->getView().get(), &FITSView::actionUpdated, this, &FITSViewer::updateAction);
458 connect(tab->getView().get(), &FITSView::wcsToggled, this, &FITSViewer::updateWCSFunctions);
459 connect(tab->getView().get(), &FITSView::starProfileWindowClosed, this, &FITSViewer::starProfileButtonOff);
460
461 switch (mode)
462 {
463 case FITS_NORMAL:
464 case FITS_CALIBRATE:
465 fitsTabWidget->addTab(tab.get(), previewText.isEmpty() ? imageName.fileName() : previewText);
466 break;
467
468 case FITS_FOCUS:
469 fitsTabWidget->addTab(tab.get(), i18n("Focus"));
470 break;
471
472 case FITS_GUIDE:
473 fitsTabWidget->addTab(tab.get(), i18n("Guide"));
474 break;
475
476 case FITS_ALIGN:
477 fitsTabWidget->addTab(tab.get(), i18n("Align"));
478 break;
479
480 case FITS_UNKNOWN:
481 break;
482 }
483
484 saveFileAction->setEnabled(true);
485 saveFileAsAction->setEnabled(true);
486
487 undoGroup->addStack(tab->getUndoStack());
488
489 fitsMap[fitsID] = tab;
490
491 fitsTabWidget->setCurrentWidget(tab.get());
492
493 actionCollection()->action("fits_debayer")->setEnabled(tab->getView()->imageData()->hasDebayer());
494
495 tab->tabPositionUpdated();
496
497 tab->setUID(fitsID);
498
499 led.setColor(Qt::green);
500
501 if (tab->shouldComputeHFR())
502 updateStatusBar(HFRStatusString(tab->getView()->imageData()), FITS_HFR);
503 else
504 updateStatusBar("", FITS_HFR);
505 updateStatusBar(i18n("Ready."), FITS_MESSAGE);
506
507 updateStatusBar(HFRClipString(tab->getView().get()), FITS_CLIP);
508
509 tab->getView()->setCursorMode(FITSView::dragCursor);
510
511 actionCollection()->action("next_blink")->setEnabled(tab->blinkFilenames().size() > 1);
512 actionCollection()->action("previous_blink")->setEnabled(tab->blinkFilenames().size() > 1);
513
515
516 return true;
517}
518
519void FITSViewer::loadFiles()
520{
521 if (m_urls.size() == 0)
522 return;
523
524 const QUrl imageName = m_urls[0];
525 m_urls.pop_front();
526
527 // Make sure we don't have it open already, if yes, switch to it
528 QString fpath = imageName.toLocalFile();
529 for (auto tab : m_Tabs)
530 {
531 const QString cpath = tab->getCurrentURL()->path();
532 if (fpath == cpath)
533 {
534 fitsTabWidget->setCurrentWidget(tab.get());
535 if (m_urls.size() > 0)
536 loadFiles();
537 return;
538 }
539 }
540
541 led.setColor(Qt::yellow);
543
544 QSharedPointer<FITSTab> tab(new FITSTab(this));
545
546 m_Tabs.push_back(tab);
547
548 connect(tab.get(), &FITSTab::failed, this, [ this ](const QString & errorMessage)
549 {
550 QApplication::restoreOverrideCursor();
551 led.setColor(Qt::red);
552 m_Tabs.removeLast();
553 emit failed(errorMessage);
554 if (m_Tabs.size() == 0)
555 {
556 // Close FITS Viewer and let KStars know it is no longer needed in memory.
557 close();
558 }
559
560 if (m_urls.size() > 0)
561 loadFiles();
562 });
563
564 connect(tab.get(), &FITSTab::loaded, this, [ = ]()
565 {
566 if (addFITSCommon(m_Tabs.last(), imageName, FITS_NORMAL, ""))
567 emit loaded(fitsID++);
568 else
569 m_Tabs.removeLast();
570
571 if (m_urls.size() > 0)
572 loadFiles();
573 });
574
575 tab->loadFile(imageName, FITS_NORMAL, FITS_NONE);
576}
577
578void FITSViewer::loadFile(const QUrl &imageName, FITSMode mode, FITSScale filter, const QString &previewText)
579{
580 led.setColor(Qt::yellow);
582
583 QSharedPointer<FITSTab> tab(new FITSTab(this));
584
585 m_Tabs.push_back(tab);
586
587 connect(tab.get(), &FITSTab::failed, this, [ this ](const QString & errorMessage)
588 {
589 QApplication::restoreOverrideCursor();
590 led.setColor(Qt::red);
591 m_Tabs.removeLast();
592 emit failed(errorMessage);
593 if (m_Tabs.size() == 0)
594 {
595 // Close FITS Viewer and let KStars know it is no longer needed in memory.
596 close();
597 }
598 });
599
600 connect(tab.get(), &FITSTab::loaded, this, [ = ]()
601 {
602 if (addFITSCommon(m_Tabs.last(), imageName, mode, previewText))
603 emit loaded(fitsID++);
604 else
605 m_Tabs.removeLast();
606 });
607
608 tab->loadFile(imageName, mode, filter);
609}
610
611bool FITSViewer::loadData(const QSharedPointer<FITSData> &data, const QUrl &imageName, int *tab_uid, FITSMode mode,
612 FITSScale filter, const QString &previewText)
613{
614 led.setColor(Qt::yellow);
616
617 QSharedPointer<FITSTab> tab(new FITSTab(this));
618
619 m_Tabs.push_back(tab);
620
621 if (!tab->loadData(data, mode, filter))
622 {
623 auto errorMessage = tab->getView()->imageData()->getLastError();
625 led.setColor(Qt::red);
626 m_Tabs.removeLast();
627 emit failed(errorMessage);
628 if (m_Tabs.size() == 0)
629 {
630 // Close FITS Viewer and let KStars know it is no longer needed in memory.
631 close();
632 }
633 return false;
634 }
635
636 if (!addFITSCommon(tab, imageName, mode, previewText))
637 {
638 m_Tabs.removeLast();
639 return false;
640 }
641
642 *tab_uid = fitsID++;
643 return true;
644}
645
646bool FITSViewer::removeFITS(int fitsUID)
647{
648 auto tab = fitsMap.value(fitsUID);
649
650 if (tab.isNull())
651 {
652 qCWarning(KSTARS_FITS) << "Cannot find tab with UID " << fitsUID << " in the FITS Viewer";
653 return false;
654 }
655
656 int index = m_Tabs.indexOf(tab);
657
658 if (index >= 0)
659 {
660 closeTab(index);
661 return true;
662 }
663
664 return false;
665}
666
667void FITSViewer::updateFile(const QUrl &imageName, int fitsUID, FITSScale filter)
668{
669 static bool updateBusy = false;
670 if (updateBusy)
671 return;
672 updateBusy = true;
673
674 auto tab = fitsMap.value(fitsUID);
675
676 if (tab.isNull())
677 {
678 QString message = i18n("Cannot find tab with UID %1 in the FITS Viewer", fitsUID);
679 emit failed(message);
680 updateBusy = false;
681 return;
682 }
683
684 if (tab->isVisible())
685 led.setColor(Qt::yellow);
686
687 // On tab load success
688 auto conn = std::make_shared<QMetaObject::Connection>();
689 *conn = connect(tab.get(), &FITSTab::loaded, this, [ = ]()
690 {
691 if (updateFITSCommon(tab, imageName))
692 {
693 QObject::disconnect(*conn);
694 emit loaded(tab->getUID());
695 updateBusy = false;
696 }
697 });
698
699 auto conn2 = std::make_shared<QMetaObject::Connection>();
700 *conn2 = connect(tab.get(), &FITSTab::failed, this, [ = ](const QString & errorMessage)
701 {
702 Q_UNUSED(errorMessage);
703 QObject::disconnect(*conn2);
704 updateBusy = false;
705 });
706
707 tab->loadFile(imageName, tab->getView()->getMode(), filter);
708}
709
710bool FITSViewer::updateFITSCommon(const QSharedPointer<FITSTab> &tab, const QUrl &imageName)
711{
712 // On tab load success
713 int tabIndex = fitsTabWidget->indexOf(tab.get());
714 if (tabIndex == -1)
715 return false;
716
717 if (tab->getView()->getMode() == FITS_NORMAL)
718 {
719 if ((imageName.fileName() == "Preview" ||
720 imageName.path().startsWith(QLatin1String("/tmp")) ||
721 imageName.path().contains("/Temp")) &&
722 Options::singlePreviewFITS())
723 fitsTabWidget->setTabText(tabIndex,
724 tab->getPreviewText().isEmpty() ? i18n("Preview") : tab->getPreviewText());
725 else if (tab->getPreviewText() != i18n("Preview") || imageName.fileName().size() > 0)
726 fitsTabWidget->setTabText(tabIndex, imageName.fileName());
727 }
728
729 tab->getUndoStack()->clear();
730
731 if (tab->isVisible())
732 led.setColor(Qt::green);
733
734 if (tab->shouldComputeHFR())
735 updateStatusBar(HFRStatusString(tab->getView()->imageData()), FITS_HFR);
736 else
737 updateStatusBar("", FITS_HFR);
738
739 updateStatusBar(HFRClipString(tab->getView().get()), FITS_CLIP);
740
741 actionCollection()->action("next_blink")->setEnabled(tab->blinkFilenames().size() > 1);
742 actionCollection()->action("previous_blink")->setEnabled(tab->blinkFilenames().size() > 1);
743
744 return true;
745}
746
747bool FITSViewer::updateData(const QSharedPointer<FITSData> &data, const QUrl &imageName, int fitsUID, int *tab_uid,
748 FITSScale filter, FITSMode mode)
749{
750 auto tab = fitsMap.value(fitsUID);
751
752 if (tab.isNull())
753 return false;
754
755 if (mode != FITS_UNKNOWN)
756 tab->getView()->updateMode(mode);
757
758 if (tab->isVisible())
759 led.setColor(Qt::yellow);
760
761 if (!tab->loadData(data, tab->getView()->getMode(), filter))
762 return false;
763
764 if (!updateFITSCommon(tab, imageName))
765 return false;
766
767 *tab_uid = tab->getUID();
768 return true;
769}
770
771void FITSViewer::tabFocusUpdated(int currentIndex)
772{
773 if (currentIndex < 0 || m_Tabs.empty())
774 return;
775
776 m_Tabs[currentIndex]->tabPositionUpdated();
777
778 auto view = m_Tabs[currentIndex]->getView();
779
780 view->toggleStars(markStars);
781
782 if (isVisible())
783 view->updateFrame();
784
785 if (m_Tabs[currentIndex]->shouldComputeHFR())
786 updateStatusBar(HFRStatusString(view->imageData()), FITS_HFR);
787 else
788 updateStatusBar("", FITS_HFR);
789
790 updateStatusBar(HFRClipString(m_Tabs[currentIndex]->getView().get()), FITS_CLIP);
791
792 if (view->imageData()->hasDebayer())
793 {
794 actionCollection()->action("fits_debayer")->setEnabled(true);
795
796 if (debayerDialog)
797 {
798 BayerParams param;
799 view->imageData()->getBayerParams(&param);
800 debayerDialog->setBayerParams(&param);
801 }
802 }
803 else
804 actionCollection()->action("fits_debayer")->setEnabled(false);
805
806 updateStatusBar("", FITS_WCS);
807 connect(view.get(), &FITSView::starProfileWindowClosed, this, &FITSViewer::starProfileButtonOff);
808 QSharedPointer<FITSView> currentView;
809 if (getCurrentView(currentView))
810 {
811 updateButtonStatus("toggle_3D_graph", i18n("currentView 3D Graph"), currentView->isStarProfileShown());
812 updateButtonStatus("view_crosshair", i18n("Cross Hairs"), currentView->isCrosshairShown());
813 updateButtonStatus("view_clipping", i18n("Clipping"), currentView->isClippingShown());
814 updateButtonStatus("view_eq_grid", i18n("Equatorial Gridlines"), currentView->isEQGridShown());
815 updateButtonStatus("view_objects", i18n("Objects in Image"), currentView->areObjectsShown());
816 updateButtonStatus("view_pixel_grid", i18n("Pixel Gridlines"), currentView->isPixelGridShown());
817 updateButtonStatus("view_hips_overlay", i18n("HiPS Overlay"), currentView->isHiPSOverlayShown());
818 }
819
820 actionCollection()->action("next_blink")->setEnabled(m_Tabs[currentIndex]->blinkFilenames().size() > 1);
821 actionCollection()->action("previous_blink")->setEnabled(m_Tabs[currentIndex]->blinkFilenames().size() > 1);
822
823 updateScopeButton();
825}
826
827void FITSViewer::starProfileButtonOff()
828{
829 updateButtonStatus("toggle_3D_graph", i18n("View 3D Graph"), false);
830}
831
832
833QList<QString> findAllImagesBelowDir(const QDir &topDir)
834{
835 QList<QString> result;
836 QList<QString> nameFilter = { "*" };
838
840 dirs.push_back(topDir);
841
842 QRegularExpression re(".*(fits|fits.fz|fit|fts|xisf|jpg|jpeg|png|gif|bmp|cr2|cr3|crw|nef|raf|dng|arw|orf)$");
843 while (!dirs.empty())
844 {
845 auto dir = dirs.back();
846 dirs.removeLast();
847 auto list = dir.entryInfoList( nameFilter, filter );
848 foreach( const QFileInfo &entry, list)
849 {
850 if( entry.isDir() )
851 dirs.push_back(entry.filePath());
852 else
853 {
854 const QString suffix = entry.completeSuffix();
855 QRegularExpressionMatch match = re.match(suffix);
856 if (match.hasMatch())
857 result.append(entry.absoluteFilePath());
858 }
859 }
860 }
861 return result;
862}
863
864void FITSViewer::blink()
865{
866 if (m_BlinkBusy)
867 return;
868 m_BlinkBusy = true;
869 QFileDialog dialog(KStars::Instance(), i18nc("@title:window", "Blink Top Directory"));
870 dialog.setFileMode(QFileDialog::Directory);
871 dialog.setDirectoryUrl(lastURL);
872
873 if (!dialog.exec())
874 {
875 m_BlinkBusy = false;
876 return;
877 }
878 QStringList selected = dialog.selectedFiles();
879 if (selected.size() < 1)
880 {
881 m_BlinkBusy = false;
882 return;
883 }
884 QString topDir = selected[0];
885
886 auto allImages = findAllImagesBelowDir(QDir(topDir));
887 if (allImages.size() == 0)
888 {
889 m_BlinkBusy = false;
890 return;
891 }
892
893 const QUrl imageName(QUrl::fromLocalFile(allImages[0]));
894
895 led.setColor(Qt::yellow);
897
898 QSharedPointer<FITSTab> tab(new FITSTab(this));
899
900 int tabIndex = m_Tabs.size();
901 if (allImages.size() > 1)
902 {
903 m_Tabs.push_back(tab);
904 tab->initBlink(allImages);
905 tab->setBlinkUpto(1);
906 }
907 QString tabName = QString("%1/%2 %3")
908 .arg(1).arg(allImages.size()).arg(QFileInfo(allImages[0]).fileName());
909 connect(tab.get(), &FITSTab::failed, this, [ this ](const QString & errorMessage)
910 {
911 Q_UNUSED(errorMessage);
912 QObject::sender()->disconnect(this);
913 QApplication::restoreOverrideCursor();
914 led.setColor(Qt::red);
915 m_BlinkBusy = false;
917
918 connect(tab.get(), &FITSTab::loaded, this, [ = ]()
919 {
920 QObject::sender()->disconnect(this);
921 addFITSCommon(m_Tabs.last(), imageName, FITS_NORMAL, "");
922 //fitsTabWidget->tabBar()->setTabTextColor(tabIndex, Qt::red);
923 fitsTabWidget->setTabText(tabIndex, tabName);
924 m_BlinkBusy = false;
926
927 actionCollection()->action("next_blink")->setEnabled(allImages.size() > 1);
928 actionCollection()->action("previous_blink")->setEnabled(allImages.size() > 1);
929
930 tab->loadFile(imageName, FITS_NORMAL, FITS_NONE);
931}
932
933
934void FITSViewer::changeBlink(bool increment)
935{
936 if (m_Tabs.empty() || m_BlinkBusy)
937 return;
938
939 m_BlinkBusy = true;
940 const int tabIndex = fitsTabWidget->currentIndex();
941 if (tabIndex >= m_Tabs.count() || tabIndex < 0)
942 {
943 m_BlinkBusy = false;
944 return;
945 }
946 auto tab = m_Tabs[tabIndex];
947 const QList<QString> &filenames = tab->blinkFilenames();
948 if (filenames.size() <= 1)
949 {
950 m_BlinkBusy = false;
951 return;
952 }
953
954 int blinkIndex = tab->blinkUpto() + (increment ? 1 : -1);
955 if (blinkIndex >= filenames.size())
956 blinkIndex = 0;
957 else if (blinkIndex < 0)
958 blinkIndex = filenames.size() - 1;
959
961 QString tabName = QString("%1/%2 %3")
962 .arg(blinkIndex + 1).arg(filenames.size()).arg(QFileInfo(nextFilename).fileName());
963 tab->disconnect(this);
964 connect(tab.get(), &FITSTab::failed, this, [ this, nextFilename ](const QString & errorMessage)
965 {
966 Q_UNUSED(errorMessage);
967 QObject::sender()->disconnect(this);
968 QApplication::restoreOverrideCursor();
969 led.setColor(Qt::red);
970 m_BlinkBusy = false;
972
973 connect(tab.get(), &FITSTab::loaded, this, [ = ]()
974 {
975 QObject::sender()->disconnect(this);
976 updateFITSCommon(tab, QUrl::fromLocalFile(nextFilename));
977 fitsTabWidget->setTabText(tabIndex, tabName);
978 m_BlinkBusy = false;
980
981 tab->setBlinkUpto(blinkIndex);
982 tab->loadFile(QUrl::fromLocalFile(nextFilename), FITS_NORMAL, FITS_NONE);
983}
984
985void FITSViewer::nextBlink()
986{
987 changeBlink(true);
988}
989
990void FITSViewer::previousBlink()
991{
992 changeBlink(false);
993}
994
995void FITSViewer::openFile()
996{
997 QFileDialog dialog(KStars::Instance(), i18nc("@title:window", "Open Image"));
998 dialog.setFileMode(QFileDialog::ExistingFiles);
999 dialog.setDirectoryUrl(lastURL);
1000 dialog.setNameFilter("Images (*.fits *.fits.fz *.fit *.fts *.xisf "
1001 "*.jpg *.jpeg *.png *.gif *.bmp "
1002 "*.cr2 *.cr3 *.crw *.nef *.raf *.dng *.arw *.orf)");
1003 if (!dialog.exec())
1004 return;
1005 m_urls = dialog.selectedUrls();
1006 if (m_urls.size() < 1)
1007 return;
1008 // Protect against, e.g. opening 1000 tabs. Not sure what the right number is.
1009 constexpr int MAX_NUM_OPENS = 40;
1010 if (m_urls.size() > MAX_NUM_OPENS)
1011 return;
1012
1013 lastURL = QUrl(m_urls[0].url(QUrl::RemoveFilename));
1014 loadFiles();
1015}
1016
1017void FITSViewer::saveFile()
1018{
1019 m_Tabs[fitsTabWidget->currentIndex()]->saveFile();
1020}
1021
1022void FITSViewer::saveFileAs()
1023{
1024 if (m_Tabs.empty())
1025 return;
1026
1027 if (m_Tabs[fitsTabWidget->currentIndex()]->saveFileAs() &&
1028 m_Tabs[fitsTabWidget->currentIndex()]->getView()->getMode() == FITS_NORMAL)
1029 fitsTabWidget->setTabText(fitsTabWidget->currentIndex(),
1030 m_Tabs[fitsTabWidget->currentIndex()]->getCurrentURL()->fileName());
1031}
1032
1033void FITSViewer::copyFITS()
1034{
1035 if (m_Tabs.empty())
1036 return;
1037
1038 m_Tabs[fitsTabWidget->currentIndex()]->copyFITS();
1039}
1040
1041void FITSViewer::histoFITS()
1042{
1043 if (m_Tabs.empty())
1044 return;
1045
1046 m_Tabs[fitsTabWidget->currentIndex()]->histoFITS();
1047}
1048
1049void FITSViewer::statFITS()
1050{
1051 if (m_Tabs.empty())
1052 return;
1053
1054 m_Tabs[fitsTabWidget->currentIndex()]->statFITS();
1055}
1056
1057void FITSViewer::rotateCW()
1058{
1059 applyFilter(FITS_ROTATE_CW);
1060}
1061
1062void FITSViewer::rotateCCW()
1063{
1064 applyFilter(FITS_ROTATE_CCW);
1065}
1066
1067void FITSViewer::flipHorizontal()
1068{
1069 applyFilter(FITS_MOUNT_FLIP_H);
1070}
1071
1072void FITSViewer::flipVertical()
1073{
1074 applyFilter(FITS_MOUNT_FLIP_V);
1075}
1076
1077void FITSViewer::headerFITS()
1078{
1079 if (m_Tabs.empty())
1080 return;
1081
1082 m_Tabs[fitsTabWidget->currentIndex()]->headerFITS();
1083}
1084
1085void FITSViewer::debayerFITS()
1086{
1087 if (debayerDialog == nullptr)
1088 {
1089 debayerDialog = new FITSDebayer(this);
1090 }
1091
1093 if (getCurrentView(view))
1094 {
1095 BayerParams param;
1096 view->imageData()->getBayerParams(&param);
1097 debayerDialog->setBayerParams(&param);
1098 debayerDialog->show();
1099 }
1100}
1101
1102void FITSViewer::updateStatusBar(const QString &msg, FITSBar id)
1103{
1104 switch (id)
1105 {
1106 case FITS_POSITION:
1107 fitsPosition.setText(msg);
1108 break;
1109 case FITS_RESOLUTION:
1110 fitsResolution.setText(msg);
1111 break;
1112 case FITS_ZOOM:
1113 fitsZoom.setText(msg);
1114 break;
1115 case FITS_WCS:
1116 fitsWCS.setVisible(true);
1117 fitsWCS.setText(msg);
1118 break;
1119 case FITS_VALUE:
1120 fitsValue.setText(msg);
1121 break;
1122 case FITS_HFR:
1123 fitsHFR.setText(msg);
1124 break;
1125 case FITS_CLIP:
1126 fitsClip.setText(msg);
1127 break;
1128 case FITS_MESSAGE:
1129 statusBar()->showMessage(msg);
1130 break;
1131
1132 default:
1133 break;
1134 }
1135}
1136
1137void FITSViewer::ZoomAllIn()
1138{
1139 if (m_Tabs.empty())
1140 return;
1141
1142 // Could add code to not call View::updateFrame for these
1143 for (int i = 0; i < fitsTabWidget->count(); ++i)
1144 if (i != fitsTabWidget->currentIndex())
1145 m_Tabs[i]->ZoomIn();
1146
1147 m_Tabs[fitsTabWidget->currentIndex()]->ZoomIn();
1148}
1149
1150void FITSViewer::ZoomAllOut()
1151{
1152 if (m_Tabs.empty())
1153 return;
1154
1155 // Could add code to not call View::updateFrame for these
1156 for (int i = 0; i < fitsTabWidget->count(); ++i)
1157 if (i != fitsTabWidget->currentIndex())
1158 m_Tabs[i]->ZoomOut();
1159
1160 m_Tabs[fitsTabWidget->currentIndex()]->ZoomOut();
1161}
1162
1163void FITSViewer::ZoomIn()
1164{
1165 if (m_Tabs.empty())
1166 return;
1167
1168 m_Tabs[fitsTabWidget->currentIndex()]->ZoomIn();
1169}
1170
1171void FITSViewer::ZoomOut()
1172{
1173 if (m_Tabs.empty())
1174 return;
1175
1176 m_Tabs[fitsTabWidget->currentIndex()]->ZoomOut();
1177}
1178
1179void FITSViewer::ZoomDefault()
1180{
1181 if (m_Tabs.empty())
1182 return;
1183
1184 m_Tabs[fitsTabWidget->currentIndex()]->ZoomDefault();
1185}
1186
1187void FITSViewer::ZoomToFit()
1188{
1189 if (m_Tabs.empty())
1190 return;
1191
1192 QSharedPointer<FITSView> currentView;
1193 if (getCurrentView(currentView))
1194 currentView->ZoomToFit();
1195}
1196
1197// Adjust the (top-level) window size to fit the current zoom, so that there
1198// are no scroll bars and minimal empty space in the view. Note, if the zoom
1199// is such that it would be larger than the display size than this can't be
1200// accomplished and the windowing will do its best.
1201void FITSViewer::FitToZoom()
1202{
1203 if (m_Tabs.empty())
1204 return;
1205
1206 QSharedPointer<FITSView> currentView;
1207 if (!getCurrentView(currentView))
1208 return;
1209
1210 const double zoom = currentView->getCurrentZoom() * .01;
1211 const int w = zoom * currentView->imageData()->width();
1212 const int h = zoom * currentView->imageData()->height();
1213 const int extraW = width() - currentView->width();
1214 const int extraH = height() - currentView->height();
1215 // These 4s seem to be needed to make sure we don't wind up with scroll bars.
1216 // Not sure why, would be great to figure out how to remove them.
1217 const int wNeeded = w + extraW + 4;
1218 const int hNeeded = h + extraH + 4;
1220}
1221
1222void FITSViewer::updateAction(const QString &name, bool enable)
1223{
1224 QAction *toolAction = actionCollection()->action(name);
1225
1226 if (toolAction != nullptr)
1227 toolAction->setEnabled(enable);
1228}
1229
1230void FITSViewer::updateTabStatus(bool clean, const QUrl &imageURL)
1231{
1232 if (m_Tabs.empty() || (fitsTabWidget->currentIndex() >= m_Tabs.size()))
1233 return;
1234
1235 if (m_Tabs[fitsTabWidget->currentIndex()]->getView()->getMode() != FITS_NORMAL)
1236 return;
1237
1238 //QString tabText = fitsImages[fitsTab->currentIndex()]->getCurrentURL()->fileName();
1239
1240 QString tabText = imageURL.isEmpty() ? fitsTabWidget->tabText(fitsTabWidget->currentIndex()) : imageURL.fileName();
1241
1242 fitsTabWidget->setTabText(fitsTabWidget->currentIndex(), clean ? tabText.remove('*') : tabText + '*');
1243}
1244
1245void FITSViewer::closeTab(int index)
1246{
1247 if (m_Tabs.empty())
1248 return;
1249
1250 auto tab = m_Tabs[index];
1251
1252 int UID = tab->getUID();
1253
1254 fitsMap.remove(UID);
1255 m_Tabs.removeOne(tab);
1256
1257 if (m_Tabs.empty())
1258 {
1259 saveFileAction->setEnabled(false);
1260 saveFileAsAction->setEnabled(false);
1261 }
1262
1263 emit closed(UID);
1264}
1265
1266/**
1267 This is helper function to make it really easy to make the update the state of toggle buttons
1268 that either show or hide information in the Current view. This method would get called both
1269 when one of them gets pushed and also when tabs are switched.
1270 */
1271
1272void FITSViewer::updateButtonStatus(const QString &action, const QString &item, bool showing)
1273{
1274 QAction *a = actionCollection()->action(action);
1275 if (a == nullptr)
1276 return;
1277
1278 if (showing)
1279 {
1280 a->setText(i18n("Hide %1", item));
1281 a->setChecked(true);
1282 }
1283 else
1284 {
1285 a->setText(i18n("Show %1", item));
1286 a->setChecked(false);
1287 }
1288}
1289
1290/**
1291This is a method that either enables or disables the WCS based features in the Current View.
1292 */
1293
1295{
1296 QSharedPointer<FITSView> currentView;
1297 if (!getCurrentView(currentView))
1298 return;
1299
1300 if (currentView->imageHasWCS())
1301 {
1302 actionCollection()->action("view_eq_grid")->setDisabled(false);
1303 actionCollection()->action("view_eq_grid")->setText(i18n("Show Equatorial Gridlines"));
1304 actionCollection()->action("view_objects")->setDisabled(false);
1305 actionCollection()->action("view_objects")->setText(i18n("Show Objects in Image"));
1306 if (currentView->isTelescopeActive())
1307 {
1308 actionCollection()->action("center_telescope")->setDisabled(false);
1309 actionCollection()->action("center_telescope")->setText(i18n("Center Telescope\n*Ready*"));
1310 }
1311 else
1312 {
1313 actionCollection()->action("center_telescope")->setDisabled(true);
1314 actionCollection()->action("center_telescope")->setText(i18n("Center Telescope\n*No Telescopes Detected*"));
1315 }
1316 actionCollection()->action("view_hips_overlay")->setDisabled(false);
1317 }
1318 else
1319 {
1320 actionCollection()->action("view_eq_grid")->setDisabled(true);
1321 actionCollection()->action("view_eq_grid")->setText(i18n("Show Equatorial Gridlines\n*No WCS Info*"));
1322 actionCollection()->action("center_telescope")->setDisabled(true);
1323 actionCollection()->action("center_telescope")->setText(i18n("Center Telescope\n*No WCS Info*"));
1324 actionCollection()->action("view_objects")->setDisabled(true);
1325 actionCollection()->action("view_objects")->setText(i18n("Show Objects in Image\n*No WCS Info*"));
1326 actionCollection()->action("view_hips_overlay")->setDisabled(true);
1327 }
1328}
1329
1330void FITSViewer::updateScopeButton()
1331{
1332 QSharedPointer<FITSView> currentView;
1333 if (!getCurrentView(currentView))
1334 return;
1335
1336 if (currentView->getCursorMode() == FITSView::scopeCursor)
1337 {
1338 actionCollection()->action("center_telescope")->setChecked(true);
1339 }
1340 else
1341 {
1342 actionCollection()->action("center_telescope")->setChecked(false);
1343 }
1344}
1345
1346void FITSViewer::ROIFixedSize(int s)
1347{
1348 if (m_Tabs.empty())
1349 return;
1350
1351 QSharedPointer<FITSView> currentView;
1352 if (getCurrentView(currentView))
1353 {
1354 if(!currentView->isSelectionRectShown())
1355 {
1356 toggleSelectionMode();
1357 updateButtonStatus("image_roi_stats", i18n("Selection Rectangle"), currentView->isSelectionRectShown());
1358 }
1359 currentView->processRectangleFixed(s);
1360 }
1361}
1362
1363void FITSViewer::customROIInputWindow()
1364{
1365 if(m_Tabs.empty())
1366 return;
1367
1368 QSharedPointer<FITSView> currentView;
1369 if (getCurrentView(currentView))
1370 {
1371 if(!currentView->isSelectionRectShown())
1372 return;
1373
1374 int mh = currentView->imageData()->height();
1375 int mw = currentView->imageData()->width();
1376
1377 if(mh % 2)
1378 mh++;
1379 if(mw % 2)
1380 mw++;
1381
1385
1386 form.addRow(new QLabel(i18n("Size")));
1387
1390
1391 wle.setValidator(new QIntValidator(1, mw, &wle));
1392 hle.setValidator(new QIntValidator(1, mh, &hle));
1393
1394 form.addRow(i18n("Width"), &wle);
1395 form.addRow(i18n("Height"), &hle);
1396 form.addRow(&buttonBox);
1397
1400
1402 {
1403 QPoint resetCenter = currentView->getSelectionRegion().center();
1404 int newheight = hle.text().toInt();
1405 int newwidth = wle.text().toInt();
1406
1408 newheight = qMax(newheight, 1) ;
1410 newwidth = qMax(newwidth, 1);
1411
1412 QPoint topLeft = resetCenter;
1414
1415 topLeft.setX((topLeft.x() - newwidth / 2));
1416 topLeft.setY((topLeft.y() - newheight / 2));
1417 botRight.setX((botRight.x() + newwidth / 2));
1418 botRight.setY((botRight.y() + newheight / 2));
1419
1420 emit currentView->setRubberBand(QRect(topLeft, botRight));
1421 currentView->processRectangle(topLeft, botRight, true);
1422 }
1423 }
1424}
1425/**
1426 This method either enables or disables the scope mouse mode so you can slew your scope to coordinates
1427 just by clicking the mouse on a spot in the image.
1428 */
1429
1431{
1432 QSharedPointer<FITSView> currentView;
1433 if (!getCurrentView(currentView))
1434 return;
1435
1436 currentView->setScopeButton(actionCollection()->action("center_telescope"));
1437 if (currentView->getCursorMode() == FITSView::scopeCursor)
1438 {
1439 currentView->setCursorMode(currentView->lastMouseMode);
1440 }
1441 else
1442 {
1443 currentView->lastMouseMode = currentView->getCursorMode();
1444 currentView->setCursorMode(FITSView::scopeCursor);
1445 }
1446 updateScopeButton();
1447}
1448
1449void FITSViewer::toggleCrossHair()
1450{
1451 if (m_Tabs.empty())
1452 return;
1453
1454 QSharedPointer<FITSView> currentView;
1455 if (!getCurrentView(currentView))
1456 return;
1457
1458 currentView->toggleCrosshair();
1459 updateButtonStatus("view_crosshair", i18n("Cross Hairs"), currentView->isCrosshairShown());
1460}
1461
1462void FITSViewer::toggleClipping()
1463{
1464 if (m_Tabs.empty())
1465 return;
1466
1467 QSharedPointer<FITSView> currentView;
1468 if (!getCurrentView(currentView))
1469 return;
1470 currentView->toggleClipping();
1471 if (!currentView->isClippingShown())
1472 fitsClip.clear();
1473 updateButtonStatus("view_clipping", i18n("Clipping"), currentView->isClippingShown());
1474}
1475
1476void FITSViewer::toggleEQGrid()
1477{
1478 if (m_Tabs.empty())
1479 return;
1480
1481 QSharedPointer<FITSView> currentView;
1482 if (!getCurrentView(currentView))
1483 return;
1484
1485 currentView->toggleEQGrid();
1486 updateButtonStatus("view_eq_grid", i18n("Equatorial Gridlines"), currentView->isEQGridShown());
1487}
1488
1489void FITSViewer::toggleHiPSOverlay()
1490{
1491 if (m_Tabs.empty())
1492 return;
1493
1494 QSharedPointer<FITSView> currentView;
1495 if (!getCurrentView(currentView))
1496 return;
1497
1498 currentView->toggleHiPSOverlay();
1499 updateButtonStatus("view_hips_overlay", i18n("HiPS Overlay"), currentView->isHiPSOverlayShown());
1500}
1501
1502void FITSViewer::toggleSelectionMode()
1503{
1504 if (m_Tabs.empty())
1505 return;
1506
1507 QSharedPointer<FITSView> currentView;
1508 if (!getCurrentView(currentView))
1509 return;
1510
1511 currentView->toggleSelectionMode();
1512 updateButtonStatus("image_roi_stats", i18n("Selection Rectangle"), currentView->isSelectionRectShown());
1513}
1514
1515void FITSViewer::toggleObjects()
1516{
1517 if (m_Tabs.empty())
1518 return;
1519
1520 QSharedPointer<FITSView> currentView;
1521 if (!getCurrentView(currentView))
1522 return;
1523
1524 currentView->toggleObjects();
1525 updateButtonStatus("view_objects", i18n("Objects in Image"), currentView->areObjectsShown());
1526}
1527
1528void FITSViewer::togglePixelGrid()
1529{
1530 if (m_Tabs.empty())
1531 return;
1532
1533 QSharedPointer<FITSView> currentView;
1534 if (!getCurrentView(currentView))
1535 return;
1536
1537 currentView->togglePixelGrid();
1538 updateButtonStatus("view_pixel_grid", i18n("Pixel Gridlines"), currentView->isPixelGridShown());
1539}
1540
1541void FITSViewer::toggle3DGraph()
1542{
1543 if (m_Tabs.empty())
1544 return;
1545
1546 QSharedPointer<FITSView> currentView;
1547 if (!getCurrentView(currentView))
1548 return;
1549
1550 currentView->toggleStarProfile();
1551 updateButtonStatus("toggle_3D_graph", i18n("View 3D Graph"), currentView->isStarProfileShown());
1552}
1553
1554void FITSViewer::nextTab()
1555{
1556 if (m_Tabs.empty())
1557 return;
1558
1559 int index = fitsTabWidget->currentIndex() + 1;
1560 if (index >= m_Tabs.count() || index < 0)
1561 index = 0;
1562 fitsTabWidget->setCurrentIndex(index);
1563
1564 actionCollection()->action("next_blink")->setEnabled(m_Tabs[index]->blinkFilenames().size() > 1);
1565 actionCollection()->action("previous_blink")->setEnabled(m_Tabs[index]->blinkFilenames().size() > 1);
1566}
1567
1568void FITSViewer::previousTab()
1569{
1570 if (m_Tabs.empty())
1571 return;
1572
1573 int index = fitsTabWidget->currentIndex() - 1;
1574 if (index >= m_Tabs.count() || index < 0)
1575 index = m_Tabs.count() - 1;
1576 fitsTabWidget->setCurrentIndex(index);
1577
1578 actionCollection()->action("next_blink")->setEnabled(m_Tabs[index]->blinkFilenames().size() > 1);
1579 actionCollection()->action("previous_blink")->setEnabled(m_Tabs[index]->blinkFilenames().size() > 1);
1580
1581}
1582
1583void FITSViewer::toggleStars()
1584{
1585 if (markStars)
1586 {
1587 markStars = false;
1588 actionCollection()->action("mark_stars")->setText(i18n("Mark Stars"));
1589 }
1590 else
1591 {
1592 markStars = true;
1593 actionCollection()->action("mark_stars")->setText(i18n("Unmark Stars"));
1594 }
1595
1596 for (auto tab : m_Tabs)
1597 {
1598 tab->getView()->toggleStars(markStars);
1599 tab->getView()->updateFrame();
1600 }
1601}
1602
1603void FITSViewer::applyFilter(int ftype)
1604{
1605 if (m_Tabs.empty())
1606 return;
1607
1609 updateStatusBar(i18n("Processing %1...", filterTypes[ftype - 1]), FITS_MESSAGE);
1610 qApp->processEvents();
1611 m_Tabs[fitsTabWidget->currentIndex()]->getHistogram()->applyFilter(static_cast<FITSScale>(ftype));
1612 qApp->processEvents();
1613 m_Tabs[fitsTabWidget->currentIndex()]->getView()->updateFrame();
1615 updateStatusBar(i18n("Ready."), FITS_MESSAGE);
1616}
1617
1618bool FITSViewer::getView(int fitsUID, QSharedPointer<FITSView> &view)
1619{
1620 auto tab = fitsMap.value(fitsUID);
1621 if (tab)
1622 {
1623 view = tab->getView();
1624 return true;
1625 }
1626 return false;
1627
1628}
1629
1630bool FITSViewer::getCurrentView(QSharedPointer<FITSView> &view)
1631{
1632 if (m_Tabs.empty() || fitsTabWidget->currentIndex() >= m_Tabs.count())
1633 return false;
1634
1635 view = m_Tabs[fitsTabWidget->currentIndex()]->getView();
1636 return true;
1637}
1638
1639void FITSViewer::setDebayerAction(bool enable)
1640{
1641 actionCollection()->addAction("fits_debayer")->setEnabled(enable);
1642}
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 * zoom(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)
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 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 Fri May 17 2024 11:48:26 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.