File indexing completed on 2024-05-12 15:28:19

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