File indexing completed on 2025-09-14 03:43:28

0001 /*
0002     File                 : FITSHeaderEditWidget.cpp
0003     Project              : LabPlot
0004     Description          : Widget for listing/editing FITS header keywords
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2016-2017 Fabian Kristof <fkristofszabolcs@gmail.com>
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "FITSHeaderEditWidget.h"
0011 #include "FITSHeaderEditAddUnitDialog.h"
0012 #include "FITSHeaderEditNewKeywordDialog.h"
0013 #include "backend/core/Settings.h"
0014 #include "backend/datasources/filters/FITSFilter.h"
0015 #include "backend/lib/macros.h"
0016 #include "ui_fitsheadereditwidget.h"
0017 
0018 #include <KConfigGroup>
0019 #include <KMessageBox>
0020 
0021 #include <kcoreaddons_version.h>
0022 
0023 #include <QContextMenuEvent>
0024 #include <QFileDialog>
0025 #include <QMenu>
0026 #include <QPushButton>
0027 #include <QTableWidget>
0028 
0029 /*! \class FITSHeaderEditWidget
0030  * \brief Widget for listing/editing FITS header keywords
0031  * \since 2.4.0
0032  * \ingroup kdefrontend/widgets
0033  */
0034 FITSHeaderEditWidget::FITSHeaderEditWidget(QWidget* parent)
0035     : QWidget(parent)
0036     , ui(new Ui::FITSHeaderEditWidget())
0037     , m_fitsFilter(new FITSFilter()) {
0038     ui->setupUi(this);
0039     initActions();
0040     connectActions();
0041     initContextMenus();
0042 
0043     ui->bOpen->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
0044 
0045     ui->bAddKey->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
0046     ui->bAddKey->setEnabled(false);
0047     ui->bAddKey->setToolTip(i18n("Add new keyword"));
0048 
0049     ui->bRemoveKey->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
0050     ui->bRemoveKey->setEnabled(false);
0051     ui->bRemoveKey->setToolTip(i18n("Remove selected keyword"));
0052 
0053     ui->bAddUnit->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
0054     ui->bAddUnit->setEnabled(false);
0055     ui->bAddUnit->setToolTip(i18n("Add unit to keyword"));
0056 
0057     ui->bClose->setIcon(QIcon::fromTheme(QStringLiteral("document-close")));
0058     ui->bClose->setEnabled(false);
0059     ui->bClose->setToolTip(i18n("Close file"));
0060 
0061     ui->twKeywordsTable->setColumnCount(3);
0062     ui->twExtensions->setSelectionMode(QAbstractItemView::SingleSelection);
0063     ui->twExtensions->headerItem()->setText(0, i18n("Content"));
0064     ui->twKeywordsTable->setHorizontalHeaderItem(0, new QTableWidgetItem(i18n("Key")));
0065     ui->twKeywordsTable->setHorizontalHeaderItem(1, new QTableWidgetItem(i18n("Value")));
0066     ui->twKeywordsTable->setHorizontalHeaderItem(2, new QTableWidgetItem(i18n("Comment")));
0067     ui->twKeywordsTable->setAlternatingRowColors(true);
0068     ui->twKeywordsTable->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents);
0069     ui->twKeywordsTable->horizontalHeader()->setStretchLastSection(true);
0070     ui->twKeywordsTable->installEventFilter(this);
0071     ui->twExtensions->installEventFilter(this);
0072 
0073     setAttribute(Qt::WA_DeleteOnClose);
0074 
0075     connect(ui->bAddUnit, &QPushButton::clicked, m_actionAddmodifyUnit, &QAction::triggered);
0076     connect(ui->bClose, &QPushButton::clicked, this, &FITSHeaderEditWidget::closeFile);
0077     connect(ui->bOpen, &QPushButton::clicked, this, &FITSHeaderEditWidget::openFile);
0078     connect(ui->bAddKey, &QPushButton::clicked, this, &FITSHeaderEditWidget::addKeyword);
0079     connect(ui->bRemoveKey, &QPushButton::clicked, this, &FITSHeaderEditWidget::removeKeyword);
0080     connect(ui->twKeywordsTable, &QTableWidget::itemClicked, this, &FITSHeaderEditWidget::enableButtonAddUnit);
0081     connect(ui->twKeywordsTable, &QTableWidget::itemChanged, this, &FITSHeaderEditWidget::updateKeyword);
0082     connect(ui->twExtensions, &QTreeWidget::itemClicked, this, &FITSHeaderEditWidget::fillTableSlot);
0083     connect(ui->twExtensions, &QTreeWidget::itemClicked, this, &FITSHeaderEditWidget::enableButtonCloseFile);
0084 }
0085 
0086 /*!
0087  * \brief Destructor
0088  */
0089 FITSHeaderEditWidget::~FITSHeaderEditWidget() {
0090     delete m_fitsFilter;
0091     delete ui;
0092 }
0093 
0094 /*!
0095  * \brief Fills the keywords tablewidget.
0096  * If the selected extension was not yet selected before, then the keywords are read from the file
0097  * and then the table is filled, otherwise the table is filled using the already existing keywords.
0098  */
0099 void FITSHeaderEditWidget::fillTable() {
0100     m_initializingTable = true;
0101     if (!m_extensionData.contains(m_seletedExtension)) {
0102         m_extensionData[m_seletedExtension].keywords = m_fitsFilter->chduKeywords(m_seletedExtension);
0103         m_extensionData[m_seletedExtension].updates.updatedKeywords.reserve(m_extensionData[m_seletedExtension].keywords.size());
0104         m_extensionData[m_seletedExtension].updates.updatedKeywords.resize(m_extensionData[m_seletedExtension].keywords.size());
0105 
0106         m_fitsFilter->parseHeader(m_seletedExtension, ui->twKeywordsTable);
0107     } else {
0108         QList<FITSFilter::Keyword> keywords = m_extensionData[m_seletedExtension].keywords;
0109         for (int i = 0; i < m_extensionData[m_seletedExtension].updates.updatedKeywords.size(); ++i) {
0110             FITSFilter::Keyword keyword = m_extensionData[m_seletedExtension].updates.updatedKeywords.at(i);
0111             if (!keyword.key.isEmpty())
0112                 keywords.operator[](i).key = keyword.key;
0113             if (!keyword.value.isEmpty())
0114                 keywords.operator[](i).value = keyword.value;
0115             if (!keyword.comment.isEmpty())
0116                 keywords.operator[](i).comment = keyword.comment;
0117         }
0118         for (const FITSFilter::Keyword& key : m_extensionData[m_seletedExtension].updates.newKeywords)
0119             keywords.append(key);
0120         m_fitsFilter->parseHeader(QString(), ui->twKeywordsTable, false, keywords);
0121     }
0122     m_initializingTable = false;
0123 }
0124 
0125 /*!
0126  * \brief Fills the tablewidget with the keywords of extension \a item
0127  * \param item the extension selected
0128  * \param col the column of the selected item
0129  */
0130 void FITSHeaderEditWidget::fillTableSlot(QTreeWidgetItem* item, int col) {
0131     WAIT_CURSOR;
0132     const QString& itemText = item->text(col);
0133     QString selectedExtension;
0134     int extType = 0;
0135     if (itemText.contains(QLatin1String("IMAGE #")) || itemText.contains(QLatin1String("ASCII_TBL #")) || itemText.contains(QLatin1String("BINARY_TBL #")))
0136         extType = 1;
0137     else if (!itemText.compare(QLatin1String("Primary header")))
0138         extType = 2;
0139     if (extType == 0) {
0140         if (item->parent() != nullptr) {
0141             if (item->parent()->parent() != nullptr)
0142                 selectedExtension = item->parent()->parent()->text(0) + QStringLiteral("[") + item->text(col) + QStringLiteral("]");
0143         }
0144     } else if (extType == 1) {
0145         if (item->parent() != nullptr) {
0146             if (item->parent()->parent() != nullptr) {
0147                 bool ok;
0148                 int hduNum = itemText.right(1).toInt(&ok);
0149                 selectedExtension = item->parent()->parent()->text(0) + QStringLiteral("[") + QString::number(hduNum - 1) + QStringLiteral("]");
0150             }
0151         }
0152     } else {
0153         if (item->parent()->parent() != nullptr)
0154             selectedExtension = item->parent()->parent()->text(col);
0155     }
0156 
0157     if (!selectedExtension.isEmpty()) {
0158         if (!(m_seletedExtension == selectedExtension)) {
0159             m_seletedExtension = selectedExtension;
0160             fillTable();
0161         }
0162     }
0163     RESET_CURSOR;
0164 }
0165 
0166 /*!
0167  * \brief Shows a dialog for opening a FITS file
0168  * If the returned file name is not empty (so a FITS file was selected) and it's not opened yet
0169  * then the file is parsed, so the treeview for the extensions is built and the table is filled.
0170  */
0171 void FITSHeaderEditWidget::openFile() {
0172     KConfigGroup conf = Settings::group(QStringLiteral("FITSHeaderEditWidget"));
0173     QString dir = conf.readEntry("LastDir", "");
0174     QString fileName = QFileDialog::getOpenFileName(this, i18nc("@title:window", "Open FITS File"), dir, i18n("FITS files (*.fits *.fit *.fts)"));
0175     if (fileName.isEmpty())
0176         return;
0177 
0178     int pos = fileName.lastIndexOf(QLatin1String("/"));
0179     if (pos != -1) {
0180         QString newDir = fileName.left(pos);
0181         if (newDir != dir)
0182             conf.writeEntry("LastDir", newDir);
0183     }
0184 
0185     WAIT_CURSOR;
0186     QTreeWidgetItem* root = ui->twExtensions->invisibleRootItem();
0187     const int childCount = root->childCount();
0188     bool opened = false;
0189     for (int i = 0; i < childCount; ++i) {
0190         if (root->child(i)->text(0) == fileName) {
0191             opened = true;
0192             break;
0193         }
0194     }
0195     if (!opened) {
0196         for (QTreeWidgetItem* item : ui->twExtensions->selectedItems())
0197             item->setSelected(false);
0198         m_fitsFilter->parseExtensions(fileName, ui->twExtensions);
0199         ui->twExtensions->resizeColumnToContents(0);
0200         if (ui->twExtensions->selectedItems().size() > 0)
0201             fillTableSlot(ui->twExtensions->selectedItems().at(0), 0);
0202 
0203         ui->bAddKey->setEnabled(true);
0204         ui->bRemoveKey->setEnabled(true);
0205         ui->bAddUnit->setEnabled(true);
0206         ui->bClose->setEnabled(false);
0207 
0208     } else {
0209         KMessageBox::information(this, i18n("Cannot open file, file already opened."), i18n("File already opened"));
0210     }
0211     enableButtonAddUnit();
0212     RESET_CURSOR;
0213 }
0214 
0215 /*!
0216  * \brief Triggered when clicking the Save button
0217  * Saves the modifications (new keywords, new keyword units, keyword modifications,
0218  * deleted keywords, deleted extensions) to the FITS files.
0219  * \return \c true if there was something saved, otherwise false
0220  */
0221 bool FITSHeaderEditWidget::save() {
0222     bool saved = false;
0223 
0224     QMap<QString, ExtensionData>::const_iterator it = m_extensionData.constBegin();
0225     while (it != m_extensionData.constEnd()) {
0226         const QString& fileName = it.key();
0227         const auto& data = it.value();
0228         if (data.updates.newKeywords.size() > 0) {
0229             m_fitsFilter->addNewKeyword(fileName, data.updates.newKeywords);
0230             if (!saved)
0231                 saved = true;
0232         }
0233         if (data.updates.removedKeywords.size() > 0) {
0234             m_fitsFilter->deleteKeyword(fileName, data.updates.removedKeywords);
0235             if (!saved)
0236                 saved = true;
0237         }
0238         if (!saved) {
0239             for (const FITSFilter::Keyword& key : data.updates.updatedKeywords) {
0240                 if (!key.isEmpty()) {
0241                     saved = true;
0242                     break;
0243                 }
0244             }
0245         }
0246 
0247         m_fitsFilter->updateKeywords(fileName, data.keywords, data.updates.updatedKeywords);
0248         m_fitsFilter->addKeywordUnit(fileName, data.keywords);
0249         m_fitsFilter->addKeywordUnit(fileName, data.updates.newKeywords);
0250 
0251         ++it;
0252     }
0253 
0254     if (m_removedExtensions.size() > 0) {
0255         m_fitsFilter->removeExtensions(m_removedExtensions);
0256         if (!saved)
0257             saved = true;
0258     }
0259     if (saved) {
0260         // to reset the window title
0261         Q_EMIT changed(false);
0262     }
0263 
0264     return saved;
0265 }
0266 
0267 /*!
0268  * \brief Initializes the context menu's actions.
0269  */
0270 void FITSHeaderEditWidget::initActions() {
0271     m_actionAddKeyword = new QAction(QIcon::fromTheme(QStringLiteral("list-add")), i18n("Add New Keyword"), this);
0272     m_actionRemoveKeyword = new QAction(QIcon::fromTheme(QStringLiteral("list-remove")), i18n("Remove Keyword"), this);
0273     m_actionRemoveExtension = new QAction(i18n("Delete"), this);
0274     m_actionAddmodifyUnit = new QAction(i18n("Add Unit"), this);
0275 }
0276 
0277 /*!
0278  * \brief Connects signals of the actions to the appropriate slots.
0279  */
0280 void FITSHeaderEditWidget::connectActions() {
0281     connect(m_actionAddKeyword, &QAction::triggered, this, [=]() {
0282         addKeyword();
0283     });
0284     connect(m_actionRemoveKeyword, &QAction::triggered, this, [=]() {
0285         removeKeyword();
0286     });
0287     connect(m_actionRemoveExtension, &QAction::triggered, this, [=]() {
0288         removeExtension();
0289     });
0290     connect(m_actionAddmodifyUnit, &QAction::triggered, this, [=]() {
0291         addModifyKeywordUnit();
0292     });
0293 }
0294 
0295 /*!
0296  * \brief Initializes the context menus.
0297  */
0298 void FITSHeaderEditWidget::initContextMenus() {
0299     m_keywordActionsMenu = new QMenu(this);
0300     m_keywordActionsMenu->addAction(m_actionAddKeyword);
0301     m_keywordActionsMenu->addAction(m_actionRemoveKeyword);
0302     m_keywordActionsMenu->addSeparator();
0303     m_keywordActionsMenu->addAction(m_actionAddmodifyUnit);
0304 
0305     m_extensionActionsMenu = new QMenu(this);
0306     m_extensionActionsMenu->addAction(m_actionRemoveExtension);
0307 }
0308 
0309 /*!
0310  * \brief Shows a FITSHeaderEditNewKeywordDialog and decides whether the new keyword provided in the dialog
0311  * can be added to the new keywords or not. Updates the tablewidget if it's needed.
0312  */
0313 void FITSHeaderEditWidget::addKeyword() {
0314     auto* newKeywordDialog = new FITSHeaderEditNewKeywordDialog;
0315     m_initializingTable = true;
0316     if (newKeywordDialog->exec() == QDialog::Accepted) {
0317         FITSFilter::Keyword newKeyWord = newKeywordDialog->newKeyword();
0318         QList<FITSFilter::Keyword> currentKeywords = m_extensionData[m_seletedExtension].keywords;
0319 
0320         for (const FITSFilter::Keyword& keyword : currentKeywords) {
0321             if (keyword.operator==(newKeyWord)) {
0322                 KMessageBox::information(this, i18n("Cannot add keyword, keyword already added"), i18n("Cannot Add Keyword"));
0323                 return;
0324             }
0325         }
0326 
0327         for (const FITSFilter::Keyword& keyword : m_extensionData[m_seletedExtension].updates.newKeywords) {
0328             if (keyword.operator==(newKeyWord)) {
0329                 KMessageBox::information(this, i18n("Cannot add keyword, keyword already added"), i18n("Cannot Add Keyword"));
0330                 return;
0331             }
0332         }
0333 
0334         for (const QString& keyword : mandatoryKeywords()) {
0335             if (!keyword.compare(newKeyWord.key)) {
0336                 KMessageBox::information(this, i18n("Cannot add mandatory keyword, they are already present"), i18n("Cannot Add Keyword"));
0337                 return;
0338             }
0339         }
0340 
0341         /*
0342         - Column related keyword (TFIELDS, TTYPEn,TFORMn, etc.) in an image
0343         - SIMPLE, EXTEND, or BLOCKED keyword in any extension
0344         - BSCALE, BZERO, BUNIT, BLANK, DATAMAX, DATAMIN keywords in a table
0345         - Keyword name contains illegal character
0346         */
0347 
0348         m_extensionData[m_seletedExtension].updates.newKeywords.append(newKeyWord);
0349 
0350         const int lastRow = ui->twKeywordsTable->rowCount();
0351         ui->twKeywordsTable->setRowCount(lastRow + 1);
0352         auto* newKeyWordItem = new QTableWidgetItem(newKeyWord.key);
0353         newKeyWordItem->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
0354         ui->twKeywordsTable->setItem(lastRow, 0, newKeyWordItem);
0355 
0356         newKeyWordItem = new QTableWidgetItem(newKeyWord.value);
0357         newKeyWordItem->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
0358         ui->twKeywordsTable->setItem(lastRow, 1, newKeyWordItem);
0359 
0360         newKeyWordItem = new QTableWidgetItem(newKeyWord.comment);
0361         newKeyWordItem->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
0362         ui->twKeywordsTable->setItem(lastRow, 2, newKeyWordItem);
0363         Q_EMIT changed(true);
0364     }
0365     m_initializingTable = false;
0366     delete newKeywordDialog;
0367 }
0368 
0369 /*!
0370  * \brief Shows a messagebox whether we want to remove the keyword or not.
0371  * Mandatory keywords cannot be deleted.
0372  */
0373 void FITSHeaderEditWidget::removeKeyword() {
0374     const int row = ui->twKeywordsTable->currentRow();
0375     if (row == -1)
0376         return;
0377 
0378     QString key = ui->twKeywordsTable->item(row, 0)->text();
0379 #if KCOREADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
0380     auto status = KMessageBox::questionTwoActions(this,
0381                                                   i18n("Are you sure you want to delete the keyword '%1'?", key),
0382                                                   i18n("Confirm Deletion"),
0383                                                   KStandardGuiItem::del(),
0384                                                   KStandardGuiItem::cancel());
0385     if (status == KMessageBox::PrimaryAction) {
0386 #else
0387     auto status = KMessageBox::questionYesNo(this, i18n("Are you sure you want to delete the keyword '%1'?", key), i18n("Confirm Deletion"));
0388     if (status == KMessageBox::Yes) {
0389 #endif
0390         bool remove = true;
0391         for (const QString& k : mandatoryKeywords()) {
0392             if (!k.compare(key)) {
0393                 remove = false;
0394                 break;
0395             }
0396         }
0397 
0398         if (remove) {
0399             FITSFilter::Keyword toRemove = FITSFilter::Keyword(key, ui->twKeywordsTable->item(row, 1)->text(), ui->twKeywordsTable->item(row, 2)->text());
0400             ui->twKeywordsTable->removeRow(row);
0401 
0402             m_extensionData[m_seletedExtension].keywords.removeAt(row);
0403             m_extensionData[m_seletedExtension].updates.removedKeywords.append(toRemove);
0404             Q_EMIT changed(true);
0405         } else
0406             KMessageBox::information(this, i18n("Cannot remove mandatory keyword."), i18n("Removing Keyword"));
0407     }
0408 
0409     enableButtonAddUnit();
0410 }
0411 
0412 /*!
0413  * \brief Trigggered when an item was updated by the user in the tablewidget
0414  * \param item the item which was updated
0415  */
0416 void FITSHeaderEditWidget::updateKeyword(QTableWidgetItem* item) {
0417     if (!m_initializingTable) {
0418         const int row = item->row();
0419         if (row < 0)
0420             return;
0421 
0422         int idx;
0423         bool fromNewKeyword = false;
0424         if (row > m_extensionData[m_seletedExtension].keywords.size() - 1) {
0425             idx = row - m_extensionData[m_seletedExtension].keywords.size();
0426             fromNewKeyword = true;
0427         } else
0428             idx = row;
0429 
0430         if (item->column() == 0) {
0431             if (!fromNewKeyword) {
0432                 m_extensionData[m_seletedExtension].updates.updatedKeywords.operator[](idx).key = item->text();
0433                 m_extensionData[m_seletedExtension].keywords.operator[](idx).updates.keyUpdated = true;
0434             } else {
0435                 m_extensionData[m_seletedExtension].updates.newKeywords.operator[](idx).key = item->text();
0436                 m_extensionData[m_seletedExtension].updates.newKeywords.operator[](idx).updates.keyUpdated = true;
0437             }
0438 
0439         } else if (item->column() == 1) {
0440             if (!fromNewKeyword) {
0441                 m_extensionData[m_seletedExtension].updates.updatedKeywords.operator[](idx).value = item->text();
0442                 m_extensionData[m_seletedExtension].keywords.operator[](idx).updates.valueUpdated = true;
0443             } else {
0444                 m_extensionData[m_seletedExtension].updates.newKeywords.operator[](idx).value = item->text();
0445                 m_extensionData[m_seletedExtension].updates.newKeywords.operator[](idx).updates.valueUpdated = true;
0446             }
0447         } else {
0448             if (!fromNewKeyword) {
0449                 m_extensionData[m_seletedExtension].updates.updatedKeywords.operator[](idx).comment = item->text();
0450                 m_extensionData[m_seletedExtension].keywords.operator[](idx).updates.commentUpdated = true;
0451             } else {
0452                 m_extensionData[m_seletedExtension].updates.newKeywords.operator[](idx).comment = item->text();
0453                 m_extensionData[m_seletedExtension].updates.newKeywords.operator[](idx).updates.commentUpdated = true;
0454             }
0455         }
0456         Q_EMIT changed(true);
0457     }
0458 }
0459 
0460 /*!
0461  * \brief Shows a FITSHeaderEditAddUnitDialog on the selected keyword (provides the keyword's unit to the
0462  * dialog if it had one) and if the dialog was accepted then the new keyword unit is set and the tablewidget
0463  * is updated (filled with the modifications).
0464  */
0465 
0466 void FITSHeaderEditWidget::addModifyKeywordUnit() {
0467     FITSHeaderEditAddUnitDialog* addUnitDialog;
0468 
0469     const int selectedRow = ui->twKeywordsTable->currentRow();
0470     int idx;
0471     bool fromNewKeyword = false;
0472     if (selectedRow > m_extensionData[m_seletedExtension].keywords.size() - 1) {
0473         idx = selectedRow - m_extensionData[m_seletedExtension].keywords.size();
0474         fromNewKeyword = true;
0475     } else
0476         idx = selectedRow;
0477 
0478     QString unit;
0479     if (fromNewKeyword) {
0480         if (!m_extensionData[m_seletedExtension].updates.newKeywords.at(idx).unit.isEmpty())
0481             unit = m_extensionData[m_seletedExtension].updates.newKeywords.at(idx).unit;
0482     } else {
0483         if (!m_extensionData[m_seletedExtension].keywords.at(idx).unit.isEmpty())
0484             unit = m_extensionData[m_seletedExtension].keywords.at(idx).unit;
0485     }
0486 
0487     addUnitDialog = new FITSHeaderEditAddUnitDialog(unit);
0488     if (addUnitDialog->exec() == QDialog::Accepted) {
0489         if (fromNewKeyword) {
0490             m_extensionData[m_seletedExtension].updates.newKeywords.operator[](idx).unit = addUnitDialog->unit();
0491             if (!m_extensionData[m_seletedExtension].updates.newKeywords.at(idx).unit.isEmpty()) {
0492                 m_extensionData[m_seletedExtension].updates.newKeywords.operator[](idx).updates.unitUpdated = true;
0493             }
0494         } else {
0495             m_extensionData[m_seletedExtension].keywords.operator[](idx).unit = addUnitDialog->unit();
0496             if (!m_extensionData[m_seletedExtension].keywords.at(idx).unit.isEmpty())
0497                 m_extensionData[m_seletedExtension].keywords.operator[](idx).updates.unitUpdated = true;
0498         }
0499         Q_EMIT changed(true);
0500         fillTable();
0501     }
0502 
0503     delete addUnitDialog;
0504 }
0505 
0506 /*!
0507  * \brief Removes the selected extension from the extensions treeview
0508  * If the last extension is removed from the tree, then the extension and the file will be removed too.
0509  */
0510 void FITSHeaderEditWidget::removeExtension() {
0511     QTreeWidgetItem* current = ui->twExtensions->currentItem();
0512     QTreeWidgetItem* newCurrent = ui->twExtensions->itemBelow(current);
0513     if (current->parent()) {
0514         if (current->parent()->childCount() < 2)
0515             delete current->parent();
0516         else
0517             delete current;
0518     }
0519     const QStringList keys = m_extensionData.keys();
0520     const int selectedidx = keys.indexOf(m_seletedExtension);
0521 
0522     if (selectedidx > 0) {
0523         const QString& ext = m_seletedExtension;
0524         m_extensionData.remove(ext);
0525         m_removedExtensions.append(ext);
0526         m_seletedExtension = keys.at(selectedidx - 1);
0527 
0528         fillTable();
0529     }
0530     ui->twExtensions->setCurrentItem(newCurrent);
0531     Q_EMIT changed(true);
0532 }
0533 
0534 /*!
0535  * \brief Returns a list of mandatory keywords according to the currently selected extension.
0536  * If the currently selected extension is an image then it returns the mandatory keywords of an image,
0537  * otherwise the mandatory keywords of a table
0538  * \return a list of mandatory keywords
0539  */
0540 QList<QString> FITSHeaderEditWidget::mandatoryKeywords() const {
0541     QList<QString> mandatoryKeywords;
0542     const QTreeWidgetItem* currentItem = ui->twExtensions->currentItem();
0543     if (currentItem->parent()->text(0).compare(QLatin1String("Images")))
0544         mandatoryKeywords = FITSFilter::mandatoryImageExtensionKeywords();
0545     else
0546         mandatoryKeywords = FITSFilter::mandatoryTableExtensionKeywords();
0547     return mandatoryKeywords;
0548 }
0549 
0550 /*!
0551  * \brief Manipulates the contextmenu event of the widget
0552  * \param watched the object on which the event occurred
0553  * \param event the event watched
0554  * \return
0555  */
0556 bool FITSHeaderEditWidget::eventFilter(QObject* watched, QEvent* event) {
0557     if (event->type() == QEvent::ContextMenu) {
0558         auto* cm_event = static_cast<QContextMenuEvent*>(event);
0559         const QPoint& global_pos = cm_event->globalPos();
0560         if (watched == ui->twKeywordsTable) {
0561             if (ui->twExtensions->selectedItems().size() != 0)
0562                 m_keywordActionsMenu->exec(global_pos);
0563         } else if (watched == ui->twExtensions) {
0564             if (ui->twExtensions->selectedItems().size() != 0) {
0565                 QTreeWidgetItem* current = ui->twExtensions->currentItem();
0566                 int col = ui->twExtensions->currentColumn();
0567                 if (current->parent()) {
0568                     if ((current->text(col) != QLatin1String("Images")) && (current->text(col) != QLatin1String("Tables")))
0569                         m_extensionActionsMenu->exec(global_pos);
0570                 }
0571             }
0572         } else
0573             return QWidget::eventFilter(watched, event);
0574         return true;
0575     } else
0576         return QWidget::eventFilter(watched, event);
0577 }
0578 
0579 void FITSHeaderEditWidget::closeFile() {
0580     if (ui->twExtensions->currentItem()) {
0581         QTreeWidgetItem* current = ui->twExtensions->currentItem();
0582 
0583         int idxOfCurrentAsTopLevel = -1;
0584         for (int i = 0; i < ui->twExtensions->topLevelItemCount(); ++i) {
0585             if (current == ui->twExtensions->topLevelItem(i)) {
0586                 idxOfCurrentAsTopLevel = i;
0587                 break;
0588             }
0589         }
0590 
0591         auto* newCurrent = (QTreeWidgetItem*)nullptr;
0592         if (idxOfCurrentAsTopLevel == 0) {
0593             if (ui->twExtensions->topLevelItemCount() == 1) {
0594                 // last file closed, deactivate action buttons, clear keywords table
0595                 ui->twKeywordsTable->setRowCount(0);
0596                 ui->bClose->setEnabled(false);
0597                 ui->bAddUnit->setEnabled(false);
0598                 ui->bAddKey->setEnabled(false);
0599                 ui->bRemoveKey->setEnabled(false);
0600             } else
0601                 newCurrent = ui->twExtensions->topLevelItem(idxOfCurrentAsTopLevel + 1);
0602         } else
0603             newCurrent = ui->twExtensions->topLevelItem(idxOfCurrentAsTopLevel - 1);
0604 
0605         if (newCurrent) {
0606             m_seletedExtension = newCurrent->text(0);
0607             fillTable();
0608         }
0609         QMap<QString, ExtensionData>::const_iterator it = m_extensionData.constBegin();
0610         while (it != m_extensionData.constEnd()) {
0611             const QString& key = it.key();
0612             if (key.startsWith(current->text(0)))
0613                 m_extensionData.remove(key);
0614             ++it;
0615         }
0616 
0617         delete current;
0618 
0619         enableButtonAddUnit();
0620         Q_EMIT changed(false);
0621     }
0622 }
0623 void FITSHeaderEditWidget::enableButtonAddUnit() {
0624     if (ui->twKeywordsTable->currentItem() != nullptr)
0625         ui->bAddUnit->setEnabled(true);
0626     else
0627         ui->bAddUnit->setEnabled(false);
0628 }
0629 
0630 void FITSHeaderEditWidget::enableButtonCloseFile(QTreeWidgetItem* item, int /*col*/) {
0631     ui->bClose->setEnabled(item->parent() ? false : true);
0632 }