diff --git a/kstars/auxiliary/ctkrangeslider.cpp b/kstars/auxiliary/ctkrangeslider.cpp index 6c29d9441..e8e1e8b0f 100644 --- a/kstars/auxiliary/ctkrangeslider.cpp +++ b/kstars/auxiliary/ctkrangeslider.cpp @@ -1,859 +1,859 @@ /*========================================================================= Library: CTK Copyright (c) Kitware Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.txt Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. =========================================================================*/ // Qt includes #include #include #include #include #include #include #include #include // CTK includes #include "ctkrangeslider.h" class ctkRangeSliderPrivate { Q_DECLARE_PUBLIC(ctkRangeSlider) protected: ctkRangeSlider* const q_ptr; public: /// Boolean indicates the selected handle /// True for the minimum range handle, false for the maximum range handle enum Handle { NoHandle = 0x0, MinimumHandle = 0x1, MaximumHandle = 0x2 }; Q_DECLARE_FLAGS(Handles, Handle) ctkRangeSliderPrivate(ctkRangeSlider& object); void init(); /// Return the handle at the given pos, or none if no handle is at the pos. /// If a handle is selected, handleRect is set to the handle rect. /// otherwise return NoHandle and handleRect is set to the combined rect of /// the min and max handles Handle handleAtPos(const QPoint& pos, QRect& handleRect)const; /// Copied verbatim from QSliderPrivate class (see QSlider.cpp) int pixelPosToRangeValue(int pos) const; int pixelPosFromRangeValue(int val) const; /// Draw the bottom and top sliders. void drawMinimumSlider( QStylePainter* painter ) const; void drawMaximumSlider( QStylePainter* painter ) const; /// End points of the range on the Model int m_MaximumValue; int m_MinimumValue; /// End points of the range on the GUI. This is synced with the model. int m_MaximumPosition; int m_MinimumPosition; /// Controls selected ? QStyle::SubControl m_MinimumSliderSelected; QStyle::SubControl m_MaximumSliderSelected; /// See QSliderPrivate::clickOffset. /// Overrides this ivar int m_SubclassClickOffset; /// See QSliderPrivate::position /// Overrides this ivar. int m_SubclassPosition; /// Original width between the 2 bounds before any moves int m_SubclassWidth; ctkRangeSliderPrivate::Handles m_SelectedHandles; /// When symmetricMoves is true, moving a handle will move the other handle /// symmetrically, otherwise the handles are independent. bool m_SymmetricMoves; QString m_HandleToolTip; private: Q_DISABLE_COPY(ctkRangeSliderPrivate) }; // -------------------------------------------------------------------------- ctkRangeSliderPrivate::ctkRangeSliderPrivate(ctkRangeSlider& object) :q_ptr(&object) { this->m_MinimumValue = 0; this->m_MaximumValue = 100; this->m_MinimumPosition = 0; this->m_MaximumPosition = 100; this->m_MinimumSliderSelected = QStyle::SC_None; this->m_MaximumSliderSelected = QStyle::SC_None; this->m_SubclassClickOffset = 0; this->m_SubclassPosition = 0; this->m_SubclassWidth = 0; this->m_SelectedHandles = ctkRangeSliderPrivate::NoHandle; this->m_SymmetricMoves = false; } // -------------------------------------------------------------------------- void ctkRangeSliderPrivate::init() { Q_Q(ctkRangeSlider); this->m_MinimumValue = q->minimum(); this->m_MaximumValue = q->maximum(); this->m_MinimumPosition = q->minimum(); this->m_MaximumPosition = q->maximum(); q->connect(q, SIGNAL(rangeChanged(int,int)), q, SLOT(onRangeChanged(int,int))); } // -------------------------------------------------------------------------- ctkRangeSliderPrivate::Handle ctkRangeSliderPrivate::handleAtPos(const QPoint& pos, QRect& handleRect)const { Q_Q(const ctkRangeSlider); QStyleOptionSlider option; q->initStyleOption( &option ); - // The functinos hitTestComplexControl only know about 1 handle. As we have + // The functions hitTestComplexControl only know about 1 handle. As we have // 2, we change the position of the handle and test if the pos correspond to // any of the 2 positions. // Test the MinimumHandle option.sliderPosition = this->m_MinimumPosition; option.sliderValue = this->m_MinimumValue; QStyle::SubControl minimumControl = q->style()->hitTestComplexControl( QStyle::CC_Slider, &option, pos, q); QRect minimumHandleRect = q->style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q); // Test if the pos is under the Maximum handle option.sliderPosition = this->m_MaximumPosition; option.sliderValue = this->m_MaximumValue; QStyle::SubControl maximumControl = q->style()->hitTestComplexControl( QStyle::CC_Slider, &option, pos, q); QRect maximumHandleRect = q->style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q); // The pos is above both handles, select the closest handle if (minimumControl == QStyle::SC_SliderHandle && maximumControl == QStyle::SC_SliderHandle) { int minDist = 0; int maxDist = 0; if (q->orientation() == Qt::Horizontal) { minDist = pos.x() - minimumHandleRect.left(); maxDist = maximumHandleRect.right() - pos.x(); } else //if (q->orientation() == Qt::Vertical) { minDist = minimumHandleRect.bottom() - pos.y(); maxDist = pos.y() - maximumHandleRect.top(); } Q_ASSERT( minDist >= 0 && maxDist >= 0); minimumControl = minDist < maxDist ? minimumControl : QStyle::SC_None; } if (minimumControl == QStyle::SC_SliderHandle) { handleRect = minimumHandleRect; return MinimumHandle; } else if (maximumControl == QStyle::SC_SliderHandle) { handleRect = maximumHandleRect; return MaximumHandle; } handleRect = minimumHandleRect.united(maximumHandleRect); return NoHandle; } // -------------------------------------------------------------------------- // Copied verbatim from QSliderPrivate::pixelPosToRangeValue. See QSlider.cpp // int ctkRangeSliderPrivate::pixelPosToRangeValue( int pos ) const { Q_Q(const ctkRangeSlider); QStyleOptionSlider option; q->initStyleOption( &option ); QRect gr = q->style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderGroove, q ); QRect sr = q->style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q ); int sliderMin, sliderMax, sliderLength; if (option.orientation == Qt::Horizontal) { sliderLength = sr.width(); sliderMin = gr.x(); sliderMax = gr.right() - sliderLength + 1; } else { sliderLength = sr.height(); sliderMin = gr.y(); sliderMax = gr.bottom() - sliderLength + 1; } return QStyle::sliderValueFromPosition( q->minimum(), q->maximum(), pos - sliderMin, sliderMax - sliderMin, option.upsideDown ); } //--------------------------------------------------------------------------- int ctkRangeSliderPrivate::pixelPosFromRangeValue( int val ) const { Q_Q(const ctkRangeSlider); QStyleOptionSlider option; q->initStyleOption( &option ); QRect gr = q->style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderGroove, q ); QRect sr = q->style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q ); int sliderMin, sliderMax, sliderLength; if (option.orientation == Qt::Horizontal) { sliderLength = sr.width(); sliderMin = gr.x(); sliderMax = gr.right() - sliderLength + 1; } else { sliderLength = sr.height(); sliderMin = gr.y(); sliderMax = gr.bottom() - sliderLength + 1; } return QStyle::sliderPositionFromValue( q->minimum(), q->maximum(), val, sliderMax - sliderMin, option.upsideDown ) + sliderMin; } //--------------------------------------------------------------------------- // Draw slider at the bottom end of the range void ctkRangeSliderPrivate::drawMinimumSlider( QStylePainter* painter ) const { Q_Q(const ctkRangeSlider); QStyleOptionSlider option; q->initMinimumSliderStyleOption( &option ); option.subControls = QStyle::SC_SliderHandle; option.sliderValue = m_MinimumValue; option.sliderPosition = m_MinimumPosition; if (q->isMinimumSliderDown()) { option.activeSubControls = QStyle::SC_SliderHandle; option.state |= QStyle::State_Sunken; } #ifdef Q_OS_MAC // On mac style, drawing just the handle actually draws also the groove. QRect clip = q->style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q); painter->setClipRect(clip); #endif painter->drawComplexControl(QStyle::CC_Slider, option); } //--------------------------------------------------------------------------- // Draw slider at the top end of the range void ctkRangeSliderPrivate::drawMaximumSlider( QStylePainter* painter ) const { Q_Q(const ctkRangeSlider); QStyleOptionSlider option; q->initMaximumSliderStyleOption( &option ); option.subControls = QStyle::SC_SliderHandle; option.sliderValue = m_MaximumValue; option.sliderPosition = m_MaximumPosition; if (q->isMaximumSliderDown()) { option.activeSubControls = QStyle::SC_SliderHandle; option.state |= QStyle::State_Sunken; } #ifdef Q_OS_MAC // On mac style, drawing just the handle actually draws also the groove. QRect clip = q->style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q); painter->setClipRect(clip); #endif painter->drawComplexControl(QStyle::CC_Slider, option); } // -------------------------------------------------------------------------- ctkRangeSlider::ctkRangeSlider(QWidget* _parent) : QSlider(_parent) , d_ptr(new ctkRangeSliderPrivate(*this)) { Q_D(ctkRangeSlider); d->init(); } // -------------------------------------------------------------------------- ctkRangeSlider::ctkRangeSlider( Qt::Orientation o, QWidget* parentObject ) :QSlider(o, parentObject) , d_ptr(new ctkRangeSliderPrivate(*this)) { Q_D(ctkRangeSlider); d->init(); } // -------------------------------------------------------------------------- ctkRangeSlider::ctkRangeSlider(ctkRangeSliderPrivate* impl, QWidget* _parent) : QSlider(_parent) , d_ptr(impl) { Q_D(ctkRangeSlider); d->init(); } // -------------------------------------------------------------------------- ctkRangeSlider::ctkRangeSlider( ctkRangeSliderPrivate* impl, Qt::Orientation o, QWidget* parentObject ) :QSlider(o, parentObject) , d_ptr(impl) { Q_D(ctkRangeSlider); d->init(); } // -------------------------------------------------------------------------- ctkRangeSlider::~ctkRangeSlider() { } // -------------------------------------------------------------------------- int ctkRangeSlider::minimumValue() const { Q_D(const ctkRangeSlider); return d->m_MinimumValue; } // -------------------------------------------------------------------------- void ctkRangeSlider::setMinimumValue( int min ) { Q_D(ctkRangeSlider); this->setValues( min, qMax(d->m_MaximumValue,min) ); } // -------------------------------------------------------------------------- int ctkRangeSlider::maximumValue() const { Q_D(const ctkRangeSlider); return d->m_MaximumValue; } // -------------------------------------------------------------------------- void ctkRangeSlider::setMaximumValue( int max ) { Q_D(ctkRangeSlider); this->setValues( qMin(d->m_MinimumValue, max), max ); } // -------------------------------------------------------------------------- void ctkRangeSlider::setValues(int l, int u) { Q_D(ctkRangeSlider); const int minValue = qBound(this->minimum(), qMin(l,u), this->maximum()); const int maxValue = qBound(this->minimum(), qMax(l,u), this->maximum()); bool emitMinValChanged = (minValue != d->m_MinimumValue); bool emitMaxValChanged = (maxValue != d->m_MaximumValue); d->m_MinimumValue = minValue; d->m_MaximumValue = maxValue; bool emitMinPosChanged = (minValue != d->m_MinimumPosition); bool emitMaxPosChanged = (maxValue != d->m_MaximumPosition); d->m_MinimumPosition = minValue; d->m_MaximumPosition = maxValue; if (isSliderDown()) { if (emitMinPosChanged || emitMaxPosChanged) { emit positionsChanged(d->m_MinimumPosition, d->m_MaximumPosition); } if (emitMinPosChanged) { emit minimumPositionChanged(d->m_MinimumPosition); } if (emitMaxPosChanged) { emit maximumPositionChanged(d->m_MaximumPosition); } } if (emitMinValChanged || emitMaxValChanged) { emit valuesChanged(d->m_MinimumValue, d->m_MaximumValue); } if (emitMinValChanged) { emit minimumValueChanged(d->m_MinimumValue); } if (emitMaxValChanged) { emit maximumValueChanged(d->m_MaximumValue); } if (emitMinPosChanged || emitMaxPosChanged || emitMinValChanged || emitMaxValChanged) { this->update(); } } // -------------------------------------------------------------------------- int ctkRangeSlider::minimumPosition() const { Q_D(const ctkRangeSlider); return d->m_MinimumPosition; } // -------------------------------------------------------------------------- int ctkRangeSlider::maximumPosition() const { Q_D(const ctkRangeSlider); return d->m_MaximumPosition; } // -------------------------------------------------------------------------- void ctkRangeSlider::setMinimumPosition(int l) { Q_D(const ctkRangeSlider); this->setPositions(l, qMax(l, d->m_MaximumPosition)); } // -------------------------------------------------------------------------- void ctkRangeSlider::setMaximumPosition(int u) { Q_D(const ctkRangeSlider); this->setPositions(qMin(d->m_MinimumPosition, u), u); } // -------------------------------------------------------------------------- void ctkRangeSlider::setPositions(int min, int max) { Q_D(ctkRangeSlider); const int minPosition = qBound(this->minimum(), qMin(min, max), this->maximum()); const int maxPosition = qBound(this->minimum(), qMax(min, max), this->maximum()); bool emitMinPosChanged = (minPosition != d->m_MinimumPosition); bool emitMaxPosChanged = (maxPosition != d->m_MaximumPosition); if (!emitMinPosChanged && !emitMaxPosChanged) { return; } d->m_MinimumPosition = minPosition; d->m_MaximumPosition = maxPosition; if (!this->hasTracking()) { this->update(); } if (isSliderDown()) { if (emitMinPosChanged) { emit minimumPositionChanged(d->m_MinimumPosition); } if (emitMaxPosChanged) { emit maximumPositionChanged(d->m_MaximumPosition); } if (emitMinPosChanged || emitMaxPosChanged) { emit positionsChanged(d->m_MinimumPosition, d->m_MaximumPosition); } } if (this->hasTracking()) { this->triggerAction(SliderMove); this->setValues(d->m_MinimumPosition, d->m_MaximumPosition); } } // -------------------------------------------------------------------------- void ctkRangeSlider::setSymmetricMoves(bool symmetry) { Q_D(ctkRangeSlider); d->m_SymmetricMoves = symmetry; } // -------------------------------------------------------------------------- bool ctkRangeSlider::symmetricMoves()const { Q_D(const ctkRangeSlider); return d->m_SymmetricMoves; } // -------------------------------------------------------------------------- void ctkRangeSlider::onRangeChanged(int _minimum, int _maximum) { Q_UNUSED(_minimum); Q_UNUSED(_maximum); Q_D(ctkRangeSlider); this->setValues(d->m_MinimumValue, d->m_MaximumValue); } // -------------------------------------------------------------------------- // Render void ctkRangeSlider::paintEvent( QPaintEvent* ) { Q_D(ctkRangeSlider); QStyleOptionSlider option; this->initStyleOption(&option); QStylePainter painter(this); option.subControls = QStyle::SC_SliderGroove; // Move to minimum to not highlight the SliderGroove. // On mac style, drawing just the slider groove also draws the handles, // therefore we give a negative (outside of view) position. option.sliderValue = this->minimum() - this->maximum(); option.sliderPosition = this->minimum() - this->maximum(); painter.drawComplexControl(QStyle::CC_Slider, option); option.sliderPosition = d->m_MinimumPosition; const QRect lr = style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, this); option.sliderPosition = d->m_MaximumPosition; const QRect ur = style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, this); QRect sr = style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderGroove, this); QRect rangeBox; if (option.orientation == Qt::Horizontal) { rangeBox = QRect( QPoint(qMin( lr.center().x(), ur.center().x() ), sr.center().y() - 2), QPoint(qMax( lr.center().x(), ur.center().x() ), sr.center().y() + 1)); } else { rangeBox = QRect( QPoint(sr.center().x() - 2, qMin( lr.center().y(), ur.center().y() )), QPoint(sr.center().x() + 1, qMax( lr.center().y(), ur.center().y() ))); } // ----------------------------- // Render the range // QRect groove = this->style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderGroove, this ); groove.adjust(0, 0, -1, 0); // Create default colors based on the transfer function. // QColor highlight = this->palette().color(QPalette::Normal, QPalette::Highlight); QLinearGradient gradient; if (option.orientation == Qt::Horizontal) { gradient = QLinearGradient( groove.center().x(), groove.top(), groove.center().x(), groove.bottom()); } else { gradient = QLinearGradient( groove.left(), groove.center().y(), groove.right(), groove.center().y()); } // TODO: Set this based on the supplied transfer function //QColor l = Qt::darkGray; //QColor u = Qt::black; gradient.setColorAt(0, highlight.darker(120)); gradient.setColorAt(1, highlight.lighter(160)); painter.setPen(QPen(highlight.darker(150), 0)); painter.setBrush(gradient); painter.drawRect( rangeBox.intersected(groove) ); // ----------------------------------- // Render the sliders // if (this->isMinimumSliderDown()) { d->drawMaximumSlider( &painter ); d->drawMinimumSlider( &painter ); } else { d->drawMinimumSlider( &painter ); d->drawMaximumSlider( &painter ); } } // -------------------------------------------------------------------------- // Standard Qt UI events void ctkRangeSlider::mousePressEvent(QMouseEvent* mouseEvent) { Q_D(ctkRangeSlider); if (minimum() == maximum() || (mouseEvent->buttons() ^ mouseEvent->button())) { mouseEvent->ignore(); return; } int mepos = this->orientation() == Qt::Horizontal ? mouseEvent->pos().x() : mouseEvent->pos().y(); QStyleOptionSlider option; this->initStyleOption( &option ); QRect handleRect; ctkRangeSliderPrivate::Handle handle_ = d->handleAtPos(mouseEvent->pos(), handleRect); if (handle_ != ctkRangeSliderPrivate::NoHandle) { d->m_SubclassPosition = (handle_ == ctkRangeSliderPrivate::MinimumHandle)? d->m_MinimumPosition : d->m_MaximumPosition; // save the position of the mouse inside the handle for later d->m_SubclassClickOffset = mepos - (this->orientation() == Qt::Horizontal ? handleRect.left() : handleRect.top()); this->setSliderDown(true); if (d->m_SelectedHandles != handle_) { d->m_SelectedHandles = handle_; this->update(handleRect); } // Accept the mouseEvent mouseEvent->accept(); return; } // if we are here, no handles have been pressed // Check if we pressed on the groove between the 2 handles QStyle::SubControl control = this->style()->hitTestComplexControl( QStyle::CC_Slider, &option, mouseEvent->pos(), this); QRect sr = style()->subControlRect( QStyle::CC_Slider, &option, QStyle::SC_SliderGroove, this); int minCenter = (this->orientation() == Qt::Horizontal ? handleRect.left() : handleRect.top()); int maxCenter = (this->orientation() == Qt::Horizontal ? handleRect.right() : handleRect.bottom()); if (control == QStyle::SC_SliderGroove && mepos > minCenter && mepos < maxCenter) { // warning lost of precision it might be fatal d->m_SubclassPosition = (d->m_MinimumPosition + d->m_MaximumPosition) / 2.; d->m_SubclassClickOffset = mepos - d->pixelPosFromRangeValue(d->m_SubclassPosition); d->m_SubclassWidth = (d->m_MaximumPosition - d->m_MinimumPosition) / 2; qMax(d->m_SubclassPosition - d->m_MinimumPosition, d->m_MaximumPosition - d->m_SubclassPosition); this->setSliderDown(true); if (!this->isMinimumSliderDown() || !this->isMaximumSliderDown()) { d->m_SelectedHandles = QFlags(ctkRangeSliderPrivate::MinimumHandle) | QFlags(ctkRangeSliderPrivate::MaximumHandle); this->update(handleRect.united(sr)); } mouseEvent->accept(); return; } mouseEvent->ignore(); } // -------------------------------------------------------------------------- // Standard Qt UI events void ctkRangeSlider::mouseMoveEvent(QMouseEvent* mouseEvent) { Q_D(ctkRangeSlider); if (!d->m_SelectedHandles) { mouseEvent->ignore(); return; } int mepos = this->orientation() == Qt::Horizontal ? mouseEvent->pos().x() : mouseEvent->pos().y(); QStyleOptionSlider option; this->initStyleOption(&option); const int m = style()->pixelMetric( QStyle::PM_MaximumDragDistance, &option, this ); int newPosition = d->pixelPosToRangeValue(mepos - d->m_SubclassClickOffset); if (m >= 0) { const QRect r = rect().adjusted(-m, -m, m, m); if (!r.contains(mouseEvent->pos())) { newPosition = d->m_SubclassPosition; } } // Only the lower/left slider is down if (this->isMinimumSliderDown() && !this->isMaximumSliderDown()) { double newMinPos = qMin(newPosition,d->m_MaximumPosition); this->setPositions(newMinPos, d->m_MaximumPosition + (d->m_SymmetricMoves ? d->m_MinimumPosition - newMinPos : 0)); } // Only the upper/right slider is down else if (this->isMaximumSliderDown() && !this->isMinimumSliderDown()) { double newMaxPos = qMax(d->m_MinimumPosition, newPosition); this->setPositions(d->m_MinimumPosition - (d->m_SymmetricMoves ? newMaxPos - d->m_MaximumPosition: 0), newMaxPos); } // Both handles are down (the user clicked in between the handles) else if (this->isMinimumSliderDown() && this->isMaximumSliderDown()) { this->setPositions(newPosition - d->m_SubclassWidth, newPosition + d->m_SubclassWidth ); } mouseEvent->accept(); } // -------------------------------------------------------------------------- // Standard Qt UI mouseEvents void ctkRangeSlider::mouseReleaseEvent(QMouseEvent* mouseEvent) { Q_D(ctkRangeSlider); this->QSlider::mouseReleaseEvent(mouseEvent); setSliderDown(false); d->m_SelectedHandles = ctkRangeSliderPrivate::NoHandle; this->update(); } // -------------------------------------------------------------------------- bool ctkRangeSlider::isMinimumSliderDown()const { Q_D(const ctkRangeSlider); return d->m_SelectedHandles & ctkRangeSliderPrivate::MinimumHandle; } // -------------------------------------------------------------------------- bool ctkRangeSlider::isMaximumSliderDown()const { Q_D(const ctkRangeSlider); return d->m_SelectedHandles & ctkRangeSliderPrivate::MaximumHandle; } // -------------------------------------------------------------------------- void ctkRangeSlider::initMinimumSliderStyleOption(QStyleOptionSlider* option) const { this->initStyleOption(option); } // -------------------------------------------------------------------------- void ctkRangeSlider::initMaximumSliderStyleOption(QStyleOptionSlider* option) const { this->initStyleOption(option); } // -------------------------------------------------------------------------- QString ctkRangeSlider::handleToolTip()const { Q_D(const ctkRangeSlider); return d->m_HandleToolTip; } // -------------------------------------------------------------------------- void ctkRangeSlider::setHandleToolTip(const QString& _toolTip) { Q_D(ctkRangeSlider); d->m_HandleToolTip = _toolTip; } // -------------------------------------------------------------------------- bool ctkRangeSlider::event(QEvent* _event) { Q_D(ctkRangeSlider); switch(_event->type()) { case QEvent::ToolTip: { QHelpEvent* helpEvent = static_cast(_event); QStyleOptionSlider opt; // Test the MinimumHandle opt.sliderPosition = d->m_MinimumPosition; opt.sliderValue = d->m_MinimumValue; this->initStyleOption(&opt); QStyle::SubControl hoveredControl = this->style()->hitTestComplexControl( QStyle::CC_Slider, &opt, helpEvent->pos(), this); if (!d->m_HandleToolTip.isEmpty() && hoveredControl == QStyle::SC_SliderHandle) { QToolTip::showText(helpEvent->globalPos(), d->m_HandleToolTip.arg(this->minimumValue())); _event->accept(); return true; } // Test the MaximumHandle opt.sliderPosition = d->m_MaximumPosition; opt.sliderValue = d->m_MaximumValue; this->initStyleOption(&opt); hoveredControl = this->style()->hitTestComplexControl( QStyle::CC_Slider, &opt, helpEvent->pos(), this); if (!d->m_HandleToolTip.isEmpty() && hoveredControl == QStyle::SC_SliderHandle) { QToolTip::showText(helpEvent->globalPos(), d->m_HandleToolTip.arg(this->maximumValue())); _event->accept(); return true; } } default: break; } return this->Superclass::event(_event); } diff --git a/kstars/ekos/focus/focus.h b/kstars/ekos/focus/focus.h index ce6ef3fb2..28d7430d2 100644 --- a/kstars/ekos/focus/focus.h +++ b/kstars/ekos/focus/focus.h @@ -1,643 +1,643 @@ /* Ekos Focus tool Copyright (C) 2012 Jasem Mutlaq This application 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. */ #pragma once #include "ui_focus.h" #include "ekos/ekos.h" #include "ekos/auxiliary/filtermanager.h" #include "fitsviewer/fitsviewer.h" #include "indi/indiccd.h" #include "indi/indifocuser.h" #include "indi/indistd.h" #include "indi/inditelescope.h" #include namespace Ekos { class FocusAlgorithmInterface; class PolynomialFit; /** * @class Focus * @short Supports manual focusing and auto focusing using relative and absolute INDI focusers. * * @author Jasem Mutlaq * @version 1.5 */ class Focus : public QWidget, public Ui::Focus { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kstars.Ekos.Focus") Q_PROPERTY(Ekos::FocusState status READ status NOTIFY newStatus) Q_PROPERTY(QStringList logText READ logText NOTIFY newLog) Q_PROPERTY(QString camera READ camera WRITE setCamera) Q_PROPERTY(QString focuser READ focuser WRITE setFocuser) Q_PROPERTY(QString filterWheel READ filterWheel WRITE setFilterWheel) Q_PROPERTY(QString filter READ filter WRITE setFilter) Q_PROPERTY(double HFR READ getHFR NOTIFY newHFR) Q_PROPERTY(double exposure READ exposure WRITE setExposure) public: Focus(); ~Focus(); typedef enum { FOCUS_NONE, FOCUS_IN, FOCUS_OUT } FocusDirection; typedef enum { FOCUS_MANUAL, FOCUS_AUTO } FocusType; typedef enum { FOCUS_ITERATIVE, FOCUS_POLYNOMIAL, FOCUS_LINEAR } FocusAlgorithm; /** @defgroup FocusDBusInterface Ekos DBus Interface - Focus Module * Ekos::Focus interface provides advanced scripting capabilities to perform manual and automatic focusing operations. */ /*@{*/ /** DBUS interface function. * select the CCD device from the available CCD drivers. * @param device The CCD device name * @return Returns true if CCD device is found and set, false otherwise. */ Q_SCRIPTABLE bool setCamera(const QString &device); Q_SCRIPTABLE QString camera(); /** DBUS interface function. * select the focuser device from the available focuser drivers. The focuser device can be the same as the CCD driver if the focuser functionality was embedded within the driver. * @param device The focuser device name * @return Returns true if focuser device is found and set, false otherwise. */ Q_SCRIPTABLE bool setFocuser(const QString &device); Q_SCRIPTABLE QString focuser(); /** DBUS interface function. * select the filter device from the available filter drivers. The filter device can be the same as the CCD driver if the filter functionality was embedded within the driver. * @param device The filter device name * @return Returns true if filter device is found and set, false otherwise. */ Q_SCRIPTABLE bool setFilterWheel(const QString &device); Q_SCRIPTABLE QString filterWheel(); /** DBUS interface function. * select the filter from the available filters. * @param filter The filter name * @return Returns true if filter is found and set, false otherwise. */ Q_SCRIPTABLE bool setFilter(const QString &filter); Q_SCRIPTABLE QString filter(); /** DBUS interface function. * @return Returns True if current focuser supports auto-focusing */ Q_SCRIPTABLE bool canAutoFocus() { return (focusType == FOCUS_AUTO); } /** DBUS interface function. * @return Returns Half-Flux-Radius in pixels. */ Q_SCRIPTABLE double getHFR() { return currentHFR; } /** DBUS interface function. * Set CCD exposure value * @param value exposure value in seconds. */ Q_SCRIPTABLE Q_NOREPLY void setExposure(double value); Q_SCRIPTABLE double exposure() { return exposureIN->value(); } /** DBUS interface function. * Set CCD binning * @param binX horizontal binning * @param binY vertical binning */ Q_SCRIPTABLE Q_NOREPLY void setBinning(int binX, int binY); /** DBUS interface function. * Set image filter to apply to the image after capture. * @param value Image filter (Auto Stretch, High Contrast, Equalize, High Pass) */ Q_SCRIPTABLE Q_NOREPLY void setImageFilter(const QString &value); /** DBUS interface function. * Set Auto Focus options. The options must be set before starting the autofocus operation. If no options are set, the options loaded from the user configuration are used. * @param enable If true, Ekos will attempt to automatically select the best focus star in the frame. If it fails to select a star, the user will be asked to select a star manually. */ Q_SCRIPTABLE Q_NOREPLY void setAutoStarEnabled(bool enable); /** DBUS interface function. * Set Auto Focus options. The options must be set before starting the autofocus operation. If no options are set, the options loaded from the user configuration are used. * @param enable if true, Ekos will capture a subframe around the selected focus star. The subframe size is determined by the boxSize parameter. */ Q_SCRIPTABLE Q_NOREPLY void setAutoSubFrameEnabled(bool enable); /** DBUS interface function. * Set Autofocus parameters * @param boxSize the box size around the focus star in pixels. The boxsize is used to subframe around the focus star. * @param stepSize the initial step size to be commanded to the focuser. If the focuser is absolute, the step size is in ticks. For relative focusers, the focuser will be commanded to focus inward for stepSize milliseconds initially. * @param maxTravel the maximum steps permitted before the autofocus operation aborts. * @param tolerance Measure of how accurate the autofocus algorithm is. If the difference between the current HFR and minimum measured HFR is less than %tolerance after the focuser traversed both ends of the V-curve, then the focusing operation * is deemed successful. Otherwise, the focusing operation will continue. */ Q_SCRIPTABLE Q_NOREPLY void setAutoFocusParameters(int boxSize, int stepSize, int maxTravel, double tolerance); /** DBUS interface function. * resetFrame Resets the CCD frame to its full native resolution. */ Q_SCRIPTABLE Q_NOREPLY void resetFrame(); /** DBUS interface function. - * Return state of Focuser modue (Ekos::FocusState) + * Return state of Focuser module (Ekos::FocusState) */ Q_SCRIPTABLE Ekos::FocusState status() { return state; } /** @}*/ /** * @brief Add CCD to the list of available CCD. * @param newCCD pointer to CCD device. */ void addCCD(ISD::GDInterface *newCCD); /** * @brief addFocuser Add focuser to the list of available focusers. * @param newFocuser pointer to focuser device. */ void addFocuser(ISD::GDInterface *newFocuser); /** * @brief addFilter Add filter to the list of available filters. * @param newFilter pointer to filter device. */ void addFilter(ISD::GDInterface *newFilter); /** * @brief removeDevice Remove device from Focus module * @param deviceRemoved pointer to device */ void removeDevice(ISD::GDInterface *deviceRemoved); void setFilterManager(const QSharedPointer &manager); void clearLog(); QStringList logText() { return m_LogText; } QString getLogText() { return m_LogText.join("\n"); } public slots: /** \addtogroup FocusDBusInterface * @{ */ /* Focus */ /** DBUS interface function. * Start the autofocus operation. */ Q_SCRIPTABLE Q_NOREPLY void start(); /** DBUS interface function. * Abort the autofocus operation. */ Q_SCRIPTABLE Q_NOREPLY void abort(); /** DBUS interface function. * Capture a focus frame. */ Q_SCRIPTABLE Q_NOREPLY void capture(); /** DBUS interface function. * Focus inward * @param ms If set, focus inward for ms ticks (Absolute Focuser), or ms milliseconds (Relative Focuser). If not set, it will use the value specified in the options. */ Q_SCRIPTABLE bool focusIn(int ms = -1); /** DBUS interface function. * Focus outward * @param ms If set, focus outward for ms ticks (Absolute Focuser), or ms milliseconds (Relative Focuser). If not set, it will use the value specified in the options. */ Q_SCRIPTABLE bool focusOut(int ms = -1); /** @}*/ /** * @brief startFraming Begins continuous capture of the CCD and calculates HFR every frame. */ void startFraming(); /** * @brief checkStopFocus Perform checks before stopping the autofocus operation. Some checks are necessary for in-sequence focusing. */ void checkStopFocus(); /** * @brief Check CCD and make sure information is updated accordingly. This simply calls syncCCDInfo for the current CCD. * @param CCDNum By default, we check the already selected CCD in the dropdown menu. If CCDNum is specified, the check is made against this specific CCD in the dropdown menu. * CCDNum is the index of the CCD in the dropdown menu. */ void checkCCD(int CCDNum = -1); /** * @brief syncCCDInfo Read current CCD information and update settings accordingly. */ void syncCCDInfo(); /** * @brief Check Focuser and make sure information is updated accordingly. * @param FocuserNum By default, we check the already selected focuser in the dropdown menu. If FocuserNum is specified, the check is made against this specific focuser in the dropdown menu. * FocuserNum is the index of the focuser in the dropdown menu. */ void checkFocuser(int FocuserNum = -1); /** * @brief Check Filter and make sure information is updated accordingly. * @param filterNum By default, we check the already selected filter in the dropdown menu. If filterNum is specified, the check is made against this specific filter in the dropdown menu. * filterNum is the index of the filter in the dropdown menu. */ void checkFilter(int filterNum = -1); /** * @brief clearDataPoints Remove all data points from HFR plots */ void clearDataPoints(); /** * @brief focusStarSelected The user selected a focus star, save its coordinates and subframe it if subframing is enabled. * @param x X coordinate * @param y Y coordinate */ void focusStarSelected(int x, int y); /** * @brief newFITS A new FITS blob is received by the CCD driver. * @param bp pointer to blob data */ void newFITS(IBLOB *bp); /** * @brief processFocusNumber Read focus number properties of interest as they arrive from the focuser driver and process them accordingly. * @param nvp pointer to updated focuser number property. */ void processFocusNumber(INumberVectorProperty *nvp); /** * @brief checkFocus Given the minimum required HFR, check focus and calculate HFR. If current HFR exceeds required HFR, start autofocus process, otherwise do nothing. * @param requiredHFR Minimum HFR to trigger autofocus process. */ void checkFocus(double requiredHFR); /** * @brief setFocusStatus Upon completion of the focusing process, set its status (fail or pass) and reset focus process to clean state. * @param status If true, the focus process finished successfully. Otherwise, it failed. */ void setAutoFocusResult(bool status); /** * @brief filterChangeWarning Warn the user it is not a good idea to apply image filter in the filter process as they can skew the HFR calculations. * @param index Index of image filter selected by the user. */ void filterChangeWarning(int index); // Log void appendLogText(const QString &); // Adjust focuser offset, relative or absolute void adjustFocusOffset(int value, bool useAbsoluteOffset); // Update Mount module status void setMountStatus(ISD::Telescope::Status newState); /** * @brief toggleVideo Turn on and off video streaming if supported by the camera. * @param enabled Set to true to start video streaming, false to stop it if active. */ void toggleVideo(bool enabled); private slots: /** * @brief toggleSubframe Process enabling and disabling subfrag. * @param enable If true, subframing is enabled. If false, subframing is disabled. Even if subframing is enabled, it must be supported by the CCD driver. */ void toggleSubframe(bool enable); void checkAutoStarTimeout(); void setAbsoluteFocusTicks(); void updateBoxSize(int value); void processCaptureTimeout(); void processCaptureFailure(); void setCaptureComplete(); void showFITSViewer(); void toggleFocusingWidgetFullScreen(); void setVideoStreamEnabled(bool enabled); void syncSettings(); void graphPolynomialFunction(); signals: void newLog(const QString &text); void newStatus(Ekos::FocusState state); void newHFR(double hfr, int position); void absolutePositionChanged(int value); void focusPositionAdjusted(); void suspendGuiding(); void resumeGuiding(); void newStarPixmap(QPixmap &); void newProfilePixmap(QPixmap &); private: //////////////////////////////////////////////////////////////////// /// Connections //////////////////////////////////////////////////////////////////// void initConnections(); //////////////////////////////////////////////////////////////////// /// Settings //////////////////////////////////////////////////////////////////// /** * @brief initSettings Connect settings to slots to update the value when changed */ void initSettingsConnections(); /** * @brief loadSettings Load setting from Options and set them accordingly. */ void loadSettings(); //////////////////////////////////////////////////////////////////// /// HFR Plot //////////////////////////////////////////////////////////////////// void initPlots(); void drawHFRPlot(); void drawHFRIndeces(); void drawProfilePlot(); //////////////////////////////////////////////////////////////////// /// Positions //////////////////////////////////////////////////////////////////// void getAbsFocusPosition(); bool autoFocusChecks(); void autoFocusAbs(); void autoFocusLinear(); void autoFocusRel(); void resetButtons(); void stop(bool aborted = false); void initView(); // Move the focuser in (negative) or out (positive amount). bool changeFocus(int amount); // Start up capture, or occasionally move focuser again, after current focus-move accomplished. void autoFocusProcessPositionChange(IPState state); // For the Linear algorithm, which always scans in (from higher position to lower position) // if we notice the new position is higher than the current position (that is, it is the start // of a new scan), we adjust the new position to be several steps further out than requested // and set focuserAdditionalMovement to the extra motion, so that after this motion completes // we will then scan back in (back to the originally requested position). This "dance" is done // to reduce backlash on such movement changes and so that we've always focused in before capture. int adjustLinearPosition(int position, int newPosition); /** * @brief syncTrackingBoxPosition Sync the tracking box to the current selected star center */ void syncTrackingBoxPosition(); /** @internal Search for stars using the method currently configured, and return the consolidated HFR. * @param image_data is the FITS frame to work with. * @return the HFR of the star or field of stars in the frame, depending on the consolidation method, or -1 if it cannot be estimated. */ double analyzeSources(FITSData *image_data); /** @internal Add a new HFR for the current focuser position. * @param newHFR is the new HFR to consider for the current focuser position. * @return true if a new sample is required, else false. */ bool appendHFR(double newHFR); /// Focuser device needed for focus operation ISD::Focuser *currentFocuser { nullptr }; /// CCD device needed for focus operation ISD::CCD *currentCCD { nullptr }; /// Optional device filter ISD::GDInterface *currentFilter { nullptr }; /// Current filter position int currentFilterPosition { -1 }; int fallbackFilterPosition { -1 }; /// True if we need to change filter position and wait for result before continuing capture bool filterPositionPending { false }; bool fallbackFilterPending { false }; /// List of Focusers QList Focusers; /// List of CCDs QList CCDs; /// They're generic GDInterface because they could be either ISD::CCD or ISD::Filter QList Filters; /// As the name implies FocusDirection lastFocusDirection { FOCUS_NONE }; /// What type of focusing are we doing right now? FocusType focusType { FOCUS_MANUAL }; /// Focus HFR & Centeroid algorithms StarAlgorithm focusDetection { ALGORITHM_GRADIENT }; /// Focus Process Algorithm FocusAlgorithm focusAlgorithm { FOCUS_ITERATIVE }; /********************* * HFR Club variables *********************/ /// Current HFR value just fetched from FITS file double currentHFR { 0 }; /// Last HFR value recorded double lastHFR { 0 }; /// If (currentHFR > deltaHFR) we start the autofocus process. double minimumRequiredHFR { -1 }; /// Maximum HFR recorded double maxHFR { 1 }; /// Is HFR increasing? We're going away from the sweet spot! If HFRInc=1, we re-capture just to make sure HFR calculations are correct, if HFRInc > 1, we switch directions int HFRInc { 0 }; /// If HFR decreasing? Well, good job. Once HFR start decreasing, we can start calculating HFR slope and estimating our next move. int HFRDec { 0 }; /**************************** * Absolute position focusers ****************************/ /// Absolute focus position double currentPosition { 0 }; /// What was our position before we started the focus process? int initialFocuserAbsPosition { -1 }; /// Pulse duration in ms for relative focusers that only support timers, or the number of ticks in a relative or absolute focuser int pulseDuration { 1000 }; /// Does the focuser support absolute motion? bool canAbsMove { false }; /// Does the focuser support relative motion? bool canRelMove { false }; /// Does the focuser support timer-based motion? bool canTimerMove { false }; /// Maximum range of motion for our lovely absolute focuser double absMotionMax { 0 }; /// Minimum range of motion for our lovely absolute focuser double absMotionMin { 0 }; /// How many iterations have we completed now in our absolute autofocus algorithm? We can't go forever int absIterations { 0 }; /**************************** * Misc. variables ****************************/ /// Are we in the process of capturing an image? bool captureInProgress { false }; // Was the frame modified by us? Better keep track since we need to return it to its previous state once we are done with the focus operation. //bool frameModified; /// Was the modified frame subFramed? bool subFramed { false }; /// If the autofocus process fails, let's not ruin the capture session probably taking place in the next tab. Instead, we should restart it and try again, but we keep count until we hit MAXIMUM_RESET_ITERATIONS /// and then we truly give up. int resetFocusIteration { 0 }; /// Which filter must we use once the autofocus process kicks in? int lockedFilterIndex { -1 }; /// Keep track of what we're doing right now bool inAutoFocus { false }; bool inFocusLoop { false }; bool inSequenceFocus { false }; bool resetFocus { false }; /// Did we reverse direction? bool reverseDir { false }; /// Did the user or the auto selection process finish selecting our focus star? bool starSelected { false }; /// Adjust the focus position to a target value bool adjustFocus { false }; // Target frame dimensions //int fx,fy,fw,fh; /// If HFR=-1 which means no stars detected, we need to decide how many times should the re-capture process take place before we give up or reverse direction. int noStarCount { 0 }; /// Track which upload mode the CCD is set to. If set to UPLOAD_LOCAL, then we need to switch it to UPLOAD_CLIENT in order to do focusing, and then switch it back to UPLOAD_LOCAL ISD::CCD::UploadMode rememberUploadMode { ISD::CCD::UPLOAD_CLIENT }; /// Previous binning setting int activeBin { 0 }; /// HFR values for captured frames before averages QVector HFRFrames; // CCD Exposure Looping bool rememberCCDExposureLooping = { false }; QStringList m_LogText; ITextVectorProperty *filterName { nullptr }; INumberVectorProperty *filterSlot { nullptr }; /**************************** * Plot variables ****************************/ /// Plot minimum positions double minPos { 1e6 }; /// Plot maximum positions double maxPos { 0 }; /// List of V curve plot points /// V-Curve graph QCPGraph *v_graph { nullptr }; // Last gaussian fit values QVector lastGausIndexes; QVector lastGausFrequencies; QCPGraph *currentGaus { nullptr }; QCPGraph *firstGaus { nullptr }; QCPGraph *lastGaus { nullptr }; QVector hfr_position, hfr_value; // Pixmaps QPixmap profilePixmap; /// State Ekos::FocusState state { Ekos::FOCUS_IDLE }; /// FITS Scale FITSScale defaultScale; /// CCD Chip frame settings QMap frameSettings; /// Selected star coordinates QVector3D starCenter; // Remember last star center coordinates in case of timeout in manual select mode QVector3D rememberStarCenter; /// Focus Frame FITSView *focusView { nullptr }; /// Star Select Timer QTimer waitStarSelectTimer; /// FITS Viewer in case user want to display in it instead of internal view QPointer fv; /// Track star position and HFR to know if we're detecting bogus stars due to detection algorithm false positive results QVector starsHFR; /// Relative Profile QCustomPlot *profilePlot { nullptr }; QDialog *profileDialog { nullptr }; /// Polynomial fitting. std::unique_ptr polynomialFit; int polySolutionFound { 0 }; QCPGraph *polynomialGraph = nullptr; QCPGraph *focusPoint = nullptr; bool polynomialGraphIsShown = false; // Capture timeout timer QTimer captureTimeout; uint8_t captureTimeoutCounter { 0 }; uint8_t captureFailureCounter { 0 }; // Guide Suspend bool m_GuidingSuspended { false }; // Filter Manager QSharedPointer filterManager; // Linear focuser. std::unique_ptr linearFocuser; int focuserAdditionalMovement { 0 }; int linearRequestedPosition { 0 }; bool hasDeviation { false }; }; } diff --git a/kstars/ekos/scheduler/scheduler.h b/kstars/ekos/scheduler/scheduler.h index b24c86c06..85dc651b2 100644 --- a/kstars/ekos/scheduler/scheduler.h +++ b/kstars/ekos/scheduler/scheduler.h @@ -1,800 +1,800 @@ /* Ekos Scheduler Module Copyright (C) 2015 Jasem Mutlaq DBus calls from GSoC 2015 Ekos Scheduler project by Daniel Leu This application 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. */ #pragma once #include "ui_scheduler.h" #include "ekos/align/align.h" #include "indi/indiweather.h" #include #include #include #include #include #include #include class QProgressIndicator; class GeoLocation; class SchedulerJob; class SkyObject; class KConfigDialog; namespace Ekos { class SequenceJob; /** * @brief The Ekos scheduler is a simple scheduler class to orchestrate automated multi object observation jobs. * @author Jasem Mutlaq * @version 1.2 */ class Scheduler : public QWidget, public Ui::Scheduler { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kstars.Ekos.Scheduler") Q_PROPERTY(Ekos::SchedulerState status READ status NOTIFY newStatus) Q_PROPERTY(QStringList logText READ logText NOTIFY newLog) Q_PROPERTY(QString profile READ profile WRITE setProfile) public: typedef enum { EKOS_IDLE, EKOS_STARTING, EKOS_STOPPING, EKOS_READY } EkosState; typedef enum { INDI_IDLE, INDI_CONNECTING, INDI_DISCONNECTING, INDI_PROPERTY_CHECK, INDI_READY } INDIState; typedef enum { STARTUP_IDLE, STARTUP_SCRIPT, STARTUP_UNPARK_DOME, STARTUP_UNPARKING_DOME, STARTUP_UNPARK_MOUNT, STARTUP_UNPARKING_MOUNT, STARTUP_UNPARK_CAP, STARTUP_UNPARKING_CAP, STARTUP_ERROR, STARTUP_COMPLETE } StartupState; typedef enum { SHUTDOWN_IDLE, SHUTDOWN_PARK_CAP, SHUTDOWN_PARKING_CAP, SHUTDOWN_PARK_MOUNT, SHUTDOWN_PARKING_MOUNT, SHUTDOWN_PARK_DOME, SHUTDOWN_PARKING_DOME, SHUTDOWN_SCRIPT, SHUTDOWN_SCRIPT_RUNNING, SHUTDOWN_ERROR, SHUTDOWN_COMPLETE } ShutdownState; typedef enum { PARKWAIT_IDLE, PARKWAIT_PARK, PARKWAIT_PARKING, PARKWAIT_PARKED, PARKWAIT_UNPARK, PARKWAIT_UNPARKING, PARKWAIT_UNPARKED, PARKWAIT_ERROR } ParkWaitStatus; /** @brief options what should happen if an error or abort occurs */ typedef enum { ERROR_DONT_RESTART, ERROR_RESTART_AFTER_TERMINATION, ERROR_RESTART_IMMEDIATELY } ErrorHandlingStrategy; /** @brief Columns, in the same order as UI. */ typedef enum { SCHEDCOL_NAME = 0, SCHEDCOL_STATUS, SCHEDCOL_CAPTURES, SCHEDCOL_ALTITUDE, SCHEDCOL_SCORE, SCHEDCOL_STARTTIME, SCHEDCOL_ENDTIME, SCHEDCOL_DURATION, SCHEDCOL_LEADTIME, SCHEDCOL_COUNT } SchedulerColumns; Scheduler(); ~Scheduler() = default; QString getCurrentJobName(); void appendLogText(const QString &); QStringList logText() { return m_LogText; } QString getLogText() { return m_LogText.join("\n"); } void clearLog(); void applyConfig(); void addObject(SkyObject *object); /** * @brief startSlew DBus call for initiating slew */ void startSlew(); /** * @brief startFocusing DBus call for feeding ekos the specified settings and initiating focus operation */ void startFocusing(); /** * @brief startAstrometry initiation of the capture and solve operation. We change the job state * after solver is started */ void startAstrometry(); /** - * @brief startGuiding After ekos is fed the calibration options, we start the guiging process + * @brief startGuiding After ekos is fed the calibration options, we start the guiding process * @param resetCalibration By default calibration is not reset until it is explicitly requested */ void startGuiding(bool resetCalibration = false); /** * @brief startCapture The current job file name is solved to an url which is fed to ekos. We then start the capture process * @param restart Set to true if the goal to restart an existing sequence. The only difference is that when a sequence is restarted, sequence file * is not loaded from disk again since that results in erasing all the history of the capture process. */ void startCapture(bool restart = false); /** * @brief getNextAction Checking for the next appropriate action regarding the current state of the scheduler and execute it */ void getNextAction(); /** * @brief disconnectINDI disconnect all INDI devices from server. */ void disconnectINDI(); /** * @brief stopEkos shutdown Ekos completely */ void stopEkos(); /** * @brief stopGuiding After guiding is done we need to stop the process */ void stopGuiding(); /** * @brief setSolverAction set the GOTO mode for the solver * @param mode 0 For Sync, 1 for SlewToTarget, 2 for Nothing */ void setSolverAction(Align::GotoMode mode); /** @defgroup SchedulerDBusInterface Ekos DBus Interface - Scheduler Module * Ekos::Align interface provides primary functions to run and stop the scheduler. */ /*@{*/ /** DBUS interface function. * @brief Start the scheduler main loop and evaluate jobs and execute them accordingly. */ Q_SCRIPTABLE Q_NOREPLY void start(); /** DBUS interface function. * @brief Stop the scheduler. */ Q_SCRIPTABLE Q_NOREPLY void stop(); /** DBUS interface function. * @brief Loads the Ekos Scheduler List (.esl) file. * @param fileURL path to a file * @return true if loading file is successful, false otherwise. */ Q_SCRIPTABLE bool loadScheduler(const QString &fileURL); /** DBUS interface function. * @brief Resets all jobs to IDLE */ Q_SCRIPTABLE void resetAllJobs(); /** DBUS interface function. * @brief Resets all jobs to IDLE */ Q_SCRIPTABLE void sortJobsPerAltitude(); Ekos::SchedulerState status() { return state; } void setProfile(const QString &profile) { schedulerProfileCombo->setCurrentText(profile); } QString profile() { return schedulerProfileCombo->currentText(); } /** * @brief retrieve the error handling strategy from the UI */ ErrorHandlingStrategy getErrorHandlingStrategy(); /** * @brief select the error handling strategy (no restart, restart after all terminated, restart immediately) */ void setErrorHandlingStrategy (ErrorHandlingStrategy strategy); /** @}*/ /** @{ */ private: /** @internal Safeguard flag to avoid registering signals from widgets multiple times. */ bool jobChangesAreWatched { false }; protected: /** @internal Enables signal watch on SchedulerJob form values in order to apply changes to current job. * @param enable is the toggle flag, true to watch for changes, false to ignore them. */ void watchJobChanges(bool enable); /** @internal Marks the currently selected SchedulerJob as modified change. * * This triggers job re-evaluation. * Next time save button is invoked, the complete content is written to disk. */ void setDirty(); /** @} */ protected: /** @internal Associate job table cells on a row to the corresponding SchedulerJob. * @param row is an integer indexing the row to associate cells from, and also the index of the job in the job list.. */ void setJobStatusCells(int row); protected slots: /** * @brief registerNewModule Register an Ekos module as it arrives via DBus * and create the appropriate DBus interface to communicate with it. * @param name of module */ void registerNewModule(const QString &name); /** * @brief syncProperties Sync startup properties from the various device to enable/disable features in the scheduler * like the ability to park/unpark..etc */ void syncProperties(); void setAlignStatus(Ekos::AlignState status); void setGuideStatus(Ekos::GuideState status); void setCaptureStatus(Ekos::CaptureState status); void setFocusStatus(Ekos::FocusState status); void setMountStatus(ISD::Telescope::Status status); void setWeatherStatus(ISD::Weather::Status status); /** * @brief select object from KStars's find dialog. */ void selectObject(); /** * @brief Selects FITS file for solving. */ void selectFITS(); /** * @brief Selects sequence queue. */ void selectSequence(); /** * @brief Selects sequence queue. */ void selectStartupScript(); /** * @brief Selects sequence queue. */ void selectShutdownScript(); /** * @brief addToQueue Construct a SchedulerJob and add it to the queue or save job settings from current form values. * jobUnderEdit determines whether to add or edit */ void saveJob(); /** * @brief addJob Add a new job from form values */ void addJob(); /** * @brief editJob Edit an observation job * @param i index model in queue table */ void loadJob(QModelIndex i); /** * @brief removeJob Remove a job from the currently selected row. If no row is selected, it remove the last job in the queue. */ void removeJob(); /** * @brief setJobAddApply Set first button state to add new job or apply changes. */ void setJobAddApply(bool add_mode); /** * @brief setJobManipulation Enable or disable job manipulation buttons. */ void setJobManipulation(bool can_reorder, bool can_delete); /** * @brief set all GUI fields to the values of the given scheduler job */ void syncGUIToJob(SchedulerJob *job); /** * @brief jobSelectionChanged Update UI state when the job list is clicked once. */ void clickQueueTable(QModelIndex index); /** * @brief Update scheduler parameters to the currently selected scheduler job * @param current table position * @param previous table position */ void queueTableSelectionChanged(QModelIndex current, QModelIndex previous); /** * @brief reorderJobs Change the order of jobs in the UI based on a subset of its jobs. */ bool reorderJobs(QList reordered_sublist); /** * @brief moveJobUp Move the selected job up in the job list. */ void moveJobUp(); /** * @brief moveJobDown Move the selected job down in the list. */ void moveJobDown(); /** * @brief shouldSchedulerSleep Check if the scheduler needs to sleep until the job is ready * @param currentJob Job to check * @return True if we set the scheduler to sleep mode. False, if not required and we need to execute now */ bool shouldSchedulerSleep(SchedulerJob *currentJob); void toggleScheduler(); void pause(); void setPaused(); void save(); void saveAs(); void load(); void resetJobEdit(); /** * @brief checkJobStatus Check the overall state of the scheduler, Ekos, and INDI. When all is OK, it calls evaluateJobs() when no job is current or executeJob() if a job is selected. * @return False if this function needs to be called again later, true if situation is stable and operations may continue. */ bool checkStatus(); /** * @brief checkJobStage Check the progress of the job states and make DBUS call to start the next stage until the job is complete. */ void checkJobStage(); /** * @brief findNextJob Check if the job met the completion criteria, and if it did, then it search for next job candidate. If no jobs are found, it starts the shutdown stage. */ void findNextJob(); /** * @brief stopCurrentJobAction Stop whatever action taking place in the current job (eg. capture, guiding...etc). */ void stopCurrentJobAction(); /** * @brief manageConnectionLoss Mitigate loss of connection with the INDI server. * @return true if connection to Ekos/INDI should be attempted again, false if not mitigation is available or needed. */ bool manageConnectionLoss(); /** * @brief readProcessOutput read running script process output and display it in Ekos */ void readProcessOutput(); /** * @brief checkProcessExit Check script process exist status. This is called when the process exists either normally or abnormally. * @param exitCode exit code from the script process. Depending on the exist code, the status of startup/shutdown procedure is set accordingly. */ void checkProcessExit(int exitCode); /** * @brief resumeCheckStatus If the scheduler primary loop was suspended due to weather or sleep event, resume it again. */ void resumeCheckStatus(); /** * @brief checkWeather Check weather status and act accordingly depending on the current status of the scheduler and running jobs. */ //void checkWeather(); /** * @brief wakeUpScheduler Wake up scheduler from sleep state */ void wakeUpScheduler(); /** * @brief startJobEvaluation Start job evaluation only without starting the scheduler process itself. Display the result to the user. */ void startJobEvaluation(); /** * @brief startMosaicTool Start Mosaic tool and create jobs if necessary. */ void startMosaicTool(); /** * @brief displayTwilightWarning Display twilight warning to user if it is unchecked. */ void checkTwilightWarning(bool enabled); void runStartupProcedure(); void checkStartupProcedure(); void runShutdownProcedure(); void checkShutdownProcedure(); void setINDICommunicationStatus(Ekos::CommunicationStatus status); void setEkosCommunicationStatus(Ekos::CommunicationStatus status); void simClockScaleChanged(float); signals: void newLog(const QString &text); void newStatus(Ekos::SchedulerState state); void weatherChanged(ISD::Weather::Status state); void newTarget(const QString &); private: /** * @brief evaluateJobs evaluates the current state of each objects and gives each one a score based on the constraints. * Given that score, the scheduler will decide which is the best job that needs to be executed. */ void evaluateJobs(); /** * @brief executeJob After the best job is selected, we call this in order to start the process that will execute the job. * checkJobStatus slot will be connected in order to figure the exact state of the current job each second * @param value */ void executeJob(SchedulerJob *job); void executeScript(const QString &filename); /** * @brief getDarkSkyScore Get the dark sky score of a date and time. The further from dawn the better. * @param when date and time to check the dark sky score, now if omitted * @return Dark sky score. Daylight get bad score, as well as pre-dawn to dawn. */ int16_t getDarkSkyScore(QDateTime const &when = QDateTime()) const; /** * @brief calculateJobScore Calculate job dark sky score, altitude score, and moon separation scores and returns the sum. * @param job Target * @param when date and time to evaluate constraints, now if omitted. * @return Total score */ int16_t calculateJobScore(SchedulerJob const *job, QDateTime const &when = QDateTime()) const; /** * @brief getWeatherScore Get current weather condition score. * @return If weather condition OK, return score 0, else bad score. */ int16_t getWeatherScore() const; /** * @brief calculateDawnDusk Get dawn and dusk times for today */ void calculateDawnDusk(); /** * @brief checkEkosState Check ekos startup stages and take whatever action necessary to get Ekos up and running * @return True if Ekos is running, false if Ekos start up is in progress. */ bool checkEkosState(); /** * @brief isINDIConnected Determines the status of the INDI connection. * @return True if INDI connection is up and usable, else false. */ bool isINDIConnected(); /** * @brief checkINDIState Check INDI startup stages and take whatever action necessary to get INDI devices connected. * @return True if INDI devices are connected, false if it is under progress. */ bool checkINDIState(); /** * @brief checkStartupState Check startup procedure stages and make sure all stages are complete. * @return True if startup is complete, false otherwise. */ bool checkStartupState(); /** * @brief checkShutdownState Check shutdown procedure stages and make sure all stages are complete. * @return */ bool checkShutdownState(); /** * @brief checkParkWaitState Check park wait state. * @return If parking/unparking in progress, return false. If parking/unparking complete, return true. */ bool checkParkWaitState(); /** * @brief parkMount Park mount */ void parkMount(); /** * @brief unParkMount Unpark mount */ void unParkMount(); /** * @return True if mount is parked */ bool isMountParked(); /** * @brief parkDome Park dome */ void parkDome(); /** * @brief unParkDome Unpark dome */ void unParkDome(); /** * @return True if dome is parked */ bool isDomeParked(); /** * @brief parkCap Close dust cover */ void parkCap(); /** * @brief unCap Open dust cover */ void unParkCap(); /** * @brief checkMountParkingStatus check mount parking status and updating corresponding states accordingly. */ void checkMountParkingStatus(); /** * @brief checkDomeParkingStatus check dome parking status and updating corresponding states accordingly. */ void checkDomeParkingStatus(); /** * @brief checkDomeParkingStatus check dome parking status and updating corresponding states accordingly. */ void checkCapParkingStatus(); /** * @brief saveScheduler Save scheduler jobs to a file * @param path path of a file * @return true on success, false on failure. */ bool saveScheduler(const QUrl &fileURL); /** * @brief processJobInfo Process the job information from a scheduler file and populate jobs accordingly * @param root XML root element of JOB * @return true on success, false on failure. */ bool processJobInfo(XMLEle *root); /** * @brief updatePreDawn Update predawn time depending on current time and user offset */ void updatePreDawn(); /** * @brief estimateJobTime Estimates the time the job takes to complete based on the sequence file and what modules to utilize during the observation run. * @param job target job * @return Estimated time in seconds. */ bool estimateJobTime(SchedulerJob *schedJob); /** * @brief createJobSequence Creates a job sequence for the mosaic tool given the prefix and output dir. The currently selected sequence file is modified * and a new version given the supplied parameters are saved to the output directory * @param prefix Prefix to set for the job sequence * @param outputDir Output dir to set for the job sequence * @return True if new file is saved, false otherwise */ bool createJobSequence(XMLEle *root, const QString &prefix, const QString &outputDir); /** @internal Change the current job, updating associated widgets. * @param job is an existing SchedulerJob to set as current, or nullptr. */ void setCurrentJob(SchedulerJob *job); /** * @brief processFITSSelection When a FITS file is selected, open it and try to guess * the object name, and its J2000 RA/DE to fill the UI with such info automatically. */ void processFITSSelection(); void loadProfiles(); XMLEle *getSequenceJobRoot(); bool isWeatherOK(SchedulerJob *job); /** * @brief updateCompletedJobsCount For each scheduler job, examine sequence job storage and count captures. * @param forced forces recounting captures unconditionally if true, else only IDLE, EVALUATION or new jobs are examined. */ void updateCompletedJobsCount(bool forced = false); SequenceJob *processJobInfo(XMLEle *root, SchedulerJob *schedJob); bool loadSequenceQueue(const QString &fileURL, SchedulerJob *schedJob, QList &jobs, bool &hasAutoFocus); int getCompletedFiles(const QString &path, const QString &seqPrefix); // retrieve the guiding status GuideState getGuidingStatus(); Ekos::Scheduler *ui { nullptr }; //DBus interfaces QPointer focusInterface { nullptr }; QPointer ekosInterface { nullptr }; QPointer captureInterface { nullptr }; QPointer mountInterface { nullptr }; QPointer alignInterface { nullptr }; QPointer guideInterface { nullptr }; QPointer domeInterface { nullptr }; QPointer weatherInterface { nullptr }; QPointer capInterface { nullptr }; // Scheduler and job state and stages SchedulerState state { SCHEDULER_IDLE }; EkosState ekosState { EKOS_IDLE }; INDIState indiState { INDI_IDLE }; StartupState startupState { STARTUP_IDLE }; ShutdownState shutdownState { SHUTDOWN_IDLE }; ParkWaitStatus parkWaitState { PARKWAIT_IDLE }; Ekos::CommunicationStatus m_EkosCommunicationStatus { Ekos::Idle }; Ekos::CommunicationStatus m_INDICommunicationStatus { Ekos::Idle }; /// List of all jobs as entered by the user or file QList jobs; /// Active job SchedulerJob *currentJob { nullptr }; /// URL to store the scheduler file QUrl schedulerURL; /// URL for Ekos Sequence QUrl sequenceURL; /// FITS URL to solve QUrl fitsURL; /// Startup script URL QUrl startupScriptURL; /// Shutdown script URL QUrl shutdownScriptURL; /// Store all log strings QStringList m_LogText; /// Busy indicator widget QProgressIndicator *pi { nullptr }; /// Are we editing a job right now? Job row index int jobUnderEdit { -1 }; /// Pointer to Geographic location GeoLocation *geo { nullptr }; /// How many repeated job batches did we complete thus far? uint16_t captureBatch { 0 }; /// Startup and Shutdown scripts process QProcess scriptProcess; /// Store day fraction of dawn to calculate dark skies range double Dawn { -1 }; /// Store day fraction of dusk to calculate dark skies range double Dusk { -1 }; /// Pre-dawn is where we stop all jobs, it is a user-configurable value before Dawn. QDateTime preDawnDateTime; /// Dusk date time QDateTime duskDateTime; /// Was job modified and needs saving? bool mDirty { false }; /// Keep watch of weather status ISD::Weather::Status weatherStatus { ISD::Weather::WEATHER_IDLE }; /// Keep track of how many times we didn't receive weather updates uint8_t noWeatherCounter { 0 }; /// Are we shutting down until later? bool preemptiveShutdown { false }; /// Only run job evaluation bool jobEvaluationOnly { false }; /// Keep track of Load & Slew operation bool loadAndSlewProgress { false }; /// Check if initial autofocus is completed and do not run autofocus until there is a change is telescope position/alignment. bool autofocusCompleted { false }; /// Keep track of INDI connection failures uint8_t indiConnectFailureCount { 0 }; /// Keep track of Ekos connection failures uint8_t ekosConnectFailureCount { 0 }; /// Keep track of Ekos focus module failures uint8_t focusFailureCount { 0 }; /// Keep track of Ekos guide module failures uint8_t guideFailureCount { 0 }; /// Keep track of Ekos align module failures uint8_t alignFailureCount { 0 }; /// Keep track of Ekos capture module failures uint8_t captureFailureCount { 0 }; /// Counter to keep debug logging in check uint8_t checkJobStageCounter { 0 }; /// Call checkWeather when weatherTimer time expires. It is equal to the UpdatePeriod time in INDI::Weather device. //QTimer weatherTimer; /// Timer to put the scheduler into sleep mode until a job is ready QTimer sleepTimer; /// To call checkStatus QTimer schedulerTimer; /// To call checkJobStage QTimer jobTimer; /// Delay for restarting the guider QTimer restartGuidingTimer; /// Generic time to track timeout of current operation in progress QTime currentOperationTime; QUrl dirPath; QMap capturedFramesCount; bool m_MountReady { false }; bool m_CaptureReady { false }; bool m_DomeReady { false }; bool m_CapReady { false }; // When a module is commanded to perform an action, wait this many milliseconds // before check its state again. If State is still IDLE, then it either didn't received the command // or there is another problem. static const uint32_t ALIGN_INACTIVITY_TIMEOUT = 120000; static const uint32_t FOCUS_INACTIVITY_TIMEOUT = 120000; static const uint32_t CAPTURE_INACTIVITY_TIMEOUT = 120000; static const uint16_t GUIDE_INACTIVITY_TIMEOUT = 60000; }; } diff --git a/kstars/fitsviewer/fitstab.cpp b/kstars/fitsviewer/fitstab.cpp index 978df698b..8b6418848 100644 --- a/kstars/fitsviewer/fitstab.cpp +++ b/kstars/fitsviewer/fitstab.cpp @@ -1,728 +1,728 @@ /*************************************************************************** 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. * * * ***************************************************************************/ #include "fitstab.h" #include "fitsdata.h" #include "fitshistogram.h" #include "fitsview.h" #include "fitsviewer.h" #include "ksnotification.h" #include "kstars.h" #include "Options.h" #include "ui_fitsheaderdialog.h" #include "ui_statform.h" #include #include #include #include namespace { const char kAutoToolTip[] = "Automatically find stretch parameters"; const char kStretchOffToolTip[] = "Stretch the image"; const char kStretchOnToolTip[] = "Disable stretching of the image."; } // namespace FITSTab::FITSTab(FITSViewer *parent) : QWidget(parent) { viewer = parent; undoStack = new QUndoStack(this); undoStack->setUndoLimit(10); undoStack->clear(); connect(undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(modifyFITSState(bool))); statWidget = new QDialog(this); fitsHeaderDialog = new QDialog(this); histogram = new FITSHistogram(this); } FITSTab::~FITSTab() { // Make sure it's done //histogramFuture.waitForFinished(); //disconnect(); } void FITSTab::saveUnsaved() { if (undoStack->isClean() || view->getMode() != FITS_NORMAL) return; 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?"); int ans = KMessageBox::warningYesNoCancel(nullptr, message, caption, KStandardGuiItem::save(), KStandardGuiItem::discard()); if (ans == KMessageBox::Yes) saveFile(); if (ans == KMessageBox::No) { undoStack->clear(); modifyFITSState(); } } void FITSTab::closeEvent(QCloseEvent *ev) { saveUnsaved(); if (undoStack->isClean()) ev->accept(); else ev->ignore(); } QString FITSTab::getPreviewText() const { return previewText; } void FITSTab::setPreviewText(const QString &value) { previewText = value; } void FITSTab::selectRecentFITS(int i) { loadFITS(QUrl::fromLocalFile(recentImages->item(i)->text())); } void FITSTab::clearRecentFITS() { disconnect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS); recentImages->clear(); connect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS); } namespace { // 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(value * 10000 / maxValue)); QString valStr = QString("%1").arg(static_cast(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, const QString& name, QHBoxLayout *layout) { 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); } - // Only activeate the auto button if stretching is on and auto-stretching is not set. + // Only activate the auto button if stretching is on and auto-stretching is not set. 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)); setupStretchSlider(highlightsSlider.get(), highlightsLabel.get(), highlightsVal.get(), fontSize, "Hightlights", stretchBarLayout); // 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); connect(stretchButton.get(), &QPushButton::clicked, [=]() { // This will toggle whether we're currently stretching. view->setStretch(!view->isImageStretched()); }); // Make rough displays for the slider movement. connect(shadowsSlider.get(), &QSlider::sliderMoved, [=](int value) { StretchParams params = view->getStretchParams(); params.grey_red.shadows = this->maxShadows * value / 10000.0f; view->setSampling(4); view->setStretchParams(params); view->setSampling(1); }); connect(midtonesSlider.get(), &QSlider::sliderMoved, [=](int value) { StretchParams params = view->getStretchParams(); params.grey_red.midtones = this->maxMidtones * value / 10000.0f; view->setSampling(4); view->setStretchParams(params); view->setSampling(1); }); connect(highlightsSlider.get(), &QSlider::sliderMoved, [=](int value) { StretchParams params = view->getStretchParams(); params.grey_red.highlights = this->maxHighlights * value / 10000.0f; view->setSampling(4); view->setStretchParams(params); view->setSampling(1); }); // Make a final full-res display when the slider is released. connect(shadowsSlider.get(), &QSlider::sliderReleased, [=]() { if (!view) return; rescaleShadows(); StretchParams params = view->getStretchParams(); view->setStretchParams(params); }); connect(midtonesSlider.get(), &QSlider::sliderReleased, [=]() { if (!view) return; rescaleMidtones(); StretchParams params = view->getStretchParams(); view->setStretchParams(params); }); connect(highlightsSlider.get(), &QSlider::sliderReleased, [=]() { if (!view) return; StretchParams params = view->getStretchParams(); view->setStretchParams(params); }); connect(autoButton.get(), &QPushButton::clicked, [=]() { // 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. connect(view.get(), &FITSView::newStatus, [=](const QString &ignored) { Q_UNUSED(ignored) bool slidersInUse = shadowsSlider->isSliderDown() || midtonesSlider->isSliderDown() || highlightsSlider->isSliderDown(); if (!slidersInUse) { rescaleShadows(); rescaleMidtones(); } setStretchUIValues(!slidersInUse); }); return stretchBarLayout; } bool FITSTab::setupView(FITSMode mode, FITSScale filter) { if (view.get() == nullptr) { view.reset(new FITSView(this, mode, filter)); view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); QVBoxLayout *vlayout = new QVBoxLayout(); fitsSplitter = new QSplitter(Qt::Horizontal, this); fitsTools = new QToolBox(); stat.setupUi(statWidget); 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); } fitsTools->addItem(statWidget, i18n("Statistics")); fitsTools->addItem(histogram, i18n("Histogram")); header.setupUi(fitsHeaderDialog); fitsTools->addItem(fitsHeaderDialog, i18n("FITS Header")); QVBoxLayout *recentPanelLayout = new QVBoxLayout(); QWidget *recentPanel = new QWidget(fitsSplitter); recentPanel->setLayout(recentPanelLayout); fitsTools->addItem(recentPanel, i18n("Recent Images")); 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); 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() << 0 << view->width() ); vlayout->addWidget(fitsSplitter); vlayout->addLayout(setupStretchBar()); connect(fitsSplitter, &QSplitter::splitterMoved, histogram, &FITSHistogram::resizePlot); setLayout(vlayout); connect(view.get(), &FITSView::newStatus, this, &FITSTab::newStatus); connect(view.get(), &FITSView::debayerToggled, this, &FITSTab::debayerToggled); // On Failure to load connect(view.get(), &FITSView::failed, this, &FITSTab::failed); return true; } // returns false if no setup needed. return false; } void FITSTab::loadFITS(const QUrl &imageURL, FITSMode mode, FITSScale filter, bool silent) { if (setupView(mode, filter)) { // On Success loading image connect(view.get(), &FITSView::loaded, [&]() { processData(); emit loaded(); }); } currentURL = imageURL; view->setFilter(filter); view->loadFITS(imageURL.toLocalFile(), silent); } void FITSTab::processData() { FITSData *image_data = view->getImageData(); histogram->reset(); image_data->setHistogram(histogram); // Only construct histogram if it is actually visible // Otherwise wait until histogram is needed before creating it. if (fitsSplitter->sizes().at(0) != 0) { histogram->constructHistogram(); } if (viewer->isStarsMarked()) { view->toggleStars(true); qCDebug(KSTARS_FITS) << "FITS HFR:" << image_data->getHFR(); } evaluateStats(); loadFITSHeader(); // Don't add it to the list if it is already there if (recentImages->findItems(currentURL.toLocalFile(), Qt::MatchExactly).count() == 0) { if(!image_data->isTempFile()) //Don't add it to the list if it is a preview { disconnect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS); recentImages->addItem(currentURL.toLocalFile()); recentImages->setCurrentRow(recentImages->count() - 1); connect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS); } } view->updateFrame(); } bool FITSTab::loadFITSFromData(FITSData* data, const QUrl &imageURL, FITSMode mode, FITSScale filter) { setupView(mode, filter); currentURL = imageURL; view->setFilter(filter); if (!view->loadFITSFromData(data, imageURL.toLocalFile())) { // On Failure to load // connect(view.get(), &FITSView::failed, this, &FITSTab::failed); return false; } processData(); return true; } void FITSTab::modifyFITSState(bool clean) { if (clean) { if (undoStack->isClean() == false) undoStack->setClean(); mDirty = false; } else mDirty = true; emit changeStatus(clean); } int FITSTab::saveFITS(const QString &filename) { return view->saveFITS(filename); } void FITSTab::copyFITS() { QApplication::clipboard()->setImage(view->getDisplayImage()); } void FITSTab::histoFITS() { if (!histogram->isConstructed()) { histogram->constructHistogram(); evaluateStats(); } fitsTools->setCurrentIndex(1); if(view->width() > 200) fitsSplitter->setSizes(QList() << 200 << view->width() - 200); else fitsSplitter->setSizes(QList() << 50 << 50); } void FITSTab::evaluateStats() { FITSData *image_data = view->getImageData(); 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++) { if (stat.statsTable->columnSpan(i, 0) != 3) stat.statsTable->setSpan(i, 0, 1, 3); } 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++) { if (stat.statsTable->columnSpan(i, 0) != 1) stat.statsTable->setSpan(i, 0, 1, 1); } stat.statsTable->horizontalHeaderItem(0)->setText(i18nc("Red", "R")); stat.statsTable->showColumn(1); stat.statsTable->showColumn(2); } if (image_data->getMedian() == 0.0 && !histogram->isConstructed()) histogram->constructHistogram(); 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)); } } void FITSTab::statFITS() { fitsTools->setCurrentIndex(0); if(view->width() > 200) fitsSplitter->setSizes(QList() << 200 << view->width() - 200); else fitsSplitter->setSizes(QList() << 50 << 50); } void FITSTab::loadFITSHeader() { FITSData *image_data = view->getImageData(); int nkeys = image_data->getRecords().size(); int counter = 0; header.tableWidget->setRowCount(nkeys); for (FITSData::Record *oneRecord : image_data->getRecords()) { QTableWidgetItem *tempItem = new QTableWidgetItem(oneRecord->key); tempItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); header.tableWidget->setItem(counter, 0, tempItem); tempItem = new QTableWidgetItem(oneRecord->value.toString()); tempItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); header.tableWidget->setItem(counter, 1, tempItem); tempItem = new QTableWidgetItem(oneRecord->comment); tempItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); header.tableWidget->setItem(counter, 2, tempItem); counter++; } header.tableWidget->setColumnWidth(0, 100); header.tableWidget->setColumnWidth(1, 100); header.tableWidget->setColumnWidth(2, 250); } void FITSTab::headerFITS() { fitsTools->setCurrentIndex(2); if(view->width() > 200) fitsSplitter->setSizes(QList() << 200 << view->width() - 200); else fitsSplitter->setSizes(QList() << 50 << 50); } bool FITSTab::saveFile() { QUrl backupCurrent = currentURL; QUrl currentDir(Options::fitsDir()); currentDir.setScheme("file"); if (currentURL.toLocalFile().startsWith(QLatin1String("/tmp/")) || currentURL.toLocalFile().contains("/Temp")) currentURL.clear(); // If no changes made, return. if (mDirty == false && !currentURL.isEmpty()) return false; if (currentURL.isEmpty()) { currentURL = QFileDialog::getSaveFileUrl(KStars::Instance(), i18n("Save FITS"), currentDir, "FITS (*.fits *.fits.gz *.fit)"); // if user presses cancel if (currentURL.isEmpty()) { currentURL = backupCurrent; return false; } if (currentURL.toLocalFile().contains('.') == 0) currentURL.setPath(currentURL.toLocalFile() + ".fits"); // Already display by dialog /*if (QFile::exists(currentURL.toLocalFile())) { int r = KMessageBox::warningContinueCancel(0, i18n( "A file named \"%1\" already exists. " "Overwrite it?", currentURL.fileName() ), i18n( "Overwrite File?" ), KGuiItem(i18n( "&Overwrite" )) ); if(r==KMessageBox::Cancel) return false; }*/ } if (currentURL.isValid()) { int err_status = 0; if ((err_status = saveFITS('!' + currentURL.toLocalFile())) != 0) { // -1000 = user canceled if (err_status == -1000) return false; char err_text[FLEN_STATUS]; fits_get_errstatus(err_status, err_text); // Use KMessageBox or something here KSNotification::error(i18n("FITS file save error: %1", QString::fromUtf8(err_text)), i18n("FITS Save")); return false; } //statusBar()->changeItem(i18n("File saved."), 3); emit newStatus(i18n("File saved to %1", currentURL.url()), FITS_MESSAGE); modifyFITSState(); return true; } else { QString message = i18n("Invalid URL: %1", currentURL.url()); KSNotification::sorry(message, i18n("Invalid URL")); return false; } } bool FITSTab::saveFileAs() { currentURL.clear(); return saveFile(); } void FITSTab::ZoomIn() { QPoint oldCenter = view->getImagePoint(view->viewport()->rect().center()); view->ZoomIn(); view->cleanUpZoom(oldCenter); } void FITSTab::ZoomOut() { QPoint oldCenter = view->getImagePoint(view->viewport()->rect().center()); view->ZoomOut(); view->cleanUpZoom(oldCenter); } void FITSTab::ZoomDefault() { QPoint oldCenter = view->getImagePoint(view->viewport()->rect().center()); view->ZoomDefault(); view->cleanUpZoom(oldCenter); } void FITSTab::tabPositionUpdated() { undoStack->setActive(true); emit newStatus(QString("%1%").arg(view->getCurrentZoom()), FITS_ZOOM); emit newStatus(QString("%1x%2").arg(view->getImageData()->width()).arg(view->getImageData()->height()), FITS_RESOLUTION); } diff --git a/kstars/fitsviewer/fitsview.cpp b/kstars/fitsviewer/fitsview.cpp index a85ea8f91..1a45cc13a 100644 --- a/kstars/fitsviewer/fitsview.cpp +++ b/kstars/fitsviewer/fitsview.cpp @@ -1,1928 +1,1928 @@ /* FITS View Copyright (C) 2003-2017 Jasem Mutlaq Copyright (C) 2016-2017 Robert Lancaster This application 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. */ #include "config-kstars.h" #include "fitsview.h" #include "fitsdata.h" #include "fitslabel.h" #include "kspopupmenu.h" #include "kstarsdata.h" #include "ksutils.h" #include "Options.h" #include "skymap.h" #include "fits_debug.h" #include "stretch.h" #ifdef HAVE_INDI #include "basedevice.h" #include "indi/indilistener.h" #endif #include #include #include #include #include #include #include #include #define BASE_OFFSET 50 #define ZOOM_DEFAULT 100.0f #define ZOOM_MIN 10 // ZOOM_MAX is adjusted in the constructor if the amount of physical memory is known. #define ZOOM_MAX 300 #define ZOOM_LOW_INCR 10 #define ZOOM_HIGH_INCR 50 #define FONT_SIZE 14 namespace { // Derive the Green and Blue stretch parameters from their previous values and the // changes made to the Red parameters. We apply the same offsets used for Red to the // other channels' parameters, but clip them. void ComputeGBStretchParams(const StretchParams &newParams, StretchParams* params) { float shadow_diff = newParams.grey_red.shadows - params->grey_red.shadows; float highlight_diff = newParams.grey_red.highlights - params->grey_red.highlights; float midtones_diff = newParams.grey_red.midtones - params->grey_red.midtones; params->green.shadows = params->green.shadows + shadow_diff; params->green.shadows = KSUtils::clamp(params->green.shadows, 0.0f, 1.0f); params->green.highlights = params->green.highlights + highlight_diff; params->green.highlights = KSUtils::clamp(params->green.highlights, 0.0f, 1.0f); params->green.midtones = params->green.midtones + midtones_diff; params->green.midtones = std::max(params->green.midtones, 0.0f); params->blue.shadows = params->blue.shadows + shadow_diff; params->blue.shadows = KSUtils::clamp(params->blue.shadows, 0.0f, 1.0f); params->blue.highlights = params->blue.highlights + highlight_diff; params->blue.highlights = KSUtils::clamp(params->blue.highlights, 0.0f, 1.0f); params->blue.midtones = params->blue.midtones + midtones_diff; params->blue.midtones = std::max(params->blue.midtones, 0.0f); } } // namespace // Runs the stretch checking the variables to see which parameters to use. // We call stretch even if we're not stretching, as the stretch code still // converts the image to the uint8 output image which will be displayed. // In that case, it will use an identity stretch. void FITSView::doStretch(FITSData *data, QImage *outputImage) { if (outputImage->isNull()) return; Stretch stretch(static_cast(data->width()), static_cast(data->height()), data->channels(), data->property("dataType").toInt()); StretchParams tempParams; if (!stretchImage) tempParams = StretchParams(); // Keeping it linear else if (autoStretch) { // Compute new auto-stretch params. stretchParams = stretch.computeParams(data->getImageBuffer()); tempParams = stretchParams; } else // Use the existing stretch params. tempParams = stretchParams; stretch.setParams(tempParams); stretch.run(data->getImageBuffer(), outputImage, sampling); } // Store stretch parameters, and turn on stretching if it isn't already on. void FITSView::setStretchParams(const StretchParams ¶ms) { if (imageData->channels() == 3) ComputeGBStretchParams(params, &stretchParams); stretchParams.grey_red = params.grey_red; stretchParams.grey_red.shadows = std::max(stretchParams.grey_red.shadows, 0.0f); stretchParams.grey_red.highlights = std::max(stretchParams.grey_red.highlights, 0.0f); stretchParams.grey_red.midtones = std::max(stretchParams.grey_red.midtones, 0.0f); autoStretch = false; stretchImage = true; if (image_frame != nullptr && rescale(ZOOM_KEEP_LEVEL)) updateFrame(); } // Turn on or off stretching, and if on, use whatever parameters are currently stored. void FITSView::setStretch(bool onOff) { if (stretchImage != onOff) { stretchImage = onOff; if (image_frame != nullptr && rescale(ZOOM_KEEP_LEVEL)) updateFrame(); } } // Turn on stretching, using automatically generated parameters. void FITSView::setAutoStretchParams() { stretchImage = true; autoStretch = true; if (image_frame != nullptr && rescale(ZOOM_KEEP_LEVEL)) updateFrame(); } FITSView::FITSView(QWidget * parent, FITSMode fitsMode, FITSScale filterType) : QScrollArea(parent), zoomFactor(1.2) { // stretchImage is whether to stretch or not--the stretch may or may not use automatically generated parameters. // The user may enter his/her own. stretchImage = Options::autoStretch(); // autoStretch means use automatically-generated parameters. This is the default, unless the user overrides // by adjusting the stretchBar's sliders. autoStretch = true; // Adjust the maximum zoom according to the amount of memory. // There have been issues with users running out system memory because of zoom memory. // Note: this is not currently image dependent. It's possible, but not implemented, // to allow for more zooming on smaller images. zoomMax = ZOOM_MAX; #if defined (Q_OS_LINUX) || defined (Q_OS_OSX) const long numPages = sysconf(_SC_PAGESIZE); const long pageSize = sysconf(_SC_PHYS_PAGES); // _SC_PHYS_PAGES "may not be standard" http://man7.org/linux/man-pages/man3/sysconf.3.html // If an OS doesn't support it, sysconf should return -1. if (numPages > 0 && pageSize > 0) { // (numPages * pageSize) will likely overflow a 32bit int, so use floating point calculations. const int memoryMb = numPages * (static_cast(pageSize) / 1e6); if (memoryMb < 2000) zoomMax = 100; else if (memoryMb < 4000) zoomMax = 200; else if (memoryMb < 8000) zoomMax = 300; else if (memoryMb < 16000) zoomMax = 400; else zoomMax = 600; } #endif grabGesture(Qt::PinchGesture); image_frame.reset(new FITSLabel(this)); filter = filterType; mode = fitsMode; setBackgroundRole(QPalette::Dark); markerCrosshair.setX(0); markerCrosshair.setY(0); setBaseSize(740, 530); connect(image_frame.get(), SIGNAL(newStatus(QString, FITSBar)), this, SIGNAL(newStatus(QString, FITSBar))); connect(image_frame.get(), SIGNAL(pointSelected(int, int)), this, SLOT(processPointSelection(int, int))); connect(image_frame.get(), SIGNAL(markerSelected(int, int)), this, SLOT(processMarkerSelection(int, int))); connect(&wcsWatcher, SIGNAL(finished()), this, SLOT(syncWCSState())); connect(&fitsWatcher, &QFutureWatcher::finished, this, &FITSView::loadInFrame); image_frame->setMouseTracking(true); setCursorMode( selectCursor); //This is the default mode because the Focus and Align FitsViews should not be in dragMouse mode noImageLabel = new QLabel(); noImage.load(":/images/noimage.png"); noImageLabel->setPixmap(noImage); noImageLabel->setAlignment(Qt::AlignCenter); this->setWidget(noImageLabel); redScopePixmap = QPixmap(":/icons/center_telescope_red.svg").scaled(32, 32, Qt::KeepAspectRatio, Qt::FastTransformation); magentaScopePixmap = QPixmap(":/icons/center_telescope_magenta.svg").scaled(32, 32, Qt::KeepAspectRatio, Qt::FastTransformation); } FITSView::~FITSView() { fitsWatcher.waitForFinished(); wcsWatcher.waitForFinished(); delete (imageData); } /** This method looks at what mouse mode is currently selected and updates the cursor to match. */ void FITSView::updateMouseCursor() { if (cursorMode == dragCursor) { if (horizontalScrollBar()->maximum() > 0 || verticalScrollBar()->maximum() > 0) { if (!image_frame->getMouseButtonDown()) viewport()->setCursor(Qt::PointingHandCursor); else viewport()->setCursor(Qt::ClosedHandCursor); } else viewport()->setCursor(Qt::CrossCursor); } else if (cursorMode == selectCursor) { viewport()->setCursor(Qt::CrossCursor); } else if (cursorMode == scopeCursor) { viewport()->setCursor(QCursor(redScopePixmap, 10, 10)); } else if (cursorMode == crosshairCursor) { viewport()->setCursor(QCursor(magentaScopePixmap, 10, 10)); } } /** This is how the mouse mode gets set. The default for a FITSView in a FITSViewer should be the dragMouse The default for a FITSView in the Focus or Align module should be the selectMouse The different defaults are accomplished by putting making the actual default mouseMode the selectMouse, but when a FITSViewer loads an image, it immediately makes it the dragMouse. */ void FITSView::setCursorMode(CursorMode mode) { cursorMode = mode; updateMouseCursor(); if (mode == scopeCursor && imageHasWCS()) { if (!imageData->isWCSLoaded() && !wcsWatcher.isRunning()) { QFuture future = QtConcurrent::run(imageData, &FITSData::loadWCS); wcsWatcher.setFuture(future); } } } void FITSView::resizeEvent(QResizeEvent * event) { if ((imageData == nullptr) && noImageLabel != nullptr) { noImageLabel->setPixmap( noImage.scaled(width() - 20, height() - 20, Qt::KeepAspectRatio, Qt::FastTransformation)); noImageLabel->setFixedSize(width() - 5, height() - 5); } QScrollArea::resizeEvent(event); } void FITSView::loadFITS(const QString &inFilename, bool silent) { if (floatingToolBar != nullptr) { floatingToolBar->setVisible(true); } bool setBayerParams = false; BayerParams param; if ((imageData != nullptr) && imageData->hasDebayer()) { setBayerParams = true; imageData->getBayerParams(¶m); } // In case image is still loading, wait until it is done. fitsWatcher.waitForFinished(); // In case loadWCS is still running for previous image data, let's wait until it's over wcsWatcher.waitForFinished(); delete imageData; imageData = nullptr; filterStack.clear(); filterStack.push(FITS_NONE); if (filter != FITS_NONE) filterStack.push(filter); imageData = new FITSData(mode); if (setBayerParams) imageData->setBayerParams(¶m); fitsWatcher.setFuture(imageData->loadFITS(inFilename, silent)); } bool FITSView::loadFITSFromData(FITSData *data, const QString &inFilename) { Q_UNUSED(inFilename) if (floatingToolBar != nullptr) { floatingToolBar->setVisible(true); } // In case loadWCS is still running for previous image data, let's wait until it's over wcsWatcher.waitForFinished(); if (imageData != nullptr) { delete imageData; imageData = nullptr; } filterStack.clear(); filterStack.push(FITS_NONE); if (filter != FITS_NONE) filterStack.push(filter); // Takes control of the objects passed in. imageData = data; return processData(); } bool FITSView::processData() { // Set current width and height if (!imageData) return false; currentWidth = imageData->width(); currentHeight = imageData->height(); int image_width = currentWidth; int image_height = currentHeight; image_frame->setSize(image_width, image_height); // Init the display image // JM 2020.01.08: Disabling as proposed by Hy //initDisplayImage(); imageData->applyFilter(filter); // Rescale to fits window on first load if (firstLoad) { currentZoom = 100; if (rescale(ZOOM_FIT_WINDOW) == false) { m_LastError = i18n("Rescaling image failed."); return false; } firstLoad = false; } else { if (rescale(ZOOM_KEEP_LEVEL) == false) { m_LastError = i18n("Rescaling image failed."); return false; } } setAlignment(Qt::AlignCenter); // Load WCS data now if selected and image contains valid WCS header if (imageData->hasWCS() && Options::autoWCS() && (mode == FITS_NORMAL || mode == FITS_ALIGN) && !wcsWatcher.isRunning()) { QFuture future = QtConcurrent::run(imageData, &FITSData::loadWCS); wcsWatcher.setFuture(future); } else syncWCSState(); if (isVisible()) emit newStatus(QString("%1x%2").arg(image_width).arg(image_height), FITS_RESOLUTION); if (showStarProfile) { if(floatingToolBar != nullptr) toggleProfileAction->setChecked(true); //Need to wait till the Focus module finds stars, if its the Focus module. QTimer::singleShot(100, this, SLOT(viewStarProfile())); } updateFrame(); return true; } void FITSView::loadInFrame() { // Check if the loading was OK if (fitsWatcher.result() == false) { m_LastError = imageData->getLastError(); emit failed(); return; } // Notify if there is debayer data. emit debayerToggled(imageData->hasDebayer()); if (processData()) emit loaded(); else emit failed(); } int FITSView::saveFITS(const QString &newFilename) { return imageData->saveFITS(newFilename); } bool FITSView::rescale(FITSZoom type) { switch (imageData->property("dataType").toInt()) { case TBYTE: return rescale(type); case TSHORT: return rescale(type); case TUSHORT: return rescale(type); case TLONG: return rescale(type); case TULONG: return rescale(type); case TFLOAT: return rescale(type); case TLONGLONG: return rescale(type); case TDOUBLE: return rescale(type); default: break; } return false; } FITSView::CursorMode FITSView::getCursorMode() { return cursorMode; } void FITSView::enterEvent(QEvent * event) { Q_UNUSED(event) if ((floatingToolBar != nullptr) && (imageData != nullptr)) { QPointer eff = new QGraphicsOpacityEffect(this); floatingToolBar->setGraphicsEffect(eff); QPointer a = new QPropertyAnimation(eff, "opacity"); a->setDuration(500); a->setStartValue(0.2); a->setEndValue(1); a->setEasingCurve(QEasingCurve::InBack); a->start(QPropertyAnimation::DeleteWhenStopped); } } void FITSView::leaveEvent(QEvent * event) { Q_UNUSED(event) if ((floatingToolBar != nullptr) && (imageData != nullptr)) { QPointer eff = new QGraphicsOpacityEffect(this); floatingToolBar->setGraphicsEffect(eff); QPointer a = new QPropertyAnimation(eff, "opacity"); a->setDuration(500); a->setStartValue(1); a->setEndValue(0.2); a->setEasingCurve(QEasingCurve::OutBack); a->start(QPropertyAnimation::DeleteWhenStopped); } } template bool FITSView::rescale(FITSZoom type) { // JM 2020.01.08: Disabling as proposed by Hy // if (rawImage.isNull()) // return false; if (!imageData) return false; int image_width = imageData->width(); int image_height = imageData->height(); currentWidth = image_width; currentHeight = image_height; if (isVisible()) emit newStatus(QString("%1x%2").arg(image_width).arg(image_height), FITS_RESOLUTION); switch (type) { case ZOOM_FIT_WINDOW: if ((image_width > width() || image_height > height())) { double w = baseSize().width() - BASE_OFFSET; double h = baseSize().height() - BASE_OFFSET; if (!firstLoad) { w = viewport()->rect().width() - BASE_OFFSET; h = viewport()->rect().height() - BASE_OFFSET; } // Find the zoom level which will enclose the current FITS in the current window size double zoomX = floor((w / static_cast(currentWidth)) * 100.); double zoomY = floor((h / static_cast(currentHeight)) * 100.); (zoomX < zoomY) ? currentZoom = zoomX : currentZoom = zoomY; currentWidth = image_width * (currentZoom / ZOOM_DEFAULT); currentHeight = image_height * (currentZoom / ZOOM_DEFAULT); if (currentZoom <= ZOOM_MIN) emit actionUpdated("view_zoom_out", false); } else { currentZoom = 100; currentWidth = image_width; currentHeight = image_height; } break; case ZOOM_KEEP_LEVEL: { currentWidth = image_width * (currentZoom / ZOOM_DEFAULT); currentHeight = image_height * (currentZoom / ZOOM_DEFAULT); } break; default: currentZoom = 100; break; } initDisplayImage(); image_frame->setScaledContents(true); doStretch(imageData, &rawImage); setWidget(image_frame.get()); // This is needed by fitstab, even if the zoom doesn't change, to change the stretch UI. emit newStatus(QString("%1%").arg(currentZoom), FITS_ZOOM); return true; } void FITSView::ZoomIn() { if (currentZoom >= ZOOM_DEFAULT && Options::limitedResourcesMode()) { emit newStatus(i18n("Cannot zoom in further due to active limited resources mode."), FITS_MESSAGE); return; } if (currentZoom < ZOOM_DEFAULT) currentZoom += ZOOM_LOW_INCR; else currentZoom += ZOOM_HIGH_INCR; emit actionUpdated("view_zoom_out", true); if (currentZoom >= zoomMax) { currentZoom = zoomMax; emit actionUpdated("view_zoom_in", false); } if (!imageData) return; currentWidth = imageData->width() * (currentZoom / ZOOM_DEFAULT); currentHeight = imageData->height() * (currentZoom / ZOOM_DEFAULT); updateFrame(); emit newStatus(QString("%1%").arg(currentZoom), FITS_ZOOM); } void FITSView::ZoomOut() { if (currentZoom <= ZOOM_DEFAULT) currentZoom -= ZOOM_LOW_INCR; else currentZoom -= ZOOM_HIGH_INCR; if (currentZoom <= ZOOM_MIN) { currentZoom = ZOOM_MIN; emit actionUpdated("view_zoom_out", false); } emit actionUpdated("view_zoom_in", true); if (!imageData) return; currentWidth = imageData->width() * (currentZoom / ZOOM_DEFAULT); currentHeight = imageData->height() * (currentZoom / ZOOM_DEFAULT); updateFrame(); emit newStatus(QString("%1%").arg(currentZoom), FITS_ZOOM); } void FITSView::ZoomToFit() { if (rawImage.isNull() == false) { rescale(ZOOM_FIT_WINDOW); updateFrame(); } } void FITSView::setStarFilterRange(float const innerRadius, float const outerRadius) { starFilter.innerRadius = innerRadius; starFilter.outerRadius = outerRadius; } int FITSView::filterStars() { return starFilter.used() ? imageData->filterStars(starFilter.innerRadius, starFilter.outerRadius) : imageData->getStarCenters().count(); } // isImageLarge() returns whether we use the large-image rendering strategy or the small-image strategy. // See the comment below in getScale() for details. bool FITSView::isLargeImage() { constexpr int largeImageNumPixels = 1000 * 1000; return rawImage.width() * rawImage.height() >= largeImageNumPixels; } // getScale() is related to the image and overlay rendering strategy used. // If we're using a pixmap apprpriate for a large image, where we draw and render on a pixmap that's the image size // and we let the QLabel deal with scaling and zooming, then the scale is 1.0. // With smaller images, where memory use is not as severe, we create a pixmap that's the size of the scaled image // and get scale returns the ratio of that pixmap size to the image size. double FITSView::getScale() { return isLargeImage() ? 1.0 : currentZoom / ZOOM_DEFAULT; } // scaleSize() is only used with the large-image rendering strategy. It may increase the line // widths or font sizes, as we draw lines and render text on the full image and when zoomed out, // these sizes may be too small. double FITSView::scaleSize(double size) { if (!isLargeImage()) return size; return currentZoom > 100.0 ? size : std::round(size * 100.0 / currentZoom); } void FITSView::updateFrame() { if (toggleStretchAction) toggleStretchAction->setChecked(stretchImage); // We employ two schemes for managing the image and its overlays, depending on the size of the image // and whether we need to therefore conserve memory. The small-image strategy explicitly scales up // the image, and writes overlays on the scaled pixmap. The large-image strategy uses a pixmap that's // the size of the image itself, never scaling that up. if (isLargeImage()) updateFrameLargeImage(); else updateFrameSmallImage(); } void FITSView::updateFrameLargeImage() { if (!displayPixmap.convertFromImage(rawImage)) return; QPainter painter(&displayPixmap); // Possibly scale the fonts as we're drawing on the full image, not just the visible part of the scroll window. QFont font = painter.font(); font.setPixelSize(scaleSize(FONT_SIZE)); painter.setFont(font); if (sampling == 1) { drawOverlay(&painter, 1.0); drawStarFilter(&painter, 1.0); } image_frame->setPixmap(displayPixmap); image_frame->resize(((sampling * currentZoom) / 100.0) * image_frame->pixmap()->size()); } void FITSView::updateFrameSmallImage() { QImage scaledImage = rawImage.scaled(currentWidth, currentHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation); if (!displayPixmap.convertFromImage(scaledImage)) return; QPainter painter(&displayPixmap); if (sampling == 1) { drawOverlay(&painter, currentZoom / ZOOM_DEFAULT); drawStarFilter(&painter, currentZoom / ZOOM_DEFAULT); } image_frame->setPixmap(displayPixmap); image_frame->resize(currentWidth, currentHeight); } void FITSView::drawStarFilter(QPainter *painter, double scale) { if (!starFilter.used()) return; const double w = imageData->width() * scale; const double h = imageData->height() * scale; double const diagonal = std::sqrt(w * w + h * h) / 2; int const innerRadius = std::lround(diagonal * starFilter.innerRadius); int const outerRadius = std::lround(diagonal * starFilter.outerRadius); QPoint const center(w / 2, h / 2); painter->save(); painter->setPen(QPen(Qt::blue, scaleSize(1), Qt::DashLine)); painter->setOpacity(0.7); painter->setBrush(QBrush(Qt::transparent)); painter->drawEllipse(center, outerRadius, outerRadius); painter->setBrush(QBrush(Qt::blue, Qt::FDiagPattern)); painter->drawEllipse(center, innerRadius, innerRadius); painter->restore(); } void FITSView::ZoomDefault() { if (image_frame != nullptr) { emit actionUpdated("view_zoom_out", true); emit actionUpdated("view_zoom_in", true); currentZoom = ZOOM_DEFAULT; currentWidth = imageData->width(); currentHeight = imageData->height(); updateFrame(); emit newStatus(QString("%1%").arg(currentZoom), FITS_ZOOM); update(); } } void FITSView::drawOverlay(QPainter * painter, double scale) { painter->setRenderHint(QPainter::Antialiasing, Options::useAntialias()); if (trackingBoxEnabled && getCursorMode() != FITSView::scopeCursor) drawTrackingBox(painter, scale); if (!markerCrosshair.isNull()) drawMarker(painter, scale); if (showCrosshair) drawCrosshair(painter, scale); if (showObjects) drawObjectNames(painter, scale); if (showEQGrid) drawEQGrid(painter, scale); if (showPixelGrid) drawPixelGrid(painter, scale); if (markStars) drawStarCentroid(painter, scale); } void FITSView::updateMode(FITSMode fmode) { mode = fmode; } void FITSView::drawMarker(QPainter * painter, double scale) { painter->setPen(QPen(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor")), scaleSize(2))); painter->setBrush(Qt::NoBrush); const float pxperdegree = scale * (57.3 / 1.8); const float s1 = 0.5 * pxperdegree; const float s2 = pxperdegree; const float s3 = 2.0 * pxperdegree; const float x0 = scale * markerCrosshair.x(); const float y0 = scale * markerCrosshair.y(); const float x1 = x0 - 0.5 * s1; const float y1 = y0 - 0.5 * s1; const float x2 = x0 - 0.5 * s2; const float y2 = y0 - 0.5 * s2; const float x3 = x0 - 0.5 * s3; const float y3 = y0 - 0.5 * s3; //Draw radial lines painter->drawLine(QPointF(x1, y0), QPointF(x3, y0)); painter->drawLine(QPointF(x0 + s2, y0), QPointF(x0 + 0.5 * s1, y0)); painter->drawLine(QPointF(x0, y1), QPointF(x0, y3)); painter->drawLine(QPointF(x0, y0 + 0.5 * s1), QPointF(x0, y0 + s2)); //Draw circles at 0.5 & 1 degrees painter->drawEllipse(QRectF(x1, y1, s1, s1)); painter->drawEllipse(QRectF(x2, y2, s2, s2)); } bool FITSView::drawHFR(QPainter * painter, const QString & hfr, int x, int y) { QRect const boundingRect(0, 0, painter->device()->width(), painter->device()->height()); QSize const hfrSize = painter->fontMetrics().size(Qt::TextSingleLine, hfr); // Store the HFR text in a rect QPoint const hfrBottomLeft(x, y); QRect const hfrRect(hfrBottomLeft.x(), hfrBottomLeft.y() - hfrSize.height(), hfrSize.width(), hfrSize.height()); // Render the HFR text only if it can be displayed entirely if (boundingRect.contains(hfrRect)) { painter->setPen(QPen(Qt::red, scaleSize(3))); painter->drawText(hfrBottomLeft, hfr); painter->setPen(QPen(Qt::red, scaleSize(2))); return true; } return false; } void FITSView::drawStarCentroid(QPainter * painter, double scale) { QFont painterFont; double fontSize = painterFont.pointSizeF() * 2; if (showStarsHFR) { // If we need to print the HFR out, give an arbitrarily sized font to the painter if (isLargeImage()) fontSize = scaleSize(painterFont.pointSizeF()); painterFont.setPointSizeF(fontSize); painter->setFont(painterFont); } painter->setPen(QPen(Qt::red, scaleSize(2))); foreach (auto const &starCenter, imageData->getStarCenters()) { int const w = std::round(starCenter->width) * scale; // Draw a circle around the detected star. // SEP coordinates are in the center of pixels, and Qt at the boundary. const double xCoord = starCenter->x - 0.5; const double yCoord = starCenter->y - 0.5; const double radius = starCenter->HFR > 0 ? 2.0f * starCenter->HFR * scale : w; painter->drawEllipse(QPointF(xCoord * scale, yCoord * scale), radius, radius); if (showStarsHFR) { int const x1 = std::round((xCoord - starCenter->width / 2.0f) * scale); int const y1 = std::round((yCoord - starCenter->width / 2.0f) * scale); // Ask the painter how large will the HFR text be QString const hfr = QString("%1").arg(starCenter->HFR, 0, 'f', 2); if (!drawHFR(painter, hfr, x1 + w + 5, y1 + w / 2)) { // Try a few more time with smaller fonts; for (int i = 0; i < 10; ++i) { const double tempFontSize = painterFont.pointSizeF() - 2; if (tempFontSize <= 0) break; painterFont.setPointSizeF(tempFontSize); painter->setFont(painterFont); if (drawHFR(painter, hfr, x1 + w + 5, y1 + w / 2)) break; } // Reset the font size. painterFont.setPointSize(fontSize); painter->setFont(painterFont); } } } } void FITSView::drawTrackingBox(QPainter * painter, double scale) { painter->setPen(QPen(Qt::green, scaleSize(2))); if (trackingBox.isNull()) return; const int x1 = trackingBox.x() * scale; const int y1 = trackingBox.y() * scale; const int w = trackingBox.width() * scale; const int h = trackingBox.height() * scale; painter->drawRect(x1, y1, w, h); } /** This Method draws a large Crosshair in the center of the image, it is like a set of axes. */ void FITSView::drawCrosshair(QPainter * painter, double scale) { if (!imageData) return; const int image_width = imageData->width(); const int image_height = imageData->height(); const QPointF c = QPointF((qreal)image_width / 2 * scale, (qreal)image_height / 2 * scale); const float midX = (float)image_width / 2 * scale; const float midY = (float)image_height / 2 * scale; const float maxX = (float)image_width * scale; const float maxY = (float)image_height * scale; const float r = 50 * scale; painter->setPen(QPen(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor")), scaleSize(1))); //Horizontal Line to Circle painter->drawLine(0, midY, midX - r, midY); //Horizontal Line past Circle painter->drawLine(midX + r, midY, maxX, midY); //Vertical Line to Circle painter->drawLine(midX, 0, midX, midY - r); //Vertical Line past Circle painter->drawLine(midX, midY + r, midX, maxY); //Circles painter->drawEllipse(c, r, r); painter->drawEllipse(c, r / 2, r / 2); } /** This method is intended to draw a pixel grid onto the image. It first determines useful information from the image. Then it draws the axes on the image if the crosshairs are not displayed. Finally it draws the gridlines so that there will be 4 Gridlines on either side of the axes. Note: This has to start drawing at the center not at the edges because the center axes must be in the center of the image. */ void FITSView::drawPixelGrid(QPainter * painter, double scale) { const float width = imageData->width() * scale; const float height = imageData->height() * scale; const float cX = width / 2; const float cY = height / 2; const float deltaX = width / 10; const float deltaY = height / 10; QFontMetrics fm(painter->font()); //draw the Axes painter->setPen(QPen(Qt::red, scaleSize(1))); painter->drawText(cX - 30, height - 5, QString::number((int)((cX) / scale))); QString str = QString::number((int)((cY) / scale)); painter->drawText(width - (fm.width(str) + 10), cY - 5, str); if (!showCrosshair) { painter->drawLine(cX, 0, cX, height); painter->drawLine(0, cY, width, cY); } painter->setPen(QPen(Qt::gray, scaleSize(1))); //Start one iteration past the Center and draw 4 lines on either side of 0 for (int x = deltaX; x < cX - deltaX; x += deltaX) { painter->drawText(cX + x - 30, height - 5, QString::number((int)(cX + x) / scale)); painter->drawText(cX - x - 30, height - 5, QString::number((int)(cX - x) / scale)); painter->drawLine(cX - x, 0, cX - x, height); painter->drawLine(cX + x, 0, cX + x, height); } //Start one iteration past the Center and draw 4 lines on either side of 0 for (int y = deltaY; y < cY - deltaY; y += deltaY) { QString str = QString::number((int)((cY + y) / scale)); painter->drawText(width - (fm.width(str) + 10), cY + y - 5, str); str = QString::number((int)((cY - y) / scale)); painter->drawText(width - (fm.width(str) + 10), cY - y - 5, str); painter->drawLine(0, cY + y, width, cY + y); painter->drawLine(0, cY - y, width, cY - y); } } bool FITSView::imageHasWCS() { if (imageData != nullptr) return imageData->hasWCS(); return false; } void FITSView::drawObjectNames(QPainter * painter, double scale) { painter->setPen(QPen(QColor(KStarsData::Instance()->colorScheme()->colorNamed("FITSObjectLabelColor")))); foreach (FITSSkyObject * listObject, imageData->getSkyObjects()) { painter->drawRect(listObject->x() * scale - 5, listObject->y() * scale - 5, 10, 10); painter->drawText(listObject->x() * scale + 10, listObject->y() * scale + 10, listObject->skyObject()->name()); } } /** This method will paint EQ Gridlines in an overlay if there is WCS data present. It determines the minimum and maximum RA and DEC, then it uses that information to judge which gridLines to draw. Then it calls the drawEQGridlines methods below to draw gridlines at those specific RA and Dec values. */ void FITSView::drawEQGrid(QPainter * painter, double scale) { const int image_width = imageData->width(); const int image_height = imageData->height(); if (imageData->hasWCS()) { wcs_point * wcs_coord = imageData->getWCSCoord(); if (wcs_coord != nullptr) { const int size = image_width * image_height; double maxRA = -1000; double minRA = 1000; double maxDec = -1000; double minDec = 1000; for (int i = 0; i < (size); i++) { double ra = wcs_coord[i].ra; double dec = wcs_coord[i].dec; if (ra > maxRA) maxRA = ra; if (ra < minRA) minRA = ra; if (dec > maxDec) maxDec = dec; if (dec < minDec) minDec = dec; } auto minDecMinutes = (int)(minDec * 12); //This will force the Dec Scale to 5 arc minutes in the loop auto maxDecMinutes = (int)(maxDec * 12); auto minRAMinutes = (int)(minRA / 15.0 * 120.0); //This will force the scale to 1/2 minutes of RA in the loop from 0 to 50 degrees auto maxRAMinutes = (int)(maxRA / 15.0 * 120.0); double raConvert = 15 / 120.0; //This will undo the calculation above to retrieve the actual RA. double decConvert = 1.0 / 12.0; //This will undo the calculation above to retrieve the actual DEC. if (maxDec > 50 || minDec < -50) { minRAMinutes = (int)(minRA / 15.0 * 60.0); //This will force the scale to 1 min of RA from 50 to 80 degrees maxRAMinutes = (int)(maxRA / 15.0 * 60.0); raConvert = 15 / 60.0; } if (maxDec > 80 || minDec < -80) { minRAMinutes = (int)(minRA / 15.0 * 30); //This will force the scale to 2 min of RA from 80 to 85 degrees maxRAMinutes = (int)(maxRA / 15.0 * 30); raConvert = 15 / 30.0; } if (maxDec > 85 || minDec < -85) { minRAMinutes = (int)(minRA / 15.0 * 6); //This will force the scale to 10 min of RA from 85 to 89 degrees maxRAMinutes = (int)(maxRA / 15.0 * 6); raConvert = 15 / 6.0; } if (maxDec >= 89.25 || minDec <= -89.25) { minRAMinutes = (int)(minRA / 15); //This will force the scale to whole hours of RA in the loop really close to the poles maxRAMinutes = (int)(maxRA / 15); raConvert = 15; } painter->setPen(QPen(Qt::yellow)); QPointF pixelPoint, imagePoint, pPoint; //This section draws the RA Gridlines for (int targetRA = minRAMinutes; targetRA <= maxRAMinutes; targetRA++) { painter->setPen(QPen(Qt::yellow)); double target = targetRA * raConvert; if (eqGridPoints.count() != 0) eqGridPoints.clear(); double increment = std::abs((maxDec - minDec) / 100.0); //This will determine how many points to use to create the RA Line for (double targetDec = minDec; targetDec <= maxDec; targetDec += increment) { SkyPoint pointToGet(target / 15.0, targetDec); bool inImage = imageData->wcsToPixel(pointToGet, pixelPoint, imagePoint); if (inImage) { QPointF pt(pixelPoint.x() * scale, pixelPoint.y() * scale); eqGridPoints.append(pt); } } if (eqGridPoints.count() > 1) { for (int i = 1; i < eqGridPoints.count(); i++) painter->drawLine(eqGridPoints.value(i - 1), eqGridPoints.value(i)); QString str = QString::number(dms(target).hour()) + "h " + QString::number(dms(target).minute()) + '\''; if (maxDec <= 50 && maxDec >= -50) str = str + " " + QString::number(dms(target).second()) + "''"; QPointF pt = getPointForGridLabel(painter, str, scale); if (pt.x() != -100) painter->drawText(pt.x(), pt.y(), str); } } //This section draws the DEC Gridlines for (int targetDec = minDecMinutes; targetDec <= maxDecMinutes; targetDec++) { if (eqGridPoints.count() != 0) eqGridPoints.clear(); double increment = std::abs((maxRA - minRA) / 100.0); //This will determine how many points to use to create the Dec Line double target = targetDec * decConvert; for (double targetRA = minRA; targetRA <= maxRA; targetRA += increment) { SkyPoint pointToGet(targetRA / 15, targetDec * decConvert); bool inImage = imageData->wcsToPixel(pointToGet, pixelPoint, imagePoint); if (inImage) { QPointF pt(pixelPoint.x(), pixelPoint.y()); eqGridPoints.append(pt); } } if (eqGridPoints.count() > 1) { for (int i = 1; i < eqGridPoints.count(); i++) painter->drawLine(eqGridPoints.value(i - 1), eqGridPoints.value(i)); QString str = QString::number(dms(target).degree()) + "° " + QString::number(dms(target).arcmin()) + '\''; QPointF pt = getPointForGridLabel(painter, str, scale); if (pt.x() != -100) painter->drawText(pt.x(), pt.y(), str); } } //This Section Draws the North Celestial Pole if present SkyPoint NCP(0, 90); bool NCPtest = imageData->wcsToPixel(NCP, pPoint, imagePoint); if (NCPtest) { bool NCPinImage = (pPoint.x() > 0 && pPoint.x() < image_width) && (pPoint.y() > 0 && pPoint.y() < image_height); if (NCPinImage) { painter->fillRect(pPoint.x() * scale - 2, pPoint.y() * scale - 2, 4, 4, KStarsData::Instance()->colorScheme()->colorNamed("TargetColor")); painter->drawText(pPoint.x() * scale + 15, pPoint.y() * scale + 15, i18nc("North Celestial Pole", "NCP")); } } //This Section Draws the South Celestial Pole if present SkyPoint SCP(0, -90); bool SCPtest = imageData->wcsToPixel(SCP, pPoint, imagePoint); if (SCPtest) { bool SCPinImage = (pPoint.x() > 0 && pPoint.x() < image_width) && (pPoint.y() > 0 && pPoint.y() < image_height); if (SCPinImage) { painter->fillRect(pPoint.x() * scale - 2, pPoint.y() * scale - 2, 4, 4, KStarsData::Instance()->colorScheme()->colorNamed("TargetColor")); painter->drawText(pPoint.x() * scale + 15, pPoint.y() * scale + 15, i18nc("South Celestial Pole", "SCP")); } } } } } bool FITSView::pointIsInImage(QPointF pt, double scale) { int image_width = imageData->width(); int image_height = imageData->height(); return pt.x() < image_width * scale && pt.y() < image_height * scale && pt.x() > 0 && pt.y() > 0; } QPointF FITSView::getPointForGridLabel(QPainter *painter, const QString &str, double scale) { QFontMetrics fm(painter->font()); int strWidth = fm.width(str); int strHeight = fm.height(); int image_width = imageData->width(); int image_height = imageData->height(); //These get the maximum X and Y points in the list that are in the image QPointF maxXPt(image_width * scale / 2, image_height * scale / 2); for (auto &p : eqGridPoints) { if (p.x() > maxXPt.x() && pointIsInImage(p, scale)) maxXPt = p; } QPointF maxYPt(image_width * scale / 2, image_height * scale / 2); for (auto &p : eqGridPoints) { if (p.y() > maxYPt.y() && pointIsInImage(p, scale)) maxYPt = p; } QPointF minXPt(image_width * scale / 2, image_height * scale / 2); for (auto &p : eqGridPoints) { if (p.x() < minXPt.x() && pointIsInImage(p, scale)) minXPt = p; } QPointF minYPt(image_width * scale / 2, image_height * scale / 2); for (auto &p : eqGridPoints) { if (p.y() < minYPt.y() && pointIsInImage(p, scale)) minYPt = p; } //This gives preference to points that are on the right hand side and bottom. //But if the line doesn't intersect the right or bottom, it then tries for the top and left. //If no points are found in the image, it returns a point off the screen //If all else fails, like in the case of a circle on the image, it returns the far right point. if (image_width * scale - maxXPt.x() < strWidth) { return QPointF( image_width * scale - (strWidth + 10), maxXPt.y() - strHeight); //This will draw the text on the right hand side, up and to the left of the point where the line intersects } if (image_height * scale - maxYPt.y() < strHeight) return QPointF( maxYPt.x() - (strWidth + 10), image_height * scale - (strHeight + 10)); //This will draw the text on the bottom side, up and to the left of the point where the line intersects if (minYPt.y() < strHeight) return QPointF( minYPt.x() * scale + 10, strHeight + 20); //This will draw the text on the top side, down and to the right of the point where the line intersects if (minXPt.x() < strWidth) return QPointF( 10, minXPt.y() * scale + strHeight + 20); //This will draw the text on the left hand side, down and to the right of the point where the line intersects if (maxXPt.x() == image_width * scale / 2 && maxXPt.y() == image_height * scale / 2) return QPointF(-100, -100); //All of the points were off the screen return QPoint(maxXPt.x() - (strWidth + 10), maxXPt.y() - (strHeight + 10)); } void FITSView::setFirstLoad(bool value) { firstLoad = value; } QPixmap &FITSView::getTrackingBoxPixmap(uint8_t margin) { if (trackingBox.isNull()) return trackingBoxPixmap; // We need to know which rendering strategy updateFrame used to determine the scaling. const float scale = getScale(); int x1 = (trackingBox.x() - margin) * scale; int y1 = (trackingBox.y() - margin) * scale; int w = (trackingBox.width() + margin * 2) * scale; int h = (trackingBox.height() + margin * 2) * scale; trackingBoxPixmap = image_frame->grab(QRect(x1, y1, w, h)); return trackingBoxPixmap; } void FITSView::setTrackingBox(const QRect &rect) { if (rect != trackingBox) { trackingBox = rect; updateFrame(); if(showStarProfile) viewStarProfile(); } } void FITSView::resizeTrackingBox(int newSize) { int x = trackingBox.x() + trackingBox.width() / 2; int y = trackingBox.y() + trackingBox.height() / 2; int delta = newSize / 2; setTrackingBox(QRect( x - delta, y - delta, newSize, newSize)); } bool FITSView::isImageStretched() { return stretchImage; } bool FITSView::isCrosshairShown() { return showCrosshair; } bool FITSView::isEQGridShown() { return showEQGrid; } bool FITSView::areObjectsShown() { return showObjects; } bool FITSView::isPixelGridShown() { return showPixelGrid; } void FITSView::toggleCrosshair() { showCrosshair = !showCrosshair; updateFrame(); } void FITSView::toggleEQGrid() { showEQGrid = !showEQGrid; if (!imageData->isWCSLoaded() && !wcsWatcher.isRunning()) { QFuture future = QtConcurrent::run(imageData, &FITSData::loadWCS); wcsWatcher.setFuture(future); return; } if (image_frame != nullptr) updateFrame(); } void FITSView::toggleObjects() { showObjects = !showObjects; if (!imageData->isWCSLoaded() && !wcsWatcher.isRunning()) { QFuture future = QtConcurrent::run(imageData, &FITSData::loadWCS); wcsWatcher.setFuture(future); return; } if (image_frame != nullptr) updateFrame(); } void FITSView::toggleStars() { toggleStars(!markStars); if (image_frame != nullptr) updateFrame(); } void FITSView::toggleStretch() { stretchImage = !stretchImage; if (image_frame != nullptr && rescale(ZOOM_KEEP_LEVEL)) updateFrame(); } void FITSView::toggleStarProfile() { #ifdef HAVE_DATAVISUALIZATION showStarProfile = !showStarProfile; if(showStarProfile && trackingBoxEnabled) viewStarProfile(); if(toggleProfileAction) toggleProfileAction->setChecked(showStarProfile); if(showStarProfile) { //The tracking box is already on for Guide and Focus Views, but off for Normal and Align views. //So for Normal and Align views, we need to set up the tracking box. if(mode == FITS_NORMAL || mode == FITS_ALIGN) { setCursorMode(selectCursor); connect(this, SIGNAL(trackingStarSelected(int, int)), this, SLOT(move3DTrackingBox(int, int))); trackingBox = QRect(0, 0, 128, 128); setTrackingBoxEnabled(true); if(starProfileWidget) connect(starProfileWidget, SIGNAL(sampleSizeUpdated(int)), this, SLOT(resizeTrackingBox(int))); } if(starProfileWidget) connect(starProfileWidget, SIGNAL(rejected()), this, SLOT(toggleStarProfile())); } else { //This shuts down the tracking box for Normal and Align Views //It doesn't touch Guide and Focus Views because they still need a tracking box if(mode == FITS_NORMAL || mode == FITS_ALIGN) { if(getCursorMode() == selectCursor) setCursorMode(dragCursor); disconnect(this, SIGNAL(trackingStarSelected(int, int)), this, SLOT(move3DTrackingBox(int, int))); setTrackingBoxEnabled(false); if(starProfileWidget) disconnect(starProfileWidget, SIGNAL(sampleSizeUpdated(int)), this, SLOT(resizeTrackingBox(int))); } if(starProfileWidget) { disconnect(starProfileWidget, SIGNAL(rejected()), this, SLOT(toggleStarProfile())); starProfileWidget->close(); starProfileWidget = nullptr; } emit starProfileWindowClosed(); } updateFrame(); #endif } void FITSView::move3DTrackingBox(int x, int y) { int boxSize = trackingBox.width(); QRect starRect = QRect(x - boxSize / 2, y - boxSize / 2, boxSize, boxSize); setTrackingBox(starRect); } void FITSView::viewStarProfile() { #ifdef HAVE_DATAVISUALIZATION if(!trackingBoxEnabled) { setTrackingBoxEnabled(true); setTrackingBox(QRect(0, 0, 128, 128)); } if(!starProfileWidget) { starProfileWidget = new StarProfileViewer(this); //This is a band-aid to fix a QT bug with createWindowContainer //It will set the cursor of the Window containing the view that called the Star Profile method to the Arrow Cursor //Note that Ekos Manager is a QDialog and FitsViewer is a KXmlGuiWindow QWidget * superParent = this->parentWidget(); while(superParent->parentWidget() != 0 && !superParent->inherits("QDialog") && !superParent->inherits("KXmlGuiWindow")) superParent = superParent->parentWidget(); superParent->setCursor(Qt::ArrowCursor); //This is the end of the band-aid connect(starProfileWidget, SIGNAL(rejected()), this, SLOT(toggleStarProfile())); if(mode == FITS_ALIGN || mode == FITS_NORMAL) { starProfileWidget->enableTrackingBox(true); imageData->setStarAlgorithm(ALGORITHM_CENTROID); connect(starProfileWidget, SIGNAL(sampleSizeUpdated(int)), this, SLOT(resizeTrackingBox(int))); } } QList starCenters = imageData->getStarCentersInSubFrame(trackingBox); if(starCenters.size() == 0) { // FIXME, the following does not work anymore. //imageData->findStars(&trackingBox, true); // FIXME replacing it with this imageData->findStars(ALGORITHM_CENTROID, trackingBox); starCenters = imageData->getStarCentersInSubFrame(trackingBox); } starProfileWidget->loadData(imageData, trackingBox, starCenters); starProfileWidget->show(); starProfileWidget->raise(); if(markStars) updateFrame(); //this is to update for the marked stars #endif } void FITSView::togglePixelGrid() { showPixelGrid = !showPixelGrid; updateFrame(); } int FITSView::findStars(StarAlgorithm algorithm, const QRect &searchBox) { int count = 0; if(trackingBoxEnabled) count = imageData->findStars(algorithm, trackingBox); else count = imageData->findStars(algorithm, searchBox); return count; } void FITSView::toggleStars(bool enable) { markStars = enable; if (markStars && !imageData->areStarsSearched()) { QApplication::setOverrideCursor(Qt::WaitCursor); emit newStatus(i18n("Finding stars..."), FITS_MESSAGE); qApp->processEvents(); int count = findStars(ALGORITHM_SEP); if (count >= 0 && isVisible()) emit newStatus(i18np("1 star detected. HFR=%2", "%1 stars detected. HFR=%2", count, imageData->getHFR()), FITS_MESSAGE); QApplication::restoreOverrideCursor(); } } void FITSView::processPointSelection(int x, int y) { emit trackingStarSelected(x, y); } void FITSView::processMarkerSelection(int x, int y) { markerCrosshair.setX(x); markerCrosshair.setY(y); updateFrame(); } void FITSView::setTrackingBoxEnabled(bool enable) { if (enable != trackingBoxEnabled) { trackingBoxEnabled = enable; //updateFrame(); } } void FITSView::wheelEvent(QWheelEvent * event) { //This attempts to send the wheel event back to the Scroll Area if it was taken from a trackpad //It should still do the zoom if it is a mouse wheel if (event->source() == Qt::MouseEventSynthesizedBySystem) { QScrollArea::wheelEvent(event); } else { QPoint mouseCenter = getImagePoint(event->pos()); if (event->angleDelta().y() > 0) ZoomIn(); else ZoomOut(); event->accept(); cleanUpZoom(mouseCenter); } } /** This method is intended to keep key locations in an image centered on the screen while zooming. If there is a marker or tracking box, it centers on those. If not, it uses the point called viewCenter that was passed as a parameter. */ void FITSView::cleanUpZoom(QPoint viewCenter) { int x0 = 0; int y0 = 0; double scale = (currentZoom / ZOOM_DEFAULT); if (!markerCrosshair.isNull()) { x0 = markerCrosshair.x() * scale; y0 = markerCrosshair.y() * scale; } else if (trackingBoxEnabled) { x0 = trackingBox.center().x() * scale; y0 = trackingBox.center().y() * scale; } else { x0 = viewCenter.x() * scale; y0 = viewCenter.y() * scale; } ensureVisible(x0, y0, width() / 2, height() / 2); updateMouseCursor(); } /** This method converts a point from the ViewPort Coordinate System to the Image Coordinate System. */ QPoint FITSView::getImagePoint(QPoint viewPortPoint) { QWidget * w = widget(); if (w == nullptr) return QPoint(0, 0); double scale = (currentZoom / ZOOM_DEFAULT); QPoint widgetPoint = w->mapFromParent(viewPortPoint); QPoint imagePoint = QPoint(widgetPoint.x() / scale, widgetPoint.y() / scale); return imagePoint; } void FITSView::initDisplayImage() { // Account for leftover when sampling. Thus a 5-wide image sampled by 2 // would result in a width of 3 (samples 0, 2 and 4). int w = (imageData->width() + sampling - 1) / sampling; int h = (imageData->height() + sampling - 1) / sampling; if (imageData->channels() == 1) { rawImage = QImage(w, h, QImage::Format_Indexed8); rawImage.setColorCount(256); for (int i = 0; i < 256; i++) rawImage.setColor(i, qRgb(i, i, i)); } else { rawImage = QImage(w, h, QImage::Format_RGB32); } } /** The Following two methods allow gestures to work with trackpads. Specifically, we are targeting the pinch events, so that if one is generated, Then the pinchTriggered method will be called. If the event is not a pinch gesture, then the event is passed back to the other event handlers. */ bool FITSView::event(QEvent * event) { if (event->type() == QEvent::Gesture) return gestureEvent(dynamic_cast(event)); return QScrollArea::event(event); } bool FITSView::gestureEvent(QGestureEvent * event) { if (QGesture * pinch = event->gesture(Qt::PinchGesture)) pinchTriggered(dynamic_cast(pinch)); return true; } /** This Method works with Trackpads to use the pinch gesture to scroll in and out It stores a point to keep track of the location where the gesture started so that while you are zooming, it tries to keep that initial point centered in the view. **/ void FITSView::pinchTriggered(QPinchGesture * gesture) { if (!zooming) { zoomLocation = getImagePoint(mapFromGlobal(QCursor::pos())); zooming = true; } if (gesture->state() == Qt::GestureFinished) { zooming = false; } zoomTime++; //zoomTime is meant to slow down the zooming with a pinch gesture. if (zoomTime > 10000) //This ensures zoomtime never gets too big. zoomTime = 0; if (zooming && (zoomTime % 10 == 0)) //zoomTime is set to slow it by a factor of 10. { if (gesture->totalScaleFactor() > 1) ZoomIn(); else ZoomOut(); } cleanUpZoom(zoomLocation); } /*void FITSView::handleWCSCompletion() { //bool hasWCS = wcsWatcher.result(); if(imageData->hasWCS()) this->updateFrame(); emit wcsToggled(imageData->hasWCS()); }*/ void FITSView::syncWCSState() { bool hasWCS = imageData->hasWCS(); bool wcsLoaded = imageData->isWCSLoaded(); if (hasWCS && wcsLoaded) this->updateFrame(); emit wcsToggled(hasWCS); if (toggleEQGridAction != nullptr) toggleEQGridAction->setEnabled(hasWCS); if (toggleObjectsAction != nullptr) toggleObjectsAction->setEnabled(hasWCS); if (centerTelescopeAction != nullptr) centerTelescopeAction->setEnabled(hasWCS); } void FITSView::createFloatingToolBar() { if (floatingToolBar != nullptr) return; floatingToolBar = new QToolBar(this); auto * eff = new QGraphicsOpacityEffect(this); floatingToolBar->setGraphicsEffect(eff); eff->setOpacity(0.2); floatingToolBar->setVisible(false); floatingToolBar->setStyleSheet( "QToolBar{background: rgba(150, 150, 150, 210); border:none; color: yellow}" "QToolButton{background: transparent; border:none; color: yellow}" "QToolButton:hover{background: rgba(200, 200, 200, 255);border:solid; color: yellow}" "QToolButton:checked{background: rgba(110, 110, 110, 255);border:solid; color: yellow}"); floatingToolBar->setFloatable(true); floatingToolBar->setIconSize(QSize(25, 25)); //floatingToolBar->setMovable(true); QAction * action = nullptr; floatingToolBar->addAction(QIcon::fromTheme("zoom-in"), i18n("Zoom In"), this, SLOT(ZoomIn())); floatingToolBar->addAction(QIcon::fromTheme("zoom-out"), i18n("Zoom Out"), this, SLOT(ZoomOut())); floatingToolBar->addAction(QIcon::fromTheme("zoom-fit-best"), i18n("Default Zoom"), this, SLOT(ZoomDefault())); floatingToolBar->addAction(QIcon::fromTheme("zoom-fit-width"), i18n("Zoom to Fit"), this, SLOT(ZoomToFit())); toggleStretchAction = floatingToolBar->addAction(QIcon::fromTheme("transform-move"), i18n("Toggle Stretch"), this, SLOT(toggleStretch())); toggleStretchAction->setCheckable(true); floatingToolBar->addSeparator(); action = floatingToolBar->addAction(QIcon::fromTheme("crosshairs"), i18n("Show Cross Hairs"), this, SLOT(toggleCrosshair())); action->setCheckable(true); action = floatingToolBar->addAction(QIcon::fromTheme("map-flat"), i18n("Show Pixel Gridlines"), this, SLOT(togglePixelGrid())); action->setCheckable(true); toggleStarsAction = floatingToolBar->addAction(QIcon::fromTheme("kstars_stars"), i18n("Detect Stars in Image"), this, SLOT(toggleStars())); toggleStarsAction->setCheckable(true); #ifdef HAVE_DATAVISUALIZATION toggleProfileAction = floatingToolBar->addAction(QIcon::fromTheme("star-profile", QIcon(":/icons/star_profile.svg")), i18n("View Star Profile"), this, SLOT(toggleStarProfile())); toggleProfileAction->setCheckable(true); #endif if (mode == FITS_NORMAL || mode == FITS_ALIGN) { floatingToolBar->addSeparator(); toggleEQGridAction = floatingToolBar->addAction(QIcon::fromTheme("kstars_grid"), i18n("Show Equatorial Gridlines"), this, SLOT(toggleEQGrid())); toggleEQGridAction->setCheckable(true); toggleEQGridAction->setEnabled(false); toggleObjectsAction = floatingToolBar->addAction(QIcon::fromTheme("help-hint"), i18n("Show Objects in Image"), this, SLOT(toggleObjects())); toggleObjectsAction->setCheckable(true); toggleEQGridAction->setEnabled(false); centerTelescopeAction = floatingToolBar->addAction(QIcon::fromTheme("center_telescope", QIcon(":/icons/center_telescope.svg")), i18n("Center Telescope"), this, SLOT(centerTelescope())); centerTelescopeAction->setCheckable(true); centerTelescopeAction->setEnabled(false); } } /** - This methood either enables or disables the scope mouse mode so you can slew your scope to coordinates + This method either enables or disables the scope mouse mode so you can slew your scope to coordinates just by clicking the mouse on a spot in the image. */ void FITSView::centerTelescope() { if (imageHasWCS()) { if (getCursorMode() == FITSView::scopeCursor) { setCursorMode(lastMouseMode); } else { lastMouseMode = getCursorMode(); setCursorMode(FITSView::scopeCursor); } updateFrame(); } updateScopeButton(); } void FITSView::updateScopeButton() { if (centerTelescopeAction != nullptr) { if (getCursorMode() == FITSView::scopeCursor) { centerTelescopeAction->setChecked(true); } else { centerTelescopeAction->setChecked(false); } } } /** This method just verifies if INDI is online, a telescope present, and is connected */ bool FITSView::isTelescopeActive() { #ifdef HAVE_INDI if (INDIListener::Instance()->size() == 0) { return false; } foreach (ISD::GDInterface * gd, INDIListener::Instance()->getDevices()) { INDI::BaseDevice * bd = gd->getBaseDevice(); if (gd->getType() != KSTARS_TELESCOPE) continue; if (bd == nullptr) continue; return bd->isConnected(); } return false; #else return false; #endif } void FITSView::setStarsEnabled(bool enable) { markStars = enable; if (floatingToolBar != nullptr) { foreach (QAction * action, floatingToolBar->actions()) { if (action->text() == i18n("Detect Stars in Image")) { action->setChecked(markStars); break; } } } } void FITSView::setStarsHFREnabled(bool enable) { showStarsHFR = enable; } diff --git a/kstars/fitsviewer/fitsviewer.cpp b/kstars/fitsviewer/fitsviewer.cpp index c8c254223..7d12cb5ea 100644 --- a/kstars/fitsviewer/fitsviewer.cpp +++ b/kstars/fitsviewer/fitsviewer.cpp @@ -1,1011 +1,1011 @@ /*************************************************************************** FITSViewer.cpp - A FITSViewer for KStars ------------------- begin : Thu Jan 22 2004 copyright : (C) 2004 by Jasem Mutlaq email : mutlaqja@ikarustech.com 2006-03-03 Using CFITSIO, Porting to Qt4 ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "fitsviewer.h" #include "config-kstars.h" #include "fitsdata.h" #include "fitsdebayer.h" #include "fitstab.h" #include "fitsview.h" #include "kstars.h" #include "ksutils.h" #include "Options.h" #ifdef HAVE_INDI #include "indi/indilistener.h" #endif #include #include #include #include #ifndef KSTARS_LITE #include "fitshistogram.h" #endif #include #define INITIAL_W 785 #define INITIAL_H 640 QStringList FITSViewer::filterTypes = QStringList() << I18N_NOOP("Auto Stretch") << I18N_NOOP("High Contrast") << I18N_NOOP("Equalize") << I18N_NOOP("High Pass") << I18N_NOOP("Median") << I18N_NOOP("Rotate Right") << I18N_NOOP("Rotate Left") << I18N_NOOP("Flip Horizontal") << I18N_NOOP("Flip Vertical"); FITSViewer::FITSViewer(QWidget *parent) : KXmlGuiWindow(parent) { #ifdef Q_OS_OSX if (Options::independentWindowFITS()) setWindowFlags(Qt::Window); else { setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint); connect(QApplication::instance(), SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(changeAlwaysOnTop(Qt::ApplicationState))); } #endif fitsTabWidget = new QTabWidget(this); undoGroup = new QUndoGroup(this); lastURL = QUrl(QDir::homePath()); fitsTabWidget->setTabsClosable(true); setWindowIcon(QIcon::fromTheme("kstars_fitsviewer")); setCentralWidget(fitsTabWidget); connect(fitsTabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabFocusUpdated(int))); connect(fitsTabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTab(int))); //These two connections will enable or disable the scope button if a scope is available or not. //Of course this is also dependent on the presence of WCS data in the image. #ifdef HAVE_INDI connect(INDIListener::Instance(), SIGNAL(newTelescope(ISD::GDInterface*)), this, SLOT(updateWCSFunctions())); connect(INDIListener::Instance(), SIGNAL(deviceRemoved(ISD::GDInterface*)), this, SLOT(updateWCSFunctions())); #endif led.setColor(Qt::green); fitsPosition.setAlignment(Qt::AlignCenter); fitsValue.setAlignment(Qt::AlignCenter); //fitsPosition.setFixedWidth(100); //fitsValue.setFixedWidth(100); fitsWCS.setVisible(false); statusBar()->insertPermanentWidget(FITS_WCS, &fitsWCS); statusBar()->insertPermanentWidget(FITS_VALUE, &fitsValue); statusBar()->insertPermanentWidget(FITS_POSITION, &fitsPosition); statusBar()->insertPermanentWidget(FITS_ZOOM, &fitsZoom); statusBar()->insertPermanentWidget(FITS_RESOLUTION, &fitsResolution); statusBar()->insertPermanentWidget(FITS_LED, &led); QAction *action = actionCollection()->addAction("rotate_right", this, SLOT(rotateCW())); action->setText(i18n("Rotate Right")); action->setIcon(QIcon::fromTheme("object-rotate-right")); action = actionCollection()->addAction("rotate_left", this, SLOT(rotateCCW())); action->setText(i18n("Rotate Left")); action->setIcon(QIcon::fromTheme("object-rotate-left")); action = actionCollection()->addAction("flip_horizontal", this, SLOT(flipHorizontal())); action->setText(i18n("Flip Horizontal")); action->setIcon( QIcon::fromTheme("object-flip-horizontal")); action = actionCollection()->addAction("flip_vertical", this, SLOT(flipVertical())); action->setText(i18n("Flip Vertical")); action->setIcon(QIcon::fromTheme("object-flip-vertical")); action = actionCollection()->addAction("image_histogram"); action->setText(i18n("Histogram")); connect(action, SIGNAL(triggered(bool)), SLOT(histoFITS())); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_T)); action->setIcon(QIcon(":/icons/histogram.png")); action = KStandardAction::open(this, SLOT(openFile()), actionCollection()); action->setIcon(QIcon::fromTheme("document-open")); saveFileAction = KStandardAction::save(this, SLOT(saveFile()), actionCollection()); saveFileAction->setIcon(QIcon::fromTheme("document-save")); saveFileAsAction = KStandardAction::saveAs(this, SLOT(saveFileAs()), actionCollection()); saveFileAsAction->setIcon( QIcon::fromTheme("document-save_as")); action = actionCollection()->addAction("fits_header"); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_H)); action->setIcon(QIcon::fromTheme("document-properties")); action->setText(i18n("FITS Header")); connect(action, SIGNAL(triggered(bool)), SLOT(headerFITS())); action = actionCollection()->addAction("fits_debayer"); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_D)); action->setIcon(QIcon::fromTheme("view-preview")); action->setText(i18n("Debayer...")); connect(action, SIGNAL(triggered(bool)), SLOT(debayerFITS())); action = KStandardAction::close(this, SLOT(close()), actionCollection()); action->setIcon(QIcon::fromTheme("window-close")); action = KStandardAction::copy(this, SLOT(copyFITS()), actionCollection()); action->setIcon(QIcon::fromTheme("edit-copy")); action = KStandardAction::zoomIn(this, SLOT(ZoomIn()), actionCollection()); action->setIcon(QIcon::fromTheme("zoom-in")); action = KStandardAction::zoomOut(this, SLOT(ZoomOut()), actionCollection()); action->setIcon(QIcon::fromTheme("zoom-out")); action = KStandardAction::actualSize(this, SLOT(ZoomDefault()), actionCollection()); action->setIcon(QIcon::fromTheme("zoom-fit-best")); QAction *kundo = KStandardAction::undo(undoGroup, SLOT(undo()), actionCollection()); kundo->setIcon(QIcon::fromTheme("edit-undo")); QAction *kredo = KStandardAction::redo(undoGroup, SLOT(redo()), actionCollection()); kredo->setIcon(QIcon::fromTheme("edit-redo")); connect(undoGroup, SIGNAL(canUndoChanged(bool)), kundo, SLOT(setEnabled(bool))); connect(undoGroup, SIGNAL(canRedoChanged(bool)), kredo, SLOT(setEnabled(bool))); action = actionCollection()->addAction("image_stats"); action->setIcon(QIcon::fromTheme("view-statistics")); action->setText(i18n("Statistics")); connect(action, SIGNAL(triggered(bool)), SLOT(statFITS())); action = actionCollection()->addAction("view_crosshair"); action->setIcon(QIcon::fromTheme("crosshairs")); action->setText(i18n("Show Cross Hairs")); action->setCheckable(true); connect(action, SIGNAL(triggered(bool)), SLOT(toggleCrossHair())); action = actionCollection()->addAction("view_pixel_grid"); action->setIcon(QIcon::fromTheme("map-flat")); action->setText(i18n("Show Pixel Gridlines")); action->setCheckable(true); connect(action, SIGNAL(triggered(bool)), SLOT(togglePixelGrid())); action = actionCollection()->addAction("view_eq_grid"); action->setIcon(QIcon::fromTheme("kstars_grid")); action->setText(i18n("Show Equatorial Gridlines")); action->setCheckable(true); action->setDisabled(true); connect(action, SIGNAL(triggered(bool)), SLOT(toggleEQGrid())); action = actionCollection()->addAction("view_objects"); action->setIcon(QIcon::fromTheme("help-hint")); action->setText(i18n("Show Objects in Image")); action->setCheckable(true); action->setDisabled(true); connect(action, SIGNAL(triggered(bool)), SLOT(toggleObjects())); action = actionCollection()->addAction("center_telescope"); action->setIcon(QIcon(":/icons/center_telescope.svg")); action->setText(i18n("Center Telescope\n*No Telescopes Detected*")); action->setDisabled(true); action->setCheckable(true); connect(action, SIGNAL(triggered(bool)), SLOT(centerTelescope())); action = actionCollection()->addAction("view_zoom_fit"); action->setIcon(QIcon::fromTheme("zoom-fit-width")); action->setText(i18n("Zoom To Fit")); connect(action, SIGNAL(triggered(bool)), SLOT(ZoomToFit())); #ifdef HAVE_DATAVISUALIZATION action = actionCollection()->addAction("toggle_3D_graph"); action->setIcon(QIcon::fromTheme("star_profile", QIcon(":/icons/star_profile.svg"))); action->setText(i18n("View 3D Graph")); action->setCheckable(true); connect(action, SIGNAL(triggered(bool)), SLOT(toggle3DGraph())); #endif action = actionCollection()->addAction("mark_stars"); action->setText(i18n("Mark Stars")); connect(action, SIGNAL(triggered(bool)), SLOT(toggleStars())); int filterCounter = 1; for (auto& filter : FITSViewer::filterTypes) { action = actionCollection()->addAction(QString("filter%1").arg(filterCounter)); action->setText(i18n(filter.toUtf8().constData())); connect(action, &QAction::triggered, this, [this, filterCounter] { applyFilter(filterCounter);}); filterCounter++; } /* Create GUI */ createGUI("fitsviewerui.rc"); setWindowTitle(i18n("KStars FITS Viewer")); /* initially resize in accord with KDE rules */ show(); resize(INITIAL_W, INITIAL_H); } void FITSViewer::changeAlwaysOnTop(Qt::ApplicationState state) { if (isVisible()) { if (state == Qt::ApplicationActive) setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint); else setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint); show(); } } FITSViewer::~FITSViewer() { // if (KStars::Instance()) // { // for (QPointer fv : KStars::Instance()->getFITSViewersList()) // { // if (fv.data() == this) // { // KStars::Instance()->getFITSViewersList().removeOne(this); // break; // } // } // } fitsTabWidget->disconnect(); qDeleteAll(fitsTabs); fitsTabs.clear(); } void FITSViewer::closeEvent(QCloseEvent * /*event*/) { KStars *ks = KStars::Instance(); if (ks) { QAction *a = KStars::Instance()->actionCollection()->action("show_fits_viewer"); QList viewers = KStars::Instance()->findChildren(); if (a && viewers.count() == 1) { a->setEnabled(false); a->setChecked(false); } } } void FITSViewer::hideEvent(QHideEvent * /*event*/) { KStars *ks = KStars::Instance(); if (ks) { QAction *a = KStars::Instance()->actionCollection()->action("show_fits_viewer"); if (a) { QList viewers = KStars::Instance()->findChildren(); if (viewers.count() <= 1) a->setChecked(false); } } } void FITSViewer::showEvent(QShowEvent * /*event*/) { QAction *a = KStars::Instance()->actionCollection()->action("show_fits_viewer"); if (a) { a->setEnabled(true); a->setChecked(true); } } bool FITSViewer::addFITSCommon(FITSTab *tab, const QUrl &imageName, FITSMode mode, const QString &previewText) { int tabIndex = fitsTabWidget->indexOf(tab); if (tabIndex != -1) return false; lastURL = QUrl(imageName.url(QUrl::RemoveFilename)); QApplication::restoreOverrideCursor(); tab->setPreviewText(previewText); // Connect tab signals connect(tab, &FITSTab::newStatus, this, &FITSViewer::updateStatusBar); connect(tab, &FITSTab::changeStatus, this, &FITSViewer::updateTabStatus); connect(tab, &FITSTab::debayerToggled, this, &FITSViewer::setDebayerAction); // Connect tab view signals connect(tab->getView(), &FITSView::actionUpdated, this, &FITSViewer::updateAction); connect(tab->getView(), &FITSView::wcsToggled, this, &FITSViewer::updateWCSFunctions); connect(tab->getView(),&FITSView::starProfileWindowClosed, this, &FITSViewer::starProfileButtonOff); switch (mode) { case FITS_NORMAL: fitsTabWidget->addTab(tab, previewText.isEmpty() ? imageName.fileName() : previewText); break; case FITS_CALIBRATE: fitsTabWidget->addTab(tab, i18n("Calibrate")); break; case FITS_FOCUS: fitsTabWidget->addTab(tab, i18n("Focus")); break; case FITS_GUIDE: fitsTabWidget->addTab(tab, i18n("Guide")); break; case FITS_ALIGN: fitsTabWidget->addTab(tab, i18n("Align")); break; } saveFileAction->setEnabled(true); saveFileAsAction->setEnabled(true); undoGroup->addStack(tab->getUndoStack()); fitsTabs.push_back(tab); fitsMap[fitsID] = tab; fitsTabWidget->setCurrentWidget(tab); actionCollection()->action("fits_debayer")->setEnabled(tab->getView()->getImageData()->hasDebayer()); tab->tabPositionUpdated(); tab->setUID(fitsID); led.setColor(Qt::green); if (markStars) updateStatusBar(i18np("%1 star detected. HFR=%2", "%1 stars detected. HFR=%2", tab->getView()->getImageData()->getDetectedStars(), tab->getView()->getImageData()->getHFR()), FITS_MESSAGE); else updateStatusBar(i18n("Ready."), FITS_MESSAGE); tab->getView()->setCursorMode(FITSView::dragCursor); updateWCSFunctions(); return true; } void FITSViewer::addFITS(const QUrl &imageName, FITSMode mode, FITSScale filter, const QString &previewText, bool silent) { led.setColor(Qt::yellow); QApplication::setOverrideCursor(Qt::WaitCursor); FITSTab *tab = new FITSTab(this); connect(tab, &FITSTab::failed, [&]() { QApplication::restoreOverrideCursor(); led.setColor(Qt::red); if (fitsTabs.size() == 0) { // Close FITS Viewer and let KStars know it is no longer needed in memory. close(); } emit failed(); }); connect(tab, &FITSTab::loaded, [=]() { if (addFITSCommon(tab, imageName, mode, previewText)) emit loaded(fitsID++); }); tab->loadFITS(imageName, mode, filter, silent); } bool FITSViewer::addFITSFromData(FITSData *data, const QUrl &imageName, int *tab_uid, FITSMode mode, FITSScale filter, const QString &previewText) { led.setColor(Qt::yellow); QApplication::setOverrideCursor(Qt::WaitCursor); FITSTab *tab = new FITSTab(this); if (!tab->loadFITSFromData(data, imageName, mode, filter)) { QApplication::restoreOverrideCursor(); led.setColor(Qt::red); if (fitsTabs.size() == 0) { // Close FITS Viewer and let KStars know it is no longer needed in memory. close(); } emit failed(); return false; } if (!addFITSCommon(tab, imageName, mode, previewText)) return false; *tab_uid = fitsID++; return true; } bool FITSViewer::removeFITS(int fitsUID) { FITSTab *tab = fitsMap.value(fitsUID); if (tab == nullptr) { qCWarning(KSTARS_FITS) << "Cannot find tab with UID " << fitsUID << " in the FITS Viewer"; return false; } int index = fitsTabs.indexOf(tab); if (index >= 0) { closeTab(index); return true; } return false; } void FITSViewer::updateFITS(const QUrl &imageName, int fitsUID, FITSScale filter, bool silent) { FITSTab *tab = fitsMap.value(fitsUID); if (tab == nullptr) { qCWarning(KSTARS_FITS) << "Cannot find tab with UID " << fitsUID << " in the FITS Viewer"; emit failed(); return; } if (tab->isVisible()) led.setColor(Qt::yellow); // On tab load success auto conn = std::make_shared(); *conn = connect(tab, &FITSTab::loaded, this, [=]() { if (updateFITSCommon(tab, imageName)) { QObject::disconnect(*conn); emit loaded(tab->getUID()); } }); tab->loadFITS(imageName, tab->getView()->getMode(), filter, silent); } bool FITSViewer::updateFITSCommon(FITSTab *tab, const QUrl &imageName) { // On tab load success int tabIndex = fitsTabWidget->indexOf(tab); if (tabIndex == -1) return false; if (tab->getView()->getMode() == FITS_NORMAL) { if ((imageName.path().startsWith(QLatin1String("/tmp")) || imageName.path().contains("/Temp")) && Options::singlePreviewFITS()) fitsTabWidget->setTabText(tabIndex, tab->getPreviewText().isEmpty() ? i18n("Preview") : tab->getPreviewText()); else fitsTabWidget->setTabText(tabIndex, imageName.fileName()); } tab->getUndoStack()->clear(); if (tab->isVisible()) led.setColor(Qt::green); return true; } bool FITSViewer::updateFITSFromData(FITSData *data, const QUrl &imageName, int fitsUID, int *tab_uid, FITSScale filter) { FITSTab *tab = fitsMap.value(fitsUID); if (tab == nullptr) return false; if (tab->isVisible()) led.setColor(Qt::yellow); if (!tab->loadFITSFromData(data, imageName, tab->getView()->getMode(), filter)) return false; if (!updateFITSCommon(tab, imageName)) return false; *tab_uid = tab->getUID(); return true; } void FITSViewer::tabFocusUpdated(int currentIndex) { if (currentIndex < 0 || fitsTabs.empty()) return; fitsTabs[currentIndex]->tabPositionUpdated(); FITSView *view = fitsTabs[currentIndex]->getView(); view->toggleStars(markStars); if (isVisible()) view->updateFrame(); if (markStars) updateStatusBar(i18np("%1 star detected. HFR=%2", "%1 stars detected. HFR=%2", view->getImageData()->getDetectedStars(), view->getImageData()->getHFR()), FITS_MESSAGE); else updateStatusBar("", FITS_MESSAGE); if (view->getImageData()->hasDebayer()) { actionCollection()->action("fits_debayer")->setEnabled(true); if (debayerDialog) { BayerParams param; view->getImageData()->getBayerParams(¶m); debayerDialog->setBayerParams(¶m); } } else actionCollection()->action("fits_debayer")->setEnabled(false); updateStatusBar("", FITS_WCS); connect(view,&FITSView::starProfileWindowClosed, this, &FITSViewer::starProfileButtonOff); updateButtonStatus("toggle_3D_graph", i18n("View 3D Graph"), getCurrentView()->isStarProfileShown()); updateButtonStatus("view_crosshair", i18n("Cross Hairs"), getCurrentView()->isCrosshairShown()); updateButtonStatus("view_eq_grid", i18n("Equatorial Gridines"), getCurrentView()->isEQGridShown()); updateButtonStatus("view_objects", i18n("Objects in Image"), getCurrentView()->areObjectsShown()); updateButtonStatus("view_pixel_grid", i18n("Pixel Gridlines"), getCurrentView()->isPixelGridShown()); updateScopeButton(); updateWCSFunctions(); } void FITSViewer::starProfileButtonOff() { updateButtonStatus("toggle_3D_graph", i18n("View 3D Graph"), false); } void FITSViewer::openFile() { QUrl fileURL = QFileDialog::getOpenFileUrl(KStars::Instance(), i18n("Open FITS Image"), lastURL, "FITS (*.fits *.fits.fz *.fit *.fts)"); if (fileURL.isEmpty()) return; lastURL = QUrl(fileURL.url(QUrl::RemoveFilename)); QString fpath = fileURL.toLocalFile(); QString cpath; // Make sure we don't have it open already, if yes, switch to it foreach (FITSTab *tab, fitsTabs) { cpath = tab->getCurrentURL()->path(); if (fpath == cpath) { fitsTabWidget->setCurrentWidget(tab); return; } } addFITS(fileURL, FITS_NORMAL, FITS_NONE, QString(), false); } void FITSViewer::saveFile() { fitsTabs[fitsTabWidget->currentIndex()]->saveFile(); } void FITSViewer::saveFileAs() { if (fitsTabs.empty()) return; if (fitsTabs[fitsTabWidget->currentIndex()]->saveFileAs() && fitsTabs[fitsTabWidget->currentIndex()]->getView()->getMode() == FITS_NORMAL) fitsTabWidget->setTabText(fitsTabWidget->currentIndex(), fitsTabs[fitsTabWidget->currentIndex()]->getCurrentURL()->fileName()); } void FITSViewer::copyFITS() { if (fitsTabs.empty()) return; fitsTabs[fitsTabWidget->currentIndex()]->copyFITS(); } void FITSViewer::histoFITS() { if (fitsTabs.empty()) return; fitsTabs[fitsTabWidget->currentIndex()]->histoFITS(); } void FITSViewer::statFITS() { if (fitsTabs.empty()) return; fitsTabs[fitsTabWidget->currentIndex()]->statFITS(); } void FITSViewer::rotateCW() { applyFilter(FITS_ROTATE_CW); } void FITSViewer::rotateCCW() { applyFilter(FITS_ROTATE_CCW); } void FITSViewer::flipHorizontal() { applyFilter(FITS_FLIP_H); } void FITSViewer::flipVertical() { applyFilter(FITS_FLIP_V); } void FITSViewer::headerFITS() { if (fitsTabs.empty()) return; fitsTabs[fitsTabWidget->currentIndex()]->headerFITS(); } void FITSViewer::debayerFITS() { if (debayerDialog == nullptr) { debayerDialog = new FITSDebayer(this); } FITSView *view = getCurrentView(); if (view == nullptr) return; BayerParams param; view->getImageData()->getBayerParams(¶m); debayerDialog->setBayerParams(¶m); debayerDialog->show(); } void FITSViewer::updateStatusBar(const QString &msg, FITSBar id) { switch (id) { case FITS_POSITION: fitsPosition.setText(msg); break; case FITS_RESOLUTION: fitsResolution.setText(msg); break; case FITS_ZOOM: fitsZoom.setText(msg); break; case FITS_WCS: fitsWCS.setVisible(true); fitsWCS.setText(msg); break; case FITS_VALUE: fitsValue.setText(msg); break; case FITS_MESSAGE: statusBar()->showMessage(msg); break; default: break; } } void FITSViewer::ZoomIn() { if (fitsTabs.empty()) return; fitsTabs[fitsTabWidget->currentIndex()]->ZoomIn(); } void FITSViewer::ZoomOut() { if (fitsTabs.empty()) return; fitsTabs[fitsTabWidget->currentIndex()]->ZoomOut(); } void FITSViewer::ZoomDefault() { if (fitsTabs.empty()) return; fitsTabs[fitsTabWidget->currentIndex()]->ZoomDefault(); } void FITSViewer::ZoomToFit() { if (fitsTabs.empty()) return; getCurrentView()->ZoomToFit(); } void FITSViewer::updateAction(const QString &name, bool enable) { QAction *toolAction = actionCollection()->action(name); if (toolAction != nullptr) toolAction->setEnabled(enable); } void FITSViewer::updateTabStatus(bool clean) { if (fitsTabs.empty() || (fitsTabWidget->currentIndex() >= fitsTabs.size())) return; if (fitsTabs[fitsTabWidget->currentIndex()]->getView()->getMode() != FITS_NORMAL) return; //QString tabText = fitsImages[fitsTab->currentIndex()]->getCurrentURL()->fileName(); QString tabText = fitsTabWidget->tabText(fitsTabWidget->currentIndex()); fitsTabWidget->setTabText(fitsTabWidget->currentIndex(), clean ? tabText.remove('*') : tabText + '*'); } void FITSViewer::closeTab(int index) { if (fitsTabs.empty()) return; FITSTab *tab = fitsTabs[index]; int UID = tab->getUID(); fitsMap.remove(UID); fitsTabs.removeOne(tab); delete tab; if (fitsTabs.empty()) { saveFileAction->setEnabled(false); saveFileAsAction->setEnabled(false); } emit closed(UID); } /** This is helper function to make it really easy to make the update the state of toggle buttons that either show or hide information in the Current view. This method would get called both when one of them gets pushed and also when tabs are switched. */ void FITSViewer::updateButtonStatus(const QString& action, const QString& item, bool showing) { QAction *a = actionCollection()->action(action); if (a == nullptr) return; if (showing) { a->setText(i18n("Hide %1", item)); a->setChecked(true); } else { a->setText(i18n("Show %1", item)); a->setChecked(false); } } /** This is a method that either enables or disables the WCS based features in the Current View. */ void FITSViewer::updateWCSFunctions() { if (getCurrentView() == nullptr) return; if (getCurrentView()->imageHasWCS()) { actionCollection()->action("view_eq_grid")->setDisabled(false); actionCollection()->action("view_eq_grid")->setText(i18n("Show Equatorial Gridlines")); actionCollection()->action("view_objects")->setDisabled(false); actionCollection()->action("view_objects")->setText(i18n("Show Objects in Image")); if (getCurrentView()->isTelescopeActive()) { actionCollection()->action("center_telescope")->setDisabled(false); actionCollection()->action("center_telescope")->setText(i18n("Center Telescope\n*Ready*")); } else { actionCollection()->action("center_telescope")->setDisabled(true); actionCollection()->action("center_telescope")->setText(i18n("Center Telescope\n*No Telescopes Detected*")); } } else { actionCollection()->action("view_eq_grid")->setDisabled(true); actionCollection()->action("view_eq_grid")->setText(i18n("Show Equatorial Gridlines\n*No WCS Info*")); actionCollection()->action("center_telescope")->setDisabled(true); actionCollection()->action("center_telescope")->setText(i18n("Center Telescope\n*No WCS Info*")); actionCollection()->action("view_objects")->setDisabled(true); actionCollection()->action("view_objects")->setText(i18n("Show Objects in Image\n*No WCS Info*")); } } void FITSViewer::updateScopeButton() { if (getCurrentView()->getCursorMode() == FITSView::scopeCursor) { actionCollection()->action("center_telescope")->setChecked(true); } else { actionCollection()->action("center_telescope")->setChecked(false); } } /** - This methood either enables or disables the scope mouse mode so you can slew your scope to coordinates + This method either enables or disables the scope mouse mode so you can slew your scope to coordinates just by clicking the mouse on a spot in the image. */ void FITSViewer::centerTelescope() { getCurrentView()->setScopeButton(actionCollection()->action("center_telescope")); if (getCurrentView()->getCursorMode() == FITSView::scopeCursor) { getCurrentView()->setCursorMode(getCurrentView()->lastMouseMode); } else { getCurrentView()->lastMouseMode = getCurrentView()->getCursorMode(); getCurrentView()->setCursorMode(FITSView::scopeCursor); } updateScopeButton(); } void FITSViewer::toggleCrossHair() { if (fitsTabs.empty()) return; getCurrentView()->toggleCrosshair(); updateButtonStatus("view_crosshair", i18n("Cross Hairs"), getCurrentView()->isCrosshairShown()); } void FITSViewer::toggleEQGrid() { if (fitsTabs.empty()) return; getCurrentView()->toggleEQGrid(); updateButtonStatus("view_eq_grid", i18n("Equatorial Gridlines"), getCurrentView()->isEQGridShown()); } void FITSViewer::toggleObjects() { if (fitsTabs.empty()) return; getCurrentView()->toggleObjects(); updateButtonStatus("view_objects", i18n("Objects in Image"), getCurrentView()->areObjectsShown()); } void FITSViewer::togglePixelGrid() { if (fitsTabs.empty()) return; getCurrentView()->togglePixelGrid(); updateButtonStatus("view_pixel_grid", i18n("Pixel Gridlines"), getCurrentView()->isPixelGridShown()); } void FITSViewer::toggle3DGraph() { if (fitsTabs.empty()) return; getCurrentView()->toggleStarProfile(); updateButtonStatus("toggle_3D_graph", i18n("View 3D Graph"), getCurrentView()->isStarProfileShown()); } void FITSViewer::toggleStars() { if (markStars) { markStars = false; actionCollection()->action("mark_stars")->setText(i18n("Mark Stars")); } else { markStars = true; actionCollection()->action("mark_stars")->setText(i18n("Unmark Stars")); } foreach (FITSTab *tab, fitsTabs) { tab->getView()->toggleStars(markStars); tab->getView()->updateFrame(); } } void FITSViewer::applyFilter(int ftype) { if (fitsTabs.empty()) return; QApplication::setOverrideCursor(Qt::WaitCursor); updateStatusBar(i18n("Processing %1...", filterTypes[ftype - 1]), FITS_MESSAGE); qApp->processEvents(); fitsTabs[fitsTabWidget->currentIndex()]->getHistogram()->applyFilter(static_cast(ftype)); qApp->processEvents(); fitsTabs[fitsTabWidget->currentIndex()]->getView()->updateFrame(); QApplication::restoreOverrideCursor(); updateStatusBar(i18n("Ready."), FITS_MESSAGE); } FITSView *FITSViewer::getView(int fitsUID) { FITSTab *tab = fitsMap.value(fitsUID); if (tab) return tab->getView(); return nullptr; } FITSView *FITSViewer::getCurrentView() { if (fitsTabs.empty() || fitsTabWidget->currentIndex() >= fitsTabs.count()) return nullptr; return fitsTabs[fitsTabWidget->currentIndex()]->getView(); } void FITSViewer::setDebayerAction(bool enable) { actionCollection()->addAction("fits_debayer")->setEnabled(enable); } diff --git a/kstars/fitsviewer/sep/sep.h b/kstars/fitsviewer/sep/sep.h index 8b0289497..beccdee2a 100644 --- a/kstars/fitsviewer/sep/sep.h +++ b/kstars/fitsviewer/sep/sep.h @@ -1,415 +1,415 @@ /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% * * This file is part of SEP * * Copyright 1993-2011 Emmanuel Bertin -- IAP/CNRS/UPMC * Copyright 2014 SEP developers * * SEP is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * SEP is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with SEP. If not, see . * *%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/ /* datatype codes */ #define SEP_TBYTE 11 /* 8-bit unsigned byte */ #define SEP_TINT 31 /* native int type */ #define SEP_TFLOAT 42 #define SEP_TDOUBLE 82 /* object & aperture flags */ #define SEP_OBJ_MERGED 0x0001 /* object is result of deblending */ #define SEP_OBJ_TRUNC 0x0002 /* object truncated at image boundary */ #define SEP_OBJ_DOVERFLOW 0x0004 /* not currently used, but could be */ #define SEP_OBJ_SINGU 0x0008 /* x,y fully correlated */ #define SEP_APER_TRUNC 0x0010 #define SEP_APER_HASMASKED 0x0020 #define SEP_APER_ALLMASKED 0x0040 #define SEP_APER_NONPOSITIVE 0x0080 /* noise_type values in sep_image */ #define SEP_NOISE_NONE 0 #define SEP_NOISE_STDDEV 1 #define SEP_NOISE_VAR 2 /* input flags for aperture photometry */ #define SEP_MASK_IGNORE 0x0004 /* threshold interpretation for sep_extract */ #define SEP_THRESH_REL 0 /* in units of standard deviations (sigma) */ #define SEP_THRESH_ABS 1 /* absolute data values */ /* filter types for sep_extract */ #define SEP_FILTER_CONV 0 #define SEP_FILTER_MATCHED 1 /* structs ------------------------------------------------------------------*/ /* sep_image * * Represents an image, including data, noise and mask arrays, and * gain. */ typedef struct { void *data; /* data array */ void *noise; /* noise array (can be NULL) */ void *mask; /* mask array (can be NULL) */ int dtype; /* element type of image */ int ndtype; /* element type of noise */ int mdtype; /* element type of mask */ int w; /* array width */ int h; /* array height */ double noiseval; /* scalar noise value; used only if noise == NULL */ short noise_type; /* interpretation of noise value */ double gain; /* (poisson counts / data unit) */ double maskthresh; /* pixel considered masked if mask > maskthresh */ } sep_image; /* sep_bkg * * The result of sep_background() -- represents a smooth image background * and its noise with splines. */ typedef struct { int w, h; /* original image width, height */ int bw, bh; /* single tile width, height */ int nx, ny; /* number of tiles in x, y */ int n; /* nx*ny */ float global; /* global mean */ float globalrms; /* global sigma */ float *back; /* node data for interpolation */ float *dback; float *sigma; float *dsigma; } sep_bkg; /* sep_catalog * * The result of sep_extract(). This is a struct of arrays. Each array has * one entry per detected object. */ typedef struct { int nobj; /* number of objects (length of all arrays) */ float *thresh; /* threshold (ADU) */ int *npix; /* # pixels extracted (size of pix array) */ int *tnpix; /* # pixels above thresh (unconvolved) */ int *xmin, *xmax; int *ymin, *ymax; double *x, *y; /* barycenter (first moments) */ double *x2, *y2, *xy; /* second moments */ double *errx2, *erry2, *errxy; /* second moment errors */ float *a, *b, *theta; /* ellipse parameters */ float *cxx, *cyy, *cxy; /* ellipse parameters (alternative) */ float *cflux; /* total flux of pixels (convolved im) */ float *flux; /* total flux of pixels (unconvolved) */ float *cpeak; /* peak intensity (ADU) (convolved) */ float *peak; /* peak intensity (ADU) (unconvolved) */ int *xcpeak, *ycpeak; /* x, y coords of peak (convolved) pixel */ int *xpeak, *ypeak; /* x, y coords of peak (unconvolved) pixel */ short *flag; /* extraction flags */ int **pix; /* array giving indicies of object's pixels in */ /* image (linearly indexed). Length is `npix`. */ /* (pointer to within the `objectspix` buffer) */ int *objectspix; /* buffer holding pixel indicies for all objects */ } sep_catalog; #ifdef __cplusplus extern "C" { #endif /*--------------------- global background estimation ------------------------*/ /* sep_background() * * Create representation of spatially varying image background and variance. * * Note that the returned pointer must eventually be freed by calling * `sep_bkg_free()`. * * In addition to the image mask (if present), pixels <= -1e30 and NaN * are ignored. * * Source Extractor defaults: * * - bw, bh = (64, 64) * - fw, fh = (3, 3) * - fthresh = 0.0 */ int sep_background(sep_image *image, int bw, int bh, /* size of a single background tile */ int fw, int fh, /* filter size in tiles */ double fthresh, /* filter threshold */ sep_bkg **bkg); /* OUTPUT */ /* sep_bkg_global[rms]() * * Get the estimate of the global background "median" or standard deviation. */ float sep_bkg_global(sep_bkg *bkg); float sep_bkg_globalrms(sep_bkg *bkg); /* sep_bkg_pix() * * Return background at (x, y). * Unlike other routines, this uses simple linear interpolation. */ float sep_bkg_pix(sep_bkg *bkg, int x, int y); /* sep_bkg_[sub,rms]line() * * Evaluate the background or RMS at line `y`. * Uses bicubic spline interpolation between background map vertices. * The second function subtracts the background from the input array. * Line must be an array with same width as original image. */ int sep_bkg_line(sep_bkg *bkg, int y, void *line, int dtype); int sep_bkg_subline(sep_bkg *bkg, int y, void *line, int dtype); int sep_bkg_rmsline(sep_bkg *bkg, int y, void *line, int dtype); /* sep_bkg_[sub,rms]array() * * Evaluate the background or RMS for entire image. * Uses bicubic spline interpolation between background map vertices. * The second function subtracts the background from the input array. * `arr` must be an array of the same size as original image. */ int sep_bkg_array(sep_bkg *bkg, void *arr, int dtype); int sep_bkg_subarray(sep_bkg *bkg, void *arr, int dtype); int sep_bkg_rmsarray(sep_bkg *bkg, void *arr, int dtype); /* sep_bkg_free() * * Free memory associated with bkg. */ void sep_bkg_free(sep_bkg *bkg); /*-------------------------- source extraction ------------------------------*/ /* sep_extract() * * Extract sources from an image. Source Extractor defaults are shown * in [ ] above. * * Notes * ----- * `dtype` and `ndtype` indicate the data type (float, int, double) of the * image and noise arrays, respectively. * * If `noise` is NULL, thresh is interpreted as an absolute threshold. * If `noise` is not null, thresh is interpreted as a relative threshold * (the absolute threshold will be thresh*noise[i,j]). * */ int sep_extract(sep_image *image, float thresh, /* detection threshold [1.5] */ int thresh_type, /* threshold units [SEP_THRESH_REL] */ int minarea, /* minimum area in pixels [5] */ float *conv, /* convolution array (can be NULL) */ /* [{1 2 1 2 4 2 1 2 1}] */ int convw, int convh, /* w, h of convolution array [3,3] */ int filter_type, /* convolution (0) or matched (1) [0] */ int deblend_nthresh, /* deblending thresholds [32] */ double deblend_cont, /* min. deblending contrast [0.005] */ int clean_flag, /* perform cleaning? [1] */ double clean_param, /* clean parameter [1.0] */ sep_catalog **catalog); /* OUTPUT catalog */ /* set and get the size of the pixel stack used in extract() */ void sep_set_extract_pixstack(size_t val); size_t sep_get_extract_pixstack(void); /* free memory associated with a catalog */ void sep_catalog_free(sep_catalog *catalog); /*-------------------------- aperture photometry ----------------------------*/ /* Sum array values within a circular aperture. * * Notes * ----- * error : Can be a scalar (default), an array, or NULL * If an array, set the flag SEP_ERROR_IS_ARRAY in `inflags`. * Can represent 1-sigma std. deviation (default) or variance. * If variance, set the flag SEP_ERROR_IS_VARIANCE in `inflags`. * * gain : If 0.0, poisson noise on sum is ignored when calculating error. * Otherwise, (sum / gain) is added to the variance on sum. * * area : Total pixel area included in sum. Includes masked pixels that were * corrected. The area can differ from the exact area of a circle due * to inexact subpixel sampling and intersection with array boundaries. */ int sep_sum_circle(sep_image *image, double x, /* center of aperture in x */ double y, /* center of aperture in y */ double r, /* radius of aperture */ int subpix, /* subpixel sampling */ short inflags, /* input flags (see below) */ double *sum, /* OUTPUT: sum */ double *sumerr, /* OUTPUT: error on sum */ double *area, /* OUTPUT: area included in sum */ short *flag); /* OUTPUT: flags */ int sep_sum_circann(sep_image *image, double x, double y, double rin, double rout, int subpix, short inflags, double *sum, double *sumerr, double *area, short *flag); int sep_sum_ellipse(sep_image *image, double x, double y, double a, double b, double theta, double r, int subpix, short inflags, double *sum, double *sumerr, double *area, short *flag); int sep_sum_ellipann(sep_image *image, double x, double y, double a, double b, double theta, double rin, double rout, int subpix, short inflags, double *sum, double *sumerr, double *area, short *flag); /* sep_sum_circann_multi() * * Sum an array of circular annuli more efficiently (but with no exact mode). * * Notable parameters: * * rmax: Input radii are [rmax/n, 2*rmax/n, 3*rmax/n, ..., rmax]. * n: Length of input and output arrays. * sum: Preallocated array of length n holding sums in annuli. sum[0] * corresponds to r=[0, rmax/n], sum[n-1] to outermost annulus. * sumvar: Preallocated array of length n holding variance on sums. * area: Preallocated array of length n holding area summed in each annulus. * maskarea: Preallocated array of length n holding masked area in each annulus (if mask not NULL). * flag: Output flag (non-array). */ int sep_sum_circann_multi(sep_image *im, double x, double y, double rmax, int n, int subpix, short inflag, double *sum, double *sumvar, double *area, double *maskarea, short *flag); /* sep_flux_radius() * * Calculate the radii enclosing the requested fraction of flux relative * to radius rmax. * * (see previous functions for most arguments) * rmax : maximum radius to analyze * fluxtot : scale requested flux fractions to this. (If NULL, flux within `rmax` is used.) * fluxfrac : array of requested fractions. * n : length of fluxfrac * r : (output) result array of length n. * flag : (output) scalar flag */ int sep_flux_radius(sep_image *im, double x, double y, double rmax, int subpix, short inflag, double *fluxtot, double *fluxfrac, int n, double *r, short *flag); /* sep_kron_radius() * * Calculate Kron radius within an ellipse given by * * cxx*(x'-x)^2 + cyy*(y'-y)^2 + cxy*(x'-x)*(y'-y) < r^2 * * The Kron radius is sum(r_i * v_i) / sum(v_i) where v_i is the value of pixel * i and r_i is the "radius" of pixel i, as given by the left hand side of * the above equation. * * Flags that might be set: * SEP_APER_HASMASKED - at least one of the pixels in the ellipse is masked. * SEP_APER_ALLMASKED - All pixels in the ellipse are masked. kronrad = 0. * SEP_APER_NONPOSITIVE - There was a non-positive numerator or denominator. * kronrad = 0. */ int sep_kron_radius(sep_image *im, double x, double y, double cxx, double cyy, double cxy, double r, double *kronrad, short *flag); /* sep_windowed() * * Calculate "windowed" position parameters via an iterative procedure. * * x, y : initial center * sig : sigma of Gaussian to use for weighting. The integration * radius is 4 * sig. * subpix : Subpixels to use in aperture-pixel overlap. * SExtractor uses 11. 0 is supported for exact overlap. * xout, yout : output center. * niter : number of iterations used. */ int sep_windowed(sep_image *im, double x, double y, double sig, int subpix, short inflag, double *xout, double *yout, int *niter, short *flag); /* sep_set_ellipse() * - * Set array elements within an ellipitcal aperture to a given value. + * Set array elements within an elliptical aperture to a given value. * * Ellipse: cxx*(x'-x)^2 + cyy*(y'-y)^2 + cxy*(x'-x)*(y'-y) = r^2 */ void sep_set_ellipse(unsigned char *arr, int w, int h, double x, double y, double cxx, double cyy, double cxy, double r, unsigned char val); /* sep_ellipse_axes() * sep_ellipse_coeffs() * * Convert between coefficient representation of ellipse, * cxx*(x'-x)^2 + cyy*(y'-y)^2 + cxy*(x'-x)*(y'-y) = r^2, * and axis representation of an ellipse. The axis representation is * defined by: * * a = semimajor axis * b = semiminor axis * theta = angle in radians counter-clockwise from positive x axis */ int sep_ellipse_axes(double cxx, double cyy, double cxy, double *a, double *b, double *theta); void sep_ellipse_coeffs(double a, double b, double theta, double *cxx, double *cyy, double *cxy); /*----------------------- info & error messaging ----------------------------*/ /* sep_version_string : library version (e.g., "0.2.0") */ extern char *sep_version_string; /* sep_get_errmsg() * * Return a short descriptive error message that corresponds to the input * error status value. The message may be up to 60 characters long, plus * the terminating null character. */ void sep_get_errmsg(int status, char *errtext); /* sep_get_errdetail() * * Return a longer error message with more specifics about the problem. * The message may be up to 512 characters. */ void sep_get_errdetail(char *errtext); #ifdef __cplusplus } #endif diff --git a/kstars/indi/inditelescope.cpp b/kstars/indi/inditelescope.cpp index 1a2c64354..8fa476de3 100644 --- a/kstars/indi/inditelescope.cpp +++ b/kstars/indi/inditelescope.cpp @@ -1,1451 +1,1451 @@ /* INDI CCD Copyright (C) 2012 Jasem Mutlaq This application 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. */ #include "inditelescope.h" #include "clientmanager.h" #include "driverinfo.h" #include "indidevice.h" #include "kstars.h" #include "Options.h" #include "skymap.h" #include "skymapcomposite.h" #include "ksnotification.h" #include #include #include namespace ISD { Telescope::Telescope(GDInterface *iPtr) : DeviceDecorator(iPtr) { dType = KSTARS_TELESCOPE; minAlt = -1; maxAlt = -1; EqCoordPreviousState = IPS_IDLE; // Set it for 5 seconds for now as not to spam the display update centerLockTimer.setInterval(5000); centerLockTimer.setSingleShot(true); connect(¢erLockTimer, &QTimer::timeout, this, [this]() { runCommand(INDI_CENTER_LOCK); }); // If after 250ms no new properties are registered then emit ready readyTimer.setInterval(250); readyTimer.setSingleShot(true); connect(&readyTimer, &QTimer::timeout, this, &Telescope::ready); qRegisterMetaType("ISD::Telescope::Status"); qDBusRegisterMetaType(); qRegisterMetaType("ISD::Telescope::PierSide"); qDBusRegisterMetaType(); } void Telescope::registerProperty(INDI::Property *prop) { if (isConnected()) readyTimer.start(); if (!strcmp(prop->getName(), "TELESCOPE_INFO")) { INumberVectorProperty *ti = prop->getNumber(); if (ti == nullptr) return; bool aperture_ok = false, focal_ok = false; double temp = 0; INumber *aperture = IUFindNumber(ti, "TELESCOPE_APERTURE"); if (aperture && aperture->value == 0) { if (getDriverInfo()->getAuxInfo().contains("TELESCOPE_APERTURE")) { temp = getDriverInfo()->getAuxInfo().value("TELESCOPE_APERTURE").toDouble(&aperture_ok); if (aperture_ok) { aperture->value = temp; INumber *g_aperture = IUFindNumber(ti, "GUIDER_APERTURE"); if (g_aperture && g_aperture->value == 0) g_aperture->value = aperture->value; } } } INumber *focal_length = IUFindNumber(ti, "TELESCOPE_FOCAL_LENGTH"); if (focal_length && focal_length->value == 0) { if (getDriverInfo()->getAuxInfo().contains("TELESCOPE_FOCAL_LENGTH")) { temp = getDriverInfo()->getAuxInfo().value("TELESCOPE_FOCAL_LENGTH").toDouble(&focal_ok); if (focal_ok) { focal_length->value = temp; INumber *g_focal = IUFindNumber(ti, "GUIDER_FOCAL_LENGTH"); if (g_focal && g_focal->value == 0) g_focal->value = focal_length->value; } } } if (aperture_ok && focal_ok) clientManager->sendNewNumber(ti); } else if (!strcmp(prop->getName(), "ON_COORD_SET")) { m_canGoto = IUFindSwitch(prop->getSwitch(), "TRACK") != nullptr; m_canSync = IUFindSwitch(prop->getSwitch(), "SYNC") != nullptr; } // Telescope Park else if (!strcmp(prop->getName(), "TELESCOPE_PARK")) { ISwitchVectorProperty *svp = prop->getSwitch(); if (svp) { ISwitch *sp = IUFindSwitch(svp, "PARK"); if (sp) { if ((sp->s == ISS_ON) && svp->s == IPS_OK) { m_ParkStatus = PARK_PARKED; emit newParkStatus(m_ParkStatus); QAction *parkAction = KStars::Instance()->actionCollection()->action("telescope_park"); if (parkAction) parkAction->setEnabled(false); QAction *unParkAction = KStars::Instance()->actionCollection()->action("telescope_unpark"); if (unParkAction) unParkAction->setEnabled(true); } else if ((sp->s == ISS_OFF) && svp->s == IPS_OK) { m_ParkStatus = PARK_UNPARKED; emit newParkStatus(m_ParkStatus); QAction *parkAction = KStars::Instance()->actionCollection()->action("telescope_park"); if (parkAction) parkAction->setEnabled(true); QAction *unParkAction = KStars::Instance()->actionCollection()->action("telescope_unpark"); if (unParkAction) unParkAction->setEnabled(false); } } } } else if (!strcmp(prop->getName(), "TELESCOPE_PIER_SIDE")) { ISwitchVectorProperty *svp = prop->getSwitch(); int currentSide = IUFindOnSwitchIndex(svp); if (currentSide != m_PierSide) { m_PierSide = static_cast(currentSide); emit pierSideChanged(m_PierSide); } } else if (!strcmp(prop->getName(), "ALIGNMENT_POINTSET_ACTION") || !strcmp(prop->getName(), "ALIGNLIST")) m_hasAlignmentModel = true; else if (!strcmp(prop->getName(), "TELESCOPE_TRACK_STATE")) m_canControlTrack = true; else if (!strcmp(prop->getName(), "TELESCOPE_TRACK_MODE")) { m_hasTrackModes = true; ISwitchVectorProperty *svp = prop->getSwitch(); for (int i = 0; i < svp->nsp; i++) { if (!strcmp(svp->sp[i].name, "TRACK_SIDEREAL")) TrackMap[TRACK_SIDEREAL] = i; else if (!strcmp(svp->sp[i].name, "TRACK_SOLAR")) TrackMap[TRACK_SOLAR] = i; else if (!strcmp(svp->sp[i].name, "TRACK_LUNAR")) TrackMap[TRACK_LUNAR] = i; else if (!strcmp(svp->sp[i].name, "TRACK_CUSTOM")) TrackMap[TRACK_CUSTOM] = i; } } else if (!strcmp(prop->getName(), "TELESCOPE_TRACK_RATE")) m_hasCustomTrackRate = true; else if (!strcmp(prop->getName(), "TELESCOPE_ABORT_MOTION")) m_canAbort = true; else if (!strcmp(prop->getName(), "TELESCOPE_PARK_OPTION")) m_hasCustomParking = true; else if (!strcmp(prop->getName(), "TELESCOPE_SLEW_RATE")) { m_hasSlewRates = true; ISwitchVectorProperty *svp = prop->getSwitch(); if (svp) { m_slewRates.clear(); for (int i = 0; i < svp->nsp; i++) m_slewRates << svp->sp[i].label; } } else if (!strcmp(prop->getName(), "EQUATORIAL_EOD_COORD")) { m_isJ2000 = false; } else if (!strcmp(prop->getName(), "EQUATORIAL_COORD")) { m_isJ2000 = true; } DeviceDecorator::registerProperty(prop); } void Telescope::processNumber(INumberVectorProperty *nvp) { if (!strcmp(nvp->name, "EQUATORIAL_EOD_COORD") || !strcmp(nvp->name, "EQUATORIAL_COORD")) { INumber *RA = IUFindNumber(nvp, "RA"); INumber *DEC = IUFindNumber(nvp, "DEC"); if (RA == nullptr || DEC == nullptr) return; currentCoord.setRA(RA->value); currentCoord.setDec(DEC->value); // If J2000, convert it to JNow if (!strcmp(nvp->name, "EQUATORIAL_COORD")) { currentCoord.setRA0(RA->value); currentCoord.setDec0(DEC->value); currentCoord.apparentCoord(static_cast(J2000), KStars::Instance()->data()->ut().djd()); } currentCoord.EquatorialToHorizontal(KStars::Instance()->data()->lst(), KStars::Instance()->data()->geo()->lat()); if (nvp->s == IPS_BUSY && EqCoordPreviousState != IPS_BUSY) { if (status() != MOUNT_PARKING) KSNotification::event(QLatin1String("SlewStarted"), i18n("Mount is slewing to target location")); } else if (EqCoordPreviousState == IPS_BUSY && nvp->s == IPS_OK) { KSNotification::event(QLatin1String("SlewCompleted"), i18n("Mount arrived at target location")); double maxrad = 1000.0 / Options::zoomFactor(); currentObject = KStarsData::Instance()->skyComposite()->objectNearest(¤tCoord, maxrad); if (currentObject != nullptr) emit newTarget(currentObject->name()); } EqCoordPreviousState = nvp->s; KStars::Instance()->map()->update(); } else if (!strcmp(nvp->name, "HORIZONTAL_COORD")) { INumber *Az = IUFindNumber(nvp, "AZ"); INumber *Alt = IUFindNumber(nvp, "ALT"); if (Az == nullptr || Alt == nullptr) return; currentCoord.setAz(Az->value); currentCoord.setAlt(Alt->value); currentCoord.HorizontalToEquatorial(KStars::Instance()->data()->lst(), KStars::Instance()->data()->geo()->lat()); KStars::Instance()->map()->update(); } DeviceDecorator::processNumber(nvp); } void Telescope::processSwitch(ISwitchVectorProperty *svp) { bool manualMotionChanged = false; if (!strcmp(svp->name, "CONNECTION")) { ISwitch *conSP = IUFindSwitch(svp, "CONNECT"); if (conSP) { // TODO We must allow for multiple mount drivers to be online, not just one // For the actions taken, the user should be able to specify which mounts shall receive the commands. It could be one // or more. For now, we enable/disable telescope group on the assumption there is only one mount present. if (isConnected() == false && conSP->s == ISS_ON) KStars::Instance()->slotSetTelescopeEnabled(true); else if (isConnected() && conSP->s == ISS_OFF) { KStars::Instance()->slotSetTelescopeEnabled(false); centerLockTimer.stop(); } } } else if (!strcmp(svp->name, "TELESCOPE_PARK")) { ISwitch *sp = IUFindSwitch(svp, "PARK"); if (sp) { if (svp->s == IPS_ALERT) { // First, inform everyone watch this that an error occurred. emit newParkStatus(PARK_ERROR); // If alert, set park status to whatever it was opposite to. That is, if it was parking and failed // then we set status to unparked since it did not successfully complete parking. if (m_ParkStatus == PARK_PARKING) m_ParkStatus = PARK_UNPARKED; else if (m_ParkStatus == PARK_UNPARKING) m_ParkStatus = PARK_PARKED; emit newParkStatus(m_ParkStatus); KSNotification::event(QLatin1String("MountParkingFailed"), i18n("Mount parking failed"), KSNotification::EVENT_ALERT); } else if (svp->s == IPS_BUSY && sp->s == ISS_ON && m_ParkStatus != PARK_PARKING) { m_ParkStatus = PARK_PARKING; KSNotification::event(QLatin1String("MountParking"), i18n("Mount parking is in progress")); currentObject = nullptr; emit newParkStatus(m_ParkStatus); } else if (svp->s == IPS_BUSY && sp->s == ISS_OFF && m_ParkStatus != PARK_UNPARKING) { m_ParkStatus = PARK_UNPARKING; KSNotification::event(QLatin1String("MountUnParking"), i18n("Mount unparking is in progress")); emit newParkStatus(m_ParkStatus); } else if (svp->s == IPS_OK && sp->s == ISS_ON && m_ParkStatus != PARK_PARKED) { m_ParkStatus = PARK_PARKED; KSNotification::event(QLatin1String("MountParked"), i18n("Mount parked")); currentObject = nullptr; emit newParkStatus(m_ParkStatus); QAction *parkAction = KStars::Instance()->actionCollection()->action("telescope_park"); if (parkAction) parkAction->setEnabled(false); QAction *unParkAction = KStars::Instance()->actionCollection()->action("telescope_unpark"); if (unParkAction) unParkAction->setEnabled(true); emit newTarget(QString()); } else if ( (svp->s == IPS_OK || svp->s == IPS_IDLE) && sp->s == ISS_OFF && m_ParkStatus != PARK_UNPARKED) { m_ParkStatus = PARK_UNPARKED; KSNotification::event(QLatin1String("MountUnparked"), i18n("Mount unparked")); currentObject = nullptr; emit newParkStatus(m_ParkStatus); QAction *parkAction = KStars::Instance()->actionCollection()->action("telescope_park"); if (parkAction) parkAction->setEnabled(true); QAction *unParkAction = KStars::Instance()->actionCollection()->action("telescope_unpark"); if (unParkAction) unParkAction->setEnabled(false); } } } else if (!strcmp(svp->name, "TELESCOPE_ABORT_MOTION")) { if (svp->s == IPS_OK) { inCustomParking = false; KSNotification::event(QLatin1String("MountAborted"), i18n("Mount motion was aborted"), KSNotification::EVENT_WARN); } } else if (!strcmp(svp->name, "TELESCOPE_PIER_SIDE")) { int currentSide = IUFindOnSwitchIndex(svp); if (currentSide != m_PierSide) { m_PierSide = static_cast(currentSide); emit pierSideChanged(m_PierSide); } } else if (!strcmp(svp->name, "TELESCOPE_TRACK_MODE")) { ISwitch *sp = IUFindOnSwitch(svp); if (sp) { if (!strcmp(sp->name, "TRACK_SIDEREAL")) currentTrackMode = TRACK_SIDEREAL; else if (!strcmp(sp->name, "TRACK_SOLAR")) currentTrackMode = TRACK_SOLAR; else if (!strcmp(sp->name, "TRACK_LUNAR")) currentTrackMode = TRACK_LUNAR; else currentTrackMode = TRACK_CUSTOM; } } else if (!strcmp(svp->name, "TELESCOPE_MOTION_NS")) manualMotionChanged = true; else if (!strcmp(svp->name, "TELESCOPE_MOTION_WE")) manualMotionChanged = true; if (manualMotionChanged) { IPState NSCurrentMotion, WECurrentMotion; NSCurrentMotion = baseDevice->getSwitch("TELESCOPE_MOTION_NS")->s; WECurrentMotion = baseDevice->getSwitch("TELESCOPE_MOTION_WE")->s; inCustomParking = false; if (NSCurrentMotion == IPS_BUSY || WECurrentMotion == IPS_BUSY || NSPreviousState == IPS_BUSY || WEPreviousState == IPS_BUSY) { if (inManualMotion == false && ((NSCurrentMotion == IPS_BUSY && NSPreviousState != IPS_BUSY) || (WECurrentMotion == IPS_BUSY && WEPreviousState != IPS_BUSY))) { inManualMotion = true; KSNotification::event(QLatin1String("MotionStarted"), i18n("Mount is manually moving")); } else if (inManualMotion && ((NSCurrentMotion != IPS_BUSY && NSPreviousState == IPS_BUSY) || (WECurrentMotion != IPS_BUSY && WEPreviousState == IPS_BUSY))) { inManualMotion = false; KSNotification::event(QLatin1String("MotionStopped"), i18n("Mount motion stopped")); } NSPreviousState = NSCurrentMotion; WEPreviousState = WECurrentMotion; } } DeviceDecorator::processSwitch(svp); } void Telescope::processText(ITextVectorProperty *tvp) { DeviceDecorator::processText(tvp); } bool Telescope::canGuide() { INumberVectorProperty *raPulse = baseDevice->getNumber("TELESCOPE_TIMED_GUIDE_WE"); INumberVectorProperty *decPulse = baseDevice->getNumber("TELESCOPE_TIMED_GUIDE_NS"); if (raPulse && decPulse) return true; else return false; } bool Telescope::canPark() { ISwitchVectorProperty *parkSP = baseDevice->getSwitch("TELESCOPE_PARK"); if (parkSP == nullptr) return false; ISwitch *parkSW = IUFindSwitch(parkSP, "PARK"); return (parkSW != nullptr); } bool Telescope::isSlewing() { INumberVectorProperty *EqProp = baseDevice->getNumber("EQUATORIAL_EOD_COORD"); if (EqProp == nullptr) return false; return (EqProp->s == IPS_BUSY); } bool Telescope::isInMotion() { return (isSlewing() || inManualMotion); } bool Telescope::doPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs) { if (canGuide() == false) return false; bool raOK = doPulse(ra_dir, ra_msecs); bool decOK = doPulse(dec_dir, dec_msecs); if (raOK && decOK) return true; else return false; } bool Telescope::doPulse(GuideDirection dir, int msecs) { INumberVectorProperty *raPulse = baseDevice->getNumber("TELESCOPE_TIMED_GUIDE_WE"); INumberVectorProperty *decPulse = baseDevice->getNumber("TELESCOPE_TIMED_GUIDE_NS"); INumberVectorProperty *npulse = nullptr; INumber *dirPulse = nullptr; if (raPulse == nullptr || decPulse == nullptr) return false; switch (dir) { case RA_INC_DIR: dirPulse = IUFindNumber(raPulse, "TIMED_GUIDE_W"); if (dirPulse == nullptr) return false; npulse = raPulse; break; case RA_DEC_DIR: dirPulse = IUFindNumber(raPulse, "TIMED_GUIDE_E"); if (dirPulse == nullptr) return false; npulse = raPulse; break; case DEC_INC_DIR: dirPulse = IUFindNumber(decPulse, "TIMED_GUIDE_N"); if (dirPulse == nullptr) return false; npulse = decPulse; break; case DEC_DEC_DIR: dirPulse = IUFindNumber(decPulse, "TIMED_GUIDE_S"); if (dirPulse == nullptr) return false; npulse = decPulse; break; default: return false; } dirPulse->value = msecs; clientManager->sendNewNumber(npulse); //qDebug() << "Sending pulse for " << npulse->name << " in direction " << dirPulse->name << " for " << msecs << " ms " << endl; return true; } bool Telescope::runCommand(int command, void *ptr) { //qDebug() << "Telescope run command is called!!!" << endl; switch (command) { // set pending based on the outcome of send coords case INDI_CUSTOM_PARKING: { bool rc = false; if (ptr == nullptr) rc = sendCoords(KStars::Instance()->map()->clickedPoint()); else rc = sendCoords(static_cast(ptr)); inCustomParking = rc; } break; case INDI_SEND_COORDS: if (ptr == nullptr) sendCoords(KStars::Instance()->map()->clickedPoint()); else sendCoords(static_cast(ptr)); break; case INDI_FIND_TELESCOPE: { SkyPoint J2000Coord(currentCoord.ra(), currentCoord.dec()); J2000Coord.apparentCoord(KStars::Instance()->data()->ut().djd(), static_cast(J2000)); currentCoord.setRA0(J2000Coord.ra()); currentCoord.setDec0(J2000Coord.dec()); double maxrad = 1000.0 / Options::zoomFactor(); SkyObject *currentObject = KStarsData::Instance()->skyComposite()->objectNearest(¤tCoord, maxrad); KStars::Instance()->map()->setFocusObject(currentObject); KStars::Instance()->map()->setDestination(currentCoord); } break; case INDI_CENTER_LOCK: { //if (currentObject == nullptr || KStars::Instance()->map()->focusObject() != currentObject) if (Options::isTracking() == false || currentCoord.angularDistanceTo(KStars::Instance()->map()->focus()).Degrees() > 0.5) { SkyPoint J2000Coord(currentCoord.ra(), currentCoord.dec()); J2000Coord.apparentCoord(KStars::Instance()->data()->ut().djd(), static_cast(J2000)); currentCoord.setRA0(J2000Coord.ra()); currentCoord.setDec0(J2000Coord.dec()); //KStars::Instance()->map()->setClickedPoint(¤tCoord); //KStars::Instance()->map()->slotCenter(); KStars::Instance()->map()->setDestination(currentCoord); KStars::Instance()->map()->setFocusPoint(¤tCoord); //KStars::Instance()->map()->setFocusObject(currentObject); KStars::Instance()->map()->setFocusObject(nullptr); Options::setIsTracking(true); } centerLockTimer.start(); } break; case INDI_CENTER_UNLOCK: KStars::Instance()->map()->stopTracking(); centerLockTimer.stop(); break; default: return DeviceDecorator::runCommand(command, ptr); } return true; } bool Telescope::sendCoords(SkyPoint *ScopeTarget) { INumber *RAEle = nullptr; INumber *DecEle = nullptr; INumber *AzEle = nullptr; INumber *AltEle = nullptr; INumberVectorProperty *EqProp = nullptr; INumberVectorProperty *HorProp = nullptr; double currentRA = 0, currentDEC = 0, currentAlt = 0, currentAz = 0, targetAlt = 0; bool useJ2000(false); EqProp = baseDevice->getNumber("EQUATORIAL_EOD_COORD"); if (EqProp == nullptr) { // J2000 Property EqProp = baseDevice->getNumber("EQUATORIAL_COORD"); if (EqProp) useJ2000 = true; } HorProp = baseDevice->getNumber("HORIZONTAL_COORD"); if (EqProp && EqProp->p == IP_RO) EqProp = nullptr; if (HorProp && HorProp->p == IP_RO) HorProp = nullptr; //qDebug() << "Skymap click - RA: " << scope_target->ra().toHMSString() << " DEC: " << scope_target->dec().toDMSString(); if (EqProp) { RAEle = IUFindNumber(EqProp, "RA"); if (!RAEle) return false; DecEle = IUFindNumber(EqProp, "DEC"); if (!DecEle) return false; //if (useJ2000) //ScopeTarget->apparentCoord( KStars::Instance()->data()->ut().djd(), static_cast(J2000)); currentRA = RAEle->value; currentDEC = DecEle->value; ScopeTarget->EquatorialToHorizontal(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat()); } if (HorProp) { AzEle = IUFindNumber(HorProp, "AZ"); if (!AzEle) return false; AltEle = IUFindNumber(HorProp, "ALT"); if (!AltEle) return false; currentAz = AzEle->value; currentAlt = AltEle->value; } /* Could not find either properties! */ if (EqProp == nullptr && HorProp == nullptr) return false; //targetAz = ScopeTarget->az().Degrees(); targetAlt = ScopeTarget->altRefracted().Degrees(); if (minAlt != -1 && maxAlt != -1) { if (targetAlt < minAlt || targetAlt > maxAlt) { KSNotification::error(i18n("Requested altitude %1 is outside the specified altitude limit boundary (%2,%3).", QString::number(targetAlt, 'g', 3), QString::number(minAlt, 'g', 3), QString::number(maxAlt, 'g', 3)), i18n("Telescope Motion")); return false; } } if (targetAlt < 0) { if (KMessageBox::warningContinueCancel( nullptr, i18n("Requested altitude is below the horizon. Are you sure you want to proceed?"), i18n("Telescope Motion"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), - QString("telescope_coordintes_below_horizon_warning")) == KMessageBox::Cancel) + QString("telescope_coordinates_below_horizon_warning")) == KMessageBox::Cancel) { if (EqProp) { RAEle->value = currentRA; DecEle->value = currentDEC; } if (HorProp) { AzEle->value = currentAz; AltEle->value = currentAlt; } return false; } } double maxrad = 1000.0 / Options::zoomFactor(); currentObject = KStarsData::Instance()->skyComposite()->objectNearest(ScopeTarget, maxrad); if (currentObject) { // Sun Warning if (currentObject->name() == i18n("Sun")) { if (KMessageBox::warningContinueCancel( nullptr, i18n("Warning! Looking at the Sun without proper protection can lead to irreversible eye damage!"), i18n("Sun Warning"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QString("telescope_ignore_sun_warning")) == KMessageBox::Cancel) return false; } if (m_hasTrackModes) { // Tracking Moon if (currentObject->type() == SkyObject::MOON) { if (currentTrackMode != TRACK_LUNAR && TrackMap.contains(TRACK_LUNAR)) setTrackMode(TrackMap.value(TRACK_LUNAR)); } // Tracking Sun else if (currentObject->name() == i18n("Sun")) { if (currentTrackMode != TRACK_SOLAR && TrackMap.contains(TRACK_SOLAR)) setTrackMode(TrackMap.value(TRACK_SOLAR)); } // If Last track mode was either set to SOLAR or LUNAR but now we are slewing to a different object // then we automatically fallback to sidereal. If the current track mode is CUSTOM or something else, nothing // changes. else if (currentTrackMode == TRACK_SOLAR || currentTrackMode == TRACK_LUNAR) setTrackMode(TRACK_SIDEREAL); } emit newTarget(currentObject->name()); } if (EqProp) { dms ra, de; if (useJ2000) { // If we have invalid DEC, then convert coords to J2000 if (ScopeTarget->dec0().Degrees() == 180.0) { ScopeTarget->setRA0(ScopeTarget->ra()); ScopeTarget->setDec0(ScopeTarget->dec()); ScopeTarget->apparentCoord( KStars::Instance()->data()->ut().djd(), static_cast(J2000)); ra = ScopeTarget->ra(); de = ScopeTarget->dec(); } else { ra = ScopeTarget->ra0(); de = ScopeTarget->dec0(); } } else { ra = ScopeTarget->ra(); de = ScopeTarget->dec(); } RAEle->value = ra.Hours(); DecEle->value = de.Degrees(); clientManager->sendNewNumber(EqProp); qCDebug(KSTARS_INDI) << "ISD:Telescope sending coords RA:" << ra.toHMSString() << "(" << RAEle->value << ") DE:" << de.toDMSString() << "(" << DecEle->value << ")"; RAEle->value = currentRA; DecEle->value = currentDEC; } // Only send Horizontal Coord property if Equatorial is not available. else if (HorProp) { AzEle->value = ScopeTarget->az().Degrees(); AltEle->value = ScopeTarget->alt().Degrees(); clientManager->sendNewNumber(HorProp); AzEle->value = currentAz; AltEle->value = currentAlt; } return true; } bool Telescope::Slew(double ra, double dec) { SkyPoint target; if (m_isJ2000) { target.setRA0(ra); target.setDec0(dec); } else { target.setRA(ra); target.setDec(dec); } return Slew(&target); } bool Telescope::Slew(SkyPoint *ScopeTarget) { ISwitchVectorProperty *motionSP = baseDevice->getSwitch("ON_COORD_SET"); if (motionSP == nullptr) return false; ISwitch *slewSW = IUFindSwitch(motionSP, "TRACK"); if (slewSW == nullptr) slewSW = IUFindSwitch(motionSP, "SLEW"); if (slewSW == nullptr) return false; if (slewSW->s != ISS_ON) { IUResetSwitch(motionSP); slewSW->s = ISS_ON; clientManager->sendNewSwitch(motionSP); qCDebug(KSTARS_INDI) << "ISD:Telescope: " << slewSW->name; } return sendCoords(ScopeTarget); } bool Telescope::Sync(double ra, double dec) { SkyPoint target; target.setRA(ra); target.setDec(dec); return Sync(&target); } bool Telescope::Sync(SkyPoint *ScopeTarget) { ISwitchVectorProperty *motionSP = baseDevice->getSwitch("ON_COORD_SET"); if (motionSP == nullptr) return false; ISwitch *syncSW = IUFindSwitch(motionSP, "SYNC"); if (syncSW == nullptr) return false; if (syncSW->s != ISS_ON) { IUResetSwitch(motionSP); syncSW->s = ISS_ON; clientManager->sendNewSwitch(motionSP); qCDebug(KSTARS_INDI) << "ISD:Telescope: Syncing..."; } return sendCoords(ScopeTarget); } bool Telescope::Abort() { ISwitchVectorProperty *motionSP = baseDevice->getSwitch("TELESCOPE_ABORT_MOTION"); if (motionSP == nullptr) return false; ISwitch *abortSW = IUFindSwitch(motionSP, "ABORT"); if (abortSW == nullptr) return false; qCDebug(KSTARS_INDI) << "ISD:Telescope: Aborted." << endl; abortSW->s = ISS_ON; clientManager->sendNewSwitch(motionSP); inCustomParking = false; return true; } bool Telescope::Park() { ISwitchVectorProperty *parkSP = baseDevice->getSwitch("TELESCOPE_PARK"); if (parkSP == nullptr) return false; ISwitch *parkSW = IUFindSwitch(parkSP, "PARK"); if (parkSW == nullptr) return false; qCDebug(KSTARS_INDI) << "ISD:Telescope: Parking..." << endl; IUResetSwitch(parkSP); parkSW->s = ISS_ON; clientManager->sendNewSwitch(parkSP); return true; } bool Telescope::UnPark() { ISwitchVectorProperty *parkSP = baseDevice->getSwitch("TELESCOPE_PARK"); if (parkSP == nullptr) return false; ISwitch *parkSW = IUFindSwitch(parkSP, "UNPARK"); if (parkSW == nullptr) return false; qCDebug(KSTARS_INDI) << "ISD:Telescope: UnParking..." << endl; IUResetSwitch(parkSP); parkSW->s = ISS_ON; clientManager->sendNewSwitch(parkSP); return true; } bool Telescope::getEqCoords(double *ra, double *dec) { INumberVectorProperty *EqProp = nullptr; INumber *RAEle = nullptr; INumber *DecEle = nullptr; EqProp = baseDevice->getNumber("EQUATORIAL_EOD_COORD"); if (EqProp == nullptr) { EqProp = baseDevice->getNumber("EQUATORIAL_COORD"); if (EqProp == nullptr) return false; } RAEle = IUFindNumber(EqProp, "RA"); if (!RAEle) return false; DecEle = IUFindNumber(EqProp, "DEC"); if (!DecEle) return false; *ra = RAEle->value; *dec = DecEle->value; return true; } bool Telescope::MoveNS(TelescopeMotionNS dir, TelescopeMotionCommand cmd) { ISwitchVectorProperty *motionSP = baseDevice->getSwitch("TELESCOPE_MOTION_NS"); if (motionSP == nullptr) return false; ISwitch *motionNorth = IUFindSwitch(motionSP, "MOTION_NORTH"); ISwitch *motionSouth = IUFindSwitch(motionSP, "MOTION_SOUTH"); if (motionNorth == nullptr || motionSouth == nullptr) return false; // If same direction, return if (dir == MOTION_NORTH && motionNorth->s == ((cmd == MOTION_START) ? ISS_ON : ISS_OFF)) return true; if (dir == MOTION_SOUTH && motionSouth->s == ((cmd == MOTION_START) ? ISS_ON : ISS_OFF)) return true; IUResetSwitch(motionSP); if (cmd == MOTION_START) { if (dir == MOTION_NORTH) motionNorth->s = ISS_ON; else motionSouth->s = ISS_ON; } clientManager->sendNewSwitch(motionSP); return true; } bool Telescope::StopWE() { ISwitchVectorProperty *motionSP = baseDevice->getSwitch("TELESCOPE_MOTION_WE"); if (motionSP == nullptr) return false; IUResetSwitch(motionSP); clientManager->sendNewSwitch(motionSP); return true; } bool Telescope::StopNS() { ISwitchVectorProperty *motionSP = baseDevice->getSwitch("TELESCOPE_MOTION_NS"); if (motionSP == nullptr) return false; IUResetSwitch(motionSP); clientManager->sendNewSwitch(motionSP); return true; } bool Telescope::MoveWE(TelescopeMotionWE dir, TelescopeMotionCommand cmd) { ISwitchVectorProperty *motionSP = baseDevice->getSwitch("TELESCOPE_MOTION_WE"); if (motionSP == nullptr) return false; ISwitch *motionWest = IUFindSwitch(motionSP, "MOTION_WEST"); ISwitch *motionEast = IUFindSwitch(motionSP, "MOTION_EAST"); if (motionWest == nullptr || motionEast == nullptr) return false; // If same direction, return if (dir == MOTION_WEST && motionWest->s == ((cmd == MOTION_START) ? ISS_ON : ISS_OFF)) return true; if (dir == MOTION_EAST && motionEast->s == ((cmd == MOTION_START) ? ISS_ON : ISS_OFF)) return true; IUResetSwitch(motionSP); if (cmd == MOTION_START) { if (dir == MOTION_WEST) motionWest->s = ISS_ON; else motionEast->s = ISS_ON; } clientManager->sendNewSwitch(motionSP); return true; } bool Telescope::setSlewRate(int index) { ISwitchVectorProperty *slewRateSP = baseDevice->getSwitch("TELESCOPE_SLEW_RATE"); if (slewRateSP == nullptr) return false; if (index < 0 || index > slewRateSP->nsp) return false; else if (IUFindOnSwitchIndex(slewRateSP) == index) return true; IUResetSwitch(slewRateSP); slewRateSP->sp[index].s = ISS_ON; clientManager->sendNewSwitch(slewRateSP); emit slewRateChanged(index); return true; } int Telescope::getSlewRate() const { ISwitchVectorProperty *slewRateSP = baseDevice->getSwitch("TELESCOPE_SLEW_RATE"); if (slewRateSP == nullptr) return -1; return IUFindOnSwitchIndex(slewRateSP); } void Telescope::setAltLimits(double minAltitude, double maxAltitude) { minAlt = minAltitude; maxAlt = maxAltitude; } bool Telescope::setAlignmentModelEnabled(bool enable) { bool wasExecuted = false; ISwitchVectorProperty *alignSwitch = nullptr; // For INDI Alignment Subsystem alignSwitch = baseDevice->getSwitch("ALIGNMENT_SUBSYSTEM_ACTIVE"); if (alignSwitch) { alignSwitch->sp[0].s = enable ? ISS_ON : ISS_OFF; clientManager->sendNewSwitch(alignSwitch); wasExecuted = true; } // For EQMod Alignment --- Temporary until all drivers switch fully to INDI Alignment Subsystem alignSwitch = baseDevice->getSwitch("ALIGNMODE"); if (alignSwitch) { IUResetSwitch(alignSwitch); // For now, always set alignment mode to NSTAR on enable. if (enable) alignSwitch->sp[2].s = ISS_ON; // Otherwise, set to NO ALIGN else alignSwitch->sp[0].s = ISS_ON; clientManager->sendNewSwitch(alignSwitch); wasExecuted = true; } return wasExecuted; } bool Telescope::clearParking() { ISwitchVectorProperty *parkSwitch = baseDevice->getSwitch("TELESCOPE_PARK_OPTION"); if (!parkSwitch) return false; ISwitch *clearParkSW = IUFindSwitch(parkSwitch, "PARK_PURGE_DATA"); if (!clearParkSW) return false; IUResetSwitch(parkSwitch); clearParkSW->s = ISS_ON; clientManager->sendNewSwitch(parkSwitch); return true; } bool Telescope::clearAlignmentModel() { bool wasExecuted = false; // Note: Should probably use INDI Alignment Subsystem Client API in the future? ISwitchVectorProperty *clearSwitch = baseDevice->getSwitch("ALIGNMENT_POINTSET_ACTION"); ISwitchVectorProperty *commitSwitch = baseDevice->getSwitch("ALIGNMENT_POINTSET_COMMIT"); if (clearSwitch && commitSwitch) { IUResetSwitch(clearSwitch); // ALIGNMENT_POINTSET_ACTION.CLEAR clearSwitch->sp[4].s = ISS_ON; clientManager->sendNewSwitch(clearSwitch); commitSwitch->sp[0].s = ISS_ON; clientManager->sendNewSwitch(commitSwitch); wasExecuted = true; } // For EQMod Alignment --- Temporary until all drivers switch fully to INDI Alignment Subsystem clearSwitch = baseDevice->getSwitch("ALIGNLIST"); if (clearSwitch) { // ALIGNLISTCLEAR IUResetSwitch(clearSwitch); clearSwitch->sp[1].s = ISS_ON; clientManager->sendNewSwitch(clearSwitch); wasExecuted = true; } return wasExecuted; } Telescope::Status Telescope::status() { INumberVectorProperty *EqProp = nullptr; EqProp = baseDevice->getNumber("EQUATORIAL_EOD_COORD"); if (EqProp == nullptr) { EqProp = baseDevice->getNumber("EQUATORIAL_COORD"); if (EqProp == nullptr) return MOUNT_ERROR; } switch (EqProp->s) { case IPS_IDLE: if (inManualMotion) return MOUNT_MOVING; else if (isParked()) return MOUNT_PARKED; else return MOUNT_IDLE; case IPS_OK: if (inManualMotion) return MOUNT_MOVING; else if (inCustomParking) { inCustomParking = false; // set CURRENT position as the desired parking position sendParkingOptionCommand(PARK_OPTION_CURRENT); // Write data to disk sendParkingOptionCommand(PARK_OPTION_WRITE_DATA); return MOUNT_TRACKING; } else return MOUNT_TRACKING; case IPS_BUSY: { ISwitchVectorProperty *parkSP = baseDevice->getSwitch("TELESCOPE_PARK"); if (parkSP && parkSP->s == IPS_BUSY) return MOUNT_PARKING; else return MOUNT_SLEWING; } case IPS_ALERT: inCustomParking = false; return MOUNT_ERROR; } return MOUNT_ERROR; } const QString Telescope::getStatusString(Telescope::Status status) { switch (status) { case ISD::Telescope::MOUNT_IDLE: return i18n("Idle"); case ISD::Telescope::MOUNT_PARKED: return i18n("Parked"); case ISD::Telescope::MOUNT_PARKING: return i18n("Parking"); case ISD::Telescope::MOUNT_SLEWING: return i18n("Slewing"); case ISD::Telescope::MOUNT_MOVING: return i18n("Moving %1", getManualMotionString()); case ISD::Telescope::MOUNT_TRACKING: return i18n("Tracking"); case ISD::Telescope::MOUNT_ERROR: return i18n("Error"); } return i18n("Error"); } QString Telescope::getManualMotionString() const { ISwitchVectorProperty *movementSP = nullptr; QString NSMotion, WEMotion; movementSP = baseDevice->getSwitch("TELESCOPE_MOTION_NS"); if (movementSP) { if (movementSP->sp[MOTION_NORTH].s == ISS_ON) NSMotion = 'N'; else if (movementSP->sp[MOTION_SOUTH].s == ISS_ON) NSMotion = 'S'; } movementSP = baseDevice->getSwitch("TELESCOPE_MOTION_WE"); if (movementSP) { if (movementSP->sp[MOTION_WEST].s == ISS_ON) WEMotion = 'W'; else if (movementSP->sp[MOTION_EAST].s == ISS_ON) WEMotion = 'E'; } return QString("%1%2").arg(NSMotion, WEMotion); } bool Telescope::setTrackEnabled(bool enable) { ISwitchVectorProperty *trackSP = baseDevice->getSwitch("TELESCOPE_TRACK_STATE"); if (trackSP == nullptr) return false; ISwitch *trackON = IUFindSwitch(trackSP, "TRACK_ON"); ISwitch *trackOFF = IUFindSwitch(trackSP, "TRACK_OFF"); if (trackON == nullptr || trackOFF == nullptr) return false; trackON->s = enable ? ISS_ON : ISS_OFF; trackOFF->s = enable ? ISS_OFF : ISS_ON; clientManager->sendNewSwitch(trackSP); return true; } bool Telescope::isTracking() { return (status() == MOUNT_TRACKING); } bool Telescope::setTrackMode(uint8_t index) { ISwitchVectorProperty *trackModeSP = baseDevice->getSwitch("TELESCOPE_TRACK_MODE"); if (trackModeSP == nullptr) return false; if (index >= trackModeSP->nsp) return false; IUResetSwitch(trackModeSP); trackModeSP->sp[index].s = ISS_ON; clientManager->sendNewSwitch(trackModeSP); return true; } bool Telescope::getTrackMode(uint8_t &index) { ISwitchVectorProperty *trackModeSP = baseDevice->getSwitch("TELESCOPE_TRACK_MODE"); if (trackModeSP == nullptr) return false; index = IUFindOnSwitchIndex(trackModeSP); return true; } bool Telescope::setCustomTrackRate(double raRate, double deRate) { INumberVectorProperty *trackRateNP = baseDevice->getNumber("TELESCOPE_TRACK_RATE"); if (trackRateNP == nullptr) return false; INumber *raRateN = IUFindNumber(trackRateNP, "TRACK_RATE_RA"); INumber *deRateN = IUFindNumber(trackRateNP, "TRACK_RATE_DE"); if (raRateN == nullptr || deRateN == nullptr) return false; raRateN->value = raRate; deRateN->value = deRate; clientManager->sendNewNumber(trackRateNP); return true; } bool Telescope::getCustomTrackRate(double &raRate, double &deRate) { INumberVectorProperty *trackRateNP = baseDevice->getNumber("TELESCOPE_TRACK_RATE"); if (trackRateNP == nullptr) return false; INumber *raRateN = IUFindNumber(trackRateNP, "TRACK_RATE_RA"); INumber *deRateN = IUFindNumber(trackRateNP, "TRACK_RATE_DE"); if (raRateN == nullptr || deRateN == nullptr) return false; raRate = raRateN->value; deRate = deRateN->value; return true; } bool Telescope::sendParkingOptionCommand(ParkOptionCommand command) { ISwitchVectorProperty *parkOptionsSP = baseDevice->getSwitch("TELESCOPE_PARK_OPTION"); if (parkOptionsSP == nullptr) return false; IUResetSwitch(parkOptionsSP); parkOptionsSP->sp[command].s = ISS_ON; clientManager->sendNewSwitch(parkOptionsSP); return true; } } QDBusArgument &operator<<(QDBusArgument &argument, const ISD::Telescope::Status &source) { argument.beginStructure(); argument << static_cast(source); argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, ISD::Telescope::Status &dest) { int a; argument.beginStructure(); argument >> a; argument.endStructure(); dest = static_cast(a); return argument; } QDBusArgument &operator<<(QDBusArgument &argument, const ISD::Telescope::PierSide &source) { argument.beginStructure(); argument << static_cast(source); argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, ISD::Telescope::PierSide &dest) { int a; argument.beginStructure(); argument >> a; argument.endStructure(); dest = static_cast(a); return argument; } diff --git a/kstars/indi/inditelescopelite.h b/kstars/indi/inditelescopelite.h index e71088ed3..691157b7f 100644 --- a/kstars/indi/inditelescopelite.h +++ b/kstars/indi/inditelescopelite.h @@ -1,139 +1,139 @@ /* INDI CCD Copyright (C) 2012 Jasem Mutlaq This application 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. */ #pragma once #include "basedevice.h" #include "skypoint.h" #include "kstarslite/skypointlite.h" #include "kstarslite/skyobjectlite.h" #include class ClientManagerLite; /** * @class TelescopeLite * - * device handle controlling telescope. It can slew and sync to a specific sky point and supports all standard propreties with INDI + * device handle controlling telescope. It can slew and sync to a specific sky point and supports all standard properties with INDI * telescope device. * * @author Jasem Mutlaq */ class TelescopeLite : public QObject { Q_OBJECT Q_PROPERTY(bool slewDecreasable READ isSlewDecreasable WRITE setSlewDecreasable NOTIFY slewDecreasableChanged) Q_PROPERTY(bool slewIncreasable READ isSlewIncreasable WRITE setSlewIncreasable NOTIFY slewIncreasableChanged) Q_PROPERTY(QString slewRateLabel READ getSlewRateLabel WRITE setSlewRateLabel NOTIFY slewRateLabelChanged) Q_PROPERTY(QString deviceName READ getDeviceName WRITE setDeviceName NOTIFY deviceNameChanged) public: explicit TelescopeLite(INDI::BaseDevice *device); TelescopeLite() { } ~TelescopeLite(); enum TelescopeMotionNS { MOTION_NORTH, MOTION_SOUTH }; enum TelescopeMotionWE { MOTION_WEST, MOTION_EAST }; enum TelescopeMotionCommand { MOTION_START, MOTION_STOP }; Q_ENUMS(TelescopeMotionNS) Q_ENUMS(TelescopeMotionWE) Q_ENUMS(TelescopeMotionCommand) void registerProperty(INDI::Property *prop); void processSwitch(ISwitchVectorProperty *svp); void processText(ITextVectorProperty *tvp); void processNumber(INumberVectorProperty *nvp); INDI::BaseDevice *getDevice() { return baseDevice; } //deviceName QString getDeviceName() { return m_deviceName; } void setDeviceName(const QString &deviceName); bool isSlewDecreasable() { return m_slewDecreasable; } bool isSlewIncreasable() { return m_slewIncreasable; } QString getSlewRateLabel() { return m_slewRateLabel; } void setSlewDecreasable(bool slewDecreasable); void setSlewIncreasable(bool slewIncreasable); void setSlewRateLabel(const QString &slewRateLabel); Q_INVOKABLE bool decreaseSlewRate(); Q_INVOKABLE bool increaseSlewRate(); // Common Commands Q_INVOKABLE bool slew(SkyPointLite *ScopeTarget) { return slew(ScopeTarget->getPoint()); } Q_INVOKABLE bool slew(SkyPoint *ScopeTarget); bool slew(double ra, double dec); Q_INVOKABLE bool sync(SkyPointLite *ScopeTarget) { return sync(ScopeTarget->getPoint()); } Q_INVOKABLE bool sync(SkyPoint *ScopeTarget); bool sync(double ra, double dec); Q_INVOKABLE bool moveNS(TelescopeMotionNS dir, TelescopeMotionCommand cmd); Q_INVOKABLE bool moveWE(TelescopeMotionWE dir, TelescopeMotionCommand cmd); bool canGuide(); bool canSync(); bool canPark(); bool isSlewing(); bool isParked(); bool isInMotion(); /*bool doPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs ); bool doPulse(GuideDirection dir, int msecs );*/ bool getEqCoords(double *ra, double *dec); void setAltLimits(double minAltitude, double maxAltitude); Q_INVOKABLE bool isConnected() { return baseDevice->isConnected(); } Q_INVOKABLE QStringList getSlewRateLabels() { return m_slewRateLabels; }; protected: bool sendCoords(SkyPoint *ScopeTarget); public slots: Q_INVOKABLE bool abort(); bool park(); bool unPark(); bool setSlewRate(int index); void updateSlewRate(const QString &deviceName, const QString &propName); signals: void slewDecreasableChanged(bool); void slewIncreasableChanged(bool); void slewRateLabelChanged(QString); void deviceNameChanged(QString); void newTelescopeLiteCreated(TelescopeLite *); void slewRateUpdate(int index, int count); private: SkyPoint currentCoord; double minAlt { 0 }; double maxAlt { 0 }; bool IsParked { false }; ClientManagerLite *clientManager { nullptr }; INDI::BaseDevice *baseDevice { nullptr }; int slewRateIndex { 0 }; QString m_deviceName; bool m_slewDecreasable { false }; bool m_slewIncreasable { false }; QString m_slewRateLabel; QStringList m_slewRateLabels; }; diff --git a/kstars/kstars.h b/kstars/kstars.h index ec19700a8..231440092 100644 --- a/kstars/kstars.h +++ b/kstars/kstars.h @@ -1,894 +1,894 @@ /** ************************************************************************* kstars.h - K Desktop Planetarium ------------------- begin : Mon Feb 5 01:11:45 PST 2001 copyright : (C) 2001 by Jason Harris email : jharris@30doradus.org ***************************************************************************/ /** ************************************************************************* * * * 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. * * * ***************************************************************************/ #pragma once #include "config-kstars.h" #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) #include #else #include #endif #ifdef HAVE_CFITSIO #include #endif // forward declaration is enough. We only need pointers class QActionGroup; class QDockWidget; class QPalette; class KActionMenu; class KConfigDialog; class KStarsData; class SkyPoint; class SkyMap; class GeoLocation; class FindDialog; class TimeStepBox; class ImageExporter; class AltVsTime; class WUTDialog; class WIView; class WILPSettings; class WIEquipSettings; class ObsConditions; class AstroCalc; class SkyCalendar; class ScriptBuilder; class PlanetViewer; class JMoonTool; class MoonPhaseTool; class FlagManager; class Execute; class ExportImageDialog; class PrintingWizard; class HorizonManager; class EyepieceField; class AddDeepSkyObject; class OpsCatalog; class OpsGuides; class OpsSolarSystem; class OpsSatellites; class OpsSupernovae; class OpsColors; class OpsAdvanced; class OpsINDI; class OpsEkos; class OpsFITS; class OpsXplanet; namespace Ekos { class Manager; } #ifdef HAVE_CFITSIO class FITSViewer; #endif /** *@class KStars *@short This is the main window for KStars. *In addition to the GUI elements, the class contains the program clock, KStarsData, and SkyMap objects. It also contains functions for the \ref DBusInterface D-Bus interface. KStars is now a singleton class. Use KStars::createInstance() to create an instance and KStars::Instance() to get a pointer to the instance *@author Jason Harris, Jasem Mutlaq *@version 1.1 */ class KStars : public KXmlGuiWindow { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kstars") private: /** * @short Constructor. * @param doSplash should the splash panel be displayed during * initialization. * @param startClockRunning should the clock be running on startup? * @param startDateString date (in string representation) to start running from. * * @todo Refer to documentation on date format. */ explicit KStars(bool doSplash, bool startClockRunning = true, const QString &startDateString = QString()); public: /** * @short Create an instance of this class. Destroy any previous instance * @param doSplash * @param clockrunning * @param startDateString * @note See KStars::KStars for details on parameters * @return a pointer to the instance */ static KStars *createInstance(bool doSplash, bool clockrunning = true, const QString &startDateString = QString()); /** @return a pointer to the instance of this class */ inline static KStars *Instance() { return pinstance; } /** Destructor. */ ~KStars() override; /** Syncs config file. Deletes objects. */ void releaseResources(); /** @return pointer to KStarsData object which contains application data. */ inline KStarsData *data() const { return m_KStarsData; } /** @return pointer to SkyMap object which is the sky display widget. */ inline SkyMap *map() const { return m_SkyMap; } inline FlagManager *flagManager() const { return m_FlagManager; } inline PrintingWizard *printingWizard() const { return m_PrintingWizard; } #ifdef HAVE_CFITSIO QPointer genericFITSViewer(); void addFITSViewer(QPointer fv); void clearAllViewers(); #endif /** Add an item to the color-scheme action manu * @param name The name to use in the menu * @param actionName The internal name for the action (derived from filename) */ void addColorMenuItem(const QString &name, const QString &actionName); /** Remove an item from the color-scheme action manu * @param actionName The internal name of the action (derived from filename) */ void removeColorMenuItem(const QString &actionName); /** @short Apply config options throughout the program. * In most cases, options are set in the "Options" object directly, * but for some things we have to manually react to config changes. - * @param doApplyFocus If true, then focus posiiton will be set + * @param doApplyFocus If true, then focus position will be set * from config file */ void applyConfig(bool doApplyFocus = true); void showImgExportDialog(); void syncFOVActions(); void hideAllFovExceptFirst(); void selectNextFov(); void selectPreviousFov(); void showWISettingsUI(); void showWI(ObsConditions *obs); /** Load HIPS information and repopulate menu. */ void repopulateHIPS(); WIEquipSettings *getWIEquipSettings() { return m_WIEquipmentSettings; } public Q_SLOTS: /** @defgroup DBusInterface DBus Interface KStars provides powerful scripting functionality via DBus. The most common DBus functions can be constructed and executed within the ScriptBuilder tool. Any 3rd party language or tool with support for DBus can access several interfaces provided by KStars:
  • KStars: Provides functions to manipulate the skymap including zoom, pan, and motion to selected objects. Add and remove object trails and labels. Wait for user input before running further actions.
  • SimClock: Provides functions to start and stop time, set a different date and time, and to set the clock scale.
  • Ekos: Provides functions to start and stop Ekos Manager, set Ekos connection mode, and access to Ekos modules:
    • Capture: Provides functions to capture images, load sequence queues, control filter wheel, and obtain information on job progress.
    • Focus: Provides functions to focus control in manual and automated mode. Start and stop focusing procedures and set autofocus options.
    • Guide: Provides functions to start and stop calibration and autoguiding procedures. Set calibration and autoguide options.
    • Align: Provides functions to solve images use online or offline astrometry.net solver.
*/ /*@{*/ /** DBUS interface function. * Set focus to given Ra/Dec coordinates * @param ra the Right Ascension coordinate for the focus (in Hours) * @param dec the Declination coordinate for the focus (in Degrees) */ Q_SCRIPTABLE Q_NOREPLY void setRaDec(double ra, double dec); /** DBUS interface function. * Set focus to given J2000.0 Ra/Dec coordinates * @param ra the J2000.0 Right Ascension coordinate for the focus (in Hours) * @param dec the J2000.0 Declination coordinate for the focus (in Degrees) */ Q_SCRIPTABLE Q_NOREPLY void setRaDecJ2000(double ra0, double dec0); /** DBUS interface function. * Set focus to given Alt/Az coordinates. * @param alt the Altitude coordinate for the focus (in Degrees) * @param az the Azimuth coordinate for the focus (in Degrees) * @param altIsRefracted If set to true, the altitude is interpreted as if it were corrected for atmospheric refraction (i.e. the altitude is an apparent altitude) */ Q_SCRIPTABLE Q_NOREPLY void setAltAz(double alt, double az, bool altIsRefracted=false); /** DBUS interface function. * Point in the direction described by the string argument. * @param direction either an object name, a compass direction (e.g., "north"), or "zenith" */ Q_SCRIPTABLE Q_NOREPLY void lookTowards(const QString &direction); /** DBUS interface function. * Add a name label to the named object * @param name the name of the object to which the label will be attached */ Q_SCRIPTABLE Q_NOREPLY void addLabel(const QString &name); /** DBUS interface function. * Remove a name label from the named object * @param name the name of the object from which the label will be removed */ Q_SCRIPTABLE Q_NOREPLY void removeLabel(const QString &name); /** DBUS interface function. * Add a trail to the named solar system body * @param name the name of the body to which the trail will be attached */ Q_SCRIPTABLE Q_NOREPLY void addTrail(const QString &name); /** DBUS interface function. * Remove a trail from the named solar system body * @param name the name of the object from which the trail will be removed */ Q_SCRIPTABLE Q_NOREPLY void removeTrail(const QString &name); /** DBUS interface function. Zoom in one step. */ Q_SCRIPTABLE Q_NOREPLY void zoomIn(); /** DBUS interface function. Zoom out one step. */ Q_SCRIPTABLE Q_NOREPLY void zoomOut(); /** DBUS interface function. reset to the default zoom level. */ Q_SCRIPTABLE Q_NOREPLY void defaultZoom(); /** DBUS interface function. Set zoom level to specified value. * @param z the zoom level. Units are pixels per radian. */ Q_SCRIPTABLE Q_NOREPLY void zoom(double z); /** DBUS interface function. Set local time and date. * @param yr year of date * @param mth month of date * @param day day of date * @param hr hour of time * @param min minute of time * @param sec second of time */ Q_SCRIPTABLE Q_NOREPLY void setLocalTime(int yr, int mth, int day, int hr, int min, int sec); /** DBUS interface function. Set local time and date to present values acc. system clock * @note Just a proxy for slotSetTimeToNow(), but it is better to * keep the DBus interface separate from the internal methods. */ Q_SCRIPTABLE Q_NOREPLY void setTimeToNow(); /** DBUS interface function. Delay further execution of DBUS commands. * @param t number of seconds to delay */ Q_SCRIPTABLE Q_NOREPLY void waitFor(double t); /** DBUS interface function. Pause further DBUS execution until a key is pressed. * @param k the key which will resume DBUS execution */ Q_SCRIPTABLE Q_NOREPLY void waitForKey(const QString &k); /** DBUS interface function. Toggle tracking. * @param track engage tracking if true; else disengage tracking */ Q_SCRIPTABLE Q_NOREPLY void setTracking(bool track); /** DBUS interface function. modify a view option. * @param option the name of the option to be modified * @param value the option's new value */ Q_SCRIPTABLE Q_NOREPLY void changeViewOption(const QString &option, const QString &value); /** DBUS interface function. * @param name the name of the option to query * @return the current value of the named option */ Q_SCRIPTABLE QString getOption(const QString &name); /** DBUS interface function. Read config file. * This function is useful for restoring the user settings from the config file, * after having modified the settings in memory. * @sa writeConfig() */ Q_SCRIPTABLE Q_NOREPLY void readConfig(); /** DBUS interface function. Write current settings to config file. * This function is useful for storing user settings before modifying them with a DBUS * script. The original settings can be restored with readConfig(). * @sa readConfig() */ Q_SCRIPTABLE Q_NOREPLY void writeConfig(); /** DBUS interface function. Show text message in a popup window. * @note Not Yet Implemented * @param x x-coordinate for message window * @param y y-coordinate for message window * @param message the text to display in the message window */ Q_SCRIPTABLE Q_NOREPLY void popupMessage(int x, int y, const QString &message); /** DBUS interface function. Draw a line on the sky map. * @note Not Yet Implemented * @param x1 starting x-coordinate of line * @param y1 starting y-coordinate of line * @param x2 ending x-coordinate of line * @param y2 ending y-coordinate of line * @param speed speed at which line should appear from start to end points (in pixels per second) */ Q_SCRIPTABLE Q_NOREPLY void drawLine(int x1, int y1, int x2, int y2, int speed); /** DBUS interface function. Set the geographic location. * @param city the city name of the location * @param province the province name of the location * @param country the country name of the location * @return True if geographic location is found and set, false otherwise. */ Q_SCRIPTABLE bool setGeoLocation(const QString &city, const QString &province, const QString &country); /** * @brief location Returns a JSON Object (as string) that contains the following information: * name: String * province: String * country: String * longitude: Double (-180 to +180) * latitude: Double (-90 to +90) * tz0 (Time zone without DST): Double * tz (Time zone with DST): Double * @return Stringified JSON object as described above. */ Q_SCRIPTABLE QString location(); /** DBUS interface function. Set the GPS geographic location. * @param longitude longitude in degrees (-180 West to +180 East) * @param latitude latitude in degrees (-90 South to +90 North) * @param elevation site elevation in meters * @param tz0 Time zone offset WITHOUT daylight saving time. * @return True if geographic location is set, false otherwise. */ Q_SCRIPTABLE bool setGPSLocation(double longitude, double latitude, double elevation, double tz0); /** DBUS interface function. Modify a color. * @param colorName the name of the color to be modified (e.g., "SkyColor") * @param value the new color to use */ Q_SCRIPTABLE Q_NOREPLY void setColor(const QString &colorName, const QString &value); /** DBUS interface function. Load a color scheme. * @param name the name of the color scheme to load (e.g., "Moonless Night") */ Q_SCRIPTABLE Q_NOREPLY void loadColorScheme(const QString &name); /** DBUS interface function. Export the sky image to a file. * @param filename the filename for the exported image * @param width the width for the exported image. Map's width will be used if nothing or an invalid value is supplied. * @param height the height for the exported image. Map's height will be used if nothing or an invalid value is supplied. * @param includeLegend should we include a legend? */ Q_SCRIPTABLE Q_NOREPLY void exportImage(const QString &filename, int width = -1, int height = -1, bool includeLegend = false); /** DBUS interface function. Return a URL to retrieve Digitized Sky Survey image. * @param objectName name of the object. * @note If the object is note found, the string "ERROR" is returned. */ Q_SCRIPTABLE QString getDSSURL(const QString &objectName); /** DBUS interface function. Return a URL to retrieve Digitized Sky Survey image. * @param RA_J2000 J2000.0 RA * @param Dec_J2000 J2000.0 Declination * @param width width of the image, in arcminutes (default = 15) * @param height height of the image, in arcminutes (default = 15) */ Q_SCRIPTABLE QString getDSSURL(double RA_J2000, double Dec_J2000, float width = 15, float height = 15); /** DBUS interface function. Return XML containing information about a sky object * @param objectName name of the object. * @note If the object was not found, the XML is empty. */ Q_SCRIPTABLE QString getObjectDataXML(const QString &objectName); /** DBUS interface function. Return XML containing position info about a sky object * @param objectName name of the object. * @note If the object was not found, the XML is empty. */ Q_SCRIPTABLE QString getObjectPositionInfo(const QString &objectName); /** DBUS interface function. Render eyepiece view and save it in the file(s) specified * @note See EyepieceField::renderEyepieceView() for more info. This is a DBus proxy that calls that method, and then writes the resulting image(s) to file(s). * @note Important: If imagePath is empty, but overlay is true, or destPathImage is supplied, this method will make a blocking DSS download. */ Q_SCRIPTABLE Q_NOREPLY void renderEyepieceView(const QString &objectName, const QString &destPathChart, const double fovWidth = -1.0, const double fovHeight = -1.0, const double rotation = 0.0, const double scale = 1.0, const bool flip = false, const bool invert = false, QString imagePath = QString(), const QString &destPathImage = QString(), const bool overlay = false, const bool invertColors = false); /** DBUS interface function. Set the approx field-of-view * @param FOV_Degrees field of view in degrees */ Q_SCRIPTABLE Q_NOREPLY void setApproxFOV(double FOV_Degrees); /** DBUS interface function. Get the dimensions of the Sky Map. * @return a string containing widthxheight in pixels. */ Q_SCRIPTABLE QString getSkyMapDimensions(); /** DBUS interface function. Return a newline-separated list of objects in the observing wishlist. * @note Unfortunately, unnamed objects are troublesome. Hopefully, we don't have them on the observing list. */ Q_SCRIPTABLE QString getObservingWishListObjectNames(); /** DBUS interface function. Return a newline-separated list of objects in the observing session plan. * @note Unfortunately, unnamed objects are troublesome. Hopefully, we don't have them on the observing list. */ Q_SCRIPTABLE QString getObservingSessionPlanObjectNames(); /** DBUS interface function. Print the sky image. * @param usePrintDialog if true, the KDE print dialog will be shown; otherwise, default parameters will be used * @param useChartColors if true, the "Star Chart" color scheme will be used for the printout, which will save ink. */ Q_SCRIPTABLE Q_NOREPLY void printImage(bool usePrintDialog, bool useChartColors); /** DBUS interface function. Open FITS image. * @param imageUrl URL of FITS image to load. For a local file the prefix must be file:// For example * if the file is located at /home/john/m42.fits then the full URL is file:///home/john/m42.fits */ Q_SCRIPTABLE Q_NOREPLY void openFITS(const QUrl &imageUrl); /** @}*/ /** * Update time-dependent data and (possibly) repaint the sky map. * @param automaticDSTchange change DST status automatically? */ void updateTime(const bool automaticDSTchange = true); /** action slot: sync kstars clock to system time */ void slotSetTimeToNow(); /** Apply new settings and redraw skymap */ void slotApplyConfigChanges(); /** Apply new settings for WI */ void slotApplyWIConfigChanges(); /** Called when zoom level is changed. Enables/disables zoom * actions and updates status bar. */ void slotZoomChanged(); /** action slot: Allow user to specify a field-of-view angle for the display window in degrees, * and set the zoom level accordingly. */ void slotSetZoom(); /** action slot: Toggle whether kstars is tracking current position */ void slotTrack(); /** action slot: open dialog for selecting a new geographic location */ void slotGeoLocator(); /** * @brief slotSetTelescopeEnabled call when telescope comes online or goes offline. * @param enable True if telescope is online and connected, false otherwise. */ void slotSetTelescopeEnabled(bool enable); /** * @brief slotSetDomeEnabled call when dome comes online or goes offline. * @param enable True if dome is online and connected, false otherwise. */ void slotSetDomeEnabled(bool enable); /** Delete FindDialog because ObjNames list has changed in KStarsData due to * reloading star data. So list in FindDialog must be new filled with current data. */ void clearCachedFindDialog(); /** Remove all trails which may have been added to solar system bodies */ void slotClearAllTrails(); /** Display position in the status bar. */ void slotShowPositionBar(SkyPoint *); /** action slot: open Flag Manager */ void slotFlagManager(); /** Show the eyepiece view tool */ void slotEyepieceView(SkyPoint *sp, const QString &imagePath = QString()); /** Show the add deep-sky object dialog */ void slotAddDeepSkyObject(); /** action slot: open KStars startup wizard */ void slotWizard(); void updateLocationFromWizard(const GeoLocation &geo); WIView *wiView() { return m_WIView; } bool isWIVisible() { if (!m_WIView) return false; if (!m_wiDock) return false; return m_wiDock->isVisible(); } //FIXME Port to QML2 //#if 0 /** action slot: open What's Interesting settings window */ void slotWISettings(); /** action slot: toggle What's Interesting window */ void slotToggleWIView(); //#endif private slots: /** action slot: open a dialog for setting the time and date */ void slotSetTime(); /** action slot: toggle whether kstars clock is running or not */ void slotToggleTimer(); /** action slot: advance one step forward in time */ void slotStepForward(); /** action slot: advance one step backward in time */ void slotStepBackward(); /** action slot: open dialog for finding a named object */ void slotFind(); /** action slot: open KNewStuff window to download extra data. */ void slotDownload(); /** action slot: open KStars calculator to compute astronomical ephemeris */ void slotCalculator(); /** action slot: open Elevation vs. Time tool */ void slotAVT(); /** action slot: open What's up tonight dialog */ void slotWUT(); /** action slot: open Sky Calendar tool */ void slotCalendar(); /** action slot: open the glossary */ void slotGlossary(); /** action slot: open ScriptBuilder dialog */ void slotScriptBuilder(); /** action slot: open Solar system viewer */ void slotSolarSystem(); /** action slot: open Jupiter Moons tool */ void slotJMoonTool(); /** action slot: open Moon Phase Calendar tool */ void slotMoonPhaseTool(); #if 0 /** action slot: open Telescope wizard */ void slotTelescopeWizard(); #endif /** action slot: open INDI driver panel */ void slotINDIDriver(); /** action slot: open INDI control panel */ void slotINDIPanel(); /** action slot: open Ekos panel */ void slotEkos(); /** action slot: Track with the telescope (INDI) */ void slotINDITelescopeTrack(); /** * Action slot: Slew with the telescope (INDI) * * @param focused_object Slew to the focused object or the mouse pointer if false. * */ void slotINDITelescopeSlew(bool focused_object = true); void slotINDITelescopeSlewMousePointer(); /** * Action slot: Sync the telescope (INDI) * * @param focused_object Sync the position of the focused object or the mouse pointer if false. * */ void slotINDITelescopeSync(bool focused_object = true); void slotINDITelescopeSyncMousePointer(); /** action slot: Abort any telescope motion (INDI) */ void slotINDITelescopeAbort(); /** action slot: Park the telescope (INDI) */ void slotINDITelescopePark(); /** action slot: Unpark the telescope (INDI) */ void slotINDITelescopeUnpark(); /** action slot: Park the dome (INDI) */ void slotINDIDomePark(); /** action slot: UnPark the dome (INDI) */ void slotINDIDomeUnpark(); /** action slot: open dialog for setting the view options */ void slotViewOps(); /** finish setting up after the kstarsData has finished */ void datainitFinished(); /** Open FITS image. */ void slotOpenFITS(); /** Action slot to save the sky image to a file.*/ void slotExportImage(); /** Action slot to select a DBUS script and run it.*/ void slotRunScript(); /** Action slot to print skymap. */ void slotPrint(); /** Action slot to start Printing Wizard. */ void slotPrintingWizard(); /** Action slot to show tip-of-the-day window. */ void slotTipOfDay(); /** Action slot to set focus coordinates manually (opens FocusDialog). */ void slotManualFocus(); /** Meta-slot to point the focus at special points (zenith, N, S, E, W). * Uses the name of the Action which sent the Signal to identify the * desired direction. */ void slotPointFocus(); /** Meta-slot to set the color scheme according to the name of the * Action which sent the activating signal. */ void slotColorScheme(); /** * @brief slotThemeChanged save theme name in options */ void slotThemeChanged(); /** Select the Target symbol (a.k.a. field-of-view indicator) */ void slotTargetSymbol(bool flag); /** Select the HIPS Source catalog. */ void slotHIPSSource(); /** Invoke the Field-of-View symbol editor window */ void slotFOVEdit(); /** Toggle between Equatorial and Ecliptic coordinate systems */ void slotCoordSys(); /** Set the map projection according to the menu selection */ void slotMapProjection(); /** Toggle display of the observing list tool*/ void slotObsList(); /** Meta-slot to handle display toggles for all of the viewtoolbar buttons. * uses the name of the sender to identify the item to change. */ void slotViewToolBar(); /** Meta-slot to handle display toggles for all of the INDItoolbar buttons. * uses the name of the sender to identify the item to change. */ void slotINDIToolBar(); /** Meta-slot to handle toggling display of GUI elements (toolbars and infoboxes) * uses name of the sender action to identify the widget to hide/show. */ void slotShowGUIItem(bool); /** Toggle to and from full screen mode */ void slotFullScreen(); /** Save data to config file before exiting.*/ void slotAboutToQuit(); void slotEquipmentWriter(); void slotObserverManager(); void slotHorizonManager(); void slotExecute(); void slotPolarisHourAngle(); /** Update comets orbital elements*/ void slotUpdateComets(bool isAutoUpdate = false); /** Update asteroids orbital elements*/ void slotUpdateAsteroids(bool isAutoUpdate = false); /** Update list of recent supernovae*/ void slotUpdateSupernovae(); /** Update satellites orbital elements*/ void slotUpdateSatellites(); /** Configure Notifications */ void slotConfigureNotifications(); private: /** Load FOV information and repopulate menu. */ void repopulateFOV(); /** * @brief populateThemes Populate application themes */ void populateThemes(); /** Initialize Menu bar, toolbars and all Actions. */ void initActions(); /** Prepare options dialog. */ KConfigDialog* prepareOps(); /** Initialize Status bar. */ void initStatusBar(); /** Initialize focus position */ void initFocus(); /** Build the KStars main window */ void buildGUI(); void closeEvent(QCloseEvent *event) override; public: /** Check if the KStars main window is shown */ bool isGUIReady() { return m_SkyMap != nullptr; } /** Was KStars started with the clock running, or paused? */ bool isStartedWithClockRunning() { return StartClockRunning; } /// Set to true when the application is being closed static bool Closing; private: /// Pointer to an instance of KStars static KStars *pinstance; KActionMenu *colorActionMenu { nullptr }; KActionMenu *fovActionMenu { nullptr }; KActionMenu *hipsActionMenu { nullptr }; KStarsData *m_KStarsData { nullptr }; SkyMap *m_SkyMap { nullptr }; // Widgets TimeStepBox *m_TimeStepBox { nullptr }; // Dialogs & Tools // File Menu ExportImageDialog *m_ExportImageDialog { nullptr }; PrintingWizard *m_PrintingWizard { nullptr }; // Tool Menu AstroCalc *m_AstroCalc { nullptr }; AltVsTime *m_AltVsTime { nullptr }; SkyCalendar *m_SkyCalendar { nullptr }; ScriptBuilder *m_ScriptBuilder { nullptr }; PlanetViewer *m_PlanetViewer { nullptr }; WUTDialog *m_WUTDialog { nullptr }; JMoonTool *m_JMoonTool { nullptr }; FlagManager *m_FlagManager { nullptr }; HorizonManager *m_HorizonManager { nullptr }; EyepieceField *m_EyepieceView { nullptr }; #ifdef HAVE_CFITSIO QPointer m_GenericFITSViewer; QList> m_FITSViewers; #endif #ifdef HAVE_INDI QPointer m_EkosManager; #endif AddDeepSkyObject *m_addDSODialog { nullptr }; // FIXME Port to QML2 //#if 0 WIView *m_WIView { nullptr }; WILPSettings *m_WISettings { nullptr }; WIEquipSettings *m_WIEquipmentSettings { nullptr }; ObsConditions *m_ObsConditions { nullptr }; QDockWidget *m_wiDock { nullptr }; //#endif QActionGroup *projectionGroup { nullptr }; QActionGroup *cschemeGroup { nullptr }; QActionGroup *hipsGroup { nullptr }; QActionGroup *telescopeGroup { nullptr }; QActionGroup *domeGroup { nullptr }; bool DialogIsObsolete { false }; bool StartClockRunning { false }; QString StartDateString; QLabel AltAzField, RADecField, J2000RADecField; //QPalette OriginalPalette, DarkPalette; OpsCatalog *opcatalog { nullptr }; OpsGuides *opguides { nullptr }; OpsSolarSystem *opsolsys { nullptr }; OpsSatellites *opssatellites { nullptr }; OpsSupernovae *opssupernovae { nullptr }; OpsColors *opcolors { nullptr }; OpsAdvanced *opadvanced { nullptr }; OpsINDI *opsindi { nullptr }; OpsEkos *opsekos { nullptr }; OpsFITS *opsfits { nullptr }; OpsXplanet *opsxplanet { nullptr }; }; diff --git a/kstars/kstarsdata.h b/kstars/kstarsdata.h index 73760c0de..6ccb25d53 100644 --- a/kstars/kstarsdata.h +++ b/kstars/kstarsdata.h @@ -1,521 +1,521 @@ /*************************************************************************** kstarsdata.h - K Desktop Planetarium ------------------- begin : Sun Jul 29 2001 copyright : (C) 2001 by Heiko Evermann email : heiko@evermann.de ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #pragma once #include "catalogdb.h" #include "colorscheme.h" #include "geolocation.h" #include "ksnumbers.h" #include "kstarsdatetime.h" #include "ksuserdb.h" #include "simclock.h" #ifndef KSTARS_LITE #include "oal/oal.h" #include "oal/log.h" #endif #include #include #include #include #include #define MINZOOM 250. #define MAXZOOM 5000000. #define DEFAULTZOOM 2000. #define DZOOM 1.189207115 // 2^(1/4) #define AU_KM 1.49605e8 //km in one AU class QFile; class Execute; class FOV; class ImageExporter; class SkyMap; class SkyMapComposite; class SkyObject; class ObservingList; class TimeZoneRule; #ifdef KSTARS_LITE //Will go away when details window will be implemented in KStars Lite struct ADVTreeData { QString Name; QString Link; int Type; }; #else struct ADVTreeData; #endif /** * @class KStarsData * KStarsData is the backbone of KStars. It contains all the data used by KStars, * including the SkyMapComposite that contains all items in the skymap * (stars, deep-sky objects, planets, constellations, etc). Other kinds of data * are stored here as well: the geographic locations, the timezone rules, etc. * * @author Heiko Evermann * @version 1.0 */ class KStarsData : public QObject { Q_OBJECT protected: /** Constructor. */ KStarsData(); public: // FIXME: It uses temporary trail. There must be way to // this better. And resumeKey in DBUS code friend class KStars; // FIXME: it uses temporary trail and resumeKey friend class SkyMap; // FIXME: uses geoList and changes it. friend class LocationDialog; friend class LocationDialogLite; static KStarsData *Create(); static inline KStarsData *Instance() { return pinstance; } /** * Initialize KStarsData while running splash screen. * @return true on success. */ bool initialize(); /** Destructor. Delete data objects. */ ~KStarsData() override; /** * Set the NextDSTChange member. * Need this accessor because I could not make KStars::privatedata a friend * class for some reason...:/ */ void setNextDSTChange(const KStarsDateTime &dt) { NextDSTChange = dt; } /** * Returns true if time is running forward else false. Used by KStars to prevent * double calculations of daylight saving change time. */ bool isTimeRunningForward() const { return TimeRunsForward; } /** @return pointer to the localization (KLocale) object */ //KLocale *getLocale() { return locale; } /** * @short Find object by name. * @param name Object name to find * @return pointer to SkyObject matching this name */ SkyObject *objectNamed(const QString &name); /** * The Sky is updated more frequently than the moon, which is updated more frequently * than the planets. The date of the last update for each category is recorded so we * know when we need to do it again (see KStars::updateTime()). * Initializing these to -1000000.0 ensures they will be updated immediately * on the first call to KStars::updateTime(). */ void setFullTimeUpdate(); /** * Change the current simulation date/time to the KStarsDateTime argument. * Specified DateTime is always universal time. * @param newDate the DateTime to set. */ void changeDateTime(const KStarsDateTime &newDate); /** @return pointer to the current simulation local time */ const KStarsDateTime <() const { return LTime; } /** @return reference to the current simulation universal time */ const KStarsDateTime &ut() const { return Clock.utc(); } /** Sync the LST with the simulation clock. */ void syncLST(); /** @return pointer to SkyComposite */ SkyMapComposite *skyComposite() { return m_SkyComposite.get(); } /** @return pointer to the ColorScheme object */ ColorScheme *colorScheme() { return &CScheme; } /** @return name of current color scheme **/ Q_INVOKABLE QString colorSchemeName() { return CScheme.fileName(); } /** @return pointer to the KSUserDB object */ KSUserDB *userdb() { return &m_ksuserdb; } /** @return pointer to the Catalog DB object */ CatalogDB *catalogdb() { return &m_catalogdb; } /** @return pointer to the simulation Clock object */ Q_INVOKABLE SimClock *clock() { return &Clock; } /** @return pointer to the local sidereal time: a dms object */ CachingDms *lst() { return &LST; } /** @return pointer to the GeoLocation object*/ GeoLocation *geo() { return &m_Geo; } /** @return list of all geographic locations */ QList &getGeoList() { return geoList; } GeoLocation *locationNamed(const QString &city, const QString &province = QString(), const QString &country = QString()); /** - * @brief nearestLocation Return nearest location to the given logntidue and latitude coordiantes + * @brief nearestLocation Return nearest location to the given longitude and latitude coordinates * @param longitude Longitude (-180 to +180) * @param latitude Latitude (-90 to +90) * @return nearest geographical location to the parameters above. */ GeoLocation *nearestLocation(double longitude, double latitude); /** * Set the GeoLocation according to the argument. * @param l reference to the new GeoLocation */ void setLocation(const GeoLocation &l); /** Set the GeoLocation according to the values stored in the configuration file. */ void setLocationFromOptions(); /** Return map for daylight saving rules. */ const QMap &getRulebook() const { return Rulebook; } /** @return whether the next Focus change will omit the slewing animation. */ bool snapNextFocus() const { return snapToFocus; } /** * Disable or re-enable the slewing animation for the next Focus change. * @note If the user has turned off all animated slewing, setSnapNextFocus(false) * will *NOT* enable animation on the next slew. A false argument would only * be used if you have previously called setSnapNextFocus(true), but then decided * you didn't want that after all. In other words, it's extremely unlikely you'd * ever want to use setSnapNextFocus(false). * @param b when true (the default), the next Focus change will omit the slewing * animation. */ void setSnapNextFocus(bool b = true) { snapToFocus = b; } /** * Execute a script. This function actually duplicates the DCOP functionality * for those cases when invoking DCOP is not practical (i.e., when preparing * a sky image in command-line dump mode). * @param name the filename of the script to "execute". * @param map pointer to the SkyMap object. * @return true if the script was successfully parsed. */ bool executeScript(const QString &name, SkyMap *map); /** Synchronize list of visible FOVs and list of selected FOVs in Options */ #ifndef KSTARS_LITE void syncFOV(); #endif /** * @return the list of visible FOVs */ inline const QList getVisibleFOVs() const { return visibleFOVs; } /** * @return the list of available FOVs */ inline const QList getAvailableFOVs() const { return availFOVs; } /** * @brief addTransientFOV Adds a new FOV to the list. * @param newFOV pointer to FOV object. */ inline void addTransientFOV(std::shared_ptr newFOV) { transientFOVs.append(newFOV); } inline void clearTransientFOVs() { transientFOVs.clear(); } /** * @return the list of transient FOVs */ inline const QList> getTransientFOVs() const { return transientFOVs; } #ifndef KSTARS_LITE /** Return log object */ OAL::Log *logObject() { return m_LogObject.get(); } /** Return ADV Tree */ QList avdTree() { return ADVtreeList; } inline ObservingList *observingList() const { return m_ObservingList; } ImageExporter *imageExporter(); Execute *executeSession(); #endif /*@short Increments the updateID, forcing a recomputation of star positions as well */ unsigned int incUpdateID(); unsigned int updateID() const { return m_updateID; } unsigned int updateNumID() const { return m_updateNumID; } KSNumbers *updateNum() { return &m_updateNum; } void syncUpdateIDs(); signals: /** Signal that specifies the text that should be drawn in the KStarsSplash window. */ void progressText(const QString &text); /** Should be used to refresh skymap. */ void skyUpdate(bool); /** If data changed, emit clearCache signal. */ void clearCache(); /** Emitted when geo location changed */ void geoChanged(); public slots: /** @short send a message to the console*/ void slotConsoleMessage(QString s) { std::cout << (const char *)(s.toLocal8Bit()) << std::endl; } /** * Update the Simulation Clock. Update positions of Planets. Update * Alt/Az coordinates of objects. Update precession. * emit the skyUpdate() signal so that SkyMap / whatever draws the sky can update itself * * This is ugly. * It _will_ change! * (JH:)hey, it's much less ugly now...can we lose the comment yet? :p */ void updateTime(GeoLocation *geo, const bool automaticDSTchange = true); /** * Sets the direction of time and stores it in bool TimeRunForwards. If scale >= 0 * time is running forward else time runs backward. We need this to calculate just * one daylight saving change time (previous or next DST change). */ void setTimeDirection(float scale); private: /** * Populate list of geographic locations from "citydb.sqlite" database. Also check for custom * locations file "mycitydb.sqlite" database, but don't require it. Each line in the file * provides the information required to create one GeoLocation object. * @short Fill list of geographic locations from file(s) * @return true if at least one city read successfully. * @see KStarsData::processCity() */ bool readCityData(); /** Read the data file that contains daylight savings time rules. */ bool readTimeZoneRulebook(); //TODO JM: ADV tree should use XML instead /** * Read Advanced interface structure to be used later to construct the list view in * the advanced tab in the Detail Dialog. * @li KSLABEL designates a top-level parent label * @li KSINTERFACE designates a common URL interface for several objects * @li END designates the end of a sub tree structure * @short read online database lookup structure. * @return true if data is successfully read. */ bool readADVTreeData(); /** Read INDI hosts from an XML file */ bool readINDIHosts(); //TODO JM: Use XML instead; The logger should have more features // that allow users to enter details about their observation logs // objects observed, eye pieces, telescope, conditions, mag..etc /** * @short read user logs. * * Read user logs. The log file is formatted as following: * @li KSLABEL designates the beginning of a log * @li KSLogEnd designates the end of a log. * * @return true if data is successfully read. */ bool readUserLog(); /** * Read in URLs to be attached to a named object's right-click popup menu. At this * point, there is no way to attach URLs to unnamed objects. There are two * kinds of URLs, each with its own data file: image links and webpage links. In addition, * there may be user-specific versions with custom URLs. Each line contains 3 fields * separated by colons (":"). Note that the last field is the URL, and as such it will * generally contain a colon itself. Only the first two colons encountered are treated * as field separators. The fields are: * * @li Object name. This must be the "primary" name of the object (the name at the top of the popup menu). * @li Menu text. The string that should appear in the popup menu to activate the link. * @li URL. * @short Read in image and information URLs. * @return true if data files were successfully read. */ bool readURLData(const QString &url, int type = 0, bool deepOnly = false); /** * @short open a file containing URL links. * @param urlfile string representation of the filename to open * @param file reference to the QFile object which will be opened to this file. * @return true if file successfully opened. */ bool openUrlFile(const QString &urlfile, QFile &file); /** * Reset local time to new daylight saving time. Use this function if DST has changed. * Used by updateTime(). */ void resetToNewDST(GeoLocation *geo, const bool automaticDSTchange); QList ADVtreeList; std::unique_ptr m_SkyComposite; GeoLocation m_Geo; SimClock Clock; KStarsDateTime LTime; KSUserDB m_ksuserdb; CatalogDB m_catalogdb; ColorScheme CScheme; #ifndef KSTARS_LITE ObservingList* m_ObservingList { nullptr }; std::unique_ptr m_LogObject; std::unique_ptr m_Execute; std::unique_ptr m_ImageExporter; #endif //EquipmentWriter *m_equipmentWriter; bool TimeRunsForward { false }; bool temporaryTrail { false }; // FIXME: Used in SkyMap only. Check! bool snapToFocus { false }; //KLocale *locale; CachingDms LST; QKeySequence resumeKey; QList availFOVs; // List of all available FOVs QList visibleFOVs; // List of visible FOVs. Cached from Options::FOVNames QList> transientFOVs; // List of non-permenant transient FOVs. KStarsDateTime LastNumUpdate, LastSkyUpdate, LastPlanetUpdate, LastMoonUpdate; KStarsDateTime NextDSTChange; // FIXME: Used in kstarsdcop.cpp only KStarsDateTime StoredDate; QList geoList; QMap Rulebook; quint32 m_preUpdateID, m_updateID; quint32 m_preUpdateNumID, m_updateNumID; KSNumbers m_preUpdateNum, m_updateNum; static KStarsData *pinstance; }; diff --git a/kstars/projections/projector.cpp b/kstars/projections/projector.cpp index 8a34b0342..92e37766c 100644 --- a/kstars/projections/projector.cpp +++ b/kstars/projections/projector.cpp @@ -1,560 +1,560 @@ /* Copyright (C) 2010 Henry de Valence 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. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "projector.h" #include "ksutils.h" #ifdef KSTARS_LITE #include "skymaplite.h" #endif #include "skycomponents/skylabeler.h" namespace { void toXYZ(const SkyPoint *p, double *x, double *y, double *z) { double sinRa, sinDec, cosRa, cosDec; p->ra().SinCos(sinRa, cosRa); p->dec().SinCos(sinDec, cosDec); *x = cosDec * cosRa; *y = cosDec * sinRa; *z = sinDec; } } SkyPoint Projector::pointAt(double az) { SkyPoint p; p.setAz(az); p.setAlt(0.0); p.HorizontalToEquatorial(KStarsData::Instance()->lst(), KStarsData::Instance()->geo()->lat()); return p; } Projector::Projector(const ViewParams &p) { m_data = KStarsData::Instance(); setViewParams(p); // Force clip polygon update updateClipPoly(); } void Projector::setViewParams(const ViewParams &p) { m_vp = p; /** Precompute cached values */ //Find Sin/Cos for focus point m_sinY0 = 0; m_cosY0 = 0; if (m_vp.useAltAz) { m_vp.focus->alt().SinCos(m_sinY0, m_cosY0); } else { m_vp.focus->dec().SinCos(m_sinY0, m_cosY0); } double currentFOV = m_fov; //Find FOV in radians m_fov = sqrt(m_vp.width * m_vp.width + m_vp.height * m_vp.height) / (2 * m_vp.zoomFactor * dms::DegToRad); //Set checkVisibility variables double Ymax; if (m_vp.useAltAz) { m_xrange = 1.2 * m_fov / cos(m_vp.focus->alt().radians()); Ymax = fabs(m_vp.focus->alt().Degrees()) + m_fov; } else { m_xrange = 1.2 * m_fov / cos(m_vp.focus->dec().radians()); Ymax = fabs(m_vp.focus->dec().Degrees()) + m_fov; } m_isPoleVisible = (Ymax >= 90.0); // Only update clipping polygon if there is an FOV change if (currentFOV != m_fov) updateClipPoly(); } double Projector::fov() const { return m_fov; } QPointF Projector::toScreen(const SkyPoint *o, bool oRefract, bool *onVisibleHemisphere) const { return KSUtils::vecToPoint(toScreenVec(o, oRefract, onVisibleHemisphere)); } bool Projector::onScreen(const QPointF &p) const { return (0 <= p.x() && p.x() <= m_vp.width && 0 <= p.y() && p.y() <= m_vp.height); } bool Projector::onScreen(const Vector2f &p) const { return onScreen(QPointF(p.x(), p.y())); } QPointF Projector::clipLine(SkyPoint *p1, SkyPoint *p2) const { return KSUtils::vecToPoint(clipLineVec(p1, p2)); } Vector2f Projector::clipLineVec(SkyPoint *p1, SkyPoint *p2) const { /* ASSUMES p1 was not clipped but p2 was. * Return the QPoint that barely clips in the line twixt p1 and p2. */ //TODO: iteration = ceil( 0.5*log2( w^2 + h^2) )?? // also possibly rewrite this // --hdevalence int iteration = 15; // For "perfect" clipping: - // 2^interations should be >= max pixels/line + // 2^iterations should be >= max pixels/line bool isVisible = true; // so we start at midpoint SkyPoint mid; Vector2f oMid; double x, y, z, dx, dy, dz, ra, dec; int newx, newy, oldx, oldy; oldx = oldy = -10000; // any old value that is not the first omid toXYZ(p1, &x, &y, &z); // -jbb printf("\np1: %6.4f %6.4f %6.4f\n", x, y, z); toXYZ(p2, &dx, &dy, &dz); // -jbb printf("p2: %6.4f %6.4f %6.4f\n", dx, dy, dz); dx -= x; dy -= y; dz -= z; // Successive approximation to point on line that just clips. while (iteration-- > 0) { dx *= .5; dy *= .5; dz *= .5; if (!isVisible) // move back toward visible p1 { x -= dx; y -= dy; z -= dz; } else // move out toward clipped p2 { x += dx; y += dy; z += dz; } // -jbb printf(" : %6.4f %6.4f %6.4f\n", x, y, z); // [x, y, z] => [ra, dec] ra = atan2(y, x); dec = asin(z / sqrt(x * x + y * y + z * z)); mid = SkyPoint(ra * 12. / dms::PI, dec * 180. / dms::PI); mid.EquatorialToHorizontal(m_data->lst(), m_data->geo()->lat()); oMid = toScreenVec(&mid, false, &isVisible); //AND the result with checkVisibility to clip things going below horizon isVisible &= checkVisibility(&mid); newx = (int)oMid.x(); newy = (int)oMid.y(); // -jbb printf("new x/y: %4d %4d", newx, newy); if ((oldx == newx) && (oldy == newy)) { break; } oldx = newx; oldy = newy; } return oMid; } bool Projector::checkVisibility(const SkyPoint *p) const { //TODO deal with alternate projections //not clear how this depends on projection //FIXME do these heuristics actually work? double dX, dY; //Skip objects below the horizon if the ground is drawn /* Is the cost of this conversion actually less than drawing it anyways? * EquatorialToHorizontal takes 3 SinCos calls -- so 6 trig calls if not using GNU exts. */ /* if( m_vp.fillGround ) { if( !m_vp.useAltAz ) p->EquatorialToHorizontal( m_data->lst(), m_data->geo()->lat() ); if( p->alt().Degrees() < -1.0 ) return false; } */ //Here we hope that the point has already been 'synchronized' if (m_vp.fillGround /*&& m_vp.useAltAz*/ && p->alt().Degrees() < -1.0) return false; if (m_vp.useAltAz) { /** To avoid calculating refraction, we just use the unrefracted altitude and add a 2-degree 'safety factor' */ dY = fabs(p->alt().Degrees() - m_vp.focus->alt().Degrees()) - 2.; } else { dY = fabs(p->dec().Degrees() - m_vp.focus->dec().Degrees()); } if (m_isPoleVisible) dY *= 0.75; //increase effective FOV when pole visible. if (dY > m_fov) return false; if (m_isPoleVisible) return true; if (m_vp.useAltAz) { dX = fabs(p->az().Degrees() - m_vp.focus->az().Degrees()); } else { dX = fabs(p->ra().Degrees() - m_vp.focus->ra().Degrees()); } if (dX > 180.0) dX = 360.0 - dX; // take shorter distance around sky return dX < m_xrange; } // FIXME: There should be a MUCH more efficient way to do this (see EyepieceField for example) double Projector::findNorthPA(SkyPoint *o, float x, float y) const { //Find position angle of North using a test point displaced to the north //displace by 100/zoomFactor radians (so distance is always 100 pixels) //this is 5730/zoomFactor degrees KStarsData *data = KStarsData::Instance(); double newDec = o->dec().Degrees() + 5730.0 / m_vp.zoomFactor; if (newDec > 90.0) newDec = 90.0; SkyPoint test(o->ra().Hours(), newDec); if (m_vp.useAltAz) test.EquatorialToHorizontal(data->lst(), data->geo()->lat()); Vector2f t = toScreenVec(&test); float dx = t.x() - x; float dy = y - t.y(); //backwards because QWidget Y-axis increases to the bottom float north; if (dy) { north = atan2f(dx, dy) * 180.0 / dms::PI; } else { north = (dx > 0.0 ? -90.0 : 90.0); } return north; } double Projector::findPA(SkyObject *o, float x, float y) const { return (findNorthPA(o, x, y) + o->pa()); } QVector Projector::groundPoly(SkyPoint *labelpoint, bool *drawLabel) const { QVector ground; static const QString horizonLabel = i18n("Horizon"); float marginLeft, marginRight, marginTop, marginBot; SkyLabeler::Instance()->getMargins(horizonLabel, &marginLeft, &marginRight, &marginTop, &marginBot); //daz is 1/2 the width of the sky in degrees double daz = 90.; if (m_vp.useAltAz) { daz = 0.5 * m_vp.width * 57.3 / m_vp.zoomFactor; //center to edge, in degrees if (type() == Projector::Orthographic) { daz = daz * 1.4; } daz = qMin(qreal(90.0), daz); } double faz = m_vp.focus->az().Degrees(); double az1 = faz - daz; double az2 = faz + daz; bool allGround = true; bool allSky = true; double inc = 1.0; //Add points along horizon for (double az = az1; az <= az2 + inc; az += inc) { SkyPoint p = pointAt(az); bool visible = false; Vector2f o = toScreenVec(&p, false, &visible); if (visible) { ground.append(o); //Set the label point if this point is onscreen if (labelpoint && o.x() < marginRight && o.y() > marginTop && o.y() < marginBot) *labelpoint = p; if (o.y() > 0.) allGround = false; if (o.y() < m_vp.height) allSky = false; } } if (allSky) { if (drawLabel) *drawLabel = false; return QVector(); } if (allGround) { ground.clear(); ground.append(Vector2f(-10., -10.)); ground.append(Vector2f(m_vp.width + 10., -10.)); ground.append(Vector2f(m_vp.width + 10., m_vp.height + 10.)); ground.append(Vector2f(-10., m_vp.height + 10.)); if (drawLabel) *drawLabel = false; return ground; } //In Gnomonic projection, or if sufficiently zoomed in, we can complete //the ground polygon by simply adding offscreen points //FIXME: not just gnomonic if (daz < 25.0 || type() == Projector::Gnomonic) { ground.append(Vector2f(m_vp.width + 10.f, ground.last().y())); ground.append(Vector2f(m_vp.width + 10.f, m_vp.height + 10.f)); ground.append(Vector2f(-10.f, m_vp.height + 10.f)); ground.append(Vector2f(-10.f, ground.first().y())); } else { double r = m_vp.zoomFactor * radius(); double t1 = atan2(-1. * (ground.last().y() - 0.5 * m_vp.height), ground.last().x() - 0.5 * m_vp.width) / dms::DegToRad; double t2 = t1 - 180.; for (double t = t1; t >= t2; t -= inc) //step along circumference { dms a(t); double sa(0.), ca(0.); a.SinCos(sa, ca); ground.append(Vector2f(0.5 * m_vp.width + r * ca, 0.5 * m_vp.height - r * sa)); } } if (drawLabel) *drawLabel = true; return ground; } void Projector::updateClipPoly() { m_clipPolygon.clear(); double r = m_vp.zoomFactor * radius(); double t1 = 0; double t2 = 360; double inc = 1.0; for (double t = t1; t <= t2; t += inc) { //step along circumference dms a(t); double sa(0.), ca(0.); a.SinCos(sa, ca); m_clipPolygon << QPointF(0.5 * m_vp.width + r * ca, 0.5 * m_vp.height - r * sa); } } QPolygonF Projector::clipPoly() const { return m_clipPolygon; } bool Projector::unusablePoint(const QPointF &p) const { //r0 is the angular size of the sky horizon, in radians double r0 = radius(); //If the zoom is high enough, all points are usable //The center-to-corner distance, in radians double r = 0.5 * 1.41421356 * m_vp.width / m_vp.zoomFactor; if (r < r0) return false; //At low zoom, we have to determine whether the point is beyond the sky horizon //Convert pixel position to x and y offsets in radians double dx = (0.5 * m_vp.width - p.x()) / m_vp.zoomFactor; double dy = (0.5 * m_vp.height - p.y()) / m_vp.zoomFactor; return (dx * dx + dy * dy) > r0 * r0; } SkyPoint Projector::fromScreen(const QPointF &p, dms *LST, const dms *lat) const { dms c; double sinc, cosc; /** N.B. We don't cache these sin/cos values in the inverse * projection because it causes 'shaking' when moving the sky. */ double sinY0, cosY0; //Convert pixel position to x and y offsets in radians double dx = (0.5 * m_vp.width - p.x()) / m_vp.zoomFactor; double dy = (0.5 * m_vp.height - p.y()) / m_vp.zoomFactor; double r = sqrt(dx * dx + dy * dy); c.setRadians(projectionL(r)); c.SinCos(sinc, cosc); if (m_vp.useAltAz) { dx = -1.0 * dx; //Azimuth goes in opposite direction compared to RA m_vp.focus->alt().SinCos(sinY0, cosY0); } else { m_vp.focus->dec().SinCos(sinY0, cosY0); } double Y = asin(cosc * sinY0 + (r == 0 ? 0 : (dy * sinc * cosY0) / r)); double atop = dx * sinc; double abot = r * cosY0 * cosc - dy * sinY0 * sinc; double A = atan2(atop, abot); SkyPoint result; if (m_vp.useAltAz) { dms alt, az; alt.setRadians(Y); az.setRadians(A + m_vp.focus->az().radians()); if (m_vp.useRefraction) alt = SkyPoint::unrefract(alt); result.setAlt(alt); result.setAz(az); result.HorizontalToEquatorial(LST, lat); } else { dms ra, dec; dec.setRadians(Y); ra.setRadians(A + m_vp.focus->ra().radians()); result.set(ra.reduce(), dec); result.EquatorialToHorizontal(LST, lat); } return result; } Vector2f Projector::toScreenVec(const SkyPoint *o, bool oRefract, bool *onVisibleHemisphere) const { double Y, dX; double sindX, cosdX, sinY, cosY; oRefract &= m_vp.useRefraction; if (m_vp.useAltAz) { if (oRefract) Y = SkyPoint::refract(o->alt()).radians(); //account for atmospheric refraction else Y = o->alt().radians(); dX = m_vp.focus->az().radians() - o->az().radians(); } else { dX = o->ra().radians() - m_vp.focus->ra().radians(); Y = o->dec().radians(); } if (!(std::isfinite(Y) && std::isfinite(dX))) { return Vector2f(0, 0); // JM: Enable this again later when trying to find a solution for it // As it is now creating too much noise in the log file. /* qDebug() << "Assert in Projector::toScreenVec failed!"; qDebug() << "using AltAz?" << m_vp.useAltAz << " Refract? " << oRefract; const SkyObject *obj; qDebug() << "Point supplied has RA0 = " << o->ra0().toHMSString() << " Dec0 = " << o->dec0().toDMSString() << "; alt = " << o->alt().toDMSString() << "; az = " << o->az().toDMSString(); if ( (obj = dynamic_cast(o) ) ) { qDebug() << "Point is object with name = " << obj->name() << " longname = " << obj->longname(); } qDebug() << "dX = " << dX << " and isfinite(dX) is" << std::isfinite(dX); qDebug() << "Y = " << Y << " and isfinite(Y) is" << std::isfinite(Y); //Q_ASSERT( false ); */ } dX = KSUtils::reduceAngle(dX, -dms::PI, dms::PI); //Convert dX, Y coords to screen pixel coords, using GNU extension if available #if (__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) sincos(dX, &sindX, &cosdX); sincos(Y, &sinY, &cosY); #else sindX = sin(dX); cosdX = cos(dX); sinY = sin(Y); cosY = cos(Y); #endif //c is the cosine of the angular distance from the center double c = m_sinY0 * sinY + m_cosY0 * cosY * cosdX; //If c is less than 0.0, then the "field angle" (angular distance from the focus) //is more than 90 degrees. This is on the "back side" of the celestial sphere //and should not be drawn. if (onVisibleHemisphere) *onVisibleHemisphere = (c > cosMaxFieldAngle()); // TODO: Isn't it more efficient to bypass the final calculation below if the object is not visible? double k = projectionK(c); double origX = m_vp.width / 2; double origY = m_vp.height / 2; double x = origX - m_vp.zoomFactor * k * cosY * sindX; double y = origY - m_vp.zoomFactor * k * (m_cosY0 * sinY - m_sinY0 * cosY * cosdX); #ifdef KSTARS_LITE double skyRotation = SkyMapLite::Instance()->getSkyRotation(); if (skyRotation != 0) { dms rotation(skyRotation); double cosT, sinT; rotation.SinCos(sinT, cosT); double newX = origX + (x - origX) * cosT - (y - origY) * sinT; double newY = origY + (x - origX) * sinT + (y - origY) * cosT; x = newX; y = newY; } #endif return Vector2f(x, y); } diff --git a/kstars/skyobjects/ksplanetbase.h b/kstars/skyobjects/ksplanetbase.h index d28d03e43..21e90c284 100644 --- a/kstars/skyobjects/ksplanetbase.h +++ b/kstars/skyobjects/ksplanetbase.h @@ -1,288 +1,288 @@ /*************************************************************************** ksplanetbase.h - K Desktop Planetarium ------------------- begin : Sun Jan 29 2002 copyright : (C) 2002 by Mark Hollomon email : mhh@mindspring.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. * * * ***************************************************************************/ #pragma once #include "trailobject.h" #include "kstarsdata.h" #include #include #include #include class KSNumbers; /** * @class EclipticPosition * @short The ecliptic position of a planet (Longitude, Latitude, and distance from Sun). * @author Mark Hollomon * @version 1.0 */ class EclipticPosition { public: dms longitude; dms latitude; double radius; /**Constructor. */ explicit EclipticPosition(dms plong = dms(), dms plat = dms(), double prad = 0.0) : longitude(plong), latitude(plat), radius(prad) { } }; /** * @class KSPlanetBase * A subclass of TrailObject that provides additional information needed for most solar system * objects. This is a base class for KSSun, KSMoon, KSPlanet, KSAsteroid and KSComet. * Those classes cover all solar system objects except planetary moons, which are * derived directly from TrailObject * @short Provides necessary information about objects in the solar system. * @author Mark Hollomon * @version 1.0 */ class KSPlanetBase : public TrailObject { public: /** * Constructor. Calls SkyObject constructor with type=2 (planet), * coordinates=0.0, mag=0.0, primary name s, and all other QStrings empty. * @param s Name of planet * @param image_file filename of the planet's image * @param c color of the symbol to use for this planet * @param pSize the planet's physical size, in km */ explicit KSPlanetBase(const QString &s = i18n("unnamed"), const QString &image_file = QString(), const QColor &c = Qt::white, double pSize = 0); /** Destructor (empty) */ ~KSPlanetBase() override = default; void init(const QString &s, const QString &image_file, const QColor &c, double pSize); //enum Planets { MERCURY=0, VENUS=1, MARS=2, JUPITER=3, SATURN=4, URANUS=5, NEPTUNE=6, PLUTO=7, SUN=8, MOON=9, UNKNOWN_PLANET }; enum Planets { MERCURY = 0, VENUS = 1, MARS = 2, JUPITER = 3, SATURN = 4, URANUS = 5, NEPTUNE = 6, SUN = 7, MOON = 8, EARTH_SHADOW = 9, UNKNOWN_PLANET }; static KSPlanetBase *createPlanet(int n); static QVector planetColor; virtual bool loadData() = 0; /** @return pointer to Ecliptic Longitude coordinate */ const dms &ecLong() const { return ep.longitude; } /** @return pointer to Ecliptic Latitude coordinate */ const dms &ecLat() const { return ep.latitude; } /** * @short Set Ecliptic Geocentric Longitude according to argument. * @param elong Ecliptic Longitude */ void setEcLong(dms elong) { ep.longitude = elong; } /** * @short Set Ecliptic Geocentric Latitude according to argument. * @param elat Ecliptic Latitude */ void setEcLat(dms elat) { ep.latitude = elat; } /** @return pointer to Ecliptic Heliocentric Longitude coordinate */ const dms &helEcLong() const { return helEcPos.longitude; } /** @return pointer to Ecliptic Heliocentric Latitude coordinate */ const dms &helEcLat() const { return helEcPos.latitude; } /** - * @short Convert Ecliptic logitude/latitude to Right Ascension/Declination. + * @short Convert Ecliptic longitude/latitude to Right Ascension/Declination. * @param Obliquity current Obliquity of the Ecliptic (angle from Equator) */ void EclipticToEquatorial(const CachingDms *Obliquity); /** - * @short Convert Right Ascension/Declination to Ecliptic logitude/latitude. + * @short Convert Right Ascension/Declination to Ecliptic longitude/latitude. * @param Obliquity current Obliquity of the Ecliptic (angle from Equator) */ void EquatorialToEcliptic(const CachingDms *Obliquity); /** @return pointer to this planet's texture */ const QImage &image() const { return m_image; } /** @return distance from Sun, in Astronomical Units (1 AU is Earth-Sun distance) */ double rsun() const { return ep.radius; } /** * @short Set the solar distance in AU. * @param r the new solar distance in AU */ void setRsun(double r) { ep.radius = r; } /** @return distance from Earth, in Astronomical Units (1 AU is Earth-Sun distance) */ double rearth() const { return Rearth; } /** * @short Set the distance from Earth, in AU. * @param r the new earth-distance in AU */ void setRearth(double r) { Rearth = r; } /** * @short compute and set the distance from Earth, in AU. * @param Earth pointer to the Earth from which to calculate the distance. */ void setRearth(const KSPlanetBase *Earth); /** * Update position of the planet (reimplemented from SkyPoint) * @param num current KSNumbers object * @param includePlanets this function does nothing if includePlanets=false * @param lat pointer to the geographic latitude; if nullptr, we skip localizeCoords() * @param LST pointer to the local sidereal time; if nullptr, we skip localizeCoords() * @param forceRecompute defines whether the data should be recomputed forcefully */ void updateCoords(const KSNumbers *num, bool includePlanets = true, const CachingDms *lat = nullptr, const CachingDms *LST = nullptr, bool forceRecompute = false) override; /** * @short Find position, including correction for Figure-of-the-Earth. * @param num KSNumbers pointer for the target date/time * @param lat pointer to the geographic latitude; if nullptr, we skip localizeCoords() * @param LST pointer to the local sidereal time; if nullptr, we skip localizeCoords() * @param Earth pointer to the Earth (not used for the Moon) */ void findPosition(const KSNumbers *num, const CachingDms *lat = nullptr, const CachingDms *LST = nullptr, const KSPlanetBase *Earth = nullptr); /** @return the Planet's position angle. */ double pa() const override { return PositionAngle; } /** * @short Set the Planet's position angle. * @param p the new position angle */ void setPA(double p) { PositionAngle = p; } /** @return the Planet's angular size, in arcminutes */ double angSize() const { return AngularSize; } /** @short set the planet's angular size, in km. * @param size the planet's size, in km */ void setAngularSize(double size) { AngularSize = size; } /** @return the Planet's physical size, in km */ double physicalSize() const { return PhysicalSize; } /** @short set the planet's physical size, in km. * @param size the planet's size, in km */ void setPhysicalSize(double size) { PhysicalSize = size; } /** @return the phase angle of this planet */ inline dms phase() { return dms(Phase); } /** @return the color for the planet symbol */ QColor &color() { return m_Color; } /** @short Set the color for the planet symbol */ void setColor(const QColor &c) { m_Color = c; } /** @return true if the KSPlanet is one of the eight major planets */ bool isMajorPlanet() const; /** @return the pixel distance for offseting the object's name label */ double labelOffset() const override; protected: /** Big object. Planet, Moon, Sun. */ static const UID UID_SOL_BIGOBJ; /** Asteroids */ static const UID UID_SOL_ASTEROID; /** Comets */ static const UID UID_SOL_COMET; /** Compute high 32-bits of UID. */ inline UID solarsysUID(UID type) const { return (SkyObject::UID_SOLARSYS << 60) | (type << 56); } /** * @short find the object's current geocentric equatorial coordinates (RA and Dec) * This function is pure virtual; it must be overloaded by subclasses. * This function is private; it is called by the public function findPosition() * which also includes the figure-of-the-earth correction, localizeCoords(). * @param num pointer to current KSNumbers object * @param Earth pointer to planet Earth (needed to calculate geocentric coords) * @return true if position was successfully calculated. */ virtual bool findGeocentricPosition(const KSNumbers *num, const KSPlanetBase *Earth = nullptr) = 0; /** * @short Computes the visual magnitude for the major planets. * @param num pointer to a ksnumbers object. Needed for the saturn rings contribution to * saturn's magnitude. */ virtual void findMagnitude(const KSNumbers *num) = 0; /** * Determine the position angle of the planet for a given date * (used internally by findPosition() ) */ void findPA(const KSNumbers *num); /** Determine the phase of the planet. */ virtual void findPhase(); virtual double findAngularSize() { return asin(physicalSize() / Rearth / AU_KM) * 60. * 180. / dms::PI; } // Geocentric ecliptic position, but distance to the Sun EclipticPosition ep; // Heliocentric ecliptic position referred to the equinox of the epoch // as obtained from VSOP. EclipticPosition helEcPos; double Rearth {NaN::d}; double Phase {NaN::d}; QImage m_image; private: /** * @short correct the position for the fact that the location is not at the center of the Earth, * but a position on its surface. This causes a small parallactic shift in a solar system * body's apparent position. The effect is most significant for the Moon. * This function is private, and should only be called from the public findPosition() function. * @param num pointer to a ksnumbers object for the target date/time * @param lat pointer to the geographic latitude of the location. * @param LST pointer to the local sidereal time. */ void localizeCoords(const KSNumbers *num, const CachingDms *lat, const CachingDms *LST); double PositionAngle, AngularSize, PhysicalSize; QColor m_Color; }; diff --git a/kstars/skyobjects/kspluto.cpp b/kstars/skyobjects/kspluto.cpp index 5a5afb4b9..16fd38c3e 100644 --- a/kstars/skyobjects/kspluto.cpp +++ b/kstars/skyobjects/kspluto.cpp @@ -1,89 +1,89 @@ /*************************************************************************** kspluto.cpp - K Desktop Planetarium ------------------- begin : Mon Sep 24 2001 copyright : (C) 2001 by Jason Harris email : kstars@30doradus.org ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "kspluto.h" #include #include #include #include #include "ksnumbers.h" #include "kstarsdatetime.h" //for J2000 define #ifdef B0 // There are systems that #define B0 as a special termios flag (for baud rate) #undef B0 #endif KSPluto::KSPluto(const QString &fn, double pSize) : KSAsteroid(0, xi18n("Pluto"), fn, J2000, 39.48168677, 0.24880766, dms(17.14175), dms(113.76329), dms(110.30347), dms(14.86205), 1.0, 0.0) { //Initialize the base orbital element values for J2000: a0 = 39.48168677; //semi-major axis (AU) e0 = 0.24880766; //eccentricity i0.setD(17.14175); //inclination (degrees) w0.setD(113.76329); //argument of perihelion (degrees) N0.setD(110.30347); //long. of ascending node (degrees) M0.setD(14.86205); //mean anomaly (degrees) //rate-of-change values for the orbital elements - a1 = -0.00076912; // da/dt (AU/cnetury) + a1 = -0.00076912; // da/dt (AU/century) e1 = 0.00006465; // de/dt (1/century) i1 = 11.07 / 3600.; // di/dt (degrees/century) w1 = -94.92 / 3600.; // dw/dt (degrees/century) N1 = -37.33 / 3600.; // dN/dt (degrees/century) M1 = 522880.15 / 3600.; // dM/dt (degrees/century) setPhysicalSize(pSize); } KSPluto *KSPluto::clone() const { Q_ASSERT(typeid(this) == typeid(static_cast(this))); // Ensure we are not slicing a derived class return new KSPluto(*this); } KSPluto::~KSPluto() { } //Determine values for the orbital elements for the requested JD, then //call KSAsteroid::findGeocentricPosition() bool KSPluto::findGeocentricPosition(const KSNumbers *num, const KSPlanetBase *Earth) { //First, set the orbital element values according to the current epoch double t = num->julianCenturies(); set_a(a0 + a1 * t); set_e(e0 + e1 * t); set_i(i0.Degrees() + i1 * t); set_N(N0.Degrees() + N1 * t); set_M(M0.Degrees() + M1 * t); set_P(365.2568984 * pow((a0 + a1 * t), 1.5)); //set period from Kepler's 3rd law setJD(num->julianDay()); return KSAsteroid::findGeocentricPosition(num, Earth); } void KSPluto::findMagnitude(const KSNumbers *) { setMag(-1.01 + 5 * log10(rsun() * rearth()) + 0.041 * phase().Degrees()); } diff --git a/kstars/tools/eclipsetool/lunareclipsehandler.cpp b/kstars/tools/eclipsetool/lunareclipsehandler.cpp index 815938cf4..b0033574c 100644 --- a/kstars/tools/eclipsetool/lunareclipsehandler.cpp +++ b/kstars/tools/eclipsetool/lunareclipsehandler.cpp @@ -1,256 +1,256 @@ /*************************************************************************** lunareclipsehandler.cpp - K Desktop Planetarium ------------------- begin : Tue 18/09/2018 copyright : (C) 2018 Valentin Boettcher email : valentin@boettcher.cf ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "lunareclipsehandler.h" #include "skymapcomposite.h" #include "solarsystemcomposite.h" #include "dms.h" LunarEclipseHandler::LunarEclipseHandler(QObject * parent) : EclipseHandler(parent), m_sun(), m_moon(), m_shadow(&m_moon, &m_sun, &m_Earth) { } EclipseHandler::EclipseVector LunarEclipseHandler::computeEclipses(long double startJD, long double endJD) { m_mode = CLOSEST_APPROACH; const long double SEARCH_INTERVAL = 5.l; // Days QVector eclipses; QVector fullMoons = getFullMoons(startJD, endJD); int total = fullMoons.length(); if (total == 0) return eclipses; float step = 1 / total; float progress = 0; connect(this, &ApproachSolver::solverMadeProgress, this, [ &, this] (int dProgress) { float tmpProgress = roundf(progress + step * dProgress); if (tmpProgress > progress) { progress = tmpProgress; emit signalProgress(static_cast(progress)); } }); for(auto date : fullMoons) { findClosestApproach(date, date + SEARCH_INTERVAL, [&eclipses, this] (long double JD, dms) { EclipseEvent::ECLIPSE_TYPE type; updatePositions(JD); KSEarthShadow::ECLIPSE_TYPE extended_type = m_shadow.getEclipseType(); switch (extended_type) { case KSEarthShadow::FULL_PENUMBRA: case KSEarthShadow::FULL_UMBRA: type = EclipseEvent::FULL; break; case KSEarthShadow::NONE: return; default: type = EclipseEvent::PARTIAL; break; } EclipseEvent_s event = std::make_shared(JD, *getGeoLocation(), type, extended_type); emit signalEventFound(event); eclipses.append(event); }); progress++; emit signalProgress(static_cast(roundf(100 * (progress / total)))); } emit signalProgress(100); emit signalComputationFinished(); return eclipses; } // FIXME: (Valentin) This doesn't work for now. We need another method. LunarEclipseDetails LunarEclipseHandler::findEclipseDetails(LunarEclipseEvent *event) { Q_UNUSED(event); // const long double INTERVAL = 1.l; // const long double JD = event->getJD(); // const long double start = JD - INTERVAL; // const long double stop = JD + INTERVAL; LunarEclipseDetails details; // details.available = true; // details.eclipseTimes.insert(JD, LunarEclipseDetails::CLOSEST_APPROACH); // auto type = event->getDetailedType(); // auto findBoth = [&](LunarEclipseDetails::EVENT ev1 /* first (temporal) */, LunarEclipseDetails::EVENT ev2) { // QMap tmpApproaches; // QPair out; // findPrecise(&out, JD, 0.001, -1); // details.eclipseTimes.insert(out.first, ev1); // findPrecise(&out, JD, 0.001, 1); // details.eclipseTimes.insert(out.first, ev2); // }; // // waterfall method... // if(type == KSEarthShadow::NONE) { // details.available = false; // return details; // } // if(type == KSEarthShadow::FULL_UMBRA) { // m_mode = UMBRA_IMMERSION; // findBoth(LunarEclipseDetails::BEGIN_FULL_PENUMRA, LunarEclipseDetails::END_FULL_PENUMRA); // m_mode = UMBRA_CONTACT; // findBoth(LunarEclipseDetails::BEGIN_UMBRA_CONTACT, LunarEclipseDetails::END_UMBRA_CONTACT); // } //// if(type == KSEarthShadow::FULL_PENUMBRA || type == KSEarthShadow::FULL_UMBRA) { //// m_mode = UMR //// }; return details; } LunarEclipseHandler::~LunarEclipseHandler() { } void LunarEclipseHandler::updatePositions(long double jd) { KStarsDateTime t(jd); KSNumbers num(jd); CachingDms LST(getGeoLocation()->GSTtoLST(t.gst())); const CachingDms * LAT = getGeoLocation()->lat(); m_Earth.findPosition(&num); m_sun.findPosition(&num, LAT, &LST, &m_Earth); m_moon.findPosition(&num, LAT, &LST, &m_Earth); m_shadow.findPosition(&num, LAT, &LST, &m_Earth); } dms LunarEclipseHandler::findDistance() { dms moon_rad = dms(m_moon.angSize() / 120); dms pen_rad = dms(m_shadow.getPenumbraAngSize() / 60); dms um_rad = dms(m_shadow.getUmbraAngSize() / 60); dms dist = findSkyPointDistance(&m_shadow, &m_moon); switch (m_mode) { case CLOSEST_APPROACH: return dist; case PENUMBRA_CONTACT: return dist - (moon_rad + pen_rad); case PUNUMBRA_IMMERSION: return dist + moon_rad - pen_rad; case UMBRA_CONTACT: return dist - (moon_rad + um_rad); case UMBRA_IMMERSION: return dist + moon_rad - um_rad; } return dms(); } double LunarEclipseHandler::getMaxSeparation() { const double SEP_QUALITY = 0.1; - // we use the penumbra as meassure :) + // we use the penumbra as measure :) if(m_mode == CLOSEST_APPROACH) return (m_shadow.getPenumbraAngSize() + m_moon.angSize()) / 60; else return SEP_QUALITY; } QVector LunarEclipseHandler::getFullMoons(long double startJD, long double endJD) { const long double NEXT_STEP = 0.5l; const long double INTERVAL = 26.5l; long double ¤tJD = startJD; QVector fullMoons; while(currentJD <= endJD) { KStarsDateTime t(currentJD); KSNumbers num(currentJD); CachingDms LST = getGeoLocation()->GSTtoLST(t.gst()); m_sun.updateCoords(&num, true, getGeoLocation()->lat(), &LST, true); m_moon.updateCoords(&num, true, getGeoLocation()->lat(), &LST, true); m_moon.findPhase(&m_sun); if(m_moon.illum() > 0.9) { fullMoons.append(currentJD); currentJD += INTERVAL; continue; } currentJD += NEXT_STEP; } return fullMoons; } LunarEclipseEvent::LunarEclipseEvent(long double jd, GeoLocation geoPlace, EclipseEvent::ECLIPSE_TYPE type, KSEarthShadow::ECLIPSE_TYPE detailed_type) : EclipseEvent (jd, geoPlace, type), m_detailedType { detailed_type } { m_details.available = false; } QString LunarEclipseEvent::getExtraInfo() { switch(m_detailedType) { case KSEarthShadow::FULL_UMBRA: return "Full Umbral"; case KSEarthShadow::FULL_PENUMBRA: return "Full Penumbral"; case KSEarthShadow::PARTIAL: case KSEarthShadow::NONE: return ""; } return ""; } SkyObject *LunarEclipseEvent::getEclipsingObjectFromSkyComposite() { return KStarsData::Instance()->skyComposite()->solarSystemComposite()->moon(); } void LunarEclipseEvent::slotShowDetails() { if(!m_details.available) { LunarEclipseHandler handler; GeoLocation loc = getGeolocation(); handler.setGeoLocation(&loc); handler.findEclipseDetails(this); } } diff --git a/kstars/tools/scriptbuilder.cpp b/kstars/tools/scriptbuilder.cpp index 15556fad2..762f3cad6 100644 --- a/kstars/tools/scriptbuilder.cpp +++ b/kstars/tools/scriptbuilder.cpp @@ -1,2871 +1,2871 @@ /*************************************************************************** scriptbuilder.cpp - description ------------------- begin : Thu Apr 17 2003 copyright : (C) 2003 by Jason Harris email : kstars@30doradus.org ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "scriptbuilder.h" #include "kspaths.h" #include "scriptfunction.h" #include "kstars.h" #include "kstarsdata.h" #include "skymap.h" #include "ksnotification.h" #include "kstarsdatetime.h" #include "dialogs/finddialog.h" #include "dialogs/locationdialog.h" #include "widgets/dmsbox.h" #include "widgets/timespinbox.h" #include "widgets/timestepbox.h" #include #include #include #include #include #include #include #include #include #include #include #include #include OptionsTreeViewWidget::OptionsTreeViewWidget(QWidget *p) : QFrame(p) { setupUi(this); } OptionsTreeView::OptionsTreeView(QWidget *p) : QDialog(p) { otvw.reset(new OptionsTreeViewWidget(this)); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(otvw.get()); setLayout(mainLayout); setWindowTitle(i18n("Options")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); mainLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); setModal(false); } void OptionsTreeView::resizeColumns() { //Size each column to the maximum width of items in that column int maxwidth[3] = { 0, 0, 0 }; QFontMetrics qfm = optionsList()->fontMetrics(); for (int i = 0; i < optionsList()->topLevelItemCount(); ++i) { QTreeWidgetItem *topitem = optionsList()->topLevelItem(i); topitem->setExpanded(true); for (int j = 0; j < topitem->childCount(); ++j) { QTreeWidgetItem *child = topitem->child(j); for (int icol = 0; icol < 3; ++icol) { child->setExpanded(true); #if QT_VERSION >= QT_VERSION_CHECK(5,11,0) int w = qfm.horizontalAdvance(child->text(icol)) + 4; #else int w = qfm.width(child->text(icol)) + 4; #endif if (icol == 0) { w += 2 * optionsList()->indentation(); } if (w > maxwidth[icol]) { maxwidth[icol] = w; } } } } for (int icol = 0; icol < 3; ++icol) { //DEBUG qDebug() << QString("max width of column %1: %2").arg(icol).arg(maxwidth[icol]); optionsList()->setColumnWidth(icol, maxwidth[icol]); } } ScriptNameWidget::ScriptNameWidget(QWidget *p) : QFrame(p) { setupUi(this); } ScriptNameDialog::ScriptNameDialog(QWidget *p) : QDialog(p) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif snw = new ScriptNameWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(snw); setLayout(mainLayout); setWindowTitle(i18n("Script Data")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); mainLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); okB = buttonBox->button(QDialogButtonBox::Ok); connect(snw->ScriptName, SIGNAL(textChanged(QString)), this, SLOT(slotEnableOkButton())); } ScriptNameDialog::~ScriptNameDialog() { delete snw; } void ScriptNameDialog::slotEnableOkButton() { okB->setEnabled(!snw->ScriptName->text().isEmpty()); } ScriptBuilderUI::ScriptBuilderUI(QWidget *p) : QFrame(p) { setupUi(this); } ScriptBuilder::ScriptBuilder(QWidget *parent) : QDialog(parent), UnsavedChanges(false), checkForChanges(true), currentFileURL(), currentDir(QDir::homePath()), currentScriptName(), currentAuthor() { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif sb = new ScriptBuilderUI(this); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(sb); setLayout(mainLayout); setWindowTitle(i18n("Script Builder")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); mainLayout->addWidget(buttonBox); connect(buttonBox, SIGNAL(rejected()), this, SLOT(slotClose())); sb->FuncDoc->setTextInteractionFlags(Qt::NoTextInteraction); //Initialize function templates and descriptions KStarsFunctionList.append(new ScriptFunction("lookTowards", i18n("Point the display at the specified location. %1 can be the name " "of an object, a cardinal point on the compass, or 'zenith'.", QString("dir")), false, "QString", "dir")); KStarsFunctionList.append(new ScriptFunction( "addLabel", i18n("Add a name label to the object named %1.", QString("name")), false, "QString", "name")); KStarsFunctionList.append( new ScriptFunction("removeLabel", i18n("Remove the name label from the object named %1.", QString("name")), false, "QString", "name")); KStarsFunctionList.append(new ScriptFunction( "addTrail", i18n("Add a trail to the solar system body named %1.", QString("name")), false, "QString", "name")); KStarsFunctionList.append(new ScriptFunction( "removeTrail", i18n("Remove the trail from the solar system body named %1.", QString("name")), false, "QString", "name")); KStarsFunctionList.append(new ScriptFunction("setRaDec", i18n("Point the display at the specified RA/Dec coordinates. RA is " "expressed in Hours; Dec is expressed in Degrees."), false, "double", "ra", "double", "dec")); KStarsFunctionList.append(new ScriptFunction( "setAltAz", i18n("Point the display at the specified Alt/Az coordinates. Alt and Az are expressed in Degrees."), false, "double", "alt", "double", "az")); KStarsFunctionList.append(new ScriptFunction("zoomIn", i18n("Increase the display Zoom Level."), false)); KStarsFunctionList.append(new ScriptFunction("zoomOut", i18n("Decrease the display Zoom Level."), false)); KStarsFunctionList.append( new ScriptFunction("defaultZoom", i18n("Set the display Zoom Level to its default value."), false)); KStarsFunctionList.append( new ScriptFunction("zoom", i18n("Set the display Zoom Level manually."), false, "double", "z")); KStarsFunctionList.append( new ScriptFunction("setLocalTime", i18n("Set the system clock to the specified Local Time."), false, "int", "year", "int", "month", "int", "day", "int", "hour", "int", "minute", "int", "second")); KStarsFunctionList.append(new ScriptFunction( "waitFor", i18n("Pause script execution for specified number of seconds."), false, "double", "sec")); KStarsFunctionList.append(new ScriptFunction("waitForKey", i18n("Halt script execution until the specified key is pressed. Only " "single-key strokes are possible; use 'space' for the spacebar."), false, "QString", "key")); KStarsFunctionList.append(new ScriptFunction( "setTracking", i18n("Set whether the display is tracking the current location."), false, "bool", "track")); KStarsFunctionList.append(new ScriptFunction( "changeViewOption", i18n("Change view option named %1 to value %2.", QString("opName"), QString("opValue")), false, "QString", "opName", "QString", "opValue")); KStarsFunctionList.append(new ScriptFunction( "setGeoLocation", i18n("Set the geographic location to the city specified by city, province and country."), false, "QString", "cityName", "QString", "provinceName", "QString", "countryName")); KStarsFunctionList.append(new ScriptFunction( "setColor", i18n("Set the color named %1 to the value %2.", QString("colorName"), QString("value")), false, "QString", "colorName", "QString", "value")); KStarsFunctionList.append(new ScriptFunction("loadColorScheme", i18n("Load the color scheme specified by name."), false, "QString", "name")); KStarsFunctionList.append( new ScriptFunction("exportImage", i18n("Export the sky image to the file, with specified width and height."), false, "QString", "fileName", "int", "width", "int", "height")); KStarsFunctionList.append( new ScriptFunction("printImage", i18n("Print the sky image to a printer or file. If %1 is true, it will show the print " "dialog. If %2 is true, it will use the Star Chart color scheme for printing.", QString("usePrintDialog"), QString("useChartColors")), false, "bool", "usePrintDialog", "bool", "useChartColors")); SimClockFunctionList.append(new ScriptFunction("stop", i18n("Halt the simulation clock."), true)); SimClockFunctionList.append(new ScriptFunction("start", i18n("Start the simulation clock."), true)); SimClockFunctionList.append(new ScriptFunction("setClockScale", i18n("Set the timescale of the simulation clock to specified scale. " " 1.0 means real-time; 2.0 means twice real-time; etc."), true, "double", "scale")); // JM: We're using QTreeWdiget for Qt4 now QTreeWidgetItem *kstars_tree = new QTreeWidgetItem(sb->FunctionTree, QStringList("KStars")); QTreeWidgetItem *simclock_tree = new QTreeWidgetItem(sb->FunctionTree, QStringList("SimClock")); for (auto &item : KStarsFunctionList) new QTreeWidgetItem(kstars_tree, QStringList(item->prototype())); for (auto &item : SimClockFunctionList) new QTreeWidgetItem(simclock_tree, QStringList(item->prototype())); kstars_tree->sortChildren(0, Qt::AscendingOrder); simclock_tree->sortChildren(0, Qt::AscendingOrder); sb->FunctionTree->setColumnCount(1); sb->FunctionTree->setHeaderLabels(QStringList(i18n("Functions"))); sb->FunctionTree->setSortingEnabled(false); //Add icons to Push Buttons sb->NewButton->setIcon(QIcon::fromTheme("document-new")); sb->OpenButton->setIcon(QIcon::fromTheme("document-open")); sb->SaveButton->setIcon(QIcon::fromTheme("document-save")); sb->SaveAsButton->setIcon( QIcon::fromTheme("document-save-as")); sb->RunButton->setIcon(QIcon::fromTheme("system-run")); sb->CopyButton->setIcon(QIcon::fromTheme("view-refresh")); sb->AddButton->setIcon(QIcon::fromTheme("go-previous")); sb->RemoveButton->setIcon(QIcon::fromTheme("go-next")); sb->UpButton->setIcon(QIcon::fromTheme("go-up")); sb->DownButton->setIcon(QIcon::fromTheme("go-down")); sb->NewButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); sb->OpenButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); sb->SaveButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); sb->SaveAsButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); sb->RunButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); sb->CopyButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); sb->AddButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); sb->RemoveButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); sb->UpButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); sb->DownButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); //Prepare the widget stack argBlank = new QWidget(); argLookToward = new ArgLookToward(sb->ArgStack); argFindObject = new ArgFindObject(sb->ArgStack); //shared by Add/RemoveLabel and Add/RemoveTrail argSetRaDec = new ArgSetRaDec(sb->ArgStack); argSetAltAz = new ArgSetAltAz(sb->ArgStack); argSetLocalTime = new ArgSetLocalTime(sb->ArgStack); argWaitFor = new ArgWaitFor(sb->ArgStack); argWaitForKey = new ArgWaitForKey(sb->ArgStack); argSetTracking = new ArgSetTrack(sb->ArgStack); argChangeViewOption = new ArgChangeViewOption(sb->ArgStack); argSetGeoLocation = new ArgSetGeoLocation(sb->ArgStack); argTimeScale = new ArgTimeScale(sb->ArgStack); argZoom = new ArgZoom(sb->ArgStack); argExportImage = new ArgExportImage(sb->ArgStack); argPrintImage = new ArgPrintImage(sb->ArgStack); argSetColor = new ArgSetColor(sb->ArgStack); argLoadColorScheme = new ArgLoadColorScheme(sb->ArgStack); sb->ArgStack->addWidget(argBlank); sb->ArgStack->addWidget(argLookToward); sb->ArgStack->addWidget(argFindObject); sb->ArgStack->addWidget(argSetRaDec); sb->ArgStack->addWidget(argSetAltAz); sb->ArgStack->addWidget(argSetLocalTime); sb->ArgStack->addWidget(argWaitFor); sb->ArgStack->addWidget(argWaitForKey); sb->ArgStack->addWidget(argSetTracking); sb->ArgStack->addWidget(argChangeViewOption); sb->ArgStack->addWidget(argSetGeoLocation); sb->ArgStack->addWidget(argTimeScale); sb->ArgStack->addWidget(argZoom); sb->ArgStack->addWidget(argExportImage); sb->ArgStack->addWidget(argPrintImage); sb->ArgStack->addWidget(argSetColor); sb->ArgStack->addWidget(argLoadColorScheme); sb->ArgStack->setCurrentIndex(0); snd = new ScriptNameDialog(KStars::Instance()); otv = new OptionsTreeView(KStars::Instance()); otv->resize(sb->width(), 0.5 * sb->height()); initViewOptions(); otv->resizeColumns(); //connect widgets in ScriptBuilderUI connect(sb->FunctionTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(slotAddFunction())); connect(sb->FunctionTree, SIGNAL(itemClicked(QTreeWidgetItem*, int)), this, SLOT(slotShowDoc())); connect(sb->UpButton, SIGNAL(clicked()), this, SLOT(slotMoveFunctionUp())); connect(sb->ScriptListBox, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(slotArgWidget())); connect(sb->DownButton, SIGNAL(clicked()), this, SLOT(slotMoveFunctionDown())); connect(sb->CopyButton, SIGNAL(clicked()), this, SLOT(slotCopyFunction())); connect(sb->RemoveButton, SIGNAL(clicked()), this, SLOT(slotRemoveFunction())); connect(sb->NewButton, SIGNAL(clicked()), this, SLOT(slotNew())); connect(sb->OpenButton, SIGNAL(clicked()), this, SLOT(slotOpen())); connect(sb->SaveButton, SIGNAL(clicked()), this, SLOT(slotSave())); connect(sb->SaveAsButton, SIGNAL(clicked()), this, SLOT(slotSaveAs())); connect(sb->AddButton, SIGNAL(clicked()), this, SLOT(slotAddFunction())); connect(sb->RunButton, SIGNAL(clicked()), this, SLOT(slotRunScript())); //Connections for Arg Widgets connect(argSetGeoLocation->FindCityButton, SIGNAL(clicked()), this, SLOT(slotFindCity())); connect(argLookToward->FindButton, SIGNAL(clicked()), this, SLOT(slotFindObject())); connect(argChangeViewOption->TreeButton, SIGNAL(clicked()), this, SLOT(slotShowOptions())); connect(argFindObject->FindButton, SIGNAL(clicked()), this, SLOT(slotFindObject())); connect(argLookToward->FocusEdit, SIGNAL(editTextChanged(QString)), this, SLOT(slotLookToward())); connect(argFindObject->NameEdit, SIGNAL(textChanged(QString)), this, SLOT(slotArgFindObject())); connect(argSetRaDec->RABox, SIGNAL(textChanged(QString)), this, SLOT(slotRa())); connect(argSetRaDec->DecBox, SIGNAL(textChanged(QString)), this, SLOT(slotDec())); connect(argSetAltAz->AltBox, SIGNAL(textChanged(QString)), this, SLOT(slotAlt())); connect(argSetAltAz->AzBox, SIGNAL(textChanged(QString)), this, SLOT(slotAz())); connect(argSetLocalTime->DateWidget, SIGNAL(dateChanged(QDate)), this, SLOT(slotChangeDate())); connect(argSetLocalTime->TimeBox, SIGNAL(timeChanged(QTime)), this, SLOT(slotChangeTime())); connect(argWaitFor->DelayBox, SIGNAL(valueChanged(int)), this, SLOT(slotWaitFor())); connect(argWaitForKey->WaitKeyEdit, SIGNAL(textChanged(QString)), this, SLOT(slotWaitForKey())); connect(argSetTracking->CheckTrack, SIGNAL(stateChanged(int)), this, SLOT(slotTracking())); connect(argChangeViewOption->OptionName, SIGNAL(activated(QString)), this, SLOT(slotViewOption())); connect(argChangeViewOption->OptionValue, SIGNAL(textChanged(QString)), this, SLOT(slotViewOption())); connect(argSetGeoLocation->CityName, SIGNAL(textChanged(QString)), this, SLOT(slotChangeCity())); connect(argSetGeoLocation->ProvinceName, SIGNAL(textChanged(QString)), this, SLOT(slotChangeProvince())); connect(argSetGeoLocation->CountryName, SIGNAL(textChanged(QString)), this, SLOT(slotChangeCountry())); connect(argTimeScale->TimeScale, SIGNAL(scaleChanged(float)), this, SLOT(slotTimeScale())); connect(argZoom->ZoomBox, SIGNAL(textChanged(QString)), this, SLOT(slotZoom())); connect(argExportImage->ExportFileName, SIGNAL(textChanged(QString)), this, SLOT(slotExportImage())); connect(argExportImage->ExportWidth, SIGNAL(valueChanged(int)), this, SLOT(slotExportImage())); connect(argExportImage->ExportHeight, SIGNAL(valueChanged(int)), this, SLOT(slotExportImage())); connect(argPrintImage->UsePrintDialog, SIGNAL(toggled(bool)), this, SLOT(slotPrintImage())); connect(argPrintImage->UseChartColors, SIGNAL(toggled(bool)), this, SLOT(slotPrintImage())); connect(argSetColor->ColorName, SIGNAL(activated(QString)), this, SLOT(slotChangeColorName())); connect(argSetColor->ColorValue, SIGNAL(changed(QColor)), this, SLOT(slotChangeColor())); connect(argLoadColorScheme->SchemeList, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(slotLoadColorScheme())); //disable some buttons sb->CopyButton->setEnabled(false); sb->AddButton->setEnabled(false); sb->RemoveButton->setEnabled(false); sb->UpButton->setEnabled(false); sb->DownButton->setEnabled(false); sb->SaveButton->setEnabled(false); sb->SaveAsButton->setEnabled(false); sb->RunButton->setEnabled(false); } ScriptBuilder::~ScriptBuilder() { while (!KStarsFunctionList.isEmpty()) delete KStarsFunctionList.takeFirst(); while (!SimClockFunctionList.isEmpty()) delete SimClockFunctionList.takeFirst(); while (!ScriptList.isEmpty()) delete ScriptList.takeFirst(); } void ScriptBuilder::initViewOptions() { otv->optionsList()->setRootIsDecorated(true); QStringList fields; //InfoBoxes opsGUI = new QTreeWidgetItem(otv->optionsList(), QStringList(i18n("InfoBoxes"))); fields << "ShowInfoBoxes" << i18n("Toggle display of all InfoBoxes") << i18n("bool"); new QTreeWidgetItem(opsGUI, fields); fields.clear(); fields << "ShowTimeBox" << i18n("Toggle display of Time InfoBox") << i18n("bool"); new QTreeWidgetItem(opsGUI, fields); fields.clear(); fields << "ShowGeoBox" << i18n("Toggle display of Geographic InfoBox") << i18n("bool"); new QTreeWidgetItem(opsGUI, fields); fields.clear(); fields << "ShowFocusBox" << i18n("Toggle display of Focus InfoBox") << i18n("bool"); new QTreeWidgetItem(opsGUI, fields); fields.clear(); fields << "ShadeTimeBox" << i18n("(un)Shade Time InfoBox") << i18n("bool"); new QTreeWidgetItem(opsGUI, fields); fields.clear(); fields << "ShadeGeoBox" << i18n("(un)Shade Geographic InfoBox") << i18n("bool"); new QTreeWidgetItem(opsGUI, fields); fields.clear(); fields << "ShadeFocusBox" << i18n("(un)Shade Focus InfoBox") << i18n("bool"); new QTreeWidgetItem(opsGUI, fields); fields.clear(); argChangeViewOption->OptionName->addItem("ShowInfoBoxes"); argChangeViewOption->OptionName->addItem("ShowTimeBox"); argChangeViewOption->OptionName->addItem("ShowGeoBox"); argChangeViewOption->OptionName->addItem("ShowFocusBox"); argChangeViewOption->OptionName->addItem("ShadeTimeBox"); argChangeViewOption->OptionName->addItem("ShadeGeoBox"); argChangeViewOption->OptionName->addItem("ShadeFocusBox"); //Toolbars opsToolbar = new QTreeWidgetItem(otv->optionsList(), QStringList(i18n("Toolbars"))); fields << "ShowMainToolBar" << i18n("Toggle display of main toolbar") << i18n("bool"); new QTreeWidgetItem(opsToolbar, fields); fields.clear(); fields << "ShowViewToolBar" << i18n("Toggle display of view toolbar") << i18n("bool"); new QTreeWidgetItem(opsToolbar, fields); fields.clear(); argChangeViewOption->OptionName->addItem("ShowMainToolBar"); argChangeViewOption->OptionName->addItem("ShowViewToolBar"); //Show Objects opsShowObj = new QTreeWidgetItem(otv->optionsList(), QStringList(i18n("Show Objects"))); fields << "ShowStars" << i18n("Toggle display of Stars") << i18n("bool"); new QTreeWidgetItem(opsShowObj, fields); fields.clear(); fields << "ShowDeepSky" << i18n("Toggle display of all deep-sky objects") << i18n("bool"); new QTreeWidgetItem(opsShowObj, fields); fields.clear(); fields << "ShowMessier" << i18n("Toggle display of Messier object symbols") << i18n("bool"); new QTreeWidgetItem(opsShowObj, fields); fields.clear(); fields << "ShowMessierImages" << i18n("Toggle display of Messier object images") << i18n("bool"); new QTreeWidgetItem(opsShowObj, fields); fields.clear(); fields << "ShowNGC" << i18n("Toggle display of NGC objects") << i18n("bool"); new QTreeWidgetItem(opsShowObj, fields); fields.clear(); fields << "ShowIC" << i18n("Toggle display of IC objects") << i18n("bool"); new QTreeWidgetItem(opsShowObj, fields); fields.clear(); fields << "ShowSolarSystem" << i18n("Toggle display of all solar system bodies") << i18n("bool"); new QTreeWidgetItem(opsShowObj, fields); fields.clear(); fields << "ShowSun" << i18n("Toggle display of Sun") << i18n("bool"); new QTreeWidgetItem(opsShowObj, fields); fields.clear(); fields << "ShowMoon" << i18n("Toggle display of Moon") << i18n("bool"); new QTreeWidgetItem(opsShowObj, fields); fields.clear(); fields << "ShowMercury" << i18n("Toggle display of Mercury") << i18n("bool"); new QTreeWidgetItem(opsShowObj, fields); fields.clear(); fields << "ShowVenus" << i18n("Toggle display of Venus") << i18n("bool"); new QTreeWidgetItem(opsShowObj, fields); fields.clear(); fields << "ShowMars" << i18n("Toggle display of Mars") << i18n("bool"); new QTreeWidgetItem(opsShowObj, fields); fields.clear(); fields << "ShowJupiter" << i18n("Toggle display of Jupiter") << i18n("bool"); new QTreeWidgetItem(opsShowObj, fields); fields.clear(); fields << "ShowSaturn" << i18n("Toggle display of Saturn") << i18n("bool"); new QTreeWidgetItem(opsShowObj, fields); fields.clear(); fields << "ShowUranus" << i18n("Toggle display of Uranus") << i18n("bool"); new QTreeWidgetItem(opsShowObj, fields); fields.clear(); fields << "ShowNeptune" << i18n("Toggle display of Neptune") << i18n("bool"); new QTreeWidgetItem(opsShowObj, fields); //fields.clear(); //fields << "ShowPluto" << i18n( "Toggle display of Pluto" ) << i18n( "bool" ); //new QTreeWidgetItem( opsShowObj, fields ); fields.clear(); fields << "ShowAsteroids" << i18n("Toggle display of Asteroids") << i18n("bool"); new QTreeWidgetItem(opsShowObj, fields); fields.clear(); fields << "ShowComets" << i18n("Toggle display of Comets") << i18n("bool"); new QTreeWidgetItem(opsShowObj, fields); fields.clear(); argChangeViewOption->OptionName->addItem("ShowStars"); argChangeViewOption->OptionName->addItem("ShowDeepSky"); argChangeViewOption->OptionName->addItem("ShowMessier"); argChangeViewOption->OptionName->addItem("ShowMessierImages"); argChangeViewOption->OptionName->addItem("ShowNGC"); argChangeViewOption->OptionName->addItem("ShowIC"); argChangeViewOption->OptionName->addItem("ShowSolarSystem"); argChangeViewOption->OptionName->addItem("ShowSun"); argChangeViewOption->OptionName->addItem("ShowMoon"); argChangeViewOption->OptionName->addItem("ShowMercury"); argChangeViewOption->OptionName->addItem("ShowVenus"); argChangeViewOption->OptionName->addItem("ShowMars"); argChangeViewOption->OptionName->addItem("ShowJupiter"); argChangeViewOption->OptionName->addItem("ShowSaturn"); argChangeViewOption->OptionName->addItem("ShowUranus"); argChangeViewOption->OptionName->addItem("ShowNeptune"); //argChangeViewOption->OptionName->addItem( "ShowPluto" ); argChangeViewOption->OptionName->addItem("ShowAsteroids"); argChangeViewOption->OptionName->addItem("ShowComets"); opsShowOther = new QTreeWidgetItem(otv->optionsList(), QStringList(i18n("Show Other"))); fields << "ShowCLines" << i18n("Toggle display of constellation lines") << i18n("bool"); new QTreeWidgetItem(opsShowOther, fields); fields.clear(); fields << "ShowCBounds" << i18n("Toggle display of constellation boundaries") << i18n("bool"); new QTreeWidgetItem(opsShowOther, fields); fields.clear(); fields << "ShowCNames" << i18n("Toggle display of constellation names") << i18n("bool"); new QTreeWidgetItem(opsShowOther, fields); fields.clear(); fields << "ShowMilkyWay" << i18n("Toggle display of Milky Way") << i18n("bool"); new QTreeWidgetItem(opsShowOther, fields); fields.clear(); fields << "ShowGrid" << i18n("Toggle display of the coordinate grid") << i18n("bool"); new QTreeWidgetItem(opsShowOther, fields); fields.clear(); fields << "ShowEquator" << i18n("Toggle display of the celestial equator") << i18n("bool"); new QTreeWidgetItem(opsShowOther, fields); fields.clear(); fields << "ShowEcliptic" << i18n("Toggle display of the ecliptic") << i18n("bool"); new QTreeWidgetItem(opsShowOther, fields); fields.clear(); fields << "ShowHorizon" << i18n("Toggle display of the horizon line") << i18n("bool"); new QTreeWidgetItem(opsShowOther, fields); fields.clear(); fields << "ShowGround" << i18n("Toggle display of the opaque ground") << i18n("bool"); new QTreeWidgetItem(opsShowOther, fields); fields.clear(); fields << "ShowStarNames" << i18n("Toggle display of star name labels") << i18n("bool"); new QTreeWidgetItem(opsShowOther, fields); fields.clear(); fields << "ShowStarMagnitudes" << i18n("Toggle display of star magnitude labels") << i18n("bool"); new QTreeWidgetItem(opsShowOther, fields); fields.clear(); fields << "ShowAsteroidNames" << i18n("Toggle display of asteroid name labels") << i18n("bool"); new QTreeWidgetItem(opsShowOther, fields); fields.clear(); fields << "ShowCometNames" << i18n("Toggle display of comet name labels") << i18n("bool"); new QTreeWidgetItem(opsShowOther, fields); fields.clear(); fields << "ShowPlanetNames" << i18n("Toggle display of planet name labels") << i18n("bool"); new QTreeWidgetItem(opsShowOther, fields); fields.clear(); fields << "ShowPlanetImages" << i18n("Toggle display of planet images") << i18n("bool"); new QTreeWidgetItem(opsShowOther, fields); fields.clear(); argChangeViewOption->OptionName->addItem("ShowCLines"); argChangeViewOption->OptionName->addItem("ShowCBounds"); argChangeViewOption->OptionName->addItem("ShowCNames"); argChangeViewOption->OptionName->addItem("ShowMilkyWay"); argChangeViewOption->OptionName->addItem("ShowGrid"); argChangeViewOption->OptionName->addItem("ShowEquator"); argChangeViewOption->OptionName->addItem("ShowEcliptic"); argChangeViewOption->OptionName->addItem("ShowHorizon"); argChangeViewOption->OptionName->addItem("ShowGround"); argChangeViewOption->OptionName->addItem("ShowStarNames"); argChangeViewOption->OptionName->addItem("ShowStarMagnitudes"); argChangeViewOption->OptionName->addItem("ShowAsteroidNames"); argChangeViewOption->OptionName->addItem("ShowCometNames"); argChangeViewOption->OptionName->addItem("ShowPlanetNames"); argChangeViewOption->OptionName->addItem("ShowPlanetImages"); opsCName = new QTreeWidgetItem(otv->optionsList(), QStringList(i18n("Constellation Names"))); fields << "UseLatinConstellNames" << i18n("Show Latin constellation names") << i18n("bool"); new QTreeWidgetItem(opsCName, fields); fields.clear(); fields << "UseLocalConstellNames" << i18n("Show constellation names in local language") << i18n("bool"); new QTreeWidgetItem(opsCName, fields); fields.clear(); fields << "UseAbbrevConstellNames" << i18n("Show IAU-standard constellation abbreviations") << i18n("bool"); new QTreeWidgetItem(opsCName, fields); fields.clear(); argChangeViewOption->OptionName->addItem("UseLatinConstellNames"); argChangeViewOption->OptionName->addItem("UseLocalConstellNames"); argChangeViewOption->OptionName->addItem("UseAbbrevConstellNames"); opsHide = new QTreeWidgetItem(otv->optionsList(), QStringList(i18n("Hide Items"))); fields << "HideOnSlew" << i18n("Toggle whether objects hidden while slewing display") << i18n("bool"); new QTreeWidgetItem(opsHide, fields); fields.clear(); fields << "SlewTimeScale" << i18n("Timestep threshold (in seconds) for hiding objects") << i18n("double"); new QTreeWidgetItem(opsHide, fields); fields.clear(); fields << "HideStars" << i18n("Hide faint stars while slewing?") << i18n("bool"); new QTreeWidgetItem(opsHide, fields); fields.clear(); fields << "HidePlanets" << i18n("Hide solar system bodies while slewing?") << i18n("bool"); new QTreeWidgetItem(opsHide, fields); fields.clear(); fields << "HideMessier" << i18n("Hide Messier objects while slewing?") << i18n("bool"); new QTreeWidgetItem(opsHide, fields); fields.clear(); fields << "HideNGC" << i18n("Hide NGC objects while slewing?") << i18n("bool"); new QTreeWidgetItem(opsHide, fields); fields.clear(); fields << "HideIC" << i18n("Hide IC objects while slewing?") << i18n("bool"); new QTreeWidgetItem(opsHide, fields); fields.clear(); fields << "HideMilkyWay" << i18n("Hide Milky Way while slewing?") << i18n("bool"); new QTreeWidgetItem(opsHide, fields); fields.clear(); fields << "HideCNames" << i18n("Hide constellation names while slewing?") << i18n("bool"); new QTreeWidgetItem(opsHide, fields); fields.clear(); fields << "HideCLines" << i18n("Hide constellation lines while slewing?") << i18n("bool"); new QTreeWidgetItem(opsHide, fields); fields.clear(); fields << "HideCBounds" << i18n("Hide constellation boundaries while slewing?") << i18n("bool"); new QTreeWidgetItem(opsHide, fields); fields.clear(); fields << "HideGrid" << i18n("Hide coordinate grid while slewing?") << i18n("bool"); new QTreeWidgetItem(opsHide, fields); fields.clear(); argChangeViewOption->OptionName->addItem("HideOnSlew"); argChangeViewOption->OptionName->addItem("SlewTimeScale"); argChangeViewOption->OptionName->addItem("HideStars"); argChangeViewOption->OptionName->addItem("HidePlanets"); argChangeViewOption->OptionName->addItem("HideMessier"); argChangeViewOption->OptionName->addItem("HideNGC"); argChangeViewOption->OptionName->addItem("HideIC"); argChangeViewOption->OptionName->addItem("HideMilkyWay"); argChangeViewOption->OptionName->addItem("HideCNames"); argChangeViewOption->OptionName->addItem("HideCLines"); argChangeViewOption->OptionName->addItem("HideCBounds"); argChangeViewOption->OptionName->addItem("HideGrid"); opsSkymap = new QTreeWidgetItem(otv->optionsList(), QStringList(i18n("Skymap Options"))); fields << "UseAltAz" << i18n("Use Horizontal coordinates? (otherwise, use Equatorial)") << i18n("bool"); new QTreeWidgetItem(opsSkymap, fields); fields.clear(); fields << "ZoomFactor" << i18n("Set the Zoom Factor") << i18n("double"); new QTreeWidgetItem(opsSkymap, fields); fields.clear(); fields << "FOVName" << i18n("Select angular size for the FOV symbol (in arcmin)") << i18n("double"); new QTreeWidgetItem(opsSkymap, fields); fields.clear(); fields << "FOVShape" << i18n("Select shape for the FOV symbol (0=Square, 1=Circle, 2=Crosshairs, 4=Bullseye)") << i18n("int"); new QTreeWidgetItem(opsSkymap, fields); fields.clear(); fields << "FOVColor" << i18n("Select color for the FOV symbol") << i18n("string"); new QTreeWidgetItem(opsSkymap, fields); fields.clear(); fields << "UseAnimatedSlewing" << i18n("Use animated slewing? (otherwise, \"snap\" to new focus)") << i18n("bool"); new QTreeWidgetItem(opsSkymap, fields); fields.clear(); fields << "UseRefraction" << i18n("Correct for atmospheric refraction?") << i18n("bool"); new QTreeWidgetItem(opsSkymap, fields); fields.clear(); fields << "UseAutoLabel" << i18n("Automatically attach name label to centered object?") << i18n("bool"); new QTreeWidgetItem(opsSkymap, fields); fields.clear(); fields << "UseHoverLabel" << i18n("Attach temporary name label when hovering mouse over an object?") << i18n("bool"); new QTreeWidgetItem(opsSkymap, fields); fields.clear(); fields << "UseAutoTrail" << i18n("Automatically add trail to centered solar system body?") << i18n("bool"); new QTreeWidgetItem(opsSkymap, fields); fields.clear(); fields << "FadePlanetTrails" << i18n("Planet trails fade to sky color? (otherwise color is constant)") << i18n("bool"); new QTreeWidgetItem(opsSkymap, fields); fields.clear(); argChangeViewOption->OptionName->addItem("UseAltAz"); argChangeViewOption->OptionName->addItem("ZoomFactor"); argChangeViewOption->OptionName->addItem("FOVName"); argChangeViewOption->OptionName->addItem("FOVSize"); argChangeViewOption->OptionName->addItem("FOVShape"); argChangeViewOption->OptionName->addItem("FOVColor"); argChangeViewOption->OptionName->addItem("UseRefraction"); argChangeViewOption->OptionName->addItem("UseAutoLabel"); argChangeViewOption->OptionName->addItem("UseHoverLabel"); argChangeViewOption->OptionName->addItem("UseAutoTrail"); argChangeViewOption->OptionName->addItem("AnimateSlewing"); argChangeViewOption->OptionName->addItem("FadePlanetTrails"); opsLimit = new QTreeWidgetItem(otv->optionsList(), QStringList(i18n("Limits"))); /* fields << "magLimitDrawStar" << i18n( "magnitude of faintest star drawn on map when zoomed in" ) << i18n( "double" ); new QTreeWidgetItem( opsLimit, fields ); fields.clear(); fields << "magLimitDrawStarZoomOut" << i18n( "magnitude of faintest star drawn on map when zoomed out" ) << i18n( "double" ); new QTreeWidgetItem( opsLimit, fields ); fields.clear(); */ // TODO: We have disabled the following two features. Enable them when feasible... /* fields << "magLimitDrawDeepSky" << i18n( "magnitude of faintest nonstellar object drawn on map when zoomed in" ) << i18n( "double" ); new QTreeWidgetItem( opsLimit, fields ); fields.clear(); fields << "magLimitDrawDeepSkyZoomOut" << i18n( "magnitude of faintest nonstellar object drawn on map when zoomed out" ) << i18n( "double" ); new QTreeWidgetItem( opsLimit, fields ); fields.clear(); */ //FIXME: This description is incorrect! Fix after strings freeze fields << "starLabelDensity" << i18n("magnitude of faintest star labeled on map") << i18n("double"); new QTreeWidgetItem(opsLimit, fields); fields.clear(); fields << "magLimitHideStar" << i18n("magnitude of brightest star hidden while slewing") << i18n("double"); new QTreeWidgetItem(opsLimit, fields); fields.clear(); fields << "magLimitAsteroid" << i18n("magnitude of faintest asteroid drawn on map") << i18n("double"); new QTreeWidgetItem(opsLimit, fields); fields.clear(); //FIXME: This description is incorrect! Fix after strings freeze fields << "asteroidLabelDensity" << i18n("magnitude of faintest asteroid labeled on map") << i18n("double"); new QTreeWidgetItem(opsLimit, fields); fields.clear(); fields << "maxRadCometName" << i18n("comets nearer to the Sun than this (in AU) are labeled on map") << i18n("double"); new QTreeWidgetItem(opsLimit, fields); fields.clear(); // argChangeViewOption->OptionName->addItem( "magLimitDrawStar" ); // argChangeViewOption->OptionName->addItem( "magLimitDrawStarZoomOut" ); argChangeViewOption->OptionName->addItem("magLimitDrawDeepSky"); argChangeViewOption->OptionName->addItem("magLimitDrawDeepSkyZoomOut"); argChangeViewOption->OptionName->addItem("starLabelDensity"); argChangeViewOption->OptionName->addItem("magLimitHideStar"); argChangeViewOption->OptionName->addItem("magLimitAsteroid"); argChangeViewOption->OptionName->addItem("asteroidLabelDensity"); argChangeViewOption->OptionName->addItem("maxRadCometName"); //init the list of color names and values for (unsigned int i = 0; i < KStarsData::Instance()->colorScheme()->numberOfColors(); ++i) { argSetColor->ColorName->addItem(KStarsData::Instance()->colorScheme()->nameAt(i)); } //init list of color scheme names argLoadColorScheme->SchemeList->addItem(i18nc("use default color scheme", "Default Colors")); argLoadColorScheme->SchemeList->addItem(i18nc("use 'star chart' color scheme", "Star Chart")); argLoadColorScheme->SchemeList->addItem(i18nc("use 'night vision' color scheme", "Night Vision")); argLoadColorScheme->SchemeList->addItem(i18nc("use 'moonless night' color scheme", "Moonless Night")); QFile file; QString line; file.setFileName(KSPaths::locate(QStandardPaths::GenericDataLocation, "colors.dat")); //determine filename in local user KDE directory tree. if (file.open(QIODevice::ReadOnly)) { QTextStream stream(&file); while (!stream.atEnd()) { line = stream.readLine(); argLoadColorScheme->SchemeList->addItem(line.left(line.indexOf(':'))); } file.close(); } } //Slots defined in ScriptBuilderUI void ScriptBuilder::slotNew() { saveWarning(); if (!UnsavedChanges) { ScriptList.clear(); sb->ScriptListBox->clear(); sb->ArgStack->setCurrentWidget(argBlank); sb->CopyButton->setEnabled(false); sb->RemoveButton->setEnabled(false); sb->RunButton->setEnabled(false); sb->SaveAsButton->setEnabled(false); currentFileURL.clear(); currentScriptName.clear(); } } void ScriptBuilder::slotOpen() { saveWarning(); QString fname; QTemporaryFile tmpfile; tmpfile.open(); if (!UnsavedChanges) { currentFileURL = QFileDialog::getOpenFileUrl( KStars::Instance(), QString(), QUrl(currentDir), "*.kstars|" + i18nc("Filter by file type: KStars Scripts.", "KStars Scripts (*.kstars)")); if (currentFileURL.isValid()) { currentDir = currentFileURL.toLocalFile(); ScriptList.clear(); sb->ScriptListBox->clear(); sb->ArgStack->setCurrentWidget(argBlank); if (currentFileURL.isLocalFile()) { fname = currentFileURL.toLocalFile(); } else { fname = tmpfile.fileName(); if (KIO::copy(currentFileURL, QUrl(fname))->exec() == false) //if ( ! KIO::NetAccess::download( currentFileURL, fname, (QWidget*) 0 ) ) KSNotification::sorry(i18n("Could not download remote file."), i18n("Download Error")); } QFile f(fname); if (!f.open(QIODevice::ReadOnly)) { KSNotification::sorry(i18n("Could not open file %1.", f.fileName()), i18n("Could Not Open File")); currentFileURL.clear(); return; } QTextStream istream(&f); readScript(istream); f.close(); } else if (!currentFileURL.url().isEmpty()) { KSNotification::sorry(i18n("Invalid URL: %1", currentFileURL.url()), i18n("Invalid URL")); currentFileURL.clear(); } } } void ScriptBuilder::slotSave() { QString fname; QTemporaryFile tmpfile; tmpfile.open(); if (currentScriptName.isEmpty()) { //Get Script Name and Author info if (snd->exec() == QDialog::Accepted) { currentScriptName = snd->scriptName(); currentAuthor = snd->authorName(); } else { return; } } bool newFilename = false; if (currentFileURL.isEmpty()) { currentFileURL = QFileDialog::getSaveFileUrl( KStars::Instance(), QString(), QUrl(currentDir), "*.kstars|" + i18nc("Filter by file type: KStars Scripts.", "KStars Scripts (*.kstars)")); newFilename = true; } if (currentFileURL.isValid()) { currentDir = currentFileURL.toLocalFile(); if (currentFileURL.isLocalFile()) { fname = currentFileURL.toLocalFile(); //Warn user if file exists if (newFilename == true && QFile::exists(currentFileURL.toLocalFile())) { int r = KMessageBox::warningContinueCancel(static_cast(parent()), i18n("A file named \"%1\" already exists. " "Overwrite it?", currentFileURL.fileName()), i18n("Overwrite File?"), KStandardGuiItem::overwrite()); if (r == KMessageBox::Cancel) return; } } else { fname = tmpfile.fileName(); } if (fname.right(7).toLower() != ".kstars") fname += ".kstars"; QFile f(fname); if (!f.open(QIODevice::WriteOnly)) { QString message = i18n("Could not open file %1.", f.fileName()); KSNotification::sorry(message, i18n("Could Not Open File")); currentFileURL.clear(); return; } QTextStream ostream(&f); writeScript(ostream); f.close(); #ifndef _WIN32 //set rwx for owner, rx for group, rx for other chmod(fname.toLatin1(), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); #endif if (tmpfile.fileName() == fname) { //need to upload to remote location //if ( ! KIO::NetAccess::upload( tmpfile.fileName(), currentFileURL, (QWidget*) 0 ) ) if (KIO::storedHttpPost(&tmpfile, currentFileURL)->exec() == false) { QString message = i18n("Could not upload image to remote location: %1", currentFileURL.url()); KSNotification::sorry(message, i18n("Could not upload file")); } } setUnsavedChanges(false); } else { QString message = i18n("Invalid URL: %1", currentFileURL.url()); KSNotification::sorry(message, i18n("Invalid URL")); currentFileURL.clear(); } } void ScriptBuilder::slotSaveAs() { currentFileURL.clear(); currentScriptName.clear(); slotSave(); } void ScriptBuilder::saveWarning() { if (UnsavedChanges) { QString caption = i18n("Save Changes to Script?"); QString message = i18n("The current script has unsaved changes. Would you like to save before closing it?"); int ans = KMessageBox::warningYesNoCancel(nullptr, message, caption, KStandardGuiItem::save(), KStandardGuiItem::discard()); if (ans == KMessageBox::Yes) { slotSave(); setUnsavedChanges(false); } else if (ans == KMessageBox::No) { setUnsavedChanges(false); } //Do nothing if 'cancel' selected } } void ScriptBuilder::slotRunScript() { //hide window while script runs // If this is uncommented, the program hangs before the script is executed. Why? // hide(); //Save current script to a temporary file, then execute that file. //For some reason, I can't use KTempFile here! If I do, then the temporary script //is not executable. Bizarre... //KTempFile tmpfile; //QString fname = tmpfile.name(); QString fname = QDir::tempPath() + QDir::separator() + "kstars-tempscript"; QFile f(fname); if (f.exists()) f.remove(); if (!f.open(QIODevice::WriteOnly)) { QString message = i18n("Could not open file %1.", f.fileName()); KSNotification::sorry(message, i18n("Could Not Open File")); currentFileURL.clear(); return; } QTextStream ostream(&f); writeScript(ostream); f.close(); #ifndef _WIN32 //set rwx for owner, rx for group, rx for other chmod(QFile::encodeName(f.fileName()), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); #endif QProcess p; #ifdef Q_OS_OSX QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); QString path = env.value("PATH", ""); env.insert("PATH", "/usr/local/bin:" + QCoreApplication::applicationDirPath() + ':' + path); p.setProcessEnvironment(env); #endif p.start(f.fileName()); if (!p.waitForStarted()) qDebug() << "Process did not start."; while (!p.waitForFinished(10)) { qApp->processEvents(); //otherwise tempfile may get deleted before script completes. if (p.state() != QProcess::Running) break; } //delete temp file if (f.exists()) f.remove(); //uncomment if 'hide()' is uncommented... // show(); } /* - This can't work anymore and is also not protable in any way :( + This can't work anymore and is also not portable in any way :( */ void ScriptBuilder::writeScript(QTextStream &ostream) { // FIXME Without --print-reply, the dbus-send doesn't do anything, why?? QString dbus_call = "dbus-send --dest=org.kde.kstars --print-reply "; QString main_method = "/KStars org.kde.kstars."; QString clock_method = "/KStars/SimClock org.kde.kstars.SimClock."; //Write script header ostream << "#!/bin/bash\n"; ostream << "#KStars DBus script: " << currentScriptName << '\n'; ostream << "#by " << currentAuthor << '\n'; ostream << "#last modified: " << KStarsDateTime::currentDateTime().toString(Qt::ISODate) << '\n'; ostream << "#\n"; foreach (ScriptFunction *sf, ScriptList) { if (!sf->valid()) continue; if (sf->isClockFunction()) { ostream << dbus_call << clock_method << sf->scriptLine() << '\n'; } else { ostream << dbus_call << main_method << sf->scriptLine() << '\n'; } } //Write script footer ostream << "##\n"; ostream.flush(); } void ScriptBuilder::readScript(QTextStream &istream) { QString line; QString service_name = "org.kde.kstars."; QString fn_name; while (!istream.atEnd()) { line = istream.readLine(); //look for name of script if (line.contains("#KStars DBus script: ")) currentScriptName = line.mid(21).trimmed(); //look for author of scriptbuilder if (line.contains("#by ")) currentAuthor = line.mid(4).trimmed(); //Actual script functions if (line.left(4) == "dbus") { //is ClockFunction? if (line.contains("SimClock")) { service_name += "SimClock."; } //remove leading dbus prefix line = line.mid(line.lastIndexOf(service_name) + service_name.count()); fn_name = line.left(line.indexOf(' ')); line = line.mid(line.indexOf(' ') + 1); //construct a stringlist that is fcn name and its arg name/value pairs QStringList fn; // If the function lacks any arguments, do not attempt to split if (fn_name != line) fn = line.split(' '); if (parseFunction(fn_name, fn)) { sb->ScriptListBox->addItem(ScriptList.last()->name()); // Initially, any read script is valid! ScriptList.last()->setValid(true); } else qWarning() << i18n("Could not parse script. Line was: %1", line); } // end if left(4) == "dcop" } // end while !atEnd() //Select first item in sb->ScriptListBox if (sb->ScriptListBox->count()) { sb->ScriptListBox->setCurrentItem(nullptr); slotArgWidget(); } } bool ScriptBuilder::parseFunction(QString fn_name, QStringList &fn) { // clean up the string list first if needed // We need to perform this in case we havea quoted string "NGC 3000" because this will counted // as two arguments, and it should be counted as one. bool foundQuote(false), quoteProcessed(false); QString cur, arg; QStringList::iterator it; for (it = fn.begin(); it != fn.end(); ++it) { cur = (*it); cur = cur.mid(cur.indexOf(":") + 1).remove('\''); (*it) = cur; if (cur.startsWith('\"')) { arg += cur.rightRef(cur.length() - 1); arg += ' '; foundQuote = true; quoteProcessed = true; } else if (cur.endsWith('\"')) { arg += cur.leftRef(cur.length() - 1); arg += '\''; foundQuote = false; } else if (foundQuote) { arg += cur; arg += ' '; } else { arg += cur; arg += '\''; } } if (quoteProcessed) fn = arg.split('\'', QString::SkipEmptyParts); //loop over known functions to find a name match foreach (ScriptFunction *sf, KStarsFunctionList) { if (fn_name == sf->name()) { if (fn_name == "setGeoLocation") { QString city(fn[0]), prov, cntry(fn[1]); prov.clear(); if (fn.count() == 4) { prov = fn[1]; cntry = fn[2]; } if (fn.count() == 3 || fn.count() == 4) { ScriptList.append(new ScriptFunction(sf)); ScriptList.last()->setArg(0, city); ScriptList.last()->setArg(1, prov); ScriptList.last()->setArg(2, cntry); } else return false; } else if (fn.count() != sf->numArgs()) return false; ScriptList.append(new ScriptFunction(sf)); for (int i = 0; i < sf->numArgs(); ++i) ScriptList.last()->setArg(i, fn[i]); return true; } foreach (ScriptFunction *sf, SimClockFunctionList) { if (fn_name == sf->name()) { if (fn.count() != sf->numArgs()) return false; ScriptList.append(new ScriptFunction(sf)); for (int i = 0; i < sf->numArgs(); ++i) ScriptList.last()->setArg(i, fn[i]); return true; } } } //if we get here, no function-name match was found return false; } void ScriptBuilder::setUnsavedChanges(bool b) { if (checkForChanges) { UnsavedChanges = b; sb->SaveButton->setEnabled(b); } } void ScriptBuilder::slotCopyFunction() { if (!UnsavedChanges) setUnsavedChanges(true); int Pos = sb->ScriptListBox->currentRow() + 1; ScriptList.insert(Pos, new ScriptFunction(ScriptList[Pos - 1])); //copy ArgVals for (int i = 0; i < ScriptList[Pos - 1]->numArgs(); ++i) ScriptList[Pos]->setArg(i, ScriptList[Pos - 1]->argVal(i)); sb->ScriptListBox->insertItem(Pos, ScriptList[Pos]->name()); //sb->ScriptListBox->setSelected( Pos, true ); sb->ScriptListBox->setCurrentRow(Pos); slotArgWidget(); } void ScriptBuilder::slotRemoveFunction() { setUnsavedChanges(true); int Pos = sb->ScriptListBox->currentRow(); ScriptList.removeAt(Pos); sb->ScriptListBox->takeItem(Pos); if (sb->ScriptListBox->count() == 0) { sb->ArgStack->setCurrentWidget(argBlank); sb->CopyButton->setEnabled(false); sb->RemoveButton->setEnabled(false); sb->RunButton->setEnabled(false); sb->SaveAsButton->setEnabled(false); } else { //sb->ScriptListBox->setSelected( Pos, true ); if (Pos == sb->ScriptListBox->count()) { Pos = Pos - 1; } sb->ScriptListBox->setCurrentRow(Pos); } slotArgWidget(); } void ScriptBuilder::slotAddFunction() { ScriptFunction *found = nullptr; QTreeWidgetItem *currentItem = sb->FunctionTree->currentItem(); if (currentItem == nullptr || currentItem->parent() == nullptr) return; for (auto &sc : KStarsFunctionList) { if (sc->prototype() == currentItem->text(0)) { found = sc; break; } } for (auto &sc : SimClockFunctionList) { if (sc->prototype() == currentItem->text(0)) { found = sc; break; } } if (found == nullptr) return; setUnsavedChanges(true); int Pos = sb->ScriptListBox->currentRow() + 1; ScriptList.insert(Pos, new ScriptFunction(found)); sb->ScriptListBox->insertItem(Pos, ScriptList[Pos]->name()); sb->ScriptListBox->setCurrentRow(Pos); slotArgWidget(); } void ScriptBuilder::slotMoveFunctionUp() { if (sb->ScriptListBox->currentRow() > 0) { setUnsavedChanges(true); //QString t = sb->ScriptListBox->currentItem()->text(); QString t = sb->ScriptListBox->currentItem()->text(); unsigned int n = sb->ScriptListBox->currentRow(); ScriptFunction *tmp = ScriptList.takeAt(n); ScriptList.insert(n - 1, tmp); sb->ScriptListBox->takeItem(n); sb->ScriptListBox->insertItem(n - 1, t); sb->ScriptListBox->setCurrentRow(n - 1); slotArgWidget(); } } void ScriptBuilder::slotMoveFunctionDown() { if (sb->ScriptListBox->currentRow() > -1 && sb->ScriptListBox->currentRow() < ((int)sb->ScriptListBox->count()) - 1) { setUnsavedChanges(true); QString t = sb->ScriptListBox->currentItem()->text(); unsigned int n = sb->ScriptListBox->currentRow(); ScriptFunction *tmp = ScriptList.takeAt(n); ScriptList.insert(n + 1, tmp); sb->ScriptListBox->takeItem(n); sb->ScriptListBox->insertItem(n + 1, t); sb->ScriptListBox->setCurrentRow(n + 1); slotArgWidget(); } } void ScriptBuilder::slotArgWidget() { //First, setEnabled on buttons that act on the selected script function if (sb->ScriptListBox->currentRow() == -1) //no selection { sb->CopyButton->setEnabled(false); sb->RemoveButton->setEnabled(false); sb->UpButton->setEnabled(false); sb->DownButton->setEnabled(false); } else if (sb->ScriptListBox->count() == 1) //only one item, so disable up/down buttons { sb->CopyButton->setEnabled(true); sb->RemoveButton->setEnabled(true); sb->UpButton->setEnabled(false); sb->DownButton->setEnabled(false); } else if (sb->ScriptListBox->currentRow() == 0) //first item selected { sb->CopyButton->setEnabled(true); sb->RemoveButton->setEnabled(true); sb->UpButton->setEnabled(false); sb->DownButton->setEnabled(true); } else if (sb->ScriptListBox->currentRow() == ((int)sb->ScriptListBox->count()) - 1) //last item selected { sb->CopyButton->setEnabled(true); sb->RemoveButton->setEnabled(true); sb->UpButton->setEnabled(true); sb->DownButton->setEnabled(false); } else //other item selected { sb->CopyButton->setEnabled(true); sb->RemoveButton->setEnabled(true); sb->UpButton->setEnabled(true); sb->DownButton->setEnabled(true); } //RunButton and SaveAs button enabled when script not empty. if (sb->ScriptListBox->count()) { sb->RunButton->setEnabled(true); sb->SaveAsButton->setEnabled(true); } else { sb->RunButton->setEnabled(false); sb->SaveAsButton->setEnabled(false); setUnsavedChanges(false); } //Display the function's arguments widget if (sb->ScriptListBox->currentRow() > -1 && sb->ScriptListBox->currentRow() < ((int)sb->ScriptListBox->count())) { unsigned int n = sb->ScriptListBox->currentRow(); ScriptFunction *sf = ScriptList.at(n); checkForChanges = false; //Don't signal unsaved changes if (sf->name() == "lookTowards") { sb->ArgStack->setCurrentWidget(argLookToward); QString s = sf->argVal(0); argLookToward->FocusEdit->setEditText(s); } else if (sf->name() == "addLabel" || sf->name() == "removeLabel" || sf->name() == "addTrail" || sf->name() == "removeTrail") { sb->ArgStack->setCurrentWidget(argFindObject); QString s = sf->argVal(0); argFindObject->NameEdit->setText(s); } else if (sf->name() == "setRaDec") { bool ok(false); double r(0.0), d(0.0); dms ra(0.0); sb->ArgStack->setCurrentWidget(argSetRaDec); ok = !sf->argVal(0).isEmpty(); if (ok) r = sf->argVal(0).toDouble(&ok); else argSetRaDec->RABox->clear(); if (ok) { ra.setH(r); argSetRaDec->RABox->showInHours(ra); } ok = !sf->argVal(1).isEmpty(); if (ok) d = sf->argVal(1).toDouble(&ok); else argSetRaDec->DecBox->clear(); if (ok) argSetRaDec->DecBox->showInDegrees(dms(d)); } else if (sf->name() == "setAltAz") { bool ok(false); double x(0.0), y(0.0); sb->ArgStack->setCurrentWidget(argSetAltAz); ok = !sf->argVal(0).isEmpty(); if (ok) y = sf->argVal(0).toDouble(&ok); else argSetAltAz->AzBox->clear(); if (ok) argSetAltAz->AltBox->showInDegrees(dms(y)); else argSetAltAz->AltBox->clear(); ok = !sf->argVal(1).isEmpty(); x = sf->argVal(1).toDouble(&ok); if (ok) argSetAltAz->AzBox->showInDegrees(dms(x)); } else if (sf->name() == "zoomIn") { sb->ArgStack->setCurrentWidget(argBlank); //no Args } else if (sf->name() == "zoomOut") { sb->ArgStack->setCurrentWidget(argBlank); //no Args } else if (sf->name() == "defaultZoom") { sb->ArgStack->setCurrentWidget(argBlank); //no Args } else if (sf->name() == "zoom") { sb->ArgStack->setCurrentWidget(argZoom); bool ok(false); /*double z = */ sf->argVal(0).toDouble(&ok); if (ok) argZoom->ZoomBox->setText(sf->argVal(0)); else argZoom->ZoomBox->setText("2000."); } else if (sf->name() == "exportImage") { sb->ArgStack->setCurrentWidget(argExportImage); argExportImage->ExportFileName->setUrl(QUrl::fromUserInput(sf->argVal(0))); bool ok(false); int w = 0, h = 0; w = sf->argVal(1).toInt(&ok); if (ok) h = sf->argVal(2).toInt(&ok); if (ok) { argExportImage->ExportWidth->setValue(w); argExportImage->ExportHeight->setValue(h); } else { argExportImage->ExportWidth->setValue(SkyMap::Instance()->width()); argExportImage->ExportHeight->setValue(SkyMap::Instance()->height()); } } else if (sf->name() == "printImage") { if (sf->argVal(0) == i18n("true")) argPrintImage->UsePrintDialog->setChecked(true); else argPrintImage->UsePrintDialog->setChecked(false); if (sf->argVal(1) == i18n("true")) argPrintImage->UseChartColors->setChecked(true); else argPrintImage->UseChartColors->setChecked(false); } else if (sf->name() == "setLocalTime") { sb->ArgStack->setCurrentWidget(argSetLocalTime); bool ok(false); int year = 0, month = 0, day = 0, hour = 0, min = 0, sec = 0; year = sf->argVal(0).toInt(&ok); if (ok) month = sf->argVal(1).toInt(&ok); if (ok) day = sf->argVal(2).toInt(&ok); if (ok) argSetLocalTime->DateWidget->setDate(QDate(year, month, day)); else argSetLocalTime->DateWidget->setDate(QDate::currentDate()); hour = sf->argVal(3).toInt(&ok); if (sf->argVal(3).isEmpty()) ok = false; if (ok) min = sf->argVal(4).toInt(&ok); if (ok) sec = sf->argVal(5).toInt(&ok); if (ok) argSetLocalTime->TimeBox->setTime(QTime(hour, min, sec)); else argSetLocalTime->TimeBox->setTime(QTime(QTime::currentTime())); } else if (sf->name() == "waitFor") { sb->ArgStack->setCurrentWidget(argWaitFor); bool ok(false); int sec = sf->argVal(0).toInt(&ok); if (ok) argWaitFor->DelayBox->setValue(sec); else argWaitFor->DelayBox->setValue(0); } else if (sf->name() == "waitForKey") { sb->ArgStack->setCurrentWidget(argWaitForKey); if (sf->argVal(0).length() == 1 || sf->argVal(0).toLower() == "space") argWaitForKey->WaitKeyEdit->setText(sf->argVal(0)); else argWaitForKey->WaitKeyEdit->setText(QString()); } else if (sf->name() == "setTracking") { sb->ArgStack->setCurrentWidget(argSetTracking); if (sf->argVal(0) == i18n("true")) argSetTracking->CheckTrack->setChecked(true); else argSetTracking->CheckTrack->setChecked(false); } else if (sf->name() == "changeViewOption") { sb->ArgStack->setCurrentWidget(argChangeViewOption); argChangeViewOption->OptionName->setCurrentIndex(argChangeViewOption->OptionName->findText(sf->argVal(0))); argChangeViewOption->OptionValue->setText(sf->argVal(1)); } else if (sf->name() == "setGeoLocation") { sb->ArgStack->setCurrentWidget(argSetGeoLocation); argSetGeoLocation->CityName->setText(sf->argVal(0)); argSetGeoLocation->ProvinceName->setText(sf->argVal(1)); argSetGeoLocation->CountryName->setText(sf->argVal(2)); } else if (sf->name() == "setColor") { sb->ArgStack->setCurrentWidget(argSetColor); if (sf->argVal(0).isEmpty()) sf->setArg(0, "SkyColor"); //initialize default value argSetColor->ColorName->setCurrentIndex( argSetColor->ColorName->findText(KStarsData::Instance()->colorScheme()->nameFromKey(sf->argVal(0)))); argSetColor->ColorValue->setColor(QColor(sf->argVal(1).remove('\\'))); } else if (sf->name() == "loadColorScheme") { sb->ArgStack->setCurrentWidget(argLoadColorScheme); argLoadColorScheme->SchemeList->setCurrentItem( argLoadColorScheme->SchemeList->findItems(sf->argVal(0).remove('\"'), Qt::MatchExactly).at(0)); } else if (sf->name() == "stop") { sb->ArgStack->setCurrentWidget(argBlank); //no Args } else if (sf->name() == "start") { sb->ArgStack->setCurrentWidget(argBlank); //no Args } else if (sf->name() == "setClockScale") { sb->ArgStack->setCurrentWidget(argTimeScale); bool ok(false); double ts = sf->argVal(0).toDouble(&ok); if (ok) argTimeScale->TimeScale->tsbox()->changeScale(float(ts)); else argTimeScale->TimeScale->tsbox()->changeScale(0.0); } checkForChanges = true; //signal unsaved changes if the argument widgets are changed } } void ScriptBuilder::slotShowDoc() { ScriptFunction *found = nullptr; QTreeWidgetItem *currentItem = sb->FunctionTree->currentItem(); if (currentItem == nullptr || currentItem->parent() == nullptr) return; for (auto &sc : KStarsFunctionList) { if (sc->prototype() == currentItem->text(0)) { found = sc; break; } } for (auto &sc : SimClockFunctionList) { if (sc->prototype() == currentItem->text(0)) { found = sc; break; } } if (found == nullptr) { sb->AddButton->setEnabled(false); qWarning() << i18n("Function index out of bounds."); return; } sb->AddButton->setEnabled(true); sb->FuncDoc->setHtml(found->description()); } //Slots for Arg Widgets void ScriptBuilder::slotFindCity() { QPointer ld = new LocationDialog(this); if (ld->exec() == QDialog::Accepted) { if (ld->selectedCity()) { // set new location names argSetGeoLocation->CityName->setText(ld->selectedCityName()); if (!ld->selectedProvinceName().isEmpty()) { argSetGeoLocation->ProvinceName->setText(ld->selectedProvinceName()); } else { argSetGeoLocation->ProvinceName->clear(); } argSetGeoLocation->CountryName->setText(ld->selectedCountryName()); ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "setGeoLocation") { setUnsavedChanges(true); sf->setArg(0, ld->selectedCityName()); sf->setArg(1, ld->selectedProvinceName()); sf->setArg(2, ld->selectedCountryName()); } else { warningMismatch("setGeoLocation"); } } } delete ld; } void ScriptBuilder::slotFindObject() { if (FindDialog::Instance()->exec() == QDialog::Accepted && FindDialog::Instance()->targetObject()) { setUnsavedChanges(true); if (sender() == argLookToward->FindButton) argLookToward->FocusEdit->setEditText(FindDialog::Instance()->targetObject()->name()); else argFindObject->NameEdit->setText(FindDialog::Instance()->targetObject()->name()); } } void ScriptBuilder::slotShowOptions() { //Show tree-view of view options if (otv->exec() == QDialog::Accepted) { argChangeViewOption->OptionName->setCurrentIndex( argChangeViewOption->OptionName->findText(otv->optionsList()->currentItem()->text(0))); } } void ScriptBuilder::slotLookToward() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "lookTowards") { setUnsavedChanges(true); sf->setArg(0, argLookToward->FocusEdit->currentText()); sf->setValid(true); } else { warningMismatch("lookTowards"); } } void ScriptBuilder::slotArgFindObject() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "addLabel" || sf->name() == "removeLabel" || sf->name() == "addTrail" || sf->name() == "removeTrail") { setUnsavedChanges(true); sf->setArg(0, argFindObject->NameEdit->text()); sf->setValid(true); } else { warningMismatch(sf->name()); } } void ScriptBuilder::slotRa() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "setRaDec") { //do nothing if box is blank (because we could be clearing boxes while switching argWidgets) if (argSetRaDec->RABox->text().isEmpty()) return; bool ok(false); dms ra = argSetRaDec->RABox->createDms(false, &ok); if (ok) { setUnsavedChanges(true); sf->setArg(0, QString("%1").arg(ra.Hours())); if (!sf->argVal(1).isEmpty()) sf->setValid(true); } else { sf->setArg(0, QString()); sf->setValid(false); } } else { warningMismatch("setRaDec"); } } void ScriptBuilder::slotDec() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "setRaDec") { //do nothing if box is blank (because we could be clearing boxes while switching argWidgets) if (argSetRaDec->DecBox->text().isEmpty()) return; bool ok(false); dms dec = argSetRaDec->DecBox->createDms(true, &ok); if (ok) { setUnsavedChanges(true); sf->setArg(1, QString("%1").arg(dec.Degrees())); if (!sf->argVal(0).isEmpty()) sf->setValid(true); } else { sf->setArg(1, QString()); sf->setValid(false); } } else { warningMismatch("setRaDec"); } } void ScriptBuilder::slotAz() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "setAltAz") { //do nothing if box is blank (because we could be clearing boxes while switching argWidgets) if (argSetAltAz->AzBox->text().isEmpty()) return; bool ok(false); dms az = argSetAltAz->AzBox->createDms(true, &ok); if (ok) { setUnsavedChanges(true); sf->setArg(1, QString("%1").arg(az.Degrees())); if (!sf->argVal(0).isEmpty()) sf->setValid(true); } else { sf->setArg(1, QString()); sf->setValid(false); } } else { warningMismatch("setAltAz"); } } void ScriptBuilder::slotAlt() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "setAltAz") { //do nothing if box is blank (because we could be clearing boxes while switching argWidgets) if (argSetAltAz->AltBox->text().isEmpty()) return; bool ok(false); dms alt = argSetAltAz->AltBox->createDms(true, &ok); if (ok) { setUnsavedChanges(true); sf->setArg(0, QString("%1").arg(alt.Degrees())); if (!sf->argVal(1).isEmpty()) sf->setValid(true); } else { sf->setArg(0, QString()); sf->setValid(false); } } else { warningMismatch("setAltAz"); } } void ScriptBuilder::slotChangeDate() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "setLocalTime") { setUnsavedChanges(true); QDate date = argSetLocalTime->DateWidget->date(); sf->setArg(0, QString("%1").arg(date.year())); sf->setArg(1, QString("%1").arg(date.month())); sf->setArg(2, QString("%1").arg(date.day())); if (!sf->argVal(3).isEmpty()) sf->setValid(true); } else { warningMismatch("setLocalTime"); } } void ScriptBuilder::slotChangeTime() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "setLocalTime") { setUnsavedChanges(true); QTime time = argSetLocalTime->TimeBox->time(); sf->setArg(3, QString("%1").arg(time.hour())); sf->setArg(4, QString("%1").arg(time.minute())); sf->setArg(5, QString("%1").arg(time.second())); if (!sf->argVal(0).isEmpty()) sf->setValid(true); } else { warningMismatch("setLocalTime"); } } void ScriptBuilder::slotWaitFor() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "waitFor") { bool ok(false); int delay = argWaitFor->DelayBox->text().toInt(&ok); if (ok) { setUnsavedChanges(true); sf->setArg(0, QString("%1").arg(delay)); sf->setValid(true); } else { sf->setValid(false); } } else { warningMismatch("waitFor"); } } void ScriptBuilder::slotWaitForKey() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "waitForKey") { QString sKey = argWaitForKey->WaitKeyEdit->text().trimmed(); //DCOP script can only use single keystrokes; make sure entry is either one character, //or the word 'space' if (sKey.length() == 1 || sKey == "space") { setUnsavedChanges(true); sf->setArg(0, sKey); sf->setValid(true); } else { sf->setValid(false); } } else { warningMismatch("waitForKey"); } } void ScriptBuilder::slotTracking() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "setTracking") { setUnsavedChanges(true); sf->setArg(0, (argSetTracking->CheckTrack->isChecked() ? i18n("true") : i18n("false"))); sf->setValid(true); } else { warningMismatch("setTracking"); } } void ScriptBuilder::slotViewOption() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "changeViewOption") { if (argChangeViewOption->OptionName->currentIndex() >= 0 && argChangeViewOption->OptionValue->text().length()) { setUnsavedChanges(true); sf->setArg(0, argChangeViewOption->OptionName->currentText()); sf->setArg(1, argChangeViewOption->OptionValue->text()); sf->setValid(true); } else { sf->setValid(false); } } else { warningMismatch("changeViewOption"); } } void ScriptBuilder::slotChangeCity() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "setGeoLocation") { QString city = argSetGeoLocation->CityName->text(); if (city.length()) { setUnsavedChanges(true); sf->setArg(0, city); if (sf->argVal(2).length()) sf->setValid(true); } else { sf->setArg(0, QString()); sf->setValid(false); } } else { warningMismatch("setGeoLocation"); } } void ScriptBuilder::slotChangeProvince() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "setGeoLocation") { QString province = argSetGeoLocation->ProvinceName->text(); if (province.length()) { setUnsavedChanges(true); sf->setArg(1, province); if (sf->argVal(0).length() && sf->argVal(2).length()) sf->setValid(true); } else { sf->setArg(1, QString()); //might not be invalid } } else { warningMismatch("setGeoLocation"); } } void ScriptBuilder::slotChangeCountry() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "setGeoLocation") { QString country = argSetGeoLocation->CountryName->text(); if (country.length()) { setUnsavedChanges(true); sf->setArg(2, country); if (sf->argVal(0).length()) sf->setValid(true); } else { sf->setArg(2, QString()); sf->setValid(false); } } else { warningMismatch("setGeoLocation"); } } void ScriptBuilder::slotTimeScale() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "setClockScale") { setUnsavedChanges(true); sf->setArg(0, QString("%1").arg(argTimeScale->TimeScale->tsbox()->timeScale())); sf->setValid(true); } else { warningMismatch("setClockScale"); } } void ScriptBuilder::slotZoom() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "zoom") { setUnsavedChanges(true); bool ok(false); argZoom->ZoomBox->text().toDouble(&ok); if (ok) { sf->setArg(0, argZoom->ZoomBox->text()); sf->setValid(true); } } else { warningMismatch("zoom"); } } void ScriptBuilder::slotExportImage() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "exportImage") { setUnsavedChanges(true); sf->setArg(0, argExportImage->ExportFileName->url().url()); sf->setArg(1, QString("%1").arg(argExportImage->ExportWidth->value())); sf->setArg(2, QString("%1").arg(argExportImage->ExportHeight->value())); sf->setValid(true); } else { warningMismatch("exportImage"); } } void ScriptBuilder::slotPrintImage() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "printImage") { setUnsavedChanges(true); sf->setArg(0, (argPrintImage->UsePrintDialog->isChecked() ? i18n("true") : i18n("false"))); sf->setArg(1, (argPrintImage->UseChartColors->isChecked() ? i18n("true") : i18n("false"))); sf->setValid(true); } else { warningMismatch("exportImage"); } } void ScriptBuilder::slotChangeColorName() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "setColor") { setUnsavedChanges(true); argSetColor->ColorValue->setColor(KStarsData::Instance()->colorScheme()->colorAt(argSetColor->ColorName->currentIndex())); sf->setArg(0, KStarsData::Instance()->colorScheme()->keyAt(argSetColor->ColorName->currentIndex())); QString cname(argSetColor->ColorValue->color().name()); //if ( cname.at(0) == '#' ) cname = "\\" + cname; //prepend a "\" so bash doesn't think we have a comment sf->setArg(1, cname); sf->setValid(true); } else { warningMismatch("setColor"); } } void ScriptBuilder::slotChangeColor() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "setColor") { setUnsavedChanges(true); sf->setArg(0, KStarsData::Instance()->colorScheme()->keyAt(argSetColor->ColorName->currentIndex())); QString cname(argSetColor->ColorValue->color().name()); //if ( cname.at(0) == '#' ) cname = "\\" + cname; //prepend a "\" so bash doesn't think we have a comment sf->setArg(1, cname); sf->setValid(true); } else { warningMismatch("setColor"); } } void ScriptBuilder::slotLoadColorScheme() { ScriptFunction *sf = ScriptList[sb->ScriptListBox->currentRow()]; if (sf->name() == "loadColorScheme") { setUnsavedChanges(true); sf->setArg(0, '\"' + argLoadColorScheme->SchemeList->currentItem()->text() + '\"'); sf->setValid(true); } else { warningMismatch("loadColorScheme"); } } void ScriptBuilder::slotClose() { saveWarning(); if (!UnsavedChanges) { ScriptList.clear(); sb->ScriptListBox->clear(); sb->ArgStack->setCurrentWidget(argBlank); close(); } } //TODO JM: INDI Scripting to be included in KDE 4.1 #if 0 void ScriptBuilder::slotINDIStartDeviceName() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "startINDI" ) { setUnsavedChanges( true ); sf->setArg(0, argStartINDI->deviceName->text()); sf->setArg(1, argStartINDI->LocalButton->isChecked() ? "true" : "false"); sf->setValid(true); } else { warningMismatch( "startINDI" ); } } void ScriptBuilder::slotINDIStartDeviceMode() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "startINDI" ) { setUnsavedChanges( true ); sf->setArg(1, argStartINDI->LocalButton->isChecked() ? "true" : "false"); sf->setValid(true); } else { warningMismatch( "startINDI" ); } } void ScriptBuilder::slotINDISetDevice() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "setINDIDevice" ) { setUnsavedChanges( true ); sf->setArg(0, argSetDeviceINDI->deviceName->text()); sf->setValid(true); } else { warningMismatch( "startINDI" ); } } void ScriptBuilder::slotINDIShutdown() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "shutdownINDI" ) { if (argShutdownINDI->deviceName->text().isEmpty()) { sf->setValid(false); return; } if (sf->argVal(0) != argShutdownINDI->deviceName->text()) setUnsavedChanges( true ); sf->setArg(0, argShutdownINDI->deviceName->text()); sf->setValid(true); } else { warningMismatch( "shutdownINDI" ); } } void ScriptBuilder::slotINDISwitchDeviceConnection() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "switchINDI" ) { if (sf->argVal(0) != (argSwitchINDI->OnButton->isChecked() ? "true" : "false")) setUnsavedChanges( true ); sf->setArg(0, argSwitchINDI->OnButton->isChecked() ? "true" : "false"); sf->setValid(true); } else { warningMismatch( "switchINDI" ); } } void ScriptBuilder::slotINDISetPortDevicePort() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "setINDIPort" ) { if (argSetPortINDI->devicePort->text().isEmpty()) { sf->setValid(false); return; } if (sf->argVal(0) != argSetPortINDI->devicePort->text()) setUnsavedChanges( true ); sf->setArg(0, argSetPortINDI->devicePort->text()); sf->setValid(true); } else { warningMismatch( "setINDIPort" ); } } void ScriptBuilder::slotINDISetTargetCoordDeviceRA() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "setINDITargetCoord" ) { //do nothing if box is blank (because we could be clearing boxes while switching argWidgets) if ( argSetTargetCoordINDI->RABox->text().isEmpty() ) { sf->setValid(false); return; } bool ok(false); dms ra = argSetTargetCoordINDI->RABox->createDms(false, &ok); if ( ok ) { if (sf->argVal(0) != QString( "%1" ).arg( ra.Hours() )) setUnsavedChanges( true ); sf->setArg( 0, QString( "%1" ).arg( ra.Hours() ) ); if ( ( ! sf->argVal(1).isEmpty() )) sf->setValid( true ); else sf->setValid(false); } else { sf->setArg( 0, QString() ); sf->setValid( false ); } } else { warningMismatch( "setINDITargetCoord" ); } } void ScriptBuilder::slotINDISetTargetCoordDeviceDEC() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "setINDITargetCoord" ) { //do nothing if box is blank (because we could be clearing boxes while switching argWidgets) if ( argSetTargetCoordINDI->DecBox->text().isEmpty() ) { sf->setValid(false); return; } bool ok(false); dms dec = argSetTargetCoordINDI->DecBox->createDms(true, &ok); if ( ok ) { if (sf->argVal(1) != QString( "%1" ).arg( dec.Degrees() )) setUnsavedChanges( true ); sf->setArg( 1, QString( "%1" ).arg( dec.Degrees() ) ); if ( ( ! sf->argVal(0).isEmpty() )) sf->setValid( true ); else sf->setValid(false); } else { sf->setArg( 1, QString() ); sf->setValid( false ); } } else { warningMismatch( "setINDITargetCoord" ); } } void ScriptBuilder::slotINDISetTargetNameTargetName() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "setINDITargetName" ) { if (argSetTargetNameINDI->targetName->text().isEmpty()) { sf->setValid(false); return; } if (sf->argVal(0) != argSetTargetNameINDI->targetName->text()) setUnsavedChanges( true ); sf->setArg(0, argSetTargetNameINDI->targetName->text()); sf->setValid(true); } else { warningMismatch( "setINDITargetName" ); } } void ScriptBuilder::slotINDISetActionName() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "setINDIAction" ) { if (argSetActionINDI->actionName->text().isEmpty()) { sf->setValid(false); return; } if (sf->argVal(0) != argSetActionINDI->actionName->text()) setUnsavedChanges( true ); sf->setArg(0, argSetActionINDI->actionName->text()); sf->setValid(true); } else { warningMismatch( "setINDIAction" ); } } void ScriptBuilder::slotINDIWaitForActionName() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "waitForINDIAction" ) { if (argWaitForActionINDI->actionName->text().isEmpty()) { sf->setValid(false); return; } if (sf->argVal(0) != argWaitForActionINDI->actionName->text()) setUnsavedChanges( true ); sf->setArg(0, argWaitForActionINDI->actionName->text()); sf->setValid(true); } else { warningMismatch( "waitForINDIAction" ); } } void ScriptBuilder::slotINDISetFocusSpeed() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "setINDIFocusSpeed" ) { if (sf->argVal(0).toInt() != argSetFocusSpeedINDI->speedIN->value()) setUnsavedChanges( true ); sf->setArg(0, QString("%1").arg(argSetFocusSpeedINDI->speedIN->value())); sf->setValid(true); } else { warningMismatch( "setINDIFocusSpeed" ); } } void ScriptBuilder::slotINDIStartFocusDirection() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "startINDIFocus" ) { if (sf->argVal(0) != argStartFocusINDI->directionCombo->currentText()) setUnsavedChanges( true ); sf->setArg(0, argStartFocusINDI->directionCombo->currentText()); sf->setValid(true); } else { warningMismatch( "startINDIFocus" ); } } void ScriptBuilder::slotINDISetFocusTimeout() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "setINDIFocusTimeout" ) { if (sf->argVal(0).toInt() != argSetFocusTimeoutINDI->timeOut->value()) setUnsavedChanges( true ); sf->setArg(0, QString("%1").arg(argSetFocusTimeoutINDI->timeOut->value())); sf->setValid(true); } else { warningMismatch( "setINDIFocusTimeout" ); } } void ScriptBuilder::slotINDISetGeoLocationDeviceLong() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "setINDIGeoLocation" ) { //do nothing if box is blank (because we could be clearing boxes while switching argWidgets) if ( argSetGeoLocationINDI->longBox->text().isEmpty()) { sf->setValid(false); return; } bool ok(false); dms longitude = argSetGeoLocationINDI->longBox->createDms(true, &ok); if ( ok ) { if (sf->argVal(0) != QString( "%1" ).arg( longitude.Degrees())) setUnsavedChanges( true ); sf->setArg( 0, QString( "%1" ).arg( longitude.Degrees() ) ); if ( ! sf->argVal(1).isEmpty() ) sf->setValid( true ); else sf->setValid(false); } else { sf->setArg( 0, QString() ); sf->setValid( false ); } } else { warningMismatch( "setINDIGeoLocation" ); } } void ScriptBuilder::slotINDISetGeoLocationDeviceLat() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "setINDIGeoLocation" ) { //do nothing if box is blank (because we could be clearing boxes while switching argWidgets) if ( argSetGeoLocationINDI->latBox->text().isEmpty() ) { sf->setValid(false); return; } bool ok(false); dms latitude = argSetGeoLocationINDI->latBox->createDms(true, &ok); if ( ok ) { if (sf->argVal(1) != QString( "%1" ).arg( latitude.Degrees())) setUnsavedChanges( true ); sf->setArg( 1, QString( "%1" ).arg( latitude.Degrees() ) ); if ( ! sf->argVal(0).isEmpty() ) sf->setValid( true ); else sf->setValid(false); } else { sf->setArg( 1, QString() ); sf->setValid( false ); } } else { warningMismatch( "setINDIGeoLocation" ); } } void ScriptBuilder::slotINDIStartExposureTimeout() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "startINDIExposure" ) { if (sf->argVal(0).toInt() != argStartExposureINDI->timeOut->value()) setUnsavedChanges( true ); sf->setArg(0, QString("%1").arg(argStartExposureINDI->timeOut->value())); sf->setValid(true); } else { warningMismatch( "startINDIExposure" ); } } void ScriptBuilder::slotINDISetUTC() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "setINDIUTC" ) { if (argSetUTCINDI->UTC->text().isEmpty()) { sf->setValid(false); return; } if (sf->argVal(0) != argSetUTCINDI->UTC->text()) setUnsavedChanges( true ); sf->setArg(0, argSetUTCINDI->UTC->text()); sf->setValid(true); } else { warningMismatch( "setINDIUTC" ); } } void ScriptBuilder::slotINDISetScopeAction() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "setINDIScopeAction" ) { if (sf->argVal(0) != argSetScopeActionINDI->actionCombo->currentText()) setUnsavedChanges( true ); sf->setArg(0, argSetScopeActionINDI->actionCombo->currentText()); sf->setINDIProperty("CHECK"); sf->setValid(true); } else { warningMismatch( "setINDIScopeAction" ); } } void ScriptBuilder::slotINDISetFrameType() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "setINDIFrameType" ) { if (sf->argVal(0) != argSetFrameTypeINDI->typeCombo->currentText()) setUnsavedChanges( true ); sf->setArg(0, argSetFrameTypeINDI->typeCombo->currentText()); sf->setValid(true); } else { warningMismatch( "setINDIFrameType" ); } } void ScriptBuilder::slotINDISetCCDTemp() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "setINDICCDTemp" ) { if (sf->argVal(0).toInt() != argSetCCDTempINDI->temp->value()) setUnsavedChanges( true ); sf->setArg(0, QString("%1").arg(argSetCCDTempINDI->temp->value())); sf->setValid(true); } else { warningMismatch( "setINDICCDTemp" ); } } void ScriptBuilder::slotINDISetFilterNum() { ScriptFunction * sf = ScriptList[ sb->ScriptListBox->currentRow() ]; if ( sf->name() == "setINDIFilterNum" ) { if (sf->argVal(0).toInt() != argSetFilterNumINDI->filter_num->value()) setUnsavedChanges( true ); sf->setArg(0, QString("%1").arg(argSetFilterNumINDI->filter_num->value())); sf->setValid(true); } else { warningMismatch( "setINDIFilterNum" ); } } #endif void ScriptBuilder::warningMismatch(const QString &expected) const { qWarning() << i18n("Mismatch between function and Arg widget (expected %1.)", QString(expected)); } diff --git a/kstars/tools/whatsinteresting/obsconditions.h b/kstars/tools/whatsinteresting/obsconditions.h index 6802dc1fa..7fee1c58c 100644 --- a/kstars/tools/whatsinteresting/obsconditions.h +++ b/kstars/tools/whatsinteresting/obsconditions.h @@ -1,130 +1,130 @@ /*************************************************************************** obsconditions.h - K Desktop Planetarium ------------------- begin : 2012/10/07 copyright : (C) 2012 by Samikshan Bairagya email : samikshan@gmail.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. * * * ***************************************************************************/ #pragma once #include "kstarsdata.h" /** * @class ObsConditions * * This class deals with the observing conditions of the night sky. - * The limiting magntude is calculated depending on the equipment + * The limiting magnitude is calculated depending on the equipment * available to the user and the amount of light-pollution in the * user's current observing location. * * @author Samikshan Bairagya */ class ObsConditions { public: /** * @enum Equipment * * Equipment available to the user. */ enum Equipment { Telescope, Binoculars, Both, None }; /** * @enum TelescopeType * * Telescope Type (Reflector/Refractor) */ enum TelescopeType { Reflector = 0, Refractor, Invalid }; /** * @brief Constructor * * @param bortle Rating of light pollution based on the bortle dark-sky scale. * @param aperture Aperture of equipment. * @param equip Equipment available to the user. * @param telType Reflector/Refractor type of telescope (if available) */ ObsConditions(int bortle, double aperture, Equipment equip, TelescopeType telType); ~ObsConditions() = default; /** Inline method to set available equipment */ inline void setEquipment(Equipment equip) { m_Equip = equip; } /** Inline method to set reflector/refractor type for telescope. */ inline void setTelescopeType(TelescopeType telType) { m_TelType = telType; } /** Set limiting magnitude depending on Bortle dark-sky rating. */ void setLimMagnitude(); /** Set new observing conditions. */ void setObsConditions(int bortle, double aperture, Equipment equip, TelescopeType telType); /** * @brief Get optimum magnification under current observing conditions. * * @return Get optimum magnification under current observing conditions */ double getOptimumMAG(); /** * @brief Get true limiting magnitude after taking equipment specifications into consideration. * * @return True limiting magnitude after taking equipment specifications into consideration. */ double getTrueMagLim(); /** * @brief Evaluate visibility of sky-object based on current observing conditions. * * @param geo Geographic location of user. * @param lst Local standard time expressed as a dms object. * @param so SkyObject for which visibility is to be evaluated. * @return Visibility of sky-object based on current observing conditions as a boolean. */ bool isVisible(GeoLocation *geo, dms *lst, SkyObject *so); /** * @brief Create QMap to be initialised to static member variable m_LMMap * * @return QMap to be initialised to static member variable m_LMMap */ static QMap setLMMap(); private: /// Bortle dark-sky rating (from 1-9) int m_BortleClass { 0 }; /// Equipment type Equipment m_Equip; /// Telescope type TelescopeType m_TelType; /// Aperture of equipment double m_Aperture { 0 }; /// t-parameter corresponding to telescope type double m_tParam { 0 }; /// Naked-eye limiting magnitude depending on m_BortleClass double m_LM { 0 }; /// Lookup table mapping Bortle Scale values to corresponding limiting magnitudes static const QMap m_LMMap; };