Kstars

fitstab.cpp
1/*
2 SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "fitstab.h"
8
9#include "QtNetwork/qnetworkreply.h"
10#include "fitsdata.h"
11#include "fitshistogramcommand.h"
12#include "fitsview.h"
13#include "fitsviewer.h"
14#include "ksnotification.h"
15#include "kstars.h"
16#include "Options.h"
17#include "platesolve.h"
18#include "ui_fitsheaderdialog.h"
19#include "ui_statform.h"
20#include "fitsstretchui.h"
21#include "skymap.h"
22#include <KMessageBox>
23#include <QFileDialog>
24#include <QClipboard>
25#include <QIcon>
26
27#include <fits_debug.h>
28#include "fitscommon.h"
29#include <QDesktopServices>
30#include <QUrl>
31#include <QDialog>
32
33constexpr int CAT_OBJ_SORT_ROLE = Qt::UserRole + 1;
34
35FITSTab::FITSTab(FITSViewer *parent) : QWidget(parent)
36{
37 viewer = parent;
38 undoStack = new QUndoStack(this);
39 undoStack->setUndoLimit(10);
40 undoStack->clear();
41 connect(undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(modifyFITSState(bool)));
42
43 m_PlateSolve.reset(new PlateSolve(this));
44 m_CatalogObjectWidget = new QDialog(this);
45 statWidget = new QDialog(this);
46 fitsHeaderDialog = new QDialog(this);
47 m_HistogramEditor = new FITSHistogramEditor(this);
48 connect(m_HistogramEditor, &FITSHistogramEditor::newHistogramCommand, this, [this](FITSHistogramCommand * command)
49 {
50 undoStack->push(command);
51 });
52}
53
54FITSTab::~FITSTab()
55{
56}
57
58void FITSTab::saveUnsaved()
59{
60 if (undoStack->isClean() || m_View->getMode() != FITS_NORMAL)
61 return;
62
63 QString caption = i18n("Save Changes to FITS?");
64 QString message = i18n("The current FITS file has unsaved changes. Would you like to save before closing it?");
65
66 int ans = KMessageBox::warningContinueCancel(nullptr, message, caption, KStandardGuiItem::save(),
68 if (ans == KMessageBox::Continue)
69 saveFile();
70 if (ans == KMessageBox::Cancel)
71 {
72 undoStack->clear();
73 modifyFITSState();
74 }
75}
76
77void FITSTab::closeEvent(QCloseEvent *ev)
78{
79 saveUnsaved();
80
81 if (undoStack->isClean())
82 ev->accept();
83 else
84 ev->ignore();
85}
86QString FITSTab::getPreviewText() const
87{
88 return previewText;
89}
90
91void FITSTab::setPreviewText(const QString &value)
92{
93 previewText = value;
94}
95
96void FITSTab::selectRecentFITS(int i)
97{
98 loadFile(QUrl::fromLocalFile(recentImages->item(i)->text()));
99}
100
101void FITSTab::clearRecentFITS()
102{
103 disconnect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS);
104 recentImages->clear();
105 connect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS);
106}
107
108bool FITSTab::setupView(FITSMode mode, FITSScale filter)
109{
110 if (m_View.isNull())
111 {
112 m_View.reset(new FITSView(this, mode, filter));
113 m_View->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
114 QVBoxLayout *vlayout = new QVBoxLayout();
115
116 connect(m_View.get(), &FITSView::rectangleUpdated, this, [this](QRect roi)
117 {
118 displayStats(roi.isValid());
119 });
120 fitsSplitter = new QSplitter(Qt::Horizontal, this);
121 fitsTools = new QToolBox();
122
123 stat.setupUi(statWidget);
124
125 for (int i = 0; i <= STAT_STDDEV; i++)
126 {
127 for (int j = 0; j < 3; j++)
128 {
129 stat.statsTable->setItem(i, j, new QTableWidgetItem());
130 stat.statsTable->item(i, j)->setTextAlignment(Qt::AlignHCenter);
131 }
132
133 // Set col span for items up to HFR
134 if (i <= STAT_HFR)
135 stat.statsTable->setSpan(i, 0, 1, 3);
136 }
137
138 connect(m_PlateSolve.get(), &PlateSolve::clicked, this, &FITSTab::extractImage);
139
140 fitsTools->addItem(statWidget, i18n("Statistics"));
141 fitsTools->addItem(m_PlateSolve.get(), i18n("Plate Solving"));
142
143 // Setup the Catalog Object page
144 m_CatalogObjectUI.setupUi(m_CatalogObjectWidget);
145 m_CatalogObjectItem = fitsTools->addItem(m_CatalogObjectWidget, i18n("Catalog Objects"));
146 initCatalogObject();
147
148 fitsTools->addItem(m_HistogramEditor, i18n("Histogram"));
149
150 header.setupUi(fitsHeaderDialog);
151 fitsTools->addItem(fitsHeaderDialog, i18n("FITS Header"));
152 connect(m_View.get(), &FITSView::headerChanged, this, &FITSTab::loadFITSHeader);
153
154 QVBoxLayout *recentPanelLayout = new QVBoxLayout();
155 QWidget *recentPanel = new QWidget(fitsSplitter);
156 recentPanel->setLayout(recentPanelLayout);
157 fitsTools->addItem(recentPanel, i18n("Recent Images"));
158 recentImages = new QListWidget(recentPanel);
159 recentPanelLayout->addWidget(recentImages);
160 QPushButton *clearRecent = new QPushButton(i18n("Clear"));
161 recentPanelLayout->addWidget(clearRecent);
162 connect(clearRecent, &QPushButton::pressed, this, &FITSTab::clearRecentFITS);
163 connect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS);
164
165 QScrollArea *scrollFitsPanel = new QScrollArea(fitsSplitter);
166 scrollFitsPanel->setWidgetResizable(true);
167 scrollFitsPanel->setWidget(fitsTools);
168
169 fitsSplitter->addWidget(scrollFitsPanel);
170 fitsSplitter->addWidget(m_View.get());
171
172 //This code allows the fitsTools to start in a closed state
173 fitsSplitter->setSizes(QList<int>() << 0 << m_View->width() );
174
175 vlayout->addWidget(fitsSplitter);
176
177 stretchUI.reset(new FITSStretchUI(m_View, nullptr));
178 vlayout->addWidget(stretchUI.get());
179
180 connect(fitsSplitter, &QSplitter::splitterMoved, m_HistogramEditor, &FITSHistogramEditor::resizePlot);
181
182 setLayout(vlayout);
183 connect(m_View.get(), &FITSView::newStatus, this, &FITSTab::newStatus);
184 connect(m_View.get(), &FITSView::debayerToggled, this, &FITSTab::debayerToggled);
185 connect(m_View.get(), &FITSView::updated, this, &FITSTab::updated);
186
187 // On Failure to load
188 connect(m_View.get(), &FITSView::failed, this, &FITSTab::failed);
189
190 return true;
191 }
192
193 // returns false if no setup needed.
194 return false;
195}
196
197void FITSTab::loadFile(const QUrl &imageURL, FITSMode mode, FITSScale filter)
198{
199 // check if the address points to an appropriate address
200 if (imageURL.isEmpty() || !imageURL.isValid() || !QFileInfo::exists(imageURL.toLocalFile()))
201 {
202 emit failed(i18nc("File not found: %1", imageURL.toString().toLatin1()));
203 return;
204 }
205
206 if (setupView(mode, filter))
207 {
208
209 // On Success loading image
210 connect(m_View.get(), &FITSView::loaded, this, [&]()
211 {
212 processData();
213 emit loaded();
214 });
215
216 connect(m_View.get(), &FITSView::updated, this, &FITSTab::updated);
217 }
218 else
219 // update tab text
220 modifyFITSState(true, imageURL);
221
222 currentURL = imageURL;
223
224 m_View->setFilter(filter);
225
226 m_View->loadFile(imageURL.toLocalFile());
227}
228
229bool FITSTab::shouldComputeHFR() const
230{
231 if (viewer->isStarsMarked())
232 return true;
233 if (!Options::autoHFR())
234 return false;
235 return ((!m_View.isNull()) && (m_View->getMode() == FITS_NORMAL));
236}
237
238void FITSTab::processData()
239{
240 const QSharedPointer<FITSData> &imageData = m_View->imageData();
241
242 m_HistogramEditor->setImageData(imageData);
243
244 // Only construct histogram if it is actually visible
245 // Otherwise wait until histogram is needed before creating it.
246 // if (fitsSplitter->sizes().at(0) != 0 && !imageData->isHistogramConstructed() &&
247 // !Options::nonLinearHistogram())
248 // {
249 // imageData->constructHistogram();
250 // }
251
252 if (shouldComputeHFR())
253 {
254 m_View->searchStars();
255 qCDebug(KSTARS_FITS) << "FITS HFR:" << imageData->getHFR();
256 }
257
258 displayStats();
259
260 loadFITSHeader();
261
262 // Don't add it to the list if it is already there
263 if (recentImages->findItems(currentURL.toLocalFile(), Qt::MatchExactly).count() == 0)
264 {
265 //Don't add it to the list if it is a preview
266 if(!imageData->filename().startsWith(QDir::tempPath()))
267 {
268 disconnect(recentImages, &QListWidget::currentRowChanged, this,
269 &FITSTab::selectRecentFITS);
270 recentImages->addItem(imageData->filename());
271 recentImages->setCurrentRow(recentImages->count() - 1);
272 connect(recentImages, &QListWidget::currentRowChanged, this,
273 &FITSTab::selectRecentFITS);
274 }
275 }
276
277 // This could both compute the HFRs and setup the graphics, however,
278 // if shouldComputeHFR() above is true, then that will compute the HFRs
279 // and this would notice that and just setup graphics. They are separated
280 // for the case where the graphics is not desired.
281 if (viewer->isStarsMarked())
282 {
283 m_View->toggleStars(true);
284 m_View->updateFrame();
285 }
286
287 // if (Options::nonLinearHistogram())
288 // m_HistogramEditor->createNonLinearHistogram();
289
290 stretchUI->generateHistogram();
291}
292
293bool FITSTab::loadData(const QSharedPointer<FITSData> &data, FITSMode mode, FITSScale filter)
294{
295 setupView(mode, filter);
296
297 // Empty URL
298 currentURL = QUrl();
299
300 if (viewer->isStarsMarked())
301 {
302 m_View->toggleStars(true);
303 //view->updateFrame();
304 }
305
306 m_View->setFilter(filter);
307
308 if (!m_View->loadData(data))
309 {
310 // On Failure to load
311 // connect(view.get(), &FITSView::failed, this, &FITSTab::failed);
312 return false;
313 }
314
315 processData();
316 return true;
317}
318
319void FITSTab::modifyFITSState(bool clean, const QUrl &imageURL)
320{
321 if (clean)
322 {
323 if (undoStack->isClean() == false)
324 undoStack->setClean();
325
326 mDirty = false;
327 }
328 else
329 mDirty = true;
330
331 emit changeStatus(clean, imageURL);
332}
333
334bool FITSTab::saveImage(const QString &filename)
335{
336 return m_View->saveImage(filename);
337}
338
339void FITSTab::copyFITS()
340{
341 QApplication::clipboard()->setImage(m_View->getDisplayImage());
342}
343
344void FITSTab::histoFITS()
345{
346 fitsTools->setCurrentIndex(1);
347 if(m_View->width() > 200)
348 fitsSplitter->setSizes(QList<int>() << 200 << m_View->width() - 200);
349 else
350 fitsSplitter->setSizes(QList<int>() << 50 << 50);
351}
352
353void FITSTab::displayStats(bool roi)
354{
355 const QSharedPointer<FITSData> &imageData = m_View->imageData();
356
357 stat.statsTable->item(STAT_WIDTH, 0)->setText(QString::number(imageData->width(roi)));
358 stat.statsTable->item(STAT_HEIGHT, 0)->setText(QString::number(imageData->height(roi)));
359 stat.statsTable->item(STAT_BITPIX, 0)->setText(QString::number(imageData->bpp()));
360
361 if (!roi)
362 stat.statsTable->item(STAT_HFR, 0)->setText(QString::number(imageData->getHFR(), 'f', 3));
363 else
364 stat.statsTable->item(STAT_HFR, 0)->setText("---");
365
366 if (imageData->channels() == 1)
367 {
368 for (int i = STAT_MIN; i <= STAT_STDDEV; i++)
369 {
370 if (stat.statsTable->columnSpan(i, 0) != 3)
371 stat.statsTable->setSpan(i, 0, 1, 3);
372 }
373
374 stat.statsTable->horizontalHeaderItem(0)->setText(i18n("Value"));
375 stat.statsTable->hideColumn(1);
376 stat.statsTable->hideColumn(2);
377 }
378 else
379 {
380 for (int i = STAT_MIN; i <= STAT_STDDEV; i++)
381 {
382 if (stat.statsTable->columnSpan(i, 0) != 1)
383 stat.statsTable->setSpan(i, 0, 1, 1);
384 }
385
386 stat.statsTable->horizontalHeaderItem(0)->setText(i18nc("Red", "R"));
387 stat.statsTable->showColumn(1);
388 stat.statsTable->showColumn(2);
389 }
390
391 if (!Options::nonLinearHistogram() && !imageData->isHistogramConstructed())
392 imageData->constructHistogram();
393
394 for (int i = 0; i < imageData->channels(); i++)
395 {
396 stat.statsTable->item(STAT_MIN, i)->setText(QString::number(imageData->getMin(i, roi), 'f', 3));
397 stat.statsTable->item(STAT_MAX, i)->setText(QString::number(imageData->getMax(i, roi), 'f', 3));
398 stat.statsTable->item(STAT_MEAN, i)->setText(QString::number(imageData->getMean(i, roi), 'f', 3));
399 stat.statsTable->item(STAT_MEDIAN, i)->setText(QString::number(imageData->getMedian(i, roi), 'f', 3));
400 stat.statsTable->item(STAT_STDDEV, i)->setText(QString::number(imageData->getStdDev(i, roi), 'f', 3));
401 }
402}
403
404void FITSTab::statFITS()
405{
406 fitsTools->setCurrentIndex(0);
407 if(m_View->width() > 200)
408 fitsSplitter->setSizes(QList<int>() << 200 << m_View->width() - 200);
409 else
410 fitsSplitter->setSizes(QList<int>() << 50 << 50);
411 displayStats();
412}
413
414void FITSTab::loadFITSHeader()
415{
416 const QSharedPointer<FITSData> &imageData = m_View->imageData();
417 if (!imageData)
418 return;
419
420 int nkeys = imageData->getRecords().size();
421 int counter = 0;
422 header.tableWidget->setRowCount(nkeys);
423 for (const auto &oneRecord : imageData->getRecords())
424 {
425 QTableWidgetItem *tempItem = new QTableWidgetItem(oneRecord.key);
427 header.tableWidget->setItem(counter, 0, tempItem);
428 tempItem = new QTableWidgetItem(oneRecord.value.toString());
430 header.tableWidget->setItem(counter, 1, tempItem);
431 tempItem = new QTableWidgetItem(oneRecord.comment);
433 header.tableWidget->setItem(counter, 2, tempItem);
434 counter++;
435 }
436
437 header.tableWidget->setColumnWidth(0, 100);
438 header.tableWidget->setColumnWidth(1, 100);
439 header.tableWidget->setColumnWidth(2, 250);
440}
441
442void FITSTab::loadCatalogObjects()
443{
444 // Check pointers are OK
445 if (!m_View)
446 return;
447 const QSharedPointer<FITSData> &imageData = m_View->imageData();
448 if (!imageData)
449 return;
450 QList<CatObject> catObjects = imageData->getCatObjects();
451
452 // Disable sorting whilst building the table
453 m_CatalogObjectUI.tableView->setSortingEnabled(false);
454 // Remove all rows
455 m_CatObjModel.removeRows(0, m_CatObjModel.rowCount());
456
457 int counter = 0, total = 0, highlightRow = -1;
458 QPixmap cdsPortalPixmap = QPixmap(":/icons/cdsportal.svg").scaled(50, 50, Qt::KeepAspectRatio, Qt::SmoothTransformation);
459 QPixmap simbadPixmap = QPixmap(":/icons/simbad.svg").scaled(50, 50, Qt::KeepAspectRatio, Qt::SmoothTransformation);
460 QPixmap nedPixmap = QPixmap(":/icons/NED.png").scaled(50, 50, Qt::KeepAspectRatio, Qt::SmoothTransformation);
461 for (const CatObject &catObject : catObjects)
462 {
463 total++;
464 if (!catObject.show)
465 continue;
466 m_CatObjModel.setRowCount(counter + 1);
467
468 // Num
469 QStandardItem* tempItem = new QStandardItem(QString::number(catObject.num));
470 tempItem->setData(catObject.num, CAT_OBJ_SORT_ROLE);
472 m_CatObjModel.setItem(counter, CAT_NUM, tempItem);
473
474 // CDS Portal - no sorting
475 tempItem = new QStandardItem();
476 tempItem->setData(cdsPortalPixmap, Qt::DecorationRole);
477 m_CatObjModel.setItem(counter, CAT_CDSPORTAL, tempItem);
478
479 // Simbad - no sorting
480 tempItem = new QStandardItem();
481 tempItem->setData(simbadPixmap, Qt::DecorationRole);
482 m_CatObjModel.setItem(counter, CAT_SIMBAD, tempItem);
483
484 // NED - no sorting
485 tempItem = new QStandardItem();
486 tempItem->setData(nedPixmap, Qt::DecorationRole);
487 m_CatObjModel.setItem(counter, CAT_NED, tempItem);
488
489 // Object
490 tempItem = new QStandardItem(catObject.name);
491 tempItem->setData(catObject.name, CAT_OBJ_SORT_ROLE);
492 m_CatObjModel.setItem(counter, CAT_OBJECT, tempItem);
493
494 // Type code
495 tempItem = new QStandardItem(catObject.typeCode);
496 tempItem->setData(catObject.typeCode, CAT_OBJ_SORT_ROLE);
497 m_CatObjModel.setItem(counter, CAT_TYPECODE, tempItem);
498
499 // Type label
500 tempItem = new QStandardItem(catObject.typeLabel);
501 tempItem->setData(catObject.typeLabel, CAT_OBJ_SORT_ROLE);
502 m_CatObjModel.setItem(counter, CAT_TYPELABEL, tempItem);
503
504 // Coordinates
505 QString coordStr = QString("%1 %2").arg(catObject.r.toHMSString(false, true))
506 .arg(catObject.d.toDMSString(true, false, true));
507 tempItem = new QStandardItem(coordStr);
508 tempItem->setData(coordStr, CAT_OBJ_SORT_ROLE);
509 m_CatObjModel.setItem(counter, CAT_COORDS, tempItem);
510
511 // Distance
512 tempItem = new QStandardItem(QString::number(catObject.dist));
513 tempItem->setData(catObject.dist, CAT_OBJ_SORT_ROLE);
515 m_CatObjModel.setItem(counter, CAT_DISTANCE, tempItem);
516
517 // Magnitude
518 QString magStr = (catObject.magnitude <= 0.0) ? "" : QString("%1").arg(catObject.magnitude, 0, 'f', 1);
519 tempItem = new QStandardItem(magStr);
520 tempItem->setData(catObject.magnitude, CAT_OBJ_SORT_ROLE);
522 m_CatObjModel.setItem(counter, CAT_MAGNITUDE, tempItem);
523
524 // Size
525 QString sizeStr = (catObject.size <= 0.0) ? "" : QString("%1").arg(catObject.size, 0, 'f', 0);
526 tempItem = new QStandardItem(sizeStr);
527 tempItem->setData(catObject.size, CAT_OBJ_SORT_ROLE);
529 m_CatObjModel.setItem(counter, CAT_SIZE, tempItem);
530
531 if (catObject.highlight)
532 highlightRow = counter;
533
534 counter++;
535 }
536
537 // Resize the columns to the data
538 m_CatalogObjectUI.tableView->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents);
539
540 if (highlightRow >= 0)
541 catHighlightRow(highlightRow);
542
543 // Enable sorting
544 m_CatObjModel.setSortRole(CAT_OBJ_SORT_ROLE);
545 m_CatalogObjectUI.tableView->setSortingEnabled(true);
546
547 // Update the status widget unless the query is still in progress
548 if (!imageData->getCatQueryInProgress())
549 m_CatalogObjectUI.status->setText(i18n("%1 / %2 Simbad objects loaded", counter, total));
550}
551
552void FITSTab::queriedCatalogObjects()
553{
554 // Show the Catalog Objects item (unless already shown)
555 if (fitsTools->currentIndex() != m_CatalogObjectItem)
556 {
557 fitsTools->setCurrentIndex(m_CatalogObjectItem);
558 if(m_View->width() > 200)
559 fitsSplitter->setSizes(QList<int>() << 200 << m_View->width() - 200);
560 else
561 fitsSplitter->setSizes(QList<int>() << 50 << 50);
562 }
563
564 m_CatalogObjectUI.status->setText(i18n("Simbad query in progress..."));
565}
566
567void FITSTab::catQueryFailed(const QString text)
568{
569 m_CatalogObjectUI.status->setText(text);
570}
571
572void FITSTab::catReset()
573{
574 m_CatalogObjectUI.status->setText("");
575 // Remove all rows from the table
576 m_CatObjModel.removeRows(0, m_CatObjModel.rowCount());
577}
578
579void FITSTab::catHighlightRow(const int row)
580{
581 m_CatalogObjectUI.tableView->selectRow(row);
582 QModelIndex index = m_CatalogObjectUI.tableView->indexAt(QPoint(row, CAT_NUM));
583 if (index.isValid())
584 m_CatalogObjectUI.tableView->scrollTo(index, QAbstractItemView::EnsureVisible);
585}
586
587void FITSTab::catHighlightChanged(const int highlight)
588{
589 if (!m_View)
590 return;
591 const QSharedPointer<FITSData> &imageData = m_View->imageData();
592 if (!imageData)
593 return;
594 QList<CatObject> catObjects = imageData->getCatObjects();
595
596 if (highlight < 0 || highlight >= catObjects.size())
597 return;
598
599 int num = catObjects[highlight].num;
600 for (int i = 0; i < m_CatObjModel.rowCount(); i++)
601 {
602 bool ok;
603 QStandardItem *itm = m_CatObjModel.item(i, CAT_NUM);
604 if (itm->text().toInt(&ok) == num)
605 {
606 int itmRow = itm->row();
607 QModelIndex currentIndex = m_CatalogObjectUI.tableView->currentIndex();
608 if (currentIndex.isValid())
609 {
610 int currentRow = m_CatObjModel.itemFromIndex(currentIndex)->row();
611 if (currentRow == itmRow)
612 // Row to highlight is already highlighted - so nothing to do
613 break;
614 }
615 // Set the new highlight to the new row
616 catHighlightRow(itmRow);
617 }
618 }
619}
620
621void FITSTab::catRowChanged(const QModelIndex &current, const QModelIndex &previous)
622{
623 if (!m_View)
624 return;
625 const QSharedPointer<FITSData> &imageData = m_View->imageData();
626 if (!imageData)
627 return;
628
629 // Get the "Num" of the selected (current) row
630 int selNum = -1, deselNum = -1;
631 bool ok;
632 QStandardItem * selItem = m_CatObjModel.itemFromIndex(current);
633 if (selItem)
634 selNum = m_CatObjModel.item(selItem->row(), CAT_NUM)->text().toInt(&ok);
635
636 // Get the "Num" of the deselected (previous) row
637 QStandardItem * deselItem = m_CatObjModel.itemFromIndex(previous);
638 if (deselItem)
639 deselNum = m_CatObjModel.item(deselItem->row(), CAT_NUM)->text().toInt(&ok);
640
641 if (selNum >= 0)
642 {
643 imageData->highlightCatObject(selNum - 1, deselNum - 1);
644 m_View->updateFrame();
645 }
646}
647
648void FITSTab::catCellDoubleClicked(const QModelIndex &index)
649{
650 QStandardItem * item = m_CatObjModel.itemFromIndex(index);
651 int row = item->row();
652 int col = item->column();
653
654 QString catObjectName = m_CatObjModel.item(row, CAT_OBJECT)->text();
655
656 if (col == CAT_CDSPORTAL)
657 launchCDS(catObjectName);
658 else if (col == CAT_SIMBAD)
659 launchSimbad(catObjectName);
660 else if (col == CAT_NED)
661 launchNED(catObjectName);
662 else if (col == CAT_TYPECODE || col == CAT_TYPELABEL)
663 launchCatTypeFilterDialog();
664}
665
666void FITSTab::launchCatTypeFilterDialog()
667{
668 m_CatObjTypeFilterDialog->show();
669 m_CatObjTypeFilterDialog->raise();
670}
671
672void FITSTab::showCatObjNames(bool enabled)
673{
674 if (!m_View)
675 return;
676 const QSharedPointer<FITSData> &imageData = m_View->imageData();
677 if (!imageData)
678 return;
679
680 Options::setFitsCatObjShowNames(enabled);
681 m_View->updateFrame();
682}
683
684void FITSTab::launchSimbad(QString name)
685{
686 QUrl url = QUrl("https://simbad.u-strasbg.fr/simbad/sim-id");
687 QString ident = QString("Ident=%1").arg(name);
688 ident.replace("+", "%2B");
689 ident.remove(QRegularExpression("[\\[\\]]+"));
690 url.setQuery(ident);
691
693 qCDebug(KSTARS_FITS) << "Unable to open Simbad url:" << url;
694}
695
696void FITSTab::launchCDS(QString name)
697{
698 QUrl url = QUrl("https://cdsportal.u-strasbg.fr/");
699 QString ident = QString("target=%1").arg(name);
700 ident.replace("+", "%2B");
701 ident.remove(QRegularExpression("[\\[\\]]+"));
702 url.setQuery(ident);
703
705 qCDebug(KSTARS_FITS) << "Unable to open CDS Portal url:" << url;
706}
707
708void FITSTab::launchNED(QString name)
709{
710 QUrl url = QUrl("https://ned.ipac.caltech.edu/cgi-bin/objsearch");
711 QString query = QString("objname=%1").arg(name);
712 query.replace("+", "%2B");
713 query.remove(QRegularExpression("[\\[\\]]+"));
714 url.setQuery(query);
715
717 qCDebug(KSTARS_FITS) << "Unable to open NED url" << url;
718}
719
720void FITSTab::initCatalogObject()
721{
722 // Setup MVC
723 m_CatalogObjectUI.tableView->setModel(&m_CatObjModel);
724
725 // Set the column headers
726 QStringList Headers { i18n("Num"),
727 i18n("CDS Portal"),
728 i18n("Simbad"),
729 i18n("NED"),
730 i18n("Object"),
731 i18n("Type Code"),
732 i18n("Type Label"),
733 i18n("Coordinates"),
734 i18n("Distance"),
735 i18n("Magnitude"),
736 i18n("Size") };
737 m_CatObjModel.setColumnCount(CAT_MAX_COLS);
738 m_CatObjModel.setHorizontalHeaderLabels(Headers);
739
740 // Setup tooltips on column headers
741 m_CatObjModel.setHeaderData(CAT_NUM, Qt::Horizontal, i18n("Item reference number"), Qt::ToolTipRole);
742 m_CatObjModel.setHeaderData(CAT_CDSPORTAL, Qt::Horizontal, i18n("Double click to launch CDS Portal browser"),
744 m_CatObjModel.setHeaderData(CAT_SIMBAD, Qt::Horizontal, i18n("Double click to launch Simbad browser"), Qt::ToolTipRole);
745 m_CatObjModel.setHeaderData(CAT_NED, Qt::Horizontal, i18n("Double click to launch NED browser"), Qt::ToolTipRole);
746 m_CatObjModel.setHeaderData(CAT_OBJECT, Qt::Horizontal, i18n("Catalog Object"), Qt::ToolTipRole);
747 m_CatObjModel.setHeaderData(CAT_TYPECODE, Qt::Horizontal,
748 i18n("Object Type Code. Double click to launch Object Type Filter.\n\nSee https://simbad.cds.unistra.fr/guide/otypes.htx for more details"),
750 m_CatObjModel.setHeaderData(CAT_TYPELABEL, Qt::Horizontal,
751 i18n("Object Type Label. Double click to launch Object Type Filter.\n\nSee https://simbad.cds.unistra.fr/guide/otypes.htx for more details"),
753 m_CatObjModel.setHeaderData(CAT_COORDS, Qt::Horizontal, i18n("Object coordinates"), Qt::ToolTipRole);
754 m_CatObjModel.setHeaderData(CAT_DISTANCE, Qt::Horizontal, i18n("Object distance in arcmins from the search center"),
756 m_CatObjModel.setHeaderData(CAT_MAGNITUDE, Qt::Horizontal, i18n("Object V magnitude"), Qt::ToolTipRole);
757 m_CatObjModel.setHeaderData(CAT_SIZE, Qt::Horizontal, i18n("Object major coordinate size in arcsmins"), Qt::ToolTipRole);
758
759 // Setup delegates for each column
760 QStyledItemDelegate *delegate = new QStyledItemDelegate(m_CatalogObjectUI.tableView);
761 m_CatalogObjectUI.tableView->setItemDelegateForColumn(CAT_NUM, delegate);
762 m_CatalogObjectUI.tableView->setItemDelegateForColumn(CAT_CDSPORTAL, delegate);
763 m_CatalogObjectUI.tableView->setItemDelegateForColumn(CAT_SIMBAD, delegate);
764 m_CatalogObjectUI.tableView->setItemDelegateForColumn(CAT_NED, delegate);
765 m_CatalogObjectUI.tableView->setItemDelegateForColumn(CAT_OBJECT, delegate);
766 m_CatalogObjectUI.tableView->setItemDelegateForColumn(CAT_TYPECODE, delegate);
767 m_CatalogObjectUI.tableView->setItemDelegateForColumn(CAT_TYPELABEL, delegate);
768 m_CatalogObjectUI.tableView->setItemDelegateForColumn(CAT_COORDS, delegate);
769 m_CatalogObjectUI.tableView->setItemDelegateForColumn(CAT_DISTANCE, delegate);
770 m_CatalogObjectUI.tableView->setItemDelegateForColumn(CAT_MAGNITUDE, delegate);
771 m_CatalogObjectUI.tableView->setItemDelegateForColumn(CAT_SIZE, delegate);
772
773 m_CatalogObjectUI.tableView->setAutoScroll(true);
774
775 connect(m_View.get(), &FITSView::catLoaded, this, &FITSTab::loadCatalogObjects);
776 connect(m_View.get(), &FITSView::catQueried, this, &FITSTab::queriedCatalogObjects);
777 connect(m_View.get(), &FITSView::catQueryFailed, this, &FITSTab::catQueryFailed);
778 connect(m_View.get(), &FITSView::catReset, this, &FITSTab::catReset);
779 connect(m_View.get(), &FITSView::catHighlightChanged, this, &FITSTab::catHighlightChanged);
780 connect(m_CatalogObjectUI.tableView->selectionModel(), &QItemSelectionModel::currentRowChanged, this,
781 &FITSTab::catRowChanged);
782 connect(m_CatalogObjectUI.tableView, &QAbstractItemView::doubleClicked, this, &FITSTab::catCellDoubleClicked);
783 connect(m_CatalogObjectUI.filterPB, &QPushButton::clicked, this, &FITSTab::launchCatTypeFilterDialog);
784
785 // Setup the Object Type filter popup
786 setupCatObjTypeFilter();
787
788 // Set the Show Names checkbox from Options
789 m_CatalogObjectUI.kcfg_FitsCatObjShowNames->setChecked(Options::fitsCatObjShowNames());
790 connect(m_CatalogObjectUI.kcfg_FitsCatObjShowNames, &QCheckBox::toggled, this, &FITSTab::showCatObjNames);
791}
792
793void FITSTab::setupCatObjTypeFilter()
794{
795 // Setup the dialog box
796 m_CatObjTypeFilterDialog = new QDialog(this);
797 m_CatObjTypeFilterUI.setupUi(m_CatObjTypeFilterDialog);
798#ifdef Q_OS_MACOS
799 m_CatObjTypeFilterDialog->setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
800#endif
801
802 // Setup the buttons
803 QPushButton *applyButton = m_CatObjTypeFilterUI.buttonBox->button(QDialogButtonBox::Apply);
804 connect(applyButton, &QPushButton::clicked, this, &FITSTab::applyTypeFilter);
805 m_CheckAllButton = m_CatObjTypeFilterUI.buttonBox->addButton("Check All", QDialogButtonBox::ActionRole);
806 connect(m_CheckAllButton, &QPushButton::clicked, this, &FITSTab::checkAllTypeFilter);
807 m_UncheckAllButton = m_CatObjTypeFilterUI.buttonBox->addButton("Uncheck All", QDialogButtonBox::ActionRole);
808 connect(m_UncheckAllButton, &QPushButton::clicked, this, &FITSTab::uncheckAllTypeFilter);
809
810 // Setup the tree
811 QTreeWidgetItem * treeItem[CAT_OBJ_MAX_DEPTH];
812 for (int i = 0; i < MAX_CAT_OBJ_TYPES; i++)
813 {
814 const int depth = catObjTypes[i].depth;
815 if (depth < 0 || depth >= MAX_CAT_OBJ_TYPES)
816 continue;
817
818 if (depth == 0)
819 // Top level node
820 treeItem[depth] = new QTreeWidgetItem(m_CatObjTypeFilterUI.tree);
821 else
822 // Child node
823 treeItem[depth] = new QTreeWidgetItem(treeItem[depth - 1]);
824
825 treeItem[depth]->setCheckState(CATTYPE_CODE, Qt::Unchecked);
826 treeItem[depth]->setText(CATTYPE_CODE, catObjTypes[i].code);
827 treeItem[depth]->setText(CATTYPE_CANDCODE, catObjTypes[i].candidateCode);
828 treeItem[depth]->setText(CATTYPE_LABEL, catObjTypes[i].label);
829 treeItem[depth]->setText(CATTYPE_DESCRIPTION, catObjTypes[i].description);
830 treeItem[depth]->setText(CATTYPE_COMMENTS, catObjTypes[i].comments);
831 }
832 m_CatObjTypeFilterUI.tree->expandAll();
833 for (int i = 0; i < CAT_OBJ_MAX_DEPTH; i++)
834 {
835 m_CatObjTypeFilterUI.tree->resizeColumnToContents(i);
836 }
837
838 connect(m_CatObjTypeFilterUI.tree, &QTreeWidget::itemChanged, this, &FITSTab::typeFilterItemChanged);
839}
840
841void FITSTab::applyTypeFilter()
842{
843 if (!m_View)
844 return;
845 const QSharedPointer<FITSData> &imageData = m_View->imageData();
846 if (!imageData)
847 return;
848
849 QStringList filters;
850 QList<QTreeWidgetItem *> items = m_CatObjTypeFilterUI.tree->findItems("*",
852 for (auto item : items)
853 {
854 if (item->checkState(0) == Qt::Checked)
855 {
856 if (!item->text(0).isEmpty())
857 filters.push_back(item->text(0));
858 if (!item->text(1).isEmpty())
859 filters.push_back(item->text(1));
860 }
861 }
862
863 // Store the new filter paradigm
864 m_View->imageData()->setCatObjectsFilters(filters);
865 // Process the data according to the new filter paradigm
866 m_View->imageData()->filterCatObjects();
867 m_View->updateFrame();
868 // Reload FITSTab
869 loadCatalogObjects();
870}
871
872void FITSTab::checkAllTypeFilter()
873{
874 QList<QTreeWidgetItem *> items = m_CatObjTypeFilterUI.tree->findItems("*",
876 for (auto item : items)
877 {
878 item->setCheckState(0, Qt::Checked);
879 }
880}
881
882void FITSTab::uncheckAllTypeFilter()
883{
884 QList<QTreeWidgetItem *> items = m_CatObjTypeFilterUI.tree->findItems("*",
886 for (auto item : items)
887 {
889 }
890}
891
892void FITSTab::typeFilterItemChanged(QTreeWidgetItem *item, int column)
893{
894 if (column != 0)
895 return;
896
897 Qt::CheckState check = item->checkState(column);
898 for (int i = 0; i < item->childCount(); i++)
899 {
900 QTreeWidgetItem *child = item->child(i);
901 child->setCheckState(column, check);
902 }
903}
904
905void FITSTab::headerFITS()
906{
907 fitsTools->setCurrentIndex(2);
908 if(m_View->width() > 200)
909 fitsSplitter->setSizes(QList<int>() << 200 << m_View->width() - 200);
910 else
911 fitsSplitter->setSizes(QList<int>() << 50 << 50);
912}
913
914bool FITSTab::saveFile()
915{
916 QUrl backupCurrent = currentURL;
917 QUrl currentDir(Options::fitsDir());
918 currentDir.setScheme("file");
919
920 if (currentURL.toLocalFile().startsWith(QLatin1String("/tmp/")) || currentURL.toLocalFile().contains("/Temp"))
921 currentURL.clear();
922
923 // If no changes made, return.
924 if (mDirty == false && !currentURL.isEmpty())
925 return false;
926
927 if (currentURL.isEmpty())
928 {
929 QString selectedFilter;
930#ifdef Q_OS_MACOS //For some reason, the other code caused KStars to crash on MacOS
931 currentURL =
932 QFileDialog::getSaveFileUrl(KStars::Instance(), i18nc("@title:window", "Save FITS"), currentDir,
933 "Images (*.fits *.fits.gz *.fit *.xisf *.jpg *.jpeg *.png)");
934#else
935 currentURL =
936 QFileDialog::getSaveFileUrl(KStars::Instance(), i18nc("@title:window", "Save FITS"), currentDir,
937 "FITS (*.fits *.fits.gz *.fit);;XISF (*.xisf);;JPEG (*.jpg *.jpeg);;PNG (*.png)", &selectedFilter);
938#endif
939 // if user presses cancel
940 if (currentURL.isEmpty())
941 {
942 currentURL = backupCurrent;
943 return false;
944 }
945
946 // If no extension is selected append one
947 if (currentURL.toLocalFile().contains('.') == 0)
948 {
949 if (selectedFilter.contains("XISF"))
950 currentURL.setPath(currentURL.toLocalFile() + ".xisf");
951 else if (selectedFilter.contains("JPEG"))
952 currentURL.setPath(currentURL.toLocalFile() + ".jpg");
953 else if (selectedFilter.contains("PNG"))
954 currentURL.setPath(currentURL.toLocalFile() + ".png");
955 else
956 currentURL.setPath(currentURL.toLocalFile() + ".fits");
957 }
958 }
959
960 if (currentURL.isValid())
961 {
962 QString localFile = currentURL.toLocalFile();
963 // if (localFile.contains(".fit"))
964 // localFile = "!" + localFile;
965
966 if (!saveImage(localFile))
967 {
968 KSNotification::error(i18n("Image save error: %1", m_View->imageData()->getLastError()), i18n("Image Save"));
969 return false;
970 }
971
972 emit newStatus(i18n("File saved to %1", currentURL.url()), FITS_MESSAGE);
973 modifyFITSState();
974 return true;
975 }
976 else
977 {
978 QString message = i18n("Invalid URL: %1", currentURL.url());
979 KSNotification::sorry(message, i18n("Invalid URL"));
980 return false;
981 }
982}
983
984bool FITSTab::saveFileAs()
985{
986 currentURL.clear();
987 return saveFile();
988}
989
990void FITSTab::ZoomIn()
991{
992 QPoint oldCenter = m_View->getImagePoint(m_View->viewport()->rect().center());
993 m_View->ZoomIn();
994 m_View->cleanUpZoom(oldCenter);
995}
996
997void FITSTab::ZoomOut()
998{
999 QPoint oldCenter = m_View->getImagePoint(m_View->viewport()->rect().center());
1000 m_View->ZoomOut();
1001 m_View->cleanUpZoom(oldCenter);
1002}
1003
1004void FITSTab::ZoomDefault()
1005{
1006 QPoint oldCenter = m_View->getImagePoint(m_View->viewport()->rect().center());
1007 m_View->ZoomDefault();
1008 m_View->cleanUpZoom(oldCenter);
1009}
1010
1011void FITSTab::tabPositionUpdated()
1012{
1013 undoStack->setActive(true);
1014 m_View->emitZoom();
1015 emit newStatus(QString("%1x%2").arg(m_View->imageData()->width()).arg(m_View->imageData()->height()),
1016 FITS_RESOLUTION);
1017}
1018
1019void FITSTab::setStretchValues(double shadows, double midtones, double highlights)
1020{
1021 if (stretchUI)
1022 stretchUI->setStretchValues(shadows, midtones, highlights);
1023}
1024
1025void FITSTab::setAutoStretch()
1026{
1027 if (!m_View->getAutoStretch())
1028 m_View->setAutoStretchParams();
1029}
1030
1031void FITSTab::extractImage()
1032{
1033 connect(m_PlateSolve.get(), &PlateSolve::extractorSuccess, this, [this]()
1034 {
1035 m_View->updateFrame();
1036 m_PlateSolve->solveImage(m_View->imageData());
1037 });
1038 connect(m_PlateSolve.get(), &PlateSolve::extractorFailed, this, [this]()
1039 {
1040 disconnect(m_PlateSolve.get());
1041 });
1042 connect(m_PlateSolve.get(), &PlateSolve::solverFailed, this, [this]()
1043 {
1044 disconnect(m_PlateSolve.get());
1045 });
1046 connect(m_PlateSolve.get(), &PlateSolve::solverSuccess, this, [this]()
1047 {
1048 m_View->syncWCSState();
1049 if (m_View->areObjectsShown())
1050 // Requery Objects based on new plate solved solution
1051 m_View->imageData()->searchObjects();
1052 disconnect(m_PlateSolve.get());
1053 });
1054 m_PlateSolve->extractImage(m_View->imageData());
1055}
1056
Primary window to view monochrome and color FITS images.
Definition fitsviewer.h:54
static KStars * Instance()
Definition kstars.h:122
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
ButtonCode warningContinueCancel(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
KGuiItem save()
KGuiItem discard()
void clicked(bool checked)
void toggled(bool checked)
void doubleClicked(const QModelIndex &index)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
void setImage(const QImage &image, Mode mode)
bool openUrl(const QUrl &url)
QString tempPath()
void accept()
void ignore()
QUrl getSaveFileUrl(QWidget *parent, const QString &caption, const QUrl &dir, const QString &filter, QString *selectedFilter, Options options, const QStringList &supportedSchemes)
bool exists() const const
QClipboard * clipboard()
void currentRowChanged(const QModelIndex &current, const QModelIndex &previous)
void push_back(parameter_type value)
void remove(qsizetype i, qsizetype n)
void replace(qsizetype i, parameter_type value)
qsizetype size() const const
void currentRowChanged(int currentRow)
bool isValid() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
int depth() const const
void setWidget(QWidget *widget)
void setWidgetResizable(bool resizable)
void splitterMoved(int pos, int index)
Qt::CheckState checkState() const const
int column() const const
int row() const const
void setCheckState(Qt::CheckState state)
virtual void setData(const QVariant &value, int role)
void setTextAlignment(Qt::Alignment alignment)
QString text() const const
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)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
int toInt(bool *ok, int base) const const
QByteArray toLatin1() const const
AlignHCenter
KeepAspectRatio
Unchecked
UserRole
ItemIsSelectable
MatchExactly
Horizontal
SmoothTransformation
void setFlags(Qt::ItemFlags flags)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void itemChanged(QTreeWidgetItem *item, int column)
Qt::CheckState checkState(int column) const const
QTreeWidgetItem * child(int index) const const
int childCount() const const
void setCheckState(int column, Qt::CheckState state)
void setText(int column, const QString &text)
QUrl fromLocalFile(const QString &localFile)
bool isEmpty() const const
bool isValid() const const
void setQuery(const QString &query, ParsingMode mode)
QString toLocalFile() const const
QString toString(FormattingOptions options) const const
QWidget(QWidget *parent, Qt::WindowFlags f)
void setLayout(QLayout *layout)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 25 2025 11:58:36 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.