fitstab.cpp 24.1 KB
Newer Older
Jasem Mutlaq's avatar
Jasem Mutlaq committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/***************************************************************************
                          FITS Tab
                             -------------------
    copyright            : (C) 2012 by Jasem Mutlaq
    email                : mutlaqja@ikarustech.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

17
#include "fitstab.h"
18 19

#include "fitsdata.h"
20
#include "fitshistogram.h"
21
#include "fitsview.h"
22
#include "fitsviewer.h"
23
#include "ksnotification.h"
24
#include "kstars.h"
25
#include "Options.h"
26
#include "ui_fitsheaderdialog.h"
27 28 29
#include "ui_statform.h"

#include <KMessageBox>
30
#include <QtConcurrent>
31 32
#include <QIcon>

Hy Murveit's avatar
Hy Murveit committed
33
#include <fits_debug.h>
34

35 36
namespace
{
37 38 39 40
const char kAutoToolTip[] = "Automatically find stretch parameters";
const char kStretchOffToolTip[] = "Stretch the image";
const char kStretchOnToolTip[] = "Disable stretching of the image.";
}  // namespace
41

42
FITSTab::FITSTab(FITSViewer *parent) : QWidget(parent)
43
{
44
    viewer    = parent;
Jasem Mutlaq's avatar
Jasem Mutlaq committed
45
    undoStack = new QUndoStack(this);
46 47 48
    undoStack->setUndoLimit(10);
    undoStack->clear();
    connect(undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(modifyFITSState(bool)));
49 50 51 52

    statWidget = new QDialog(this);
    fitsHeaderDialog = new QDialog(this);
    histogram = new FITSHistogram(this);
53 54
}

55 56
FITSTab::~FITSTab()
{
57
    // Make sure it's done
58
    //histogramFuture.waitForFinished();
59
    //disconnect();
60 61
}

62 63
void FITSTab::saveUnsaved()
{
64
    if (undoStack->isClean() || view->getMode() != FITS_NORMAL)
65 66
        return;

67 68
    QString caption = i18n("Save Changes to FITS?");
    QString message = i18n("The current FITS file has unsaved changes.  Would you like to save before closing it?");
69

70
    int ans = KMessageBox::warningYesNoCancel(nullptr, message, caption, KStandardGuiItem::save(), KStandardGuiItem::discard());
71
    if (ans == KMessageBox::Yes)
72
        saveFile();
73
    if (ans == KMessageBox::No)
74 75 76 77 78 79
    {
        undoStack->clear();
        modifyFITSState();
    }
}

80
void FITSTab::closeEvent(QCloseEvent *ev)
81
{
Jasem Mutlaq's avatar
Jasem Mutlaq committed
82
    saveUnsaved();
83

84
    if (undoStack->isClean())
85 86 87 88
        ev->accept();
    else
        ev->ignore();
}
89 90 91 92 93 94 95 96 97 98
QString FITSTab::getPreviewText() const
{
    return previewText;
}

void FITSTab::setPreviewText(const QString &value)
{
    previewText = value;
}

Robert Lancaster's avatar
Robert Lancaster committed
99 100
void FITSTab::selectRecentFITS(int i)
{
101
    loadFile(QUrl::fromLocalFile(recentImages->item(i)->text()));
Robert Lancaster's avatar
Robert Lancaster committed
102 103 104 105 106 107 108 109 110
}

void FITSTab::clearRecentFITS()
{
    disconnect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS);
    recentImages->clear();
    connect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS);
}

111 112
namespace
{
113 114 115 116 117 118 119 120 121 122 123 124 125 126

// Sets the text value in the slider's value display, and if adjustSlider is true,
// moves the slider to the correct position.
void setSlider(QSlider *slider, QLabel *label, float value, float maxValue, bool adjustSlider)
{
    if (adjustSlider)
        slider->setValue(static_cast<int>(value * 10000 / maxValue));
    QString valStr = QString("%1").arg(static_cast<double>(value), 5, 'f', 4);
    label->setText(valStr);
}

// Adds the following to a horizontal layout (left to right): a vertical line,
// a label with the slider's name, a slider, and a text field to display the slider's value.
void setupStretchSlider(QSlider *slider, QLabel *label, QLabel *val, int fontSize,
127
                        const QString &name, QHBoxLayout *layout)
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
{
    QFrame* line = new QFrame();
    line->setFrameShape(QFrame::VLine);
    line->setFrameShadow(QFrame::Sunken);
    layout->addWidget(line);
    QFont font = label->font();
    font.setPointSize(fontSize);

    label->setText(name);
    label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
    label->setFont(font);
    layout->addWidget(label);
    slider->setMinimum(0);
    slider->setMaximum(10000);
    slider->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
    layout->addWidget(slider);
    val->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
    val->setFont(font);
    layout->addWidget(val);
}

// Adds a button with the icon and tooltip to the layout.
void setupStretchButton(QPushButton *button, const QString &iconName, const QString &tip, QHBoxLayout *layout)
{
    button->setIcon(QIcon::fromTheme(iconName));
    button->setIconSize(QSize(22, 22));
    button->setToolTip(tip);
    button->setCheckable(true);
    button->setChecked(true);
    button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
    layout->addWidget(button);
}

}  // namespace

// Updates all the widgets in the stretch area to display the view's stretch parameters.
void FITSTab::setStretchUIValues(bool adjustSliders)
{
    StretchParams1Channel params = view->getStretchParams().grey_red;
    setSlider(shadowsSlider.get(), shadowsVal.get(), params.shadows, maxShadows, adjustSliders);
    setSlider(midtonesSlider.get(), midtonesVal.get(), params.midtones, maxMidtones, adjustSliders);
    setSlider(highlightsSlider.get(), highlightsVal.get(), params.highlights, maxHighlights, adjustSliders);


    bool stretchActive = view->isImageStretched();
    if (stretchActive)
    {
        stretchButton->setChecked(true);
        stretchButton->setToolTip(kStretchOnToolTip);
    }
    else
    {
        stretchButton->setChecked(false);
        stretchButton->setToolTip(kStretchOffToolTip);
    }

Yuri Chornoivan's avatar
Yuri Chornoivan committed
184
    // Only activate the auto button if stretching is on and auto-stretching is not set.
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
    if (stretchActive && !view->getAutoStretch())
    {
        autoButton->setEnabled(true);
        autoButton->setIcon(QIcon::fromTheme("tools-wizard"));
        autoButton->setIconSize(QSize(22, 22));
        autoButton->setToolTip(kAutoToolTip);
    }
    else
    {
        autoButton->setEnabled(false);
        autoButton->setIcon(QIcon());
        autoButton->setIconSize(QSize(22, 22));
        autoButton->setToolTip("");
    }
    autoButton->setChecked(view->getAutoStretch());

    // Disable most of the UI if stretching is not active.
    shadowsSlider->setEnabled(stretchActive);
    shadowsVal->setEnabled(stretchActive);
    shadowsLabel->setEnabled(stretchActive);
    midtonesSlider->setEnabled(stretchActive);
    midtonesVal->setEnabled(stretchActive);
    midtonesLabel->setEnabled(stretchActive);
    highlightsSlider->setEnabled(stretchActive);
    highlightsVal->setEnabled(stretchActive);
    highlightsLabel->setEnabled(stretchActive);
}

// Adjusts the maxShadows value so that we have room to adjust the slider.
void FITSTab::rescaleShadows()
{
    if (!view) return;
    StretchParams1Channel params = view->getStretchParams().grey_red;
    maxShadows = std::max(0.002f, std::min(1.0f, params.shadows * 2.0f));
    setStretchUIValues(true);
}

// Adjusts the maxMidtones value so that we have room to adjust the slider.
void FITSTab::rescaleMidtones()
{
    if (!view) return;
    StretchParams1Channel params = view->getStretchParams().grey_red;
    maxMidtones = std::max(.002f, std::min(1.0f, params.midtones * 2.0f));
    setStretchUIValues(true);
}

QHBoxLayout* FITSTab::setupStretchBar()
{
    constexpr int fontSize = 12;

    QHBoxLayout *stretchBarLayout = new QHBoxLayout();

    stretchButton.reset(new QPushButton());
    setupStretchButton(stretchButton.get(), "transform-move", kStretchOffToolTip, stretchBarLayout);

    // Shadows
    shadowsLabel.reset(new QLabel());
    shadowsVal.reset(new QLabel());
    shadowsSlider.reset(new QSlider(Qt::Horizontal, this));
    setupStretchSlider(shadowsSlider.get(), shadowsLabel.get(), shadowsVal.get(), fontSize, "Shadows", stretchBarLayout);

    // Midtones
    midtonesLabel.reset(new QLabel());
    midtonesVal.reset(new QLabel());
    midtonesSlider.reset(new QSlider(Qt::Horizontal, this));
    setupStretchSlider(midtonesSlider.get(), midtonesLabel.get(), midtonesVal.get(), fontSize, "Midtones", stretchBarLayout);

    // Highlights
    highlightsLabel.reset(new QLabel());
    highlightsVal.reset(new QLabel());
    highlightsSlider.reset(new QSlider(Qt::Horizontal, this));
Jasem Mutlaq's avatar
Jasem Mutlaq committed
256
    setupStretchSlider(highlightsSlider.get(), highlightsLabel.get(), highlightsVal.get(), fontSize, "Highlights",
257
                       stretchBarLayout);
258 259 260 261 262 263 264 265 266 267

    // Separator
    QFrame* line4 = new QFrame();
    line4->setFrameShape(QFrame::VLine);
    line4->setFrameShadow(QFrame::Sunken);
    stretchBarLayout->addWidget(line4);

    autoButton.reset(new QPushButton());
    setupStretchButton(autoButton.get(), "tools-wizard", kAutoToolTip, stretchBarLayout);

268 269
    connect(stretchButton.get(), &QPushButton::clicked, [ = ]()
    {
270 271 272 273 274
        // This will toggle whether we're currently stretching.
        view->setStretch(!view->isImageStretched());
    });

    // Make rough displays for the slider movement.
275 276
    connect(shadowsSlider.get(), &QSlider::sliderMoved, [ = ](int value)
    {
277 278
        StretchParams params = view->getStretchParams();
        params.grey_red.shadows = this->maxShadows * value / 10000.0f;
279
        view->setPreviewSampling(Options::stretchPreviewSampling());
280
        view->setStretchParams(params);
281
        view->setPreviewSampling(0);
282 283 284
    });
    connect(midtonesSlider.get(), &QSlider::sliderMoved, [ = ](int value)
    {
285 286
        StretchParams params = view->getStretchParams();
        params.grey_red.midtones = this->maxMidtones * value / 10000.0f;
287
        view->setPreviewSampling(Options::stretchPreviewSampling());
288
        view->setStretchParams(params);
289
        view->setPreviewSampling(0);
290
    });
291 292
    connect(highlightsSlider.get(), &QSlider::sliderMoved, [ = ](int value)
    {
293 294
        StretchParams params = view->getStretchParams();
        params.grey_red.highlights = this->maxHighlights * value / 10000.0f;
295
        view->setPreviewSampling(Options::stretchPreviewSampling());
296
        view->setStretchParams(params);
297
        view->setPreviewSampling(0);
298 299 300
    });

    // Make a final full-res display when the slider is released.
301 302
    connect(shadowsSlider.get(), &QSlider::sliderReleased, [ = ]()
    {
303 304 305 306 307
        if (!view) return;
        rescaleShadows();
        StretchParams params = view->getStretchParams();
        view->setStretchParams(params);
    });
308 309
    connect(midtonesSlider.get(), &QSlider::sliderReleased, [ = ]()
    {
310 311 312 313 314
        if (!view) return;
        rescaleMidtones();
        StretchParams params = view->getStretchParams();
        view->setStretchParams(params);
    });
315 316
    connect(highlightsSlider.get(), &QSlider::sliderReleased, [ = ]()
    {
317 318 319 320 321
        if (!view) return;
        StretchParams params = view->getStretchParams();
        view->setStretchParams(params);
    });

322 323
    connect(autoButton.get(), &QPushButton::clicked, [ = ]()
    {
324 325 326 327 328 329 330 331 332 333 334 335
        // If we're not currently using automatic stretch parameters, turn that on.
        // If we're already using automatic parameters, don't do anything.
        // User can just move the sliders to take manual control.
        if (!view->getAutoStretch())
            view->setAutoStretchParams();
        else
            KMessageBox::information(this, "You are already using automatic stretching. To manually stretch, drag a slider.");
        setStretchUIValues(false);
    });

    // This is mostly useful right at the start, when the image is displayed without any user interaction.
    // Check for slider-in-use, as we don't wont to rescale while the user is active.
336 337
    connect(view.get(), &FITSView::newStatus, [ = ](const QString & ignored)
    {
338 339
        Q_UNUSED(ignored)
        bool slidersInUse = shadowsSlider->isSliderDown() || midtonesSlider->isSliderDown() ||
340
                            highlightsSlider->isSliderDown();
341 342 343 344 345 346 347 348 349 350 351
        if (!slidersInUse)
        {
            rescaleShadows();
            rescaleMidtones();
        }
        setStretchUIValues(!slidersInUse);
    });

    return stretchBarLayout;
}

352
bool FITSTab::setupView(FITSMode mode, FITSScale filter)
353
{
354
    if (view.get() == nullptr)
355
    {
356
        view.reset(new FITSView(this, mode, filter));
357
        view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
358
        QVBoxLayout *vlayout = new QVBoxLayout();
359

360
        fitsSplitter = new QSplitter(Qt::Horizontal, this);
361 362 363
        fitsTools = new QToolBox();

        stat.setupUi(statWidget);
364 365 366 367 368 369 370 371 372 373 374 375 376 377

        for (int i = 0; i <= STAT_STDDEV; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                stat.statsTable->setItem(i, j, new QTableWidgetItem());
                stat.statsTable->item(i, j)->setTextAlignment(Qt::AlignHCenter);
            }

            // Set col span for items up to HFR
            if (i <= STAT_HFR)
                stat.statsTable->setSpan(i, 0, 1, 3);
        }

378
        fitsTools->addItem(statWidget, i18n("Statistics"));
379

380
        fitsTools->addItem(histogram, i18n("Histogram"));
381 382

        header.setupUi(fitsHeaderDialog);
383
        fitsTools->addItem(fitsHeaderDialog, i18n("FITS Header"));
Robert Lancaster's avatar
Robert Lancaster committed
384 385 386 387

        QVBoxLayout *recentPanelLayout = new QVBoxLayout();
        QWidget *recentPanel = new QWidget(fitsSplitter);
        recentPanel->setLayout(recentPanelLayout);
388
        fitsTools->addItem(recentPanel, i18n("Recent Images"));
Robert Lancaster's avatar
Robert Lancaster committed
389 390 391 392 393 394 395
        recentImages = new QListWidget(recentPanel);
        recentPanelLayout->addWidget(recentImages);
        QPushButton *clearRecent = new QPushButton(i18n("Clear"));
        recentPanelLayout->addWidget(clearRecent);
        connect(clearRecent, &QPushButton::pressed, this, &FITSTab::clearRecentFITS);
        connect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS);

396 397 398 399 400 401 402 403 404 405 406 407
        QScrollArea *scrollFitsPanel = new QScrollArea(fitsSplitter);
        scrollFitsPanel->setWidgetResizable(true);
        scrollFitsPanel->setWidget(fitsTools);

        fitsSplitter->addWidget(scrollFitsPanel);
        fitsSplitter->addWidget(view.get());


        //This code allows the fitsTools to start in a closed state
        fitsSplitter->setSizes(QList<int>() << 0 << view->width() );

        vlayout->addWidget(fitsSplitter);
408
        vlayout->addLayout(setupStretchBar());
409 410 411

        connect(fitsSplitter, &QSplitter::splitterMoved, histogram, &FITSHistogram::resizePlot);

412
        setLayout(vlayout);
413 414
        connect(view.get(), &FITSView::newStatus, this, &FITSTab::newStatus);
        connect(view.get(), &FITSView::debayerToggled, this, &FITSTab::debayerToggled);
415

416 417
        // On Failure to load
        connect(view.get(), &FITSView::failed, this, &FITSTab::failed);
418

419 420 421 422 423 424 425
        return true;
    }

    // returns false if no setup needed.
    return false;
}

426
void FITSTab::loadFile(const QUrl &imageURL, FITSMode mode, FITSScale filter, bool silent)
427
{
428 429 430 431
    // check if the address points to an appropriate address
    if (imageURL.isEmpty() || !imageURL.isValid() || !QFileInfo(imageURL.toLocalFile()).exists())
        return;

432 433
    if (setupView(mode, filter))
    {
434

435
        // On Success loading image
436 437
        connect(view.get(), &FITSView::loaded, [&]()
        {
438 439 440 441
            processData();
            emit loaded();
        });
    }
442 443 444
    else
        // update tab text
        modifyFITSState(true, imageURL);
445

446
    currentURL = imageURL;
447

448
    view->setFilter(filter);
449

450
    view->loadFile(imageURL.toLocalFile(), silent);
451
}
Jasem Mutlaq's avatar
Jasem Mutlaq committed
452

453 454 455 456 457 458 459 460 461
bool FITSTab::shouldComputeHFR() const
{
    if (viewer->isStarsMarked())
        return true;
    if (!Options::autoHFR())
        return false;
    return (view != nullptr) && (view->getMode() == FITS_NORMAL);
}

462 463 464 465 466
void FITSTab::processData()
{
    FITSData *image_data = view->getImageData();
    histogram->reset();
    image_data->setHistogram(histogram);
467

468 469 470 471
    // Only construct histogram if it is actually visible
    // Otherwise wait until histogram is needed before creating it.
    if (fitsSplitter->sizes().at(0) != 0)
    {
472
        histogram->constructHistogram();
473
    }
Robert Lancaster's avatar
Robert Lancaster committed
474

475
    if (shouldComputeHFR())
Hy Murveit's avatar
Hy Murveit committed
476
    {
477
        view->searchStars();
Hy Murveit's avatar
Hy Murveit committed
478 479
        qCDebug(KSTARS_FITS) << "FITS HFR:" << image_data->getHFR();
    }
480 481 482 483 484 485 486
    // This could both compute the HFRs and setup the graphics, however,
    // if shouldComputeHFR() above is true, then that will compute the HFRs
    // and this would notice that and just setup graphics. They are separated
    // for the case where the graphics is not desired.
    if (viewer->isStarsMarked())
        view->toggleStars(true);

Hy Murveit's avatar
Hy Murveit committed
487
    evaluateStats();
Robert Lancaster's avatar
Robert Lancaster committed
488

489
    loadFITSHeader();
490

491 492 493
    // Don't add it to the list if it is already there
    if (recentImages->findItems(currentURL.toLocalFile(), Qt::MatchExactly).count() == 0)
    {
494 495 496 497
        if(!image_data->isTempFile()) //Don't add it to the list if it is a preview
        {
            disconnect(recentImages, &QListWidget::currentRowChanged, this,
                       &FITSTab::selectRecentFITS);
498
            recentImages->addItem(image_data->filename());
499 500 501 502
            recentImages->setCurrentRow(recentImages->count() - 1);
            connect(recentImages, &QListWidget::currentRowChanged,  this,
                    &FITSTab::selectRecentFITS);
        }
503
    }
504

505 506 507
    view->updateFrame();
}

508
bool FITSTab::loadData(const QSharedPointer<FITSData> &data, FITSMode mode, FITSScale filter)
509 510
{
    setupView(mode, filter);
511

512 513
    // Empty URL
    currentURL = QUrl();
514 515 516

    view->setFilter(filter);

517
    if (!view->loadData(data))
518
    {
519 520 521
        // On Failure to load
        // connect(view.get(), &FITSView::failed, this, &FITSTab::failed);
        return false;
522 523 524 525
    }

    processData();
    return true;
526 527
}

528
void FITSTab::modifyFITSState(bool clean, const QUrl &imageURL)
529 530 531 532 533 534 535 536 537 538 539
{
    if (clean)
    {
        if (undoStack->isClean() == false)
            undoStack->setClean();

        mDirty = false;
    }
    else
        mDirty = true;

540
    emit changeStatus(clean, imageURL);
541 542
}

543
bool FITSTab::saveImage(const QString &filename)
544
{
545
    return view->saveImage(filename);
546 547 548 549
}

void FITSTab::copyFITS()
{
550
    QApplication::clipboard()->setImage(view->getDisplayImage());
551 552 553 554
}

void FITSTab::histoFITS()
{
555 556 557 558 559 560
    if (!histogram->isConstructed())
    {
        histogram->constructHistogram();
        evaluateStats();
    }

561
    fitsTools->setCurrentIndex(1);
562
    if(view->width() > 200)
563 564 565
        fitsSplitter->setSizes(QList<int>() << 200 << view->width() - 200);
    else
        fitsSplitter->setSizes(QList<int>() << 50 << 50);
566 567
}

568
void FITSTab::evaluateStats()
569
{
570
    FITSData *image_data = view->getImageData();
Jasem Mutlaq's avatar
Jasem Mutlaq committed
571

572 573 574 575 576 577 578 579
    stat.statsTable->item(STAT_WIDTH, 0)->setText(QString::number(image_data->width()));
    stat.statsTable->item(STAT_HEIGHT, 0)->setText(QString::number(image_data->height()));
    stat.statsTable->item(STAT_BITPIX, 0)->setText(QString::number(image_data->bpp()));
    stat.statsTable->item(STAT_HFR, 0)->setText(QString::number(image_data->getHFR(), 'f', 3));

    if (image_data->channels() == 1)
    {
        for (int i = STAT_MIN; i <= STAT_STDDEV; i++)
580 581 582 583
        {
            if (stat.statsTable->columnSpan(i, 0) != 3)
                stat.statsTable->setSpan(i, 0, 1, 3);
        }
584 585 586 587 588 589 590 591

        stat.statsTable->horizontalHeaderItem(0)->setText(i18n("Value"));
        stat.statsTable->hideColumn(1);
        stat.statsTable->hideColumn(2);
    }
    else
    {
        for (int i = STAT_MIN; i <= STAT_STDDEV; i++)
592 593 594 595
        {
            if (stat.statsTable->columnSpan(i, 0) != 1)
                stat.statsTable->setSpan(i, 0, 1, 1);
        }
596 597 598 599 600

        stat.statsTable->horizontalHeaderItem(0)->setText(i18nc("Red", "R"));
        stat.statsTable->showColumn(1);
        stat.statsTable->showColumn(2);
    }
601 602 603 604

    if (image_data->getMedian() == 0.0 && !histogram->isConstructed())
        histogram->constructHistogram();

605 606 607 608 609 610 611 612
    for (int i = 0; i < image_data->channels(); i++)
    {
        stat.statsTable->item(STAT_MIN, i)->setText(QString::number(image_data->getMin(i), 'f', 3));
        stat.statsTable->item(STAT_MAX, i)->setText(QString::number(image_data->getMax(i), 'f', 3));
        stat.statsTable->item(STAT_MEAN, i)->setText(QString::number(image_data->getMean(i), 'f', 3));
        stat.statsTable->item(STAT_MEDIAN, i)->setText(QString::number(image_data->getMedian(i), 'f', 3));
        stat.statsTable->item(STAT_STDDEV, i)->setText(QString::number(image_data->getStdDev(i), 'f', 3));
    }
613
}
614

615 616 617
void FITSTab::statFITS()
{
    fitsTools->setCurrentIndex(0);
618
    if(view->width() > 200)
619 620 621
        fitsSplitter->setSizes(QList<int>() << 200 << view->width() - 200);
    else
        fitsSplitter->setSizes(QList<int>() << 50 << 50);
622 623
}

624
void FITSTab::loadFITSHeader()
Jasem Mutlaq's avatar
Jasem Mutlaq committed
625 626
{
    FITSData *image_data = view->getImageData();
627 628

    int nkeys = image_data->getRecords().size();
629
    int counter = 0;
630
    header.tableWidget->setRowCount(nkeys);
631
    for (const auto &oneRecord : image_data->getRecords())
Jasem Mutlaq's avatar
Jasem Mutlaq committed
632
    {
633
        QTableWidgetItem *tempItem = new QTableWidgetItem(oneRecord.key);
634
        tempItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
635
        header.tableWidget->setItem(counter, 0, tempItem);
636
        tempItem = new QTableWidgetItem(oneRecord.value.toString());
637 638
        tempItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
        header.tableWidget->setItem(counter, 1, tempItem);
639
        tempItem = new QTableWidgetItem(oneRecord.comment);
640 641 642
        tempItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
        header.tableWidget->setItem(counter, 2, tempItem);
        counter++;
643 644
    }

645 646 647
    header.tableWidget->setColumnWidth(0, 100);
    header.tableWidget->setColumnWidth(1, 100);
    header.tableWidget->setColumnWidth(2, 250);
648 649 650 651 652
}

void FITSTab::headerFITS()
{
    fitsTools->setCurrentIndex(2);
653
    if(view->width() > 200)
654 655 656
        fitsSplitter->setSizes(QList<int>() << 200 << view->width() - 200);
    else
        fitsSplitter->setSizes(QList<int>() << 50 << 50);
657 658
}

659
bool FITSTab::saveFile()
660
{
Jasem Mutlaq's avatar
Jasem Mutlaq committed
661
    QUrl backupCurrent = currentURL;
662 663
    QUrl currentDir(Options::fitsDir());
    currentDir.setScheme("file");
664

Csaba Kertesz's avatar
Csaba Kertesz committed
665
    if (currentURL.toLocalFile().startsWith(QLatin1String("/tmp/")) || currentURL.toLocalFile().contains("/Temp"))
666 667
        currentURL.clear();

668
    // If no changes made, return.
669
    if (mDirty == false && !currentURL.isEmpty())
670
        return false;
671 672 673

    if (currentURL.isEmpty())
    {
674
        currentURL =
675
            QFileDialog::getSaveFileUrl(KStars::Instance(), i18n("Save FITS"), currentDir,
Jasem Mutlaq's avatar
Jasem Mutlaq committed
676
                                        "FITS (*.fits *.fits.gz *.fit);;JPEG (*.jpg *.jpeg);;PNG (*.png)");
677

678 679 680 681
        // if user presses cancel
        if (currentURL.isEmpty())
        {
            currentURL = backupCurrent;
682
            return false;
683 684
        }

685
        // If no extension is selected, assume FITS.
686 687
        if (currentURL.toLocalFile().contains('.') == 0)
            currentURL.setPath(currentURL.toLocalFile() + ".fits");
688 689
    }

690
    if (currentURL.isValid())
691
    {
692
        QString localFile = currentURL.toLocalFile();
693 694
        //        if (localFile.contains(".fit"))
        //            localFile = "!" + localFile;
695

696
        if (!saveImage(localFile))
697
        {
698
            KSNotification::error(i18n("Image save error: %1", view->getImageData()->getLastError()), i18n("Image Save"));
699
            return false;
700 701
        }

702
        emit newStatus(i18n("File saved to %1", currentURL.url()), FITS_MESSAGE);
703
        modifyFITSState();
704
        return true;
705 706
    }
    else
707
    {
708
        QString message = i18n("Invalid URL: %1", currentURL.url());
709
        KSNotification::sorry(message, i18n("Invalid URL"));
710
        return false;
711 712 713
    }
}

714
bool FITSTab::saveFileAs()
715 716
{
    currentURL.clear();
717
    return saveFile();
718 719 720 721
}

void FITSTab::ZoomIn()
{
722
    QPoint oldCenter = view->getImagePoint(view->viewport()->rect().center());
723 724
    view->ZoomIn();
    view->cleanUpZoom(oldCenter);
725 726 727 728
}

void FITSTab::ZoomOut()
{
729
    QPoint oldCenter = view->getImagePoint(view->viewport()->rect().center());
730 731
    view->ZoomOut();
    view->cleanUpZoom(oldCenter);
732 733 734 735
}

void FITSTab::ZoomDefault()
{
736
    QPoint oldCenter = view->getImagePoint(view->viewport()->rect().center());
737 738
    view->ZoomDefault();
    view->cleanUpZoom(oldCenter);
739 740 741 742 743
}

void FITSTab::tabPositionUpdated()
{
    undoStack->setActive(true);
744
    emit newStatus(QString("%1%").arg(view->getCurrentZoom()), FITS_ZOOM);
745
    emit newStatus(QString("%1x%2").arg(view->getImageData()->width()).arg(view->getImageData()->height()),
746
                   FITS_RESOLUTION);
747
}