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

KDE's Doxygen guidelines are available online.