diff --git a/kstars/indi/indiccd.cpp b/kstars/indi/indiccd.cpp index e12f3d0a1..6d43ea53e 100644 --- a/kstars/indi/indiccd.cpp +++ b/kstars/indi/indiccd.cpp @@ -1,2350 +1,2345 @@ /* 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 "indiccd.h" #include "config-kstars.h" #include "indi_debug.h" #include "clientmanager.h" #include "driverinfo.h" #include "guimanager.h" #include "kspaths.h" #include "kstars.h" #include "kstarsdata.h" #include "Options.h" #include "streamwg.h" //#include "ekos/manager.h" #ifdef HAVE_CFITSIO #include "fitsviewer/fitsdata.h" #endif #include #include #include #include #ifdef HAVE_LIBRAW #include #endif const QStringList RAWFormats = { "cr2", "crw", "nef", "raf", "dng", "arw" }; namespace ISD { CCDChip::CCDChip(ISD::CCD *ccd, ChipType cType) { baseDevice = ccd->getBaseDevice(); clientManager = ccd->getDriverInfo()->getClientManager(); parentCCD = ccd; type = cType; } FITSView *CCDChip::getImageView(FITSMode imageType) { switch (imageType) { case FITS_NORMAL: return normalImage; case FITS_FOCUS: return focusImage; case FITS_GUIDE: return guideImage; case FITS_CALIBRATE: return calibrationImage; case FITS_ALIGN: return alignImage; } return nullptr; } void CCDChip::setImageView(FITSView *image, FITSMode imageType) { switch (imageType) { case FITS_NORMAL: normalImage = image; break; case FITS_FOCUS: focusImage = image; break; case FITS_GUIDE: guideImage = image; break; case FITS_CALIBRATE: calibrationImage = image; break; case FITS_ALIGN: alignImage = image; break; } if (image) imageData = image->getImageData(); } bool CCDChip::getFrameMinMax(int *minX, int *maxX, int *minY, int *maxY, int *minW, int *maxW, int *minH, int *maxH) { INumberVectorProperty *frameProp = nullptr; switch (type) { case PRIMARY_CCD: frameProp = baseDevice->getNumber("CCD_FRAME"); break; case GUIDE_CCD: frameProp = baseDevice->getNumber("GUIDER_FRAME"); break; } if (frameProp == nullptr) return false; INumber *arg = IUFindNumber(frameProp, "X"); if (arg == nullptr) return false; if (minX) *minX = arg->min; if (maxX) *maxX = arg->max; arg = IUFindNumber(frameProp, "Y"); if (arg == nullptr) return false; if (minY) *minY = arg->min; if (maxY) *maxY = arg->max; arg = IUFindNumber(frameProp, "WIDTH"); if (arg == nullptr) return false; if (minW) *minW = arg->min; if (maxW) *maxW = arg->max; arg = IUFindNumber(frameProp, "HEIGHT"); if (arg == nullptr) return false; if (minH) *minH = arg->min; if (maxH) *maxH = arg->max; return true; } bool CCDChip::setImageInfo(uint16_t width, uint16_t height, double pixelX, double pixelY, uint8_t bitdepth) { INumberVectorProperty *ccdInfoProp = nullptr; switch (type) { case PRIMARY_CCD: ccdInfoProp = baseDevice->getNumber("CCD_INFO"); break; case GUIDE_CCD: ccdInfoProp = baseDevice->getNumber("GUIDER_INFO"); break; } if (ccdInfoProp == nullptr) return false; ccdInfoProp->np[0].value = width; ccdInfoProp->np[1].value = height; ccdInfoProp->np[2].value = std::hypotf(pixelX, pixelY); ccdInfoProp->np[3].value = pixelX; ccdInfoProp->np[4].value = pixelY; ccdInfoProp->np[5].value = bitdepth; clientManager->sendNewNumber(ccdInfoProp); return true; } bool CCDChip::getPixelSize(double &x, double &y) { INumberVectorProperty *ccdInfoProp = nullptr; switch (type) { case PRIMARY_CCD: ccdInfoProp = baseDevice->getNumber("CCD_INFO"); break; case GUIDE_CCD: ccdInfoProp = baseDevice->getNumber("GUIDER_INFO"); break; } if (ccdInfoProp == nullptr) return false; INumber *pixelX = IUFindNumber(ccdInfoProp, "CCD_PIXEL_SIZE_X"); INumber *pixelY = IUFindNumber(ccdInfoProp, "CCD_PIXEL_SIZE_Y"); if (pixelX == nullptr || pixelY == nullptr) return false; x = pixelX->value; y = pixelY->value; return true; } bool CCDChip::getFrame(int *x, int *y, int *w, int *h) { INumberVectorProperty *frameProp = nullptr; switch (type) { case PRIMARY_CCD: frameProp = baseDevice->getNumber("CCD_FRAME"); break; case GUIDE_CCD: frameProp = baseDevice->getNumber("GUIDER_FRAME"); break; } if (frameProp == nullptr) return false; INumber *arg = IUFindNumber(frameProp, "X"); if (arg == nullptr) return false; *x = arg->value; arg = IUFindNumber(frameProp, "Y"); if (arg == nullptr) return false; *y = arg->value; arg = IUFindNumber(frameProp, "WIDTH"); if (arg == nullptr) return false; *w = arg->value; arg = IUFindNumber(frameProp, "HEIGHT"); if (arg == nullptr) return false; *h = arg->value; return true; } bool CCDChip::resetFrame() { INumberVectorProperty *frameProp = nullptr; switch (type) { case PRIMARY_CCD: frameProp = baseDevice->getNumber("CCD_FRAME"); break; case GUIDE_CCD: frameProp = baseDevice->getNumber("GUIDER_FRAME"); break; } if (frameProp == nullptr) return false; INumber *xarg = IUFindNumber(frameProp, "X"); INumber *yarg = IUFindNumber(frameProp, "Y"); INumber *warg = IUFindNumber(frameProp, "WIDTH"); INumber *harg = IUFindNumber(frameProp, "HEIGHT"); if (xarg && yarg && warg && harg) { if (xarg->value == xarg->min && yarg->value == yarg->min && warg->value == warg->max && harg->value == harg->max) return false; xarg->value = xarg->min; yarg->value = yarg->min; warg->value = warg->max; harg->value = harg->max; clientManager->sendNewNumber(frameProp); return true; } return false; } bool CCDChip::setFrame(int x, int y, int w, int h) { INumberVectorProperty *frameProp = nullptr; switch (type) { case PRIMARY_CCD: frameProp = baseDevice->getNumber("CCD_FRAME"); break; case GUIDE_CCD: frameProp = baseDevice->getNumber("GUIDER_FRAME"); break; } if (frameProp == nullptr) return false; INumber *xarg = IUFindNumber(frameProp, "X"); INumber *yarg = IUFindNumber(frameProp, "Y"); INumber *warg = IUFindNumber(frameProp, "WIDTH"); INumber *harg = IUFindNumber(frameProp, "HEIGHT"); if (xarg && yarg && warg && harg) { if (xarg->value == x && yarg->value == y && warg->value == w && harg->value == h) return true; xarg->value = x; yarg->value = y; warg->value = w; harg->value = h; clientManager->sendNewNumber(frameProp); return true; } return false; } bool CCDChip::capture(double exposure) { INumberVectorProperty *expProp = nullptr; switch (type) { case PRIMARY_CCD: expProp = baseDevice->getNumber("CCD_EXPOSURE"); break; case GUIDE_CCD: expProp = baseDevice->getNumber("GUIDER_EXPOSURE"); break; } if (expProp == nullptr) return false; expProp->np[0].value = exposure; clientManager->sendNewNumber(expProp); return true; } bool CCDChip::abortExposure() { ISwitchVectorProperty *abortProp = nullptr; switch (type) { case PRIMARY_CCD: abortProp = baseDevice->getSwitch("CCD_ABORT_EXPOSURE"); break; case GUIDE_CCD: abortProp = baseDevice->getSwitch("GUIDER_ABORT_EXPOSURE"); break; } if (abortProp == nullptr) return false; ISwitch *abort = IUFindSwitch(abortProp, "ABORT"); if (abort == nullptr) return false; abort->s = ISS_ON; //captureMode = FITS_NORMAL; clientManager->sendNewSwitch(abortProp); return true; } bool CCDChip::canBin() const { return CanBin; } void CCDChip::setCanBin(bool value) { CanBin = value; } bool CCDChip::canSubframe() const { return CanSubframe; } void CCDChip::setCanSubframe(bool value) { CanSubframe = value; } bool CCDChip::canAbort() const { return CanAbort; } void CCDChip::setCanAbort(bool value) { CanAbort = value; } FITSData *CCDChip::getImageData() const { return imageData; } int CCDChip::getISOIndex() const { ISwitchVectorProperty *isoProp = baseDevice->getSwitch("CCD_ISO"); if (isoProp == nullptr) return -1; return IUFindOnSwitchIndex(isoProp); } bool CCDChip::setISOIndex(int value) { ISwitchVectorProperty *isoProp = baseDevice->getSwitch("CCD_ISO"); if (isoProp == nullptr) return false; IUResetSwitch(isoProp); isoProp->sp[value].s = ISS_ON; clientManager->sendNewSwitch(isoProp); return true; } QStringList CCDChip::getISOList() const { QStringList isoList; ISwitchVectorProperty *isoProp = baseDevice->getSwitch("CCD_ISO"); if (isoProp == nullptr) return isoList; for (int i = 0; i < isoProp->nsp; i++) isoList << isoProp->sp[i].label; return isoList; } bool CCDChip::isCapturing() { INumberVectorProperty *expProp = nullptr; switch (type) { case PRIMARY_CCD: expProp = baseDevice->getNumber("CCD_EXPOSURE"); break; case GUIDE_CCD: expProp = baseDevice->getNumber("GUIDER_EXPOSURE"); break; } if (expProp == nullptr) return false; return (expProp->s == IPS_BUSY); } bool CCDChip::setFrameType(const QString &name) { CCDFrameType fType = FRAME_LIGHT; if (name == "FRAME_LIGHT" || name == "Light") fType = FRAME_LIGHT; else if (name == "FRAME_DARK" || name == "Dark") fType = FRAME_DARK; else if (name == "FRAME_BIAS" || name == "Bias") fType = FRAME_BIAS; else if (name == "FRAME_FLAT" || name == "Flat") fType = FRAME_FLAT; else { qCWarning(KSTARS_INDI) << name << " frame type is unknown." ; return false; } return setFrameType(fType); } bool CCDChip::setFrameType(CCDFrameType fType) { ISwitchVectorProperty *frameProp = nullptr; if (type == PRIMARY_CCD) frameProp = baseDevice->getSwitch("CCD_FRAME_TYPE"); else frameProp = baseDevice->getSwitch("GUIDER_FRAME_TYPE"); if (frameProp == nullptr) return false; ISwitch *ccdFrame = nullptr; if (fType == FRAME_LIGHT) ccdFrame = IUFindSwitch(frameProp, "FRAME_LIGHT"); else if (fType == FRAME_DARK) ccdFrame = IUFindSwitch(frameProp, "FRAME_DARK"); else if (fType == FRAME_BIAS) ccdFrame = IUFindSwitch(frameProp, "FRAME_BIAS"); else if (fType == FRAME_FLAT) ccdFrame = IUFindSwitch(frameProp, "FRAME_FLAT"); if (ccdFrame == nullptr) return false; if (ccdFrame->s == ISS_ON) return true; if (fType != FRAME_LIGHT) captureMode = FITS_CALIBRATE; IUResetSwitch(frameProp); ccdFrame->s = ISS_ON; clientManager->sendNewSwitch(frameProp); return true; } CCDFrameType CCDChip::getFrameType() { CCDFrameType fType = FRAME_LIGHT; ISwitchVectorProperty *frameProp = nullptr; if (type == PRIMARY_CCD) frameProp = baseDevice->getSwitch("CCD_FRAME_TYPE"); else frameProp = baseDevice->getSwitch("GUIDER_FRAME_TYPE"); if (frameProp == nullptr) return fType; ISwitch *ccdFrame = nullptr; ccdFrame = IUFindOnSwitch(frameProp); if (ccdFrame == nullptr) { qCWarning(KSTARS_INDI) << "ISD:CCD Cannot find active frame in CCD!"; return fType; } if (!strcmp(ccdFrame->name, "FRAME_LIGHT")) fType = FRAME_LIGHT; else if (!strcmp(ccdFrame->name, "FRAME_DARK")) fType = FRAME_DARK; else if (!strcmp(ccdFrame->name, "FRAME_FLAT")) fType = FRAME_FLAT; else if (!strcmp(ccdFrame->name, "FRAME_BIAS")) fType = FRAME_BIAS; return fType; } bool CCDChip::setBinning(CCDBinType binType) { switch (binType) { case SINGLE_BIN: return setBinning(1, 1); case DOUBLE_BIN: return setBinning(2, 2); case TRIPLE_BIN: return setBinning(3, 3); case QUADRAPLE_BIN: return setBinning(4, 4); } return false; } CCDBinType CCDChip::getBinning() { CCDBinType binType = SINGLE_BIN; INumberVectorProperty *binProp = nullptr; switch (type) { case PRIMARY_CCD: binProp = baseDevice->getNumber("CCD_BINNING"); break; case GUIDE_CCD: binProp = baseDevice->getNumber("GUIDER_BINNING"); break; } if (binProp == nullptr) return binType; INumber *horBin = nullptr, *verBin = nullptr; horBin = IUFindNumber(binProp, "HOR_BIN"); verBin = IUFindNumber(binProp, "VER_BIN"); if (!horBin || !verBin) return binType; switch ((int)horBin->value) { case 2: binType = DOUBLE_BIN; break; case 3: binType = TRIPLE_BIN; break; case 4: binType = QUADRAPLE_BIN; break; default: break; } return binType; } bool CCDChip::getBinning(int *bin_x, int *bin_y) { INumberVectorProperty *binProp = nullptr; *bin_x = *bin_y = 1; switch (type) { case PRIMARY_CCD: binProp = baseDevice->getNumber("CCD_BINNING"); break; case GUIDE_CCD: binProp = baseDevice->getNumber("GUIDER_BINNING"); break; } if (binProp == nullptr) return false; INumber *horBin = nullptr, *verBin = nullptr; horBin = IUFindNumber(binProp, "HOR_BIN"); verBin = IUFindNumber(binProp, "VER_BIN"); if (!horBin || !verBin) return false; *bin_x = horBin->value; *bin_y = verBin->value; return true; } bool CCDChip::getMaxBin(int *max_xbin, int *max_ybin) { if (!max_xbin || !max_ybin) return false; INumberVectorProperty *binProp = nullptr; *max_xbin = *max_ybin = 1; switch (type) { case PRIMARY_CCD: binProp = baseDevice->getNumber("CCD_BINNING"); break; case GUIDE_CCD: binProp = baseDevice->getNumber("GUIDER_BINNING"); break; } if (binProp == nullptr) return false; INumber *horBin = nullptr, *verBin = nullptr; horBin = IUFindNumber(binProp, "HOR_BIN"); verBin = IUFindNumber(binProp, "VER_BIN"); if (!horBin || !verBin) return false; *max_xbin = horBin->max; *max_ybin = verBin->max; return true; } bool CCDChip::setBinning(int bin_x, int bin_y) { INumberVectorProperty *binProp = nullptr; switch (type) { case PRIMARY_CCD: binProp = baseDevice->getNumber("CCD_BINNING"); break; case GUIDE_CCD: binProp = baseDevice->getNumber("GUIDER_BINNING"); break; } if (binProp == nullptr) return false; INumber *horBin = nullptr, *verBin = nullptr; horBin = IUFindNumber(binProp, "HOR_BIN"); verBin = IUFindNumber(binProp, "VER_BIN"); if (!horBin || !verBin) return false; if (horBin->value == bin_x && verBin->value == bin_y) return true; if (bin_x > horBin->max || bin_y > verBin->max) return false; horBin->value = bin_x; verBin->value = bin_y; clientManager->sendNewNumber(binProp); return true; } CCD::CCD(GDInterface *iPtr) : DeviceDecorator(iPtr) { primaryChip.reset(new CCDChip(this, CCDChip::PRIMARY_CCD)); readyTimer.reset(new QTimer()); readyTimer.get()->setInterval(250); readyTimer.get()->setSingleShot(true); connect(readyTimer.get(), &QTimer::timeout, this, &CCD::ready); connect(clientManager, &ClientManager::newBLOBManager, [&](const char *device, INDI::Property *prop) { if (!strcmp(device, getDeviceName())) emit newBLOBManager(prop); }); } -CCD::~CCD() -{ - delete fv; -} - void CCD::registerProperty(INDI::Property *prop) { if (isConnected()) readyTimer.get()->start(); if (!strcmp(prop->getName(), "GUIDER_EXPOSURE")) { HasGuideHead = true; guideChip.reset(new CCDChip(this, CCDChip::GUIDE_CCD)); } else if (!strcmp(prop->getName(), "CCD_FRAME_TYPE")) { ISwitchVectorProperty *ccdFrame = prop->getSwitch(); primaryChip->clearFrameTypes(); for (int i = 0; i < ccdFrame->nsp; i++) primaryChip->addFrameLabel(ccdFrame->sp[i].label); } else if (!strcmp(prop->getName(), "CCD_FRAME")) { INumberVectorProperty *np = prop->getNumber(); if (np && np->p != IP_RO) primaryChip->setCanSubframe(true); } else if (!strcmp(prop->getName(), "GUIDER_FRAME")) { INumberVectorProperty *np = prop->getNumber(); if (np && np->p != IP_RO) guideChip->setCanSubframe(true); } else if (!strcmp(prop->getName(), "CCD_BINNING")) { INumberVectorProperty *np = prop->getNumber(); if (np && np->p != IP_RO) primaryChip->setCanBin(true); } else if (!strcmp(prop->getName(), "GUIDER_BINNING")) { INumberVectorProperty *np = prop->getNumber(); if (np && np->p != IP_RO) guideChip->setCanBin(true); } else if (!strcmp(prop->getName(), "CCD_ABORT_EXPOSURE")) { ISwitchVectorProperty *sp = prop->getSwitch(); if (sp && sp->p != IP_RO) primaryChip->setCanAbort(true); } else if (!strcmp(prop->getName(), "GUIDER_ABORT_EXPOSURE")) { ISwitchVectorProperty *sp = prop->getSwitch(); if (sp && sp->p != IP_RO) guideChip->setCanAbort(true); } else if (!strcmp(prop->getName(), "CCD_TEMPERATURE")) { INumberVectorProperty *np = prop->getNumber(); HasCooler = true; CanCool = (np->p != IP_RO); if (np) emit newTemperatureValue(np->np[0].value); } else if (!strcmp(prop->getName(), "CCD_COOLER")) { // Can turn cooling on/off HasCoolerControl = true; } else if (!strcmp(prop->getName(), "CCD_VIDEO_STREAM")) { // Has Video Stream HasVideoStream = true; } else if (!strcmp(prop->getName(), "CCD_TRANSFER_FORMAT")) { ISwitchVectorProperty *sp = prop->getSwitch(); if (sp) { ISwitch *format = IUFindSwitch(sp, "FORMAT_NATIVE"); if (format && format->s == ISS_ON) transferFormat = FORMAT_NATIVE; else transferFormat = FORMAT_FITS; } } else if (!strcmp(prop->getName(), "CCD_EXPOSURE_LOOP")) { ISwitchVectorProperty *sp = prop->getSwitch(); if (sp) { ISwitch *looping = IUFindSwitch(sp, "LOOP_ON"); if (looping && looping->s == ISS_ON) IsLooping = true; else IsLooping = false; } } else if (!strcmp(prop->getName(), "TELESCOPE_TYPE")) { ISwitchVectorProperty *sp = prop->getSwitch(); if (sp) { ISwitch *format = IUFindSwitch(sp, "TELESCOPE_PRIMARY"); if (format && format->s == ISS_ON) telescopeType = TELESCOPE_PRIMARY; else telescopeType = TELESCOPE_GUIDE; } } // try to find gain property, if any else if (gainN == nullptr && prop->getType() == INDI_NUMBER) { // Since gain is spread among multiple property depending on the camera providing it // we need to search in all possible number properties INumberVectorProperty *gainNP = prop->getNumber(); if (gainNP) { for (int i = 0; i < gainNP->nnp; i++) { QString name = QString(gainNP->np[i].name).toLower(); QString label = QString(gainNP->np[i].label).toLower(); if (name == "gain" || label == "gain") { gainN = gainNP->np + i; gainPerm = gainNP->p; break; } } } } DeviceDecorator::registerProperty(prop); } void CCD::processLight(ILightVectorProperty *lvp) { DeviceDecorator::processLight(lvp); } void CCD::processNumber(INumberVectorProperty *nvp) { if (!strcmp(nvp->name, "CCD_EXPOSURE")) { INumber *np = IUFindNumber(nvp, "CCD_EXPOSURE_VALUE"); if (np) emit newExposureValue(primaryChip.get(), np->value, nvp->s); } else if (!strcmp(nvp->name, "CCD_TEMPERATURE")) { HasCooler = true; INumber *np = IUFindNumber(nvp, "CCD_TEMPERATURE_VALUE"); if (np) emit newTemperatureValue(np->value); } else if (!strcmp(nvp->name, "GUIDER_EXPOSURE")) { INumber *np = IUFindNumber(nvp, "GUIDER_EXPOSURE_VALUE"); if (np) emit newExposureValue(guideChip.get(), np->value, nvp->s); } else if (!strcmp(nvp->name, "FPS")) { emit newFPS(nvp->np[0].value, nvp->np[1].value); } else if (!strcmp(nvp->name, "CCD_RAPID_GUIDE_DATA")) { double dx = -1, dy = -1, fit = -1; INumber *np = nullptr; if (nvp->s == IPS_ALERT) { emit newGuideStarData(primaryChip.get(), -1, -1, -1); } else { np = IUFindNumber(nvp, "GUIDESTAR_X"); if (np) dx = np->value; np = IUFindNumber(nvp, "GUIDESTAR_Y"); if (np) dy = np->value; np = IUFindNumber(nvp, "GUIDESTAR_FIT"); if (np) fit = np->value; if (dx >= 0 && dy >= 0 && fit >= 0) emit newGuideStarData(primaryChip.get(), dx, dy, fit); } } else if (!strcmp(nvp->name, "GUIDER_RAPID_GUIDE_DATA")) { double dx = -1, dy = -1, fit = -1; INumber *np = nullptr; if (nvp->s == IPS_ALERT) { emit newGuideStarData(guideChip.get(), -1, -1, -1); } else { np = IUFindNumber(nvp, "GUIDESTAR_X"); if (np) dx = np->value; np = IUFindNumber(nvp, "GUIDESTAR_Y"); if (np) dy = np->value; np = IUFindNumber(nvp, "GUIDESTAR_FIT"); if (np) fit = np->value; if (dx >= 0 && dy >= 0 && fit >= 0) emit newGuideStarData(guideChip.get(), dx, dy, fit); } } DeviceDecorator::processNumber(nvp); } void CCD::processSwitch(ISwitchVectorProperty *svp) { if (!strcmp(svp->name, "CCD_COOLER")) { // Can turn cooling on/off HasCoolerControl = true; emit coolerToggled(svp->sp[0].s == ISS_ON); } else if (QString(svp->name).endsWith("VIDEO_STREAM")) { HasVideoStream = true; if (streamWindow.get() == nullptr && svp->sp[0].s == ISS_ON) { streamWindow.reset(new StreamWG(this)); INumberVectorProperty *streamFrame = baseDevice->getNumber("CCD_STREAM_FRAME"); INumber *w = nullptr, *h = nullptr; if (streamFrame) { w = IUFindNumber(streamFrame, "WIDTH"); h = IUFindNumber(streamFrame, "HEIGHT"); } if (w && h) { streamW = w->value; streamH = h->value; } else { // Only use CCD dimensions if we are receiving raw stream and not stream of images (i.e. mjpeg..etc) IBLOBVectorProperty *rawBP = baseDevice->getBLOB("CCD1"); if (rawBP) { int x = 0, y = 0, w = 0, h = 0; int binx = 0, biny = 0; primaryChip->getFrame(&x, &y, &w, &h); primaryChip->getBinning(&binx, &biny); streamW = w / binx; streamH = h / biny; } } streamWindow->setSize(streamW, streamH); } if (streamWindow.get() != nullptr) { connect(streamWindow.get(), &StreamWG::hidden, this, &CCD::StreamWindowHidden, Qt::UniqueConnection); connect(streamWindow.get(), &StreamWG::imageChanged, this, &CCD::newVideoFrame, Qt::UniqueConnection); streamWindow->enableStream(svp->sp[0].s == ISS_ON); emit videoStreamToggled(svp->sp[0].s == ISS_ON); } } else if (!strcmp(svp->name, "CCD_TRANSFER_FORMAT")) { ISwitch *format = IUFindSwitch(svp, "FORMAT_NATIVE"); if (format && format->s == ISS_ON) transferFormat = FORMAT_NATIVE; else transferFormat = FORMAT_FITS; } else if (!strcmp(svp->name, "RECORD_STREAM")) { ISwitch *recordOFF = IUFindSwitch(svp, "RECORD_OFF"); if (recordOFF && recordOFF->s == ISS_ON) { emit videoRecordToggled(false); KNotification::event(QLatin1String("RecordingStopped"), i18n("Video Recording Stopped")); } else { emit videoRecordToggled(true); KNotification::event(QLatin1String("RecordingStarted"), i18n("Video Recording Started")); } } else if (!strcmp(svp->name, "TELESCOPE_TYPE")) { ISwitch *format = IUFindSwitch(svp, "TELESCOPE_PRIMARY"); if (format && format->s == ISS_ON) telescopeType = TELESCOPE_PRIMARY; else telescopeType = TELESCOPE_GUIDE; } else if (!strcmp(svp->name, "CCD_EXPOSURE_LOOP")) { ISwitch *looping = IUFindSwitch(svp, "LOOP_ON"); if (looping && looping->s == ISS_ON) IsLooping = true; else IsLooping = false; } else if (!strcmp(svp->name, "CONNECTION")) { ISwitch *dSwitch = IUFindSwitch(svp, "DISCONNECT"); if (dSwitch && dSwitch->s == ISS_ON && streamWindow.get() != nullptr) { streamWindow->enableStream(false); emit videoStreamToggled(false); streamWindow->close(); streamWindow.reset(); } //emit switchUpdated(svp); //return; } DeviceDecorator::processSwitch(svp); } void CCD::processText(ITextVectorProperty *tvp) { if (!strcmp(tvp->name, "CCD_FILE_PATH")) { IText *filepath = IUFindText(tvp, "FILE_PATH"); if (filepath) emit newRemoteFile(QString(filepath->text)); } DeviceDecorator::processText(tvp); } void CCD::processBLOB(IBLOB *bp) { // Ignore write-only BLOBs since we only receive it for state-change if (bp->bvp->p == IP_WO || bp->size == 0) return; BType = BLOB_OTHER; QString format(bp->format); // If stream, process it first if (format.contains("stream") && streamWindow.get() != nullptr) { if (streamWindow->isStreamEnabled() == false) return; INumberVectorProperty *streamFrame = baseDevice->getNumber("CCD_STREAM_FRAME"); INumber *w = nullptr, *h = nullptr; if (streamFrame) { w = IUFindNumber(streamFrame, "WIDTH"); h = IUFindNumber(streamFrame, "HEIGHT"); } if (w && h) { streamW = w->value; streamH = h->value; } else { int x, y, w, h; int binx, biny; primaryChip->getFrame(&x, &y, &w, &h); primaryChip->getBinning(&binx, &biny); streamW = w / binx; streamH = h / biny; /*IBLOBVectorProperty *rawBP = baseDevice->getBLOB("CCD1"); if (rawBP) { rawBP->bp[0].aux0 = &(streamW); rawBP->bp[0].aux1 = &(streamH); }*/ } //if (streamWindow->getStreamWidth() != streamW || streamWindow->getStreamHeight() != streamH) streamWindow->setSize(streamW, streamH); streamWindow->show(); streamWindow->newFrame(bp); return; } QByteArray fmt = QString(bp->format).toLower().remove('.').toUtf8(); // If it's not FITS or an image, don't process it. if ((QImageReader::supportedImageFormats().contains(fmt))) BType = BLOB_IMAGE; else if (format.contains("fits")) BType = BLOB_FITS; else if (RAWFormats.contains(fmt)) BType = BLOB_RAW; if (BType == BLOB_OTHER) { DeviceDecorator::processBLOB(bp); return; } CCDChip *targetChip = nullptr; if (!strcmp(bp->name, "CCD2")) targetChip = guideChip.get(); else targetChip = primaryChip.get(); QString currentDir; if (targetChip->isBatchMode() == false) currentDir = KSPaths::writableLocation(QStandardPaths::TempLocation); else currentDir = fitsDir.isEmpty() ? Options::fitsDir() : fitsDir; int nr, n = 0; QTemporaryFile tmpFile(QDir::tempPath() + "/fitsXXXXXX"); //if (currentDir.endsWith('/')) //currentDir.truncate(currentDir.size()-1); if (QDir(currentDir).exists() == false) QDir().mkpath(currentDir); QString filename(currentDir); if (filename.endsWith('/') == false) filename.append('/'); // Create temporary name if ANY of the following conditions are met: // 1. file is preview or batch mode is not enabled // 2. file type is not FITS_NORMAL (focus, guide..etc) if (targetChip->isBatchMode() == false || targetChip->getCaptureMode() != FITS_NORMAL) { //tmpFile.setPrefix("fits"); tmpFile.setAutoRemove(false); if (!tmpFile.open()) { qCCritical(KSTARS_INDI) << "ISD:CCD Error: Unable to open " << filename; emit BLOBUpdated(nullptr); return; } QDataStream out(&tmpFile); for (nr = 0; nr < (int)bp->size; nr += n) n = out.writeRawData(static_cast(bp->blob) + nr, bp->size - nr); tmpFile.close(); filename = tmpFile.fileName(); } // Create file name for others else { // IS8601 contains colons but they are illegal under Windows OS, so replacing them with '-' // The timestamp is no longer ISO8601 but it should solve interoperality issues between different OS hosts QString ts = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss"); if (seqPrefix.contains("_ISO8601")) { QString finalPrefix = seqPrefix; finalPrefix.replace("ISO8601", ts); filename += finalPrefix + QString("_%1.%2").arg(QString().sprintf("%03d", nextSequenceID), QString(fmt)); } else filename += seqPrefix + (seqPrefix.isEmpty() ? "" : "_") + QString("%1.%2").arg(QString().sprintf("%03d", nextSequenceID), QString(fmt)); QFile fits_temp_file(filename); if (!fits_temp_file.open(QIODevice::WriteOnly)) { qCCritical(KSTARS_INDI) << "ISD:CCD Error: Unable to open " << fits_temp_file.fileName(); emit BLOBUpdated(nullptr); return; } QDataStream out(&fits_temp_file); for (nr = 0; nr < (int)bp->size; nr += n) n = out.writeRawData(static_cast(bp->blob) + nr, bp->size - nr); fits_temp_file.close(); } if (BType == BLOB_FITS) addFITSKeywords(filename); // store file name strncpy(BLOBFilename, filename.toLatin1(), MAXINDIFILENAME); bp->aux0 = targetChip; bp->aux1 = &BType; bp->aux2 = BLOBFilename; if (targetChip->getCaptureMode() == FITS_NORMAL && targetChip->isBatchMode() == true) { KStars::Instance()->statusBar()->showMessage(i18n("%1 file saved to %2", QString(fmt).toUpper(), filename), 0); qCInfo(KSTARS_INDI) << QString(fmt).toUpper() << "file saved to" << filename; } // FIXME: Why is this leaking memory in Valgrind??! KNotification::event(QLatin1String("FITSReceived"), i18n("Image file is received")); /*if (targetChip->showFITS() == false && targetChip->getCaptureMode() == FITS_NORMAL) { emit BLOBUpdated(bp); return; }*/ if (BType == BLOB_IMAGE || BType == BLOB_RAW) { if (BType == BLOB_RAW) { #ifdef HAVE_LIBRAW QString rawFileName = filename; rawFileName = rawFileName.remove(0, rawFileName.lastIndexOf(QLatin1Literal("/"))); QString templateName = QString("%1/%2.XXXXXX").arg(QDir::tempPath(), rawFileName); QTemporaryFile imgPreview(templateName); imgPreview.setAutoRemove(false); imgPreview.open(); imgPreview.close(); QString preview_filename = imgPreview.fileName(); int ret = 0; // Creation of image processing object LibRaw RawProcessor; // Let us open the file if ((ret = RawProcessor.open_file(filename.toLatin1().data())) != LIBRAW_SUCCESS) { KStars::Instance()->statusBar()->showMessage( i18n("Cannot open %1: %2", rawFileName, libraw_strerror(ret))); RawProcessor.recycle(); emit BLOBUpdated(bp); return; } // Let us unpack the image /*if( (ret = RawProcessor.unpack() ) != LIBRAW_SUCCESS) { KStars::Instance()->statusBar()->showMessage(i18n("Cannot unpack_thumb %1: %2", rawFileName, libraw_strerror(ret))); if(LIBRAW_FATAL_ERROR(ret)) { RawProcessor.recycle(); emit BLOBUpdated(bp); return; } // if there has been a non-fatal error, we will try to continue }*/ // Let us unpack the thumbnail if ((ret = RawProcessor.unpack_thumb()) != LIBRAW_SUCCESS) { KStars::Instance()->statusBar()->showMessage( i18n("Cannot unpack_thumb %1: %2", rawFileName, libraw_strerror(ret))); RawProcessor.recycle(); emit BLOBUpdated(bp); return; } else // We have successfully unpacked the thumbnail, now let us write it to a file { //snprintf(thumbfn,sizeof(thumbfn),"%s.%s",av[i],T.tformat == LIBRAW_THUMBNAIL_JPEG ? "thumb.jpg" : "thumb.ppm"); if (LIBRAW_SUCCESS != (ret = RawProcessor.dcraw_thumb_writer(preview_filename.toLatin1().data()))) { KStars::Instance()->statusBar()->showMessage( i18n("Cannot write %s %1: %2", preview_filename, libraw_strerror(ret))); RawProcessor.recycle(); emit BLOBUpdated(bp); return; } } filename = preview_filename; #else // Silently fail if KStars was not compiled with libraw //KStars::Instance()->statusBar()->showMessage(i18n("Unable to find dcraw and cjpeg. Please install the required tools to convert CR2/NEF to JPEG.")); emit BLOBUpdated(bp); return; #endif } // store file name in strncpy(BLOBFilename, filename.toLatin1(), MAXINDIFILENAME); bp->aux0 = targetChip; bp->aux1 = &BType; bp->aux2 = BLOBFilename; if (Options::useDSLRImageViewer() || targetChip->isBatchMode() == false) { - if (imageViewer.isNull()) - imageViewer = new ImageViewer(getDeviceName(), KStars::Instance()); + if (m_ImageViewerWindow.isNull()) + m_ImageViewerWindow = new ImageViewer(getDeviceName(), KStars::Instance()); - imageViewer->loadImage(filename); + m_ImageViewerWindow->loadImage(filename); } } // Unless we have cfitsio, we're done. #ifdef HAVE_CFITSIO if (BType == BLOB_FITS) { QUrl fileURL = QUrl::fromLocalFile(filename); // Get or Create FITSViewer if we are using FITSViewer // or if capture mode is calibrate since for now we are forced to open the file in the viewer // this should be fixed in the future and should only use FITSData if (Options::useFITSViewer() || targetChip->isBatchMode() == false) { - if (fv.isNull() && targetChip->getCaptureMode() != FITS_GUIDE && + if (m_FITSViewerWindows.isNull() && targetChip->getCaptureMode() != FITS_GUIDE && targetChip->getCaptureMode() != FITS_FOCUS && targetChip->getCaptureMode() != FITS_ALIGN) { normalTabID = calibrationTabID = focusTabID = guideTabID = alignTabID = -1; if (Options::singleWindowCapturedFITS()) - fv = KStars::Instance()->genericFITSViewer(); + m_FITSViewerWindows = KStars::Instance()->genericFITSViewer(); else { - fv = new FITSViewer(Options::independentWindowFITS() ? nullptr : KStars::Instance()); - KStars::Instance()->addFITSViewer(fv); + m_FITSViewerWindows = new FITSViewer(Options::independentWindowFITS() ? nullptr : KStars::Instance()); + KStars::Instance()->addFITSViewer(m_FITSViewerWindows); } - connect(fv, &FITSViewer::closed, [&](int tabIndex) { + connect(m_FITSViewerWindows, &FITSViewer::closed, [&](int tabIndex) { if (tabIndex == normalTabID) normalTabID = -1; else if (tabIndex == calibrationTabID) calibrationTabID = -1; else if (tabIndex == focusTabID) focusTabID = -1; else if (tabIndex == guideTabID) guideTabID = -1; else if (tabIndex == alignTabID) alignTabID = -1; }); //connect(fv, SIGNAL(destroyed()), this, SLOT(FITSViewerDestroyed())); //connect(fv, SIGNAL(destroyed()), this, SIGNAL(FITSViewerClosed())); } } FITSScale captureFilter = targetChip->getCaptureFilter(); FITSMode captureMode = targetChip->getCaptureMode(); QString previewTitle; // If image is preview and we should display all captured images in a single tab called "Preview" // Then set the title to "Preview" // Otherwise, the title will be the captured image name if (targetChip->isBatchMode() == false && Options::singlePreviewFITS()) { // If we are displayed all images from all cameras in a single FITS Viewer window // Then we prefix the camera name to the "Preview" string if (Options::singleWindowCapturedFITS()) previewTitle = i18n("%1 Preview", getDeviceName()); else // Otherwise, just use "Preview" previewTitle = i18n("Preview"); } switch (captureMode) { case FITS_NORMAL: case FITS_CALIBRATE: { int *tabID = (captureMode == FITS_NORMAL) ? &normalTabID : &calibrationTabID; // Check if we need to display the image if (Options::useFITSViewer() || targetChip->isBatchMode() == false) { - fv->disconnect(this); + m_FITSViewerWindows->disconnect(this); auto m_Loaded = std::make_shared(); - *m_Loaded = connect(fv, &FITSViewer::loaded, [=](int tabIndex) { + *m_Loaded = connect(m_FITSViewerWindows, &FITSViewer::loaded, [=](int tabIndex) { *tabID = tabIndex; - targetChip->setImageView(fv->getView(tabIndex), captureMode); + targetChip->setImageView(m_FITSViewerWindows->getView(tabIndex), captureMode); QObject::disconnect(*m_Loaded); emit BLOBUpdated(bp); }); auto m_Failed = std::make_shared(); - *m_Failed = connect(fv, &FITSViewer::failed, [=]() { + *m_Failed = connect(m_FITSViewerWindows, &FITSViewer::failed, [=]() { // If opening file fails, we treat it the same as exposure failure and recapture again if possible emit newExposureValue(targetChip, 0, IPS_ALERT); QObject::disconnect(*m_Failed); return; }); if (*tabID == -1 || Options::singlePreviewFITS() == false) - fv->addFITS(fileURL, captureMode, captureFilter, previewTitle); + m_FITSViewerWindows->addFITS(fileURL, captureMode, captureFilter, previewTitle); else - fv->updateFITS(fileURL, *tabID, captureFilter); + m_FITSViewerWindows->updateFITS(fileURL, *tabID, captureFilter); } else // If not displayed in FITS Viewer then we just inform that a blob was received. emit BLOBUpdated(bp); } break; case FITS_FOCUS: case FITS_GUIDE: case FITS_ALIGN: loadImageInView(bp, targetChip); break; } } else emit BLOBUpdated(bp); #endif } void CCD::loadImageInView(IBLOB *bp, ISD::CCDChip *targetChip) { FITSMode mode = targetChip->getCaptureMode(); FITSView *view = targetChip->getImageView(mode); QString filename = QString(static_cast(bp->aux2)); if (view) { auto m_Loaded = std::make_shared(); *m_Loaded = connect(view, &FITSView::loaded, [=]() { //view->updateFrame(); // FITSViewer is shown if: // Image in preview mode, or useFITSViewre is true; AND // Image type is either NORMAL or CALIBRATION since the rest have their dedicated windows. // NORMAL is used for raw INDI drivers without Ekos. if ( (Options::useFITSViewer() || targetChip->isBatchMode() == false) && (mode == FITS_NORMAL || mode == FITS_CALIBRATE)) - fv->show(); + m_FITSViewerWindows->show(); QObject::disconnect(*m_Loaded); emit BLOBUpdated(bp); }); auto m_Failed = std::make_shared(); *m_Failed = connect(view, &FITSView::failed, [=]() { QObject::disconnect(*m_Failed); emit newExposureValue(targetChip, 0, IPS_ALERT); return; }); view->setFilter(targetChip->getCaptureFilter()); view->loadFITS(filename, true); } } void CCD::addFITSKeywords(const QString& filename) { #ifdef HAVE_CFITSIO int status = 0; if (filter.isEmpty() == false) { QString key_comment("Filter name"); filter.replace(' ', '_'); fitsfile *fptr = nullptr; #if 0 if (fits_open_image(&fptr, filename.toLatin1(), READWRITE, &status)) { fits_report_error(stderr, status); return; } #endif // Use open diskfile as it does not use extended file names which has problems opening // files with [ ] or ( ) in their names. if (fits_open_diskfile(&fptr, filename.toLatin1(), READONLY, &status)) { fits_report_error(stderr, status); return; } if (fits_movabs_hdu(fptr, 1, IMAGE_HDU, &status)) { fits_report_error(stderr, status); return; } if (fits_update_key_str(fptr, "FILTER", filter.toLatin1().data(), key_comment.toLatin1().data(), &status)) { fits_report_error(stderr, status); return; } fits_close_file(fptr, &status); filter = ""; } #endif } CCD::TransferFormat CCD::getTargetTransferFormat() const { return targetTransferFormat; } void CCD::setTargetTransferFormat(const TransferFormat &value) { targetTransferFormat = value; } void CCD::FITSViewerDestroyed() { - fv = nullptr; + m_FITSViewerWindows = nullptr; normalTabID = calibrationTabID = focusTabID = guideTabID = alignTabID = -1; } void CCD::StreamWindowHidden() { if (baseDevice->isConnected()) { // We can have more than one *_VIDEO_STREAM property active so disable them all ISwitchVectorProperty *streamSP = baseDevice->getSwitch("CCD_VIDEO_STREAM"); if (streamSP) { IUResetSwitch(streamSP); streamSP->sp[0].s = ISS_OFF; streamSP->sp[1].s = ISS_ON; streamSP->s = IPS_IDLE; clientManager->sendNewSwitch(streamSP); } streamSP = baseDevice->getSwitch("VIDEO_STREAM"); if (streamSP) { IUResetSwitch(streamSP); streamSP->sp[0].s = ISS_OFF; streamSP->sp[1].s = ISS_ON; streamSP->s = IPS_IDLE; clientManager->sendNewSwitch(streamSP); } streamSP = baseDevice->getSwitch("AUX_VIDEO_STREAM"); if (streamSP) { IUResetSwitch(streamSP); streamSP->sp[0].s = ISS_OFF; streamSP->sp[1].s = ISS_ON; streamSP->s = IPS_IDLE; clientManager->sendNewSwitch(streamSP); } } if (streamWindow.get() != nullptr) streamWindow->disconnect(); } bool CCD::hasGuideHead() { return HasGuideHead; } bool CCD::hasCooler() { return HasCooler; } bool CCD::hasCoolerControl() { return HasCoolerControl; } bool CCD::setCoolerControl(bool enable) { if (HasCoolerControl == false) return false; ISwitchVectorProperty *coolerSP = baseDevice->getSwitch("CCD_COOLER"); if (coolerSP == nullptr) return false; // Cooler ON/OFF ISwitch *coolerON = IUFindSwitch(coolerSP, "COOLER_ON"); ISwitch *coolerOFF = IUFindSwitch(coolerSP, "COOLER_OFF"); if (coolerON == nullptr || coolerOFF == nullptr) return false; coolerON->s = enable ? ISS_ON : ISS_OFF; coolerOFF->s = enable ? ISS_OFF : ISS_ON; clientManager->sendNewSwitch(coolerSP); return true; } CCDChip *CCD::getChip(CCDChip::ChipType cType) { switch (cType) { case CCDChip::PRIMARY_CCD: return primaryChip.get(); break; case CCDChip::GUIDE_CCD: return guideChip.get(); break; } return nullptr; } bool CCD::setRapidGuide(CCDChip *targetChip, bool enable) { ISwitchVectorProperty *rapidSP = nullptr; ISwitch *enableS = nullptr; if (targetChip == primaryChip.get()) rapidSP = baseDevice->getSwitch("CCD_RAPID_GUIDE"); else rapidSP = baseDevice->getSwitch("GUIDER_RAPID_GUIDE"); if (rapidSP == nullptr) return false; enableS = IUFindSwitch(rapidSP, "ENABLE"); if (enableS == nullptr) return false; // Already updated, return OK if ((enable && enableS->s == ISS_ON) || (!enable && enableS->s == ISS_OFF)) return true; IUResetSwitch(rapidSP); rapidSP->sp[0].s = enable ? ISS_ON : ISS_OFF; rapidSP->sp[1].s = enable ? ISS_OFF : ISS_ON; clientManager->sendNewSwitch(rapidSP); return true; } bool CCD::configureRapidGuide(CCDChip *targetChip, bool autoLoop, bool sendImage, bool showMarker) { ISwitchVectorProperty *rapidSP = nullptr; ISwitch *autoLoopS = nullptr, *sendImageS = nullptr, *showMarkerS = nullptr; if (targetChip == primaryChip.get()) rapidSP = baseDevice->getSwitch("CCD_RAPID_GUIDE_SETUP"); else rapidSP = baseDevice->getSwitch("GUIDER_RAPID_GUIDE_SETUP"); if (rapidSP == nullptr) return false; autoLoopS = IUFindSwitch(rapidSP, "AUTO_LOOP"); sendImageS = IUFindSwitch(rapidSP, "SEND_IMAGE"); showMarkerS = IUFindSwitch(rapidSP, "SHOW_MARKER"); if (!autoLoopS || !sendImageS || !showMarkerS) return false; // If everything is already set, let's return. if (((autoLoop && autoLoopS->s == ISS_ON) || (!autoLoop && autoLoopS->s == ISS_OFF)) && ((sendImage && sendImageS->s == ISS_ON) || (!sendImage && sendImageS->s == ISS_OFF)) && ((showMarker && showMarkerS->s == ISS_ON) || (!showMarker && showMarkerS->s == ISS_OFF))) return true; autoLoopS->s = autoLoop ? ISS_ON : ISS_OFF; sendImageS->s = sendImage ? ISS_ON : ISS_OFF; showMarkerS->s = showMarker ? ISS_ON : ISS_OFF; clientManager->sendNewSwitch(rapidSP); return true; } void CCD::updateUploadSettings(const QString &remoteDir) { QString filename = seqPrefix + (seqPrefix.isEmpty() ? "" : "_") + QString("XXX"); ITextVectorProperty *uploadSettingsTP = nullptr; IText *uploadT = nullptr; uploadSettingsTP = baseDevice->getText("UPLOAD_SETTINGS"); if (uploadSettingsTP) { uploadT = IUFindText(uploadSettingsTP, "UPLOAD_DIR"); if (uploadT && remoteDir.isEmpty() == false) IUSaveText(uploadT, remoteDir.toLatin1().constData()); uploadT = IUFindText(uploadSettingsTP, "UPLOAD_PREFIX"); if (uploadT) IUSaveText(uploadT, filename.toLatin1().constData()); clientManager->sendNewText(uploadSettingsTP); } } CCD::UploadMode CCD::getUploadMode() { ISwitchVectorProperty *uploadModeSP = nullptr; uploadModeSP = baseDevice->getSwitch("UPLOAD_MODE"); if (uploadModeSP == nullptr) { qWarning() << "No UPLOAD_MODE in CCD driver. Please update driver to INDI compliant CCD driver."; return UPLOAD_CLIENT; } if (uploadModeSP) { ISwitch *modeS = nullptr; modeS = IUFindSwitch(uploadModeSP, "UPLOAD_CLIENT"); if (modeS && modeS->s == ISS_ON) return UPLOAD_CLIENT; modeS = IUFindSwitch(uploadModeSP, "UPLOAD_LOCAL"); if (modeS && modeS->s == ISS_ON) return UPLOAD_LOCAL; modeS = IUFindSwitch(uploadModeSP, "UPLOAD_BOTH"); if (modeS && modeS->s == ISS_ON) return UPLOAD_BOTH; } // Default return UPLOAD_CLIENT; } bool CCD::setUploadMode(UploadMode mode) { ISwitchVectorProperty *uploadModeSP = nullptr; ISwitch *modeS = nullptr; uploadModeSP = baseDevice->getSwitch("UPLOAD_MODE"); if (uploadModeSP == nullptr) { qWarning() << "No UPLOAD_MODE in CCD driver. Please update driver to INDI compliant CCD driver."; return false; } switch (mode) { case UPLOAD_CLIENT: modeS = IUFindSwitch(uploadModeSP, "UPLOAD_CLIENT"); if (modeS == nullptr) return false; if (modeS->s == ISS_ON) return true; break; case UPLOAD_BOTH: modeS = IUFindSwitch(uploadModeSP, "UPLOAD_BOTH"); if (modeS == nullptr) return false; if (modeS->s == ISS_ON) return true; break; case UPLOAD_LOCAL: modeS = IUFindSwitch(uploadModeSP, "UPLOAD_LOCAL"); if (modeS == nullptr) return false; if (modeS->s == ISS_ON) return true; break; } IUResetSwitch(uploadModeSP); modeS->s = ISS_ON; clientManager->sendNewSwitch(uploadModeSP); return true; } bool CCD::getTemperature(double *value) { if (HasCooler == false) return false; INumberVectorProperty *temperatureNP = baseDevice->getNumber("CCD_TEMPERATURE"); if (temperatureNP == nullptr) return false; *value = temperatureNP->np[0].value; return true; } bool CCD::setTemperature(double value) { INumberVectorProperty *nvp = baseDevice->getNumber("CCD_TEMPERATURE"); if (nvp == nullptr) return false; INumber *np = IUFindNumber(nvp, "CCD_TEMPERATURE_VALUE"); if (np == nullptr) return false; np->value = value; clientManager->sendNewNumber(nvp); return true; } bool CCD::setTransformFormat(CCD::TransferFormat format) { if (format == transferFormat) return true; ISwitchVectorProperty *svp = baseDevice->getSwitch("CCD_TRANSFER_FORMAT"); if (svp == nullptr) return false; ISwitch *formatFITS = IUFindSwitch(svp, "FORMAT_FITS"); ISwitch *formatNative = IUFindSwitch(svp, "FORMAT_NATIVE"); if (formatFITS == nullptr || formatNative == nullptr) return false; transferFormat = format; formatFITS->s = (transferFormat == FORMAT_FITS) ? ISS_ON : ISS_OFF; formatNative->s = (transferFormat == FORMAT_FITS) ? ISS_OFF : ISS_ON; clientManager->sendNewSwitch(svp); return true; } bool CCD::setTelescopeType(TelescopeType type) { if (type == telescopeType) return true; ISwitchVectorProperty *svp = baseDevice->getSwitch("TELESCOPE_TYPE"); if (svp == nullptr) return false; ISwitch *typePrimary = IUFindSwitch(svp, "TELESCOPE_PRIMARY"); ISwitch *typeGuide = IUFindSwitch(svp, "TELESCOPE_GUIDE"); if (typePrimary == nullptr || typeGuide == nullptr) return false; telescopeType = type; typePrimary->s = (telescopeType == TELESCOPE_PRIMARY) ? ISS_ON : ISS_OFF; typeGuide->s = (telescopeType == TELESCOPE_PRIMARY) ? ISS_OFF : ISS_ON; clientManager->sendNewSwitch(svp); setConfig(SAVE_CONFIG); return true; } bool CCD::setVideoStreamEnabled(bool enable) { if (HasVideoStream == false) return false; ISwitchVectorProperty *svp = baseDevice->getSwitch("CCD_VIDEO_STREAM"); if (svp == nullptr) return false; // If already on and enable is set or vice versa no need to change anything we return true if ((enable && svp->sp[0].s == ISS_ON) || (!enable && svp->sp[1].s == ISS_ON)) return true; svp->sp[0].s = enable ? ISS_ON : ISS_OFF; svp->sp[1].s = enable ? ISS_OFF : ISS_ON; clientManager->sendNewSwitch(svp); return true; } bool CCD::resetStreamingFrame() { INumberVectorProperty *frameProp = baseDevice->getNumber("CCD_STREAM_FRAME"); if (frameProp == nullptr) return false; INumber *xarg = IUFindNumber(frameProp, "X"); INumber *yarg = IUFindNumber(frameProp, "Y"); INumber *warg = IUFindNumber(frameProp, "WIDTH"); INumber *harg = IUFindNumber(frameProp, "HEIGHT"); if (xarg && yarg && warg && harg) { if (xarg->value == xarg->min && yarg->value == yarg->min && warg->value == warg->max && harg->value == harg->max) return false; xarg->value = xarg->min; yarg->value = yarg->min; warg->value = warg->max; harg->value = harg->max; clientManager->sendNewNumber(frameProp); return true; } return false; } bool CCD::setStreamingFrame(int x, int y, int w, int h) { INumberVectorProperty *frameProp = baseDevice->getNumber("CCD_STREAM_FRAME"); if (frameProp == nullptr) return false; INumber *xarg = IUFindNumber(frameProp, "X"); INumber *yarg = IUFindNumber(frameProp, "Y"); INumber *warg = IUFindNumber(frameProp, "WIDTH"); INumber *harg = IUFindNumber(frameProp, "HEIGHT"); if (xarg && yarg && warg && harg) { if (xarg->value == x && yarg->value == y && warg->value == w && harg->value == h) return true; // N.B. We add offset since the X, Y are relative to whatever streaming frame is currently active xarg->value = qBound(xarg->min, static_cast(x) + xarg->value, xarg->max); yarg->value = qBound(yarg->min, static_cast(y) + yarg->value, yarg->max); warg->value = qBound(warg->min, static_cast(w), warg->max); harg->value = qBound(harg->min, static_cast(h), harg->max); clientManager->sendNewNumber(frameProp); return true; } return false; } bool CCD::isStreamingEnabled() { if (HasVideoStream == false || streamWindow.get() == nullptr) return false; return streamWindow->isStreamEnabled(); } bool CCD::setSERNameDirectory(const QString &filename, const QString &directory) { ITextVectorProperty *tvp = baseDevice->getText("RECORD_FILE"); if (tvp == nullptr) return false; IText *filenameT = IUFindText(tvp, "RECORD_FILE_NAME"); IText *dirT = IUFindText(tvp, "RECORD_FILE_DIR"); if (filenameT == nullptr || dirT == nullptr) return false; IUSaveText(filenameT, filename.toLatin1().data()); IUSaveText(dirT, directory.toLatin1().data()); clientManager->sendNewText(tvp); return true; } bool CCD::getSERNameDirectory(QString &filename, QString &directory) { ITextVectorProperty *tvp = baseDevice->getText("RECORD_FILE"); if (tvp == nullptr) return false; IText *filenameT = IUFindText(tvp, "RECORD_FILE_NAME"); IText *dirT = IUFindText(tvp, "RECORD_FILE_DIR"); if (filenameT == nullptr || dirT == nullptr) return false; filename = QString(filenameT->text); directory = QString(dirT->text); return true; } bool CCD::startRecording() { ISwitchVectorProperty *svp = baseDevice->getSwitch("RECORD_STREAM"); if (svp == nullptr) return false; ISwitch *recordON = IUFindSwitch(svp, "RECORD_ON"); if (recordON == nullptr) return false; if (recordON->s == ISS_ON) return true; IUResetSwitch(svp); recordON->s = ISS_ON; clientManager->sendNewSwitch(svp); return true; } bool CCD::startDurationRecording(double duration) { INumberVectorProperty *nvp = baseDevice->getNumber("RECORD_OPTIONS"); if (nvp == nullptr) return false; INumber *durationN = IUFindNumber(nvp, "RECORD_DURATION"); if (durationN == nullptr) return false; ISwitchVectorProperty *svp = baseDevice->getSwitch("RECORD_STREAM"); if (svp == nullptr) return false; ISwitch *recordON = IUFindSwitch(svp, "RECORD_DURATION_ON"); if (recordON == nullptr) return false; if (recordON->s == ISS_ON) return true; durationN->value = duration; clientManager->sendNewNumber(nvp); IUResetSwitch(svp); recordON->s = ISS_ON; clientManager->sendNewSwitch(svp); return true; } bool CCD::startFramesRecording(uint32_t frames) { INumberVectorProperty *nvp = baseDevice->getNumber("RECORD_OPTIONS"); if (nvp == nullptr) return false; INumber *frameN = IUFindNumber(nvp, "RECORD_FRAME_TOTAL"); ISwitchVectorProperty *svp = baseDevice->getSwitch("RECORD_STREAM"); if (frameN == nullptr || svp == nullptr) return false; ISwitch *recordON = IUFindSwitch(svp, "RECORD_FRAME_ON"); if (recordON == nullptr) return false; if (recordON->s == ISS_ON) return true; frameN->value = frames; clientManager->sendNewNumber(nvp); IUResetSwitch(svp); recordON->s = ISS_ON; clientManager->sendNewSwitch(svp); return true; } bool CCD::stopRecording() { ISwitchVectorProperty *svp = baseDevice->getSwitch("RECORD_STREAM"); if (svp == nullptr) return false; ISwitch *recordOFF = IUFindSwitch(svp, "RECORD_OFF"); if (recordOFF == nullptr) return false; // If already set if (recordOFF->s == ISS_ON) return true; IUResetSwitch(svp); recordOFF->s = ISS_ON; clientManager->sendNewSwitch(svp); return true; } bool CCD::setFITSHeader(const QMap &values) { ITextVectorProperty *tvp = baseDevice->getText("FITS_HEADER"); if (tvp == nullptr) return false; QMapIterator i(values); while (i.hasNext()) { i.next(); IText *headerT = IUFindText(tvp, i.key().toLatin1().data()); if (headerT == nullptr) continue; IUSaveText(headerT, i.value().toLatin1().data()); } clientManager->sendNewText(tvp); return true; } bool CCD::setGain(double value) { if (gainN == nullptr) return false; gainN->value = value; clientManager->sendNewNumber(gainN->nvp); return true; } bool CCD::getGain(double *value) { if (gainN == nullptr) return false; *value = gainN->value; return true; } bool CCD::getGainMinMaxStep(double *min, double *max, double *step) { if (gainN == nullptr) return false; *min = gainN->min; *max = gainN->max; *step = gainN->step; return true; } bool CCD::isBLOBEnabled() { return (clientManager->isBLOBEnabled(getDeviceName(), "CCD1")); } bool CCD::setBLOBEnabled(bool enable, const QString &prop) { clientManager->setBLOBEnabled(enable, getDeviceName(), prop); return true; } bool CCD::setExposureLoopingEnabled(bool enable) { // Set value immediately IsLooping = enable; ISwitchVectorProperty *svp = baseDevice->getSwitch("CCD_EXPOSURE_LOOP"); if (svp == nullptr) return false; svp->sp[0].s = enable ? ISS_ON : ISS_OFF; svp->sp[1].s = enable ? ISS_OFF : ISS_ON; clientManager->sendNewSwitch(svp); return true; } bool CCD::setExposureLoopCount(uint32_t count) { INumberVectorProperty *nvp = baseDevice->getNumber("CCD_EXPOSURE_LOOP_COUNT"); if (nvp == nullptr) return false; nvp->np[0].value = count; clientManager->sendNewNumber(nvp); return true; } bool CCD::setStreamExposure(double duration) { INumberVectorProperty *nvp = baseDevice->getNumber("STREAMING_EXPOSURE"); if (nvp == nullptr) return false; nvp->np[0].value = duration; clientManager->sendNewNumber(nvp); return true; } bool CCD::getStreamExposure(double *duration) { INumberVectorProperty *nvp = baseDevice->getNumber("STREAMING_EXPOSURE"); if (nvp == nullptr) return false; *duration = nvp->np[0].value; return true; } bool CCD::isCoolerOn() { ISwitchVectorProperty *svp = baseDevice->getSwitch("CCD_COOLER"); if (svp == nullptr) return false; return (svp->sp[0].s == ISS_ON); } } diff --git a/kstars/indi/indiccd.h b/kstars/indi/indiccd.h index a91d32708..d98f780ee 100644 --- a/kstars/indi/indiccd.h +++ b/kstars/indi/indiccd.h @@ -1,294 +1,292 @@ /* 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 "indistd.h" #include "auxiliary/imageviewer.h" #include "fitsviewer/fitscommon.h" #include "fitsviewer/fitsview.h" #include "fitsviewer/fitsviewer.h" #include #include #include class FITSData; class FITSView; class QTimer; class StreamWG; /** * \namespace ISD * * ISD is a collection of INDI Standard Devices. It encapsulates common types of INDI devices such as telescopes and CCDs. * */ namespace ISD { class CCD; /** * @class CCDChip * CCDChip class controls a particular chip in CCD device. While most amateur CCDs only have a single chip on the CCD, some * CCDs have additional chips targetted for guiding purposes. */ class CCDChip { public: typedef enum { PRIMARY_CCD, GUIDE_CCD } ChipType; CCDChip(ISD::CCD *ccd, ChipType cType); FITSView *getImageView(FITSMode imageType); void setImageView(FITSView *image, FITSMode imageType); void setCaptureMode(FITSMode mode) { captureMode = mode; } void setCaptureFilter(FITSScale fType) { captureFilter = fType; } // Common commands bool getFrame(int *x, int *y, int *w, int *h); bool getFrameMinMax(int *minX, int *maxX, int *minY, int *maxY, int *minW, int *maxW, int *minH, int *maxH); bool setFrame(int x, int y, int w, int h); bool resetFrame(); bool capture(double exposure); bool setFrameType(CCDFrameType fType); bool setFrameType(const QString &name); CCDFrameType getFrameType(); bool setBinning(int bin_x, int bin_y); bool setBinning(CCDBinType binType); CCDBinType getBinning(); bool getBinning(int *bin_x, int *bin_y); bool getMaxBin(int *max_xbin, int *max_ybin); ChipType getType() const { return type; } ISD::CCD *getCCD() { return parentCCD; } // Set Image Info bool setImageInfo(uint16_t width, uint16_t height, double pixelX, double pixelY, uint8_t bitdepth); // Get Pixel size bool getPixelSize(double &x, double &y); bool isCapturing(); bool abortExposure(); FITSMode getCaptureMode() const { return captureMode; } FITSScale getCaptureFilter() const { return captureFilter; } bool isBatchMode() const { return batchMode; } void setBatchMode(bool enable) { batchMode = enable; } QStringList getFrameTypes() const { return frameTypes; } void addFrameLabel(const QString &label) { frameTypes << label; } void clearFrameTypes() { frameTypes.clear(); } bool canBin() const; void setCanBin(bool value); bool canSubframe() const; void setCanSubframe(bool value); bool canAbort() const; void setCanAbort(bool value); FITSData *getImageData() const; void setImageData(FITSData *data) { imageData = data; } int getISOIndex() const; bool setISOIndex(int value); QStringList getISOList() const; private: QPointer normalImage, focusImage, guideImage, calibrationImage, alignImage; FITSData *imageData { nullptr }; FITSMode captureMode { FITS_NORMAL }; FITSScale captureFilter { FITS_NONE }; INDI::BaseDevice *baseDevice { nullptr }; ClientManager *clientManager { nullptr }; ChipType type; bool batchMode { false }; QStringList frameTypes; bool CanBin { false }; bool CanSubframe { false }; bool CanAbort { false }; ISD::CCD *parentCCD { nullptr }; }; /** * @class CCD * CCD class controls an INDI CCD device. It can be used to issue and abort capture commands, receive and process BLOBs, * and return information on the capabilities of the CCD. * * @author Jasem Mutlaq */ class CCD : public DeviceDecorator { Q_OBJECT public: explicit CCD(GDInterface *iPtr); - ~CCD(); typedef enum { UPLOAD_CLIENT, UPLOAD_LOCAL, UPLOAD_BOTH } UploadMode; typedef enum { FORMAT_FITS, FORMAT_NATIVE } TransferFormat; enum BlobType { BLOB_IMAGE, BLOB_FITS, BLOB_RAW, BLOB_OTHER } BType; typedef enum { TELESCOPE_PRIMARY, TELESCOPE_GUIDE, TELESCOPE_UNKNOWN } TelescopeType; void registerProperty(INDI::Property *prop); void processSwitch(ISwitchVectorProperty *svp); void processText(ITextVectorProperty *tvp); void processNumber(INumberVectorProperty *nvp); void processLight(ILightVectorProperty *lvp); void processBLOB(IBLOB *bp); DeviceFamily getType() { return dType; } // Does it an on-chip dedicated guide head? bool hasGuideHead(); // Does it report temperature? bool hasCooler(); // Can temperature be controlled? bool canCool() { return CanCool; } // Does it have active cooler on/off controls? bool hasCoolerControl(); bool setCoolerControl(bool enable); bool isCoolerOn(); // Does it have a video stream? bool hasVideoStream() { return HasVideoStream; } // Temperature controls bool getTemperature(double *value); bool setTemperature(double value); // Utility functions void setISOMode(bool enable) { ISOMode = enable; } void setSeqPrefix(const QString &preFix) { seqPrefix = preFix; } void setNextSequenceID(int count) { nextSequenceID = count; } void setFilter(const QString &newFilter) { filter = newFilter; } // Gain controls bool hasGain() { return gainN != nullptr; } bool getGain(double *value); IPerm getGainPermission() const {return gainPerm;} bool setGain(double value); bool getGainMinMaxStep(double *min, double *max, double *step); // Rapid Guide bool configureRapidGuide(CCDChip *targetChip, bool autoLoop, bool sendImage = false, bool showMarker = false); bool setRapidGuide(CCDChip *targetChip, bool enable); // Upload Settings void updateUploadSettings(const QString &remoteDir); UploadMode getUploadMode(); bool setUploadMode(UploadMode mode); // Transfer Format TransferFormat getTransferFormat() { return transferFormat; } bool setTransformFormat(CCD::TransferFormat format); // BLOB control bool isBLOBEnabled(); bool setBLOBEnabled(bool enable, const QString &prop = QString()); // Video Stream bool setVideoStreamEnabled(bool enable); bool resetStreamingFrame(); bool setStreamingFrame(int x, int y, int w, int h); bool isStreamingEnabled(); bool setStreamExposure(double duration); bool getStreamExposure(double *duration); // Video Recording bool setSERNameDirectory(const QString &filename, const QString &directory); bool getSERNameDirectory(QString &filename, QString &directory); bool startRecording(); bool startDurationRecording(double duration); bool startFramesRecording(uint32_t frames); bool stopRecording(); // Telescope type TelescopeType getTelescopeType() { return telescopeType; } bool setTelescopeType(TelescopeType type); // Update FITS Header bool setFITSHeader(const QMap &values); - FITSViewer *getViewer() { return fv; } CCDChip *getChip(CCDChip::ChipType cType); void setFITSDir(const QString &dir) { fitsDir = dir; } TransferFormat getTargetTransferFormat() const; void setTargetTransferFormat(const TransferFormat &value); bool setExposureLoopingEnabled(bool enable); bool isLooping() const { return IsLooping; } bool setExposureLoopCount(uint32_t count); public slots: void FITSViewerDestroyed(); void StreamWindowHidden(); signals: //void FITSViewerClosed(); void newTemperatureValue(double value); void newExposureValue(ISD::CCDChip *chip, double value, IPState state); void newGuideStarData(ISD::CCDChip *chip, double dx, double dy, double fit); void newBLOBManager(INDI::Property *prop); void newRemoteFile(QString); void videoStreamToggled(bool enabled); void videoRecordToggled(bool enabled); void newFPS(double instantFPS, double averageFPS); void newVideoFrame(std::unique_ptr & frame); void coolerToggled(bool enabled); void ready(); private: void addFITSKeywords(const QString& filename); void loadImageInView(IBLOB *bp, ISD::CCDChip *targetChip); QString filter; bool ISOMode { true }; bool HasGuideHead { false }; bool HasCooler { false }; bool CanCool { false }; bool HasCoolerControl { false }; bool HasVideoStream { false }; bool IsLooping { false }; QString seqPrefix; QString fitsDir; char BLOBFilename[MAXINDIFILENAME+1]; int nextSequenceID { 0 }; std::unique_ptr streamWindow; int streamW { 0 }; int streamH { 0 }; int normalTabID { -1 }; int calibrationTabID { -1 }; int focusTabID { -1 }; int guideTabID { -1 }; int alignTabID { -1 }; std::unique_ptr readyTimer; std::unique_ptr primaryChip; std::unique_ptr guideChip; TransferFormat transferFormat { FORMAT_FITS }; TransferFormat targetTransferFormat { FORMAT_FITS }; TelescopeType telescopeType { TELESCOPE_UNKNOWN }; // Gain, since it is spread among different vector properties, let's try to find the property itself. INumber *gainN { nullptr }; IPerm gainPerm { IP_RO }; - QPointer fv; - QPointer imageViewer; + QPointer m_FITSViewerWindows; + QPointer m_ImageViewerWindow; }; } diff --git a/kstars/kstars.cpp b/kstars/kstars.cpp index 4ae3b7738..9ca9490bb 100644 --- a/kstars/kstars.cpp +++ b/kstars/kstars.cpp @@ -1,663 +1,666 @@ /*************************************************************************** kstars.cpp - 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. * * * ***************************************************************************/ #include "kstars.h" #include "config-kstars.h" #include "version.h" #include "fov.h" #include "kactionmenu.h" #include "kstarsadaptor.h" #include "kstarsdata.h" #include "kstarssplash.h" #include "observinglist.h" #include "Options.h" #include "skymap.h" #include "skyqpainter.h" #include "texturemanager.h" #include "dialogs/finddialog.h" #include "dialogs/exportimagedialog.h" #include "skycomponents/starblockfactory.h" #ifdef HAVE_INDI #include "ekos/manager.h" #include "indi/drivermanager.h" #include "indi/guimanager.h" #endif #ifdef HAVE_CFITSIO #include "fitsviewer/fitsviewer.h" #endif #include #include #ifdef Q_OS_WIN #include #endif #include #include #include KStars *KStars::pinstance = nullptr; bool KStars::Closing = false; KStars::KStars(bool doSplash, bool clockrun, const QString &startdate) : KXmlGuiWindow(), StartClockRunning(clockrun), StartDateString(startdate) { // FIXME Hack to set RTL direction for Arabic // This is not a solution. It seems qtbase_ar.qm needs to take care of this? // qttranslations5-l10n does not contain qtbase_ar.qm // It seems qtbase_ar.ts does not exist for Qt 5.9 at all and needs to be translated. // https://wiki.qt.io/Qt_Localization if (i18n("Sky") == "السماء") qApp->setLayoutDirection(Qt::RightToLeft); setWindowTitle(i18n("KStars")); // Set thread stack size to 32MB #if QT_VERSION >= QT_VERSION_CHECK(5,10,0) QThreadPool::globalInstance()->setStackSize(33554432); #endif // Initialize logging settings if (Options::disableLogging()) KSUtils::Logging::Disable(); else if (Options::logToFile()) KSUtils::Logging::UseFile(); else KSUtils::Logging::UseDefault(); KSUtils::Logging::SyncFilterRules(); qCInfo(KSTARS) << "Welcome to KStars" << KSTARS_VERSION; qCInfo(KSTARS) << "Build:" << KSTARS_BUILD_TS; qCInfo(KSTARS) << "OS:" << QSysInfo::productType(); qCInfo(KSTARS) << "API:" << QSysInfo::buildAbi(); qCInfo(KSTARS) << "Arch:" << QSysInfo::currentCpuArchitecture(); qCInfo(KSTARS) << "Kernel Type:" << QSysInfo::kernelType(); qCInfo(KSTARS) << "Kernel Version:" << QSysInfo::kernelVersion(); qCInfo(KSTARS) << "Qt Version:" << QT_VERSION_STR; new KstarsAdaptor( this); // NOTE the weird case convention, which cannot be changed as the file is generated by the moc. #ifdef Q_OS_OSX QString vlcPlugins = QDir(QCoreApplication::applicationDirPath() + "/../PlugIns/vlc").absolutePath(); qputenv("VLC_PLUGIN_PATH", vlcPlugins.toLatin1()); QString phonon_backend_path = QDir(QCoreApplication::applicationDirPath() + "/../PlugIns/phonon4qt5_backend/phonon_vlc.so").absolutePath(); qputenv("PHONON_BACKEND", phonon_backend_path.toLatin1()); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); QString path = env.value("PATH", ""); env.insert("PATH", "/usr/bin:/usr/local/bin:\"" + QCoreApplication::applicationDirPath() + "\":" + path); QProcess dbusCheck; dbusCheck.setProcessEnvironment(env); QString pluginsDir = QDir(QCoreApplication::applicationDirPath() + "/../PlugIns").absolutePath(); QString dbusPlist = pluginsDir + "/dbus/org.freedesktop.dbus-kstars.plist"; QFile file(dbusPlist); if (file.open(QIODevice::ReadOnly)) { QTextStream in(&file); QString pListText = in.readAll(); file.close(); int programArgsLeft = pListText.indexOf("ProgramArguments"); int programArgsRight = pListText.indexOf("", programArgsLeft) + 8 - programArgsLeft; QString currentProgramArgs = pListText.mid(programArgsLeft, programArgsRight); QString newProgramArguments = "" "ProgramArguments\n" " \n" " " + QCoreApplication::applicationDirPath() + "/dbus-daemon\n" " --nofork\n" " --config-file=" + pluginsDir + "/dbus/kstars.conf\n" " "; pListText.replace(currentProgramArgs, newProgramArguments); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); stream << pListText; file.close(); dbusCheck.start("chmod 775 " + dbusPlist); dbusCheck.waitForFinished(); dbusCheck.start("launchctl load -w \"" + dbusPlist + "\""); dbusCheck.waitForFinished(); qDebug("Starting DBus"); } else { qDebug("DBus File Write Error"); } } else { qDebug("DBus File Read Error"); } #endif QDBusConnection::sessionBus().registerObject("/KStars", this); QDBusConnection::sessionBus().registerService("org.kde.kstars"); #ifdef HAVE_CFITSIO m_GenericFITSViewer.clear(); #endif #ifdef HAVE_INDI m_EkosManager.clear(); #endif // Set pinstance to yourself pinstance = this; connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(slotAboutToQuit())); //Initialize QActionGroups projectionGroup = new QActionGroup(this); cschemeGroup = new QActionGroup(this); hipsGroup = new QActionGroup(this); telescopeGroup = new QActionGroup(this); telescopeGroup->setExclusive(false); domeGroup = new QActionGroup(this); domeGroup->setExclusive(false); m_KStarsData = KStarsData::Create(); Q_ASSERT(m_KStarsData); //Set Geographic Location from Options m_KStarsData->setLocationFromOptions(); //Initialize Time and Date bool datetimeSet=false; if (StartDateString.isEmpty() == false) { KStarsDateTime startDate = KStarsDateTime::fromString(StartDateString); if (startDate.isValid()) data()->changeDateTime(data()->geo()->LTtoUT(startDate)); else data()->changeDateTime(KStarsDateTime::currentDateTimeUtc()); datetimeSet=true; } // JM 2016-11-15: Not need to set it again as it was initialized in the ctor of SimClock /* else data()->changeDateTime( KStarsDateTime::currentDateTimeUtc() ); */ // Initialize clock. If --paused is not in the command line, look in options if (clockrun) StartClockRunning = Options::runClock(); // If we are starting paused, we need to change datetime in data if (StartClockRunning == false) { qDebug() << "KStars is started in paused state."; if (datetimeSet == false) data()->changeDateTime(KStarsDateTime::currentDateTimeUtc()); } // Setup splash screen KStarsSplash *splash = nullptr; if (doSplash) { splash = new KStarsSplash(nullptr); connect(m_KStarsData, SIGNAL(progressText(QString)), splash, SLOT(setMessage(QString))); splash->show(); } else { connect(m_KStarsData, SIGNAL(progressText(QString)), m_KStarsData, SLOT(slotConsoleMessage(QString))); } /* //set up Dark color scheme for application windows DarkPalette = QPalette(QColor("black"), QColor("black")); DarkPalette.setColor(QPalette::Inactive, QPalette::WindowText, QColor("red")); DarkPalette.setColor(QPalette::Normal, QPalette::WindowText, QColor("red")); DarkPalette.setColor(QPalette::Normal, QPalette::Base, QColor("black")); DarkPalette.setColor(QPalette::Normal, QPalette::Text, QColor(238, 0, 0)); DarkPalette.setColor(QPalette::Normal, QPalette::Highlight, QColor(238, 0, 0)); DarkPalette.setColor(QPalette::Normal, QPalette::HighlightedText, QColor("black")); DarkPalette.setColor(QPalette::Inactive, QPalette::Text, QColor(238, 0, 0)); DarkPalette.setColor(QPalette::Inactive, QPalette::Base, QColor(30, 10, 10)); //store original color scheme OriginalPalette = QApplication::palette(); */ //Initialize data. When initialization is complete, it will run dataInitFinished() if (!m_KStarsData->initialize()) return; delete splash; datainitFinished(); #if (__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1 && !defined(__UCLIBC__)) qDebug() << "glibc >= 2.1 detected. Using GNU extension sincos()"; #else qDebug() << "Did not find glibc >= 2.1. Will use ANSI-compliant sin()/cos() functions."; #endif } KStars *KStars::createInstance(bool doSplash, bool clockrun, const QString &startdate) { delete pinstance; // pinstance is set directly in constructor. new KStars(doSplash, clockrun, startdate); Q_ASSERT(pinstance && "pinstance must be non NULL"); return pinstance; } KStars::~KStars() { releaseResources(); Q_ASSERT(pinstance); pinstance = nullptr; #ifdef PROFILE_COORDINATE_CONVERSION qDebug() << "Spent " << SkyPoint::cpuTime_EqToHz << " seconds in " << SkyPoint::eqToHzCalls << " calls to SkyPoint::EquatorialToHorizontal, for an average of " << 1000. * (SkyPoint::cpuTime_EqToHz / SkyPoint::eqToHzCalls) << " ms per call"; #endif #ifdef COUNT_DMS_SINCOS_CALLS qDebug() << "Constructed " << dms::dms_constructor_calls << " dms objects, of which " << dms::dms_with_sincos_called << " had trigonometric functions called on them = " << (float(dms::dms_with_sincos_called) / float(dms::dms_constructor_calls)) * 100. << "%"; qDebug() << "Of the " << dms::trig_function_calls << " calls to sin/cos/sincos on dms objects, " << dms::redundant_trig_function_calls << " were redundant = " << ((float(dms::redundant_trig_function_calls) / float(dms::trig_function_calls)) * 100.) << "%"; qDebug() << "We had " << CachingDms::cachingdms_bad_uses << " bad uses of CachingDms in all, compared to " << CachingDms::cachingdms_constructor_calls << " constructed CachingDms objects = " << (float(CachingDms::cachingdms_bad_uses) / float(CachingDms::cachingdms_constructor_calls)) * 100. << "% bad uses"; #endif /* BUG 366596: Some KDE applications processes remain as background (zombie) processes after closing * No solution to this bug so far using Qt 5.8 as of 2016-11-24 * Therefore, the only way to solve this on Windows is to explicitly kill kstars.exe * Hopefully we do not need this hack once the above bug is resolved. */ #ifdef Q_OS_WIN QProcess::execute("taskkill /im kstars.exe /f"); #endif } void KStars::releaseResources() { delete m_KStarsData; m_KStarsData = nullptr; delete StarBlockFactory::Instance(); TextureManager::Release(); SkyQPainter::releaseImageCache(); FOVManager::releaseCache(); #ifdef HAVE_INDI delete m_EkosManager; m_EkosManager = nullptr; // GUIManager::Instance()->close(); #endif +#ifdef HAVE_CFITSIO + qDeleteAll(m_FITSViewers); +#endif + QSqlDatabase::removeDatabase("userdb"); QSqlDatabase::removeDatabase("skydb"); } void KStars::clearCachedFindDialog() { #if 0 if (m_FindDialog) // dialog is cached { /** Delete findDialog only if it is not opened */ if (m_FindDialog->isHidden()) { delete m_FindDialog; m_FindDialog = nullptr; DialogIsObsolete = false; } else DialogIsObsolete = true; // dialog was opened so it could not deleted } #endif } void KStars::applyConfig(bool doApplyFocus) { if (Options::isTracking()) { actionCollection()->action("track_object")->setText(i18n("Stop &Tracking")); actionCollection() ->action("track_object") ->setIcon(QIcon::fromTheme("document-encrypt")); } actionCollection() ->action("coordsys") ->setText(Options::useAltAz() ? i18n("Switch to star globe view (Equatorial &Coordinates)") : i18n("Switch to horizonal view (Horizontal &Coordinates)")); actionCollection()->action("show_time_box")->setChecked(Options::showTimeBox()); actionCollection()->action("show_location_box")->setChecked(Options::showGeoBox()); actionCollection()->action("show_focus_box")->setChecked(Options::showFocusBox()); actionCollection()->action("show_statusBar")->setChecked(Options::showStatusBar()); actionCollection()->action("show_sbAzAlt")->setChecked(Options::showAltAzField()); actionCollection()->action("show_sbRADec")->setChecked(Options::showRADecField()); actionCollection()->action("show_sbJ2000RADec")->setChecked(Options::showJ2000RADecField()); actionCollection()->action("show_stars")->setChecked(Options::showStars()); actionCollection()->action("show_deepsky")->setChecked(Options::showDeepSky()); actionCollection()->action("show_planets")->setChecked(Options::showSolarSystem()); actionCollection()->action("show_clines")->setChecked(Options::showCLines()); actionCollection()->action("show_constellationart")->setChecked(Options::showConstellationArt()); actionCollection()->action("show_cnames")->setChecked(Options::showCNames()); actionCollection()->action("show_cbounds")->setChecked(Options::showCBounds()); actionCollection()->action("show_mw")->setChecked(Options::showMilkyWay()); actionCollection()->action("show_equatorial_grid")->setChecked(Options::showEquatorialGrid()); actionCollection()->action("show_horizontal_grid")->setChecked(Options::showHorizontalGrid()); actionCollection()->action("show_horizon")->setChecked(Options::showGround()); actionCollection()->action("show_flags")->setChecked(Options::showFlags()); actionCollection()->action("show_supernovae")->setChecked(Options::showSupernovae()); actionCollection()->action("show_satellites")->setChecked(Options::showSatellites()); statusBar()->setVisible(Options::showStatusBar()); //color scheme m_KStarsData->colorScheme()->loadFromConfig(); //QApplication::setPalette(Options::darkAppColors() ? DarkPalette : OriginalPalette); /** //Note: This uses style sheets to set the dark colors, this should be cross platform. Palettes have a different behavior on OS X and Windows as opposed to Linux. //It might be a good idea to use stylesheets in the future instead of palettes but this will work for now for OS X. //This is also in KStarsDbus.cpp. If you change it, change it in BOTH places. @code #ifdef Q_OS_OSX if (Options::darkAppColors()) qApp->setStyleSheet( "QWidget { background-color: black; color:red; " "selection-background-color:rgb(30,30,30);selection-color:white}" "QToolBar { border:none }" "QTabBar::tab:selected { background-color:rgb(50,50,50) }" "QTabBar::tab:!selected { background-color:rgb(30,30,30) }" "QPushButton { background-color:rgb(50,50,50);border-width:1px; border-style:solid;border-color:black}" "QPushButton::disabled { background-color:rgb(10,10,10);border-width:1px; " "border-style:solid;border-color:black }" "QToolButton:Checked { background-color:rgb(30,30,30); border:none }" "QComboBox { background-color:rgb(30,30,30); }" "QComboBox::disabled { background-color:rgb(10,10,10) }" "QScrollBar::handle { background: rgb(30,30,30) }" "QSpinBox { border-width: 1px; border-style:solid; border-color:rgb(30,30,30) }" "QDoubleSpinBox { border-width:1px; border-style:solid; border-color:rgb(30,30,30) }" "QLineEdit { border-width: 1px; border-style: solid; border-color:rgb(30,30,30) }" "QCheckBox::indicator:unchecked { background-color:rgb(30,30,30);border-width:1px; " "border-style:solid;border-color:black }" "QCheckBox::indicator:checked { background-color:red;border-width:1px; " "border-style:solid;border-color:black }" "QRadioButton::indicator:unchecked { background-color:rgb(30,30,30) }" "QRadioButton::indicator:checked { background-color:red }" "QRoundProgressBar { alternate-background-color:black }" "QDateTimeEdit {background-color:rgb(30,30,30); border-width: 1px; border-style:solid; " "border-color:rgb(30,30,30) }" "QHeaderView { color:red;background-color:black }" "QHeaderView::Section { background-color:rgb(30,30,30) }" "QTableCornerButton::section{ background-color:rgb(30,30,30) }" ""); else qApp->setStyleSheet(""); #endif @endcode **/ //Set toolbar options from config file toolBar("kstarsToolBar")->applySettings(KSharedConfig::openConfig()->group("MainToolBar")); toolBar("viewToolBar")->applySettings(KSharedConfig::openConfig()->group("ViewToolBar")); //Geographic location data()->setLocationFromOptions(); //Focus if (doApplyFocus) { SkyObject *fo = data()->objectNamed(Options::focusObject()); if (fo && fo != map()->focusObject()) { map()->setClickedObject(fo); map()->setClickedPoint(fo); map()->slotCenter(); } if (!fo) { SkyPoint fp(Options::focusRA(), Options::focusDec()); if (fp.ra().Degrees() != map()->focus()->ra().Degrees() || fp.dec().Degrees() != map()->focus()->dec().Degrees()) { map()->setClickedPoint(&fp); map()->slotCenter(); } } } } void KStars::showImgExportDialog() { if (m_ExportImageDialog) m_ExportImageDialog->show(); } void KStars::syncFOVActions() { foreach (QAction *action, fovActionMenu->menu()->actions()) { if (action->text().isEmpty()) { continue; } if (Options::fOVNames().contains(action->text().remove(0, 1))) { action->setChecked(true); } else { action->setChecked(false); } } } void KStars::hideAllFovExceptFirst() { // When there is only one visible FOV symbol, we don't need to do anything // Also, don't do anything if there are no available FOV symbols. if (data()->visibleFOVs.size() == 1 || data()->availFOVs.isEmpty()) { return; } else { // If there are no visible FOVs, select first available if (data()->visibleFOVs.isEmpty()) { Q_ASSERT(!data()->availFOVs.isEmpty()); Options::setFOVNames(QStringList(data()->availFOVs.first()->name())); } else { Options::setFOVNames(QStringList(data()->visibleFOVs.first()->name())); } // Sync FOV and update skymap data()->syncFOV(); syncFOVActions(); map()->update(); // SkyMap::forceUpdate() is not required, as FOVs are drawn as overlays } } void KStars::selectNextFov() { if (data()->getVisibleFOVs().isEmpty()) return; Q_ASSERT(!data() ->getAvailableFOVs() .isEmpty()); // The available FOVs had better not be empty if the visible ones are not. FOV *currentFov = data()->getVisibleFOVs().first(); int currentIdx = data()->availFOVs.indexOf(currentFov); // If current FOV is not the available FOV list or there is only 1 FOV available, then return if (currentIdx == -1 || data()->availFOVs.size() < 2) { return; } QStringList nextFovName; if (currentIdx == data()->availFOVs.size() - 1) { nextFovName << data()->availFOVs.first()->name(); } else { nextFovName << data()->availFOVs.at(currentIdx + 1)->name(); } Options::setFOVNames(nextFovName); data()->syncFOV(); syncFOVActions(); map()->update(); } void KStars::selectPreviousFov() { if (data()->getVisibleFOVs().isEmpty()) return; Q_ASSERT(!data() ->getAvailableFOVs() .isEmpty()); // The available FOVs had better not be empty if the visible ones are not. FOV *currentFov = data()->getVisibleFOVs().first(); int currentIdx = data()->availFOVs.indexOf(currentFov); // If current FOV is not the available FOV list or there is only 1 FOV available, then return if (currentIdx == -1 || data()->availFOVs.size() < 2) { return; } QStringList prevFovName; if (currentIdx == 0) { prevFovName << data()->availFOVs.last()->name(); } else { prevFovName << data()->availFOVs.at(currentIdx - 1)->name(); } Options::setFOVNames(prevFovName); data()->syncFOV(); syncFOVActions(); map()->update(); } //FIXME Port to QML2 //#if 0 void KStars::showWISettingsUI() { slotWISettings(); } //#endif void KStars::updateTime(const bool automaticDSTchange) { // Due to frequently use of this function save data and map pointers for speedup. // Save options and geo() to a pointer would not speedup because most of time options // and geo will accessed only one time. KStarsData *Data = data(); // dms oldLST( Data->lst()->Degrees() ); Data->updateTime(Data->geo(), automaticDSTchange); //We do this outside of kstarsdata just to get the coordinates //displayed in the infobox to update every second. // if ( !Options::isTracking() && LST()->Degrees() > oldLST.Degrees() ) { // int nSec = int( 3600.*( LST()->Hours() - oldLST.Hours() ) ); // Map->focus()->setRA( Map->focus()->ra().Hours() + double( nSec )/3600. ); // if ( Options::useAltAz() ) Map->focus()->EquatorialToHorizontal( LST(), geo()->lat() ); // Map->showFocusCoords(); // } //If time is accelerated beyond slewTimescale, then the clock's timer is stopped, //so that it can be ticked manually after each update, in order to make each time //step exactly equal to the timeScale setting. //Wrap the call to manualTick() in a singleshot timer so that it doesn't get called until //the skymap has been completely updated. if (Data->clock()->isManualMode() && Data->clock()->isActive()) { // Jasem 2017-11-13: Time for each update varies. // Ideally we want to advance the simulation clock by // the current clock scale (e.g. 1 hour) every 1 second // of real time. However, the sky map update, depending on calculations and // drawing of objects, takes variable time to complete. //QTimer::singleShot(0, Data->clock(), SLOT(manualTick())); QTimer::singleShot(1000, Data->clock(), SLOT(manualTick())); } } #ifdef HAVE_CFITSIO QPointer KStars::genericFITSViewer() { if (m_GenericFITSViewer.isNull()) { m_GenericFITSViewer = new FITSViewer(Options::independentWindowFITS() ? nullptr : this); m_GenericFITSViewer->setAttribute(Qt::WA_DeleteOnClose); m_FITSViewers.append(m_GenericFITSViewer); } return m_GenericFITSViewer; } #endif #ifdef HAVE_CFITSIO void KStars::addFITSViewer(QPointer fv) { m_FITSViewers.append(fv); - int index = m_FITSViewers.count() - 1; - connect(fv.data(), &FITSViewer::destroyed, [&,index]() { - m_FITSViewers.removeAt(index); + connect(fv.data(), &FITSViewer::destroyed, [&,fv]() { + m_FITSViewers.removeOne(fv); }); } #endif #ifdef HAVE_INDI Ekos::Manager *KStars::ekosManager() { if (m_EkosManager.isNull()) m_EkosManager = new Ekos::Manager(Options::independentWindowEkos() ? nullptr : this); return m_EkosManager; } #endif void KStars::closeEvent(QCloseEvent *event) { KStars::Closing = true; QWidget::closeEvent(event); }